Compare commits

..

1 Commits

Author SHA1 Message Date
Ed
fd3f222e7d Update CP14BiomeSpawnerSystem.cs 2024-07-30 13:21:06 +03:00
2902 changed files with 494722 additions and 386291 deletions

14
.github/FUNDING.yml vendored
View File

@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: ['https://boosty.to/theshued']

6
.vscode/tasks.json vendored
View File

@@ -10,7 +10,7 @@
"args": [
"build",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build",
@@ -29,9 +29,9 @@
"build",
"${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
}

View File

@@ -46,7 +46,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", "Cog" };
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry", "Oasis" };
[ParamsSource(nameof(MapsSource))]
public string Map;

View File

@@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob));
}
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
{
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
}
@@ -55,7 +55,7 @@ namespace Content.Client.Access.UI
_window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.CurrentJobIconId);
_window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
}
}
}

View File

@@ -6,9 +6,12 @@
<LineEdit Name="NameLineEdit" />
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
<LineEdit Name="JobLineEdit" />
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
<GridContainer Name="IconGrid" Columns="10">
<!-- Job icon buttons are generated in the code -->
</GridContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
<Control HorizontalExpand="True" MinSize="50 0"/>
<GridContainer Name="IconGrid" Columns="10">
<!-- Job icon buttons are generated in the code -->
</GridContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
using System.Linq;
namespace Content.Client.Access.UI
{
@@ -24,7 +23,7 @@ namespace Content.Client.Access.UI
public event Action<string>? OnNameChanged;
public event Action<string>? OnJobChanged;
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
public event Action<ProtoId<StatusIconPrototype>>? OnJobIconChanged;
public AgentIDCardWindow()
{
@@ -39,16 +38,17 @@ namespace Content.Client.Access.UI
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
public void SetAllowedIcons(string currentJobIconId)
public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
{
IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup();
var jobIconGroup = new ButtonGroup();
var i = 0;
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList();
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
foreach (var jobIcon in icons)
foreach (var jobIconId in icons)
{
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
continue;
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
if (modulo == 0)
@@ -62,9 +62,8 @@ namespace Content.Client.Access.UI
Access = AccessLevel.Public,
StyleClasses = { styleBase },
MaxSize = new Vector2(42, 28),
Group = jobIconButtonGroup,
Pressed = currentJobIconId == jobIcon.ID,
ToolTip = jobIcon.LocalizedJobName
Group = jobIconGroup,
Pressed = i == 0,
};
// Generate buttons textures
@@ -79,6 +78,9 @@ namespace Content.Client.Access.UI
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
IconGrid.AddChild(jobIconButton);
if (jobIconId.Equals(currentJobIconId))
jobIconButton.Pressed = true;
i++;
}
}

View File

@@ -48,7 +48,6 @@ namespace Content.Client.Actions
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
}
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
@@ -77,18 +76,6 @@ namespace Content.Client.Actions
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
@@ -306,7 +293,7 @@ namespace Content.Client.Actions
continue;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn();
var actionId = Spawn(null);
AddComp(actionId, action);
AddActionDirect(user, actionId);

View File

@@ -7,66 +7,67 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Administration;
internal sealed class AdminNameOverlay : Overlay
namespace Content.Client.Administration
{
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly Font _font;
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
internal sealed class AdminNameOverlay : Overlay
{
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.WorldAABB;
foreach (var playerInfo in _system.PlayerList)
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
{
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
// Otherwise the entity can not exist yet
if (entity == null || !_entityManager.EntityExists(entity))
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.WorldAABB;
foreach (var playerInfo in _system.PlayerList)
{
continue;
}
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
// if not on the same map, continue
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
{
continue;
}
// Otherwise the entity can not exist yet
if (entity == null || !_entityManager.EntityExists(entity))
{
continue;
}
var aabb = _entityLookup.GetWorldAABB(entity.Value);
// if not on the same map, continue
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != _eyeManager.CurrentMap)
{
continue;
}
// if not on screen, continue
if (!aabb.Intersects(in viewport))
{
continue;
}
var aabb = _entityLookup.GetWorldAABB(entity.Value);
var lineoffset = new Vector2(0f, 11f);
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
// if not on screen, continue
if (!aabb.Intersects(in viewport))
{
continue;
}
var lineoffset = new Vector2(0f, 11f);
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
}
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
}
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
}
}
}

View File

@@ -88,51 +88,26 @@ namespace Content.Client.Administration.UI.Bwoink
var ach = AHelpHelper.EnsurePanel(a.SessionId);
var bch = AHelpHelper.EnsurePanel(b.SessionId);
// Pinned players first
if (a.IsPinned != b.IsPinned)
return a.IsPinned ? -1 : 1;
// First, sort by unread. Any chat with unread messages appears first.
// First, sort by unread. Any chat with unread messages appears first. We just sort based on unread
// status, not number of unread messages, so that more recent unread messages take priority.
var aUnread = ach.Unread > 0;
var bUnread = bch.Unread > 0;
if (aUnread != bUnread)
return aUnread ? -1 : 1;
// Sort by recent messages during the current round.
var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
if (aRecent != bRecent)
return aRecent ? -1 : 1;
// Next, sort by connection status. Any disconnected players are grouped towards the end.
if (a.Connected != b.Connected)
return a.Connected ? -1 : 1;
// Sort connected players by New Player status, then by Antag status
if (a.Connected && b.Connected)
{
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
if (aNewPlayer != bNewPlayer)
return aNewPlayer ? -1 : 1;
if (a.Antag != b.Antag)
return a.Antag ? -1 : 1;
}
// Sort disconnected players by participation in the round
if (!a.Connected && !b.Connected)
{
if (a.ActiveThisRound != b.ActiveThisRound)
return a.ActiveThisRound ? -1 : 1;
}
// Next, group by whether or not the players have participated in this round.
// The ahelp window shows all players that have connected since server restart, this groups them all towards the bottom.
if (a.ActiveThisRound != b.ActiveThisRound)
return a.ActiveThisRound ? -1 : 1;
// Finally, sort by the most recent message.
return bch.LastMessage.CompareTo(ach.LastMessage);
};
Bans.OnPressed += _ =>
{
if (_currentPlayer is not null)
@@ -278,20 +253,7 @@ namespace Content.Client.Administration.UI.Bwoink
public void PopulateList()
{
// Maintain existing pin statuses
var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
ChannelSelector.PopulateList();
// Restore pin statuses
foreach (var player in ChannelSelector.PlayerInfo)
{
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
{
player.IsPinned = pinnedPlayer.IsPinned;
}
}
UpdateButtons();
}
}

View File

@@ -30,11 +30,7 @@ namespace Content.Client.Administration.UI.Bwoink
}
};
OnOpen += () =>
{
Bwoink.ChannelSelector.StopFiltering();
Bwoink.PopulateList();
};
OnOpen += () => Bwoink.PopulateList();
}
}
}

View File

@@ -4,166 +4,154 @@ using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListControl : BoxContainer
namespace Content.Client.Administration.UI.CustomControls
{
private readonly AdminSystem _adminSystem;
private readonly IEntityManager _entManager;
private readonly IUserInterfaceManager _uiManager;
private PlayerInfo? _selectedPlayer;
private List<PlayerInfo> _playerList = new();
private List<PlayerInfo> _sortedPlayerList = new();
public Comparison<PlayerInfo>? Comparison;
public Func<PlayerInfo, string, string>? OverrideText;
public PlayerListControl()
[GenerateTypedNameReferences]
public sealed partial class PlayerListControl : BoxContainer
{
_entManager = IoCManager.Resolve<IEntityManager>();
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
_adminSystem = _entManager.System<AdminSystem>();
RobustXamlLoader.Load(this);
// Fill the Option data
PlayerListContainer.ItemPressed += PlayerListItemPressed;
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) };
}
private readonly AdminSystem _adminSystem;
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
private List<PlayerInfo> _playerList = new();
private readonly List<PlayerInfo> _sortedPlayerList = new();
public event Action<PlayerInfo?>? OnSelectionChanged;
public event Action<PlayerInfo?>? OnSelectionChanged;
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
private void PlayerListNoItemSelected()
{
_selectedPlayer = null;
OnSelectionChanged?.Invoke(null);
}
public Func<PlayerInfo, string, string>? OverrideText;
public Comparison<PlayerInfo>? Comparison;
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
private IEntityManager _entManager;
private IUserInterfaceManager _uiManager;
if (selectedPlayer == _selectedPlayer)
return;
private PlayerInfo? _selectedPlayer;
if (args.Event.Function != EngineKeyFunctions.UIClick)
return;
OnSelectionChanged?.Invoke(selectedPlayer);
_selectedPlayer = selectedPlayer;
// update label text. Only required if there is some override (e.g. unread bwoink count).
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
label.Text = GetText(selectedPlayer);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
public void StopFiltering()
{
FilterLineEdit.Text = string.Empty;
}
private void FilterList()
{
_sortedPlayerList.Clear();
foreach (var info in _playerList)
public PlayerListControl()
{
var displayName = $"{info.CharacterName} ({info.Username})";
if (info.IdentityName != info.CharacterName)
displayName += $" [{info.IdentityName}]";
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
continue;
_sortedPlayerList.Add(info);
_entManager = IoCManager.Resolve<IEntityManager>();
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
_adminSystem = _entManager.System<AdminSystem>();
RobustXamlLoader.Load(this);
// Fill the Option data
PlayerListContainer.ItemPressed += PlayerListItemPressed;
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
}
if (Comparison != null)
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
if (_selectedPlayer != null)
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
}
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
{
// Maintain existing pin statuses
var pinnedPlayers = _playerList.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
players ??= _adminSystem.PlayerList;
_playerList = players.ToList();
// Restore pin statuses
foreach (var player in _playerList)
private void PlayerListNoItemSelected()
{
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
{
player.IsPinned = pinnedPlayer.IsPinned;
}
}
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
_selectedPlayer = null;
OnSelectionChanged?.Invoke(null);
}
FilterList();
}
private string GetText(PlayerInfo info)
{
var text = $"{info.CharacterName} ({info.Username})";
if (OverrideText != null)
text = OverrideText.Invoke(info, text);
return text;
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
var entry = new PlayerListEntry();
entry.Setup(info, OverrideText);
entry.OnPinStatusChanged += _ =>
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData {Info: var selectedPlayer})
return;
if (selectedPlayer == _selectedPlayer)
return;
if (args.Event.Function != EngineKeyFunctions.UIClick)
return;
OnSelectionChanged?.Invoke(selectedPlayer);
_selectedPlayer = selectedPlayer;
// update label text. Only required if there is some override (e.g. unread bwoink count).
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
label.Text = GetText(selectedPlayer);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
public void StopFiltering()
{
FilterLineEdit.Text = string.Empty;
}
private void FilterList()
{
_sortedPlayerList.Clear();
foreach (var info in _playerList)
{
var displayName = $"{info.CharacterName} ({info.Username})";
if (info.IdentityName != info.CharacterName)
displayName += $" [{info.IdentityName}]";
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
continue;
_sortedPlayerList.Add(info);
}
if (Comparison != null)
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
if (_selectedPlayer != null)
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
}
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
{
players ??= _adminSystem.PlayerList;
_playerList = players.ToList();
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
_selectedPlayer = null;
FilterList();
};
}
button.AddChild(entry);
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
private string GetText(PlayerInfo info)
{
var text = $"{info.CharacterName} ({info.Username})";
if (OverrideText != null)
text = OverrideText.Invoke(info, text);
return text;
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
button.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new Label
{
ClipText = true,
Text = GetText(info)
}
}
});
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
}
}
}
public record PlayerListData(PlayerInfo Info) : ListData;
public record PlayerListData(PlayerInfo Info) : ListData;
}

View File

@@ -1,6 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal" HorizontalExpand="true">
<Label Name="PlayerEntryLabel" Text="" ClipText="True" HorizontalExpand="True" />
<TextureButton Name="PlayerEntryPinButton"
HorizontalAlignment="Right" />
</BoxContainer>

View File

@@ -1,58 +0,0 @@
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListEntry : BoxContainer
{
public PlayerListEntry()
{
RobustXamlLoader.Load(this);
}
public event Action<PlayerInfo>? OnPinStatusChanged;
public void Setup(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
{
Update(info, overrideText);
PlayerEntryPinButton.OnPressed += HandlePinButtonPressed(info);
}
private Action<BaseButton.ButtonEventArgs> HandlePinButtonPressed(PlayerInfo info)
{
return args =>
{
info.IsPinned = !info.IsPinned;
UpdatePinButtonTexture(info.IsPinned);
OnPinStatusChanged?.Invoke(info);
};
}
private void Update(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
{
PlayerEntryLabel.Text = overrideText?.Invoke(info, $"{info.CharacterName} ({info.Username})") ??
$"{info.CharacterName} ({info.Username})";
UpdatePinButtonTexture(info.IsPinned);
}
private void UpdatePinButtonTexture(bool isPinned)
{
if (isPinned)
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
}
else
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
}
}
}

View File

@@ -1,36 +0,0 @@
<ui:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc ban-panel-title}" MinSize="300 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="PlayerName"/>
<Button Name="UsernameCopyButton" Text="{Loc player-panel-copy-username}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="Whitelisted"/>
<controls:ConfirmButton Name="WhitelistToggle" Text="{Loc 'player-panel-false'}" Visible="False"></controls:ConfirmButton>
</BoxContainer>
<Label Name="Playtime"/>
<Label Name="Notes"/>
<Label Name="Bans"/>
<Label Name="RoleBans"/>
<Label Name="SharedConnections"/>
<BoxContainer Align="Center">
<GridContainer Rows="5">
<Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
<Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
<Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
<controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
<controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
</ui:FancyWindow>

View File

@@ -1,132 +0,0 @@
using Content.Client.Administration.Managers;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.PlayerPanel;
[GenerateTypedNameReferences]
public sealed partial class PlayerPanel : FancyWindow
{
private readonly IClientAdminManager _adminManager;
public event Action<string>? OnUsernameCopy;
public event Action<NetUserId?>? OnOpenNotes;
public event Action<NetUserId?>? OnOpenBans;
public event Action<NetUserId?>? OnAhelp;
public event Action<string?>? OnKick;
public event Action<NetUserId?>? OnOpenBanPanel;
public event Action<NetUserId?, bool>? OnWhitelistToggle;
public event Action? OnFreezeAndMuteToggle;
public event Action? OnFreeze;
public event Action? OnLogs;
public event Action? OnDelete;
public event Action? OnRejuvenate;
public NetUserId? TargetPlayer;
public string? TargetUsername;
private bool _isWhitelisted;
public PlayerPanel(IClientAdminManager adminManager)
{
RobustXamlLoader.Load(this);
_adminManager = adminManager;
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
WhitelistToggle.OnPressed += _ =>
{
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
SetWhitelisted(!_isWhitelisted);
};
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
LogsButton.OnPressed += _ => OnLogs?.Invoke();
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
}
public void SetUsername(string player)
{
Title = Loc.GetString("player-panel-title", ("player", player));
PlayerName.Text = Loc.GetString("player-panel-username", ("player", player));
}
public void SetWhitelisted(bool? whitelisted)
{
if (whitelisted == null)
{
Whitelisted.Text = null;
WhitelistToggle.Visible = false;
}
else
{
Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
WhitelistToggle.Text = whitelisted.Value ? Loc.GetString("player-panel-true") : Loc.GetString("player-panel-false");
WhitelistToggle.Visible = true;
_isWhitelisted = whitelisted.Value;
}
}
public void SetBans(int? totalBans, int? totalRoleBans)
{
// If one value exists then so should the other.
DebugTools.Assert(totalBans.HasValue && totalRoleBans.HasValue || totalBans == null && totalRoleBans == null);
Bans.Text = totalBans != null ? Loc.GetString("player-panel-bans", ("totalBans", totalBans)) : null;
RoleBans.Text = totalRoleBans != null ? Loc.GetString("player-panel-rolebans", ("totalRoleBans", totalRoleBans)) : null;
}
public void SetNotes(int? totalNotes)
{
Notes.Text = totalNotes != null ? Loc.GetString("player-panel-notes", ("totalNotes", totalNotes)) : null;
}
public void SetSharedConnections(int sharedConnections)
{
SharedConnections.Text = Loc.GetString("player-panel-shared-connections", ("sharedConnections", sharedConnections));
}
public void SetPlaytime(TimeSpan playtime)
{
Playtime.Text = Loc.GetString("player-panel-playtime",
("days", playtime.Days),
("hours", playtime.Hours % 24),
("minutes", playtime.Minutes % (24 * 60)));
}
public void SetFrozen(bool canFreeze, bool frozen)
{
FreezeAndMuteToggleButton.Disabled = !canFreeze;
FreezeButton.Disabled = !canFreeze || frozen;
FreezeAndMuteToggleButton.Text = Loc.GetString(!frozen ? "player-panel-freeze-and-mute" : "player-panel-unfreeze");
}
public void SetAhelp(bool canAhelp)
{
AhelpButton.Disabled = !canAhelp;
}
public void SetButtons()
{
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
KickButton.Disabled = !_adminManager.CanCommand("kick");
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
WhitelistToggle.Disabled =
!(_adminManager.CanCommand("whitelistadd") && _adminManager.CanCommand("whitelistremove"));
LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
}
}

View File

@@ -1,72 +0,0 @@
using Content.Client.Administration.Managers;
using Content.Client.Eui;
using Content.Shared.Administration;
using Content.Shared.Eui;
using JetBrains.Annotations;
using Robust.Client.Console;
using Robust.Client.UserInterface;
namespace Content.Client.Administration.UI.PlayerPanel;
[UsedImplicitly]
public sealed class PlayerPanelEui : BaseEui
{
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
private PlayerPanel PlayerPanel { get; }
public PlayerPanelEui()
{
PlayerPanel = new PlayerPanel(_admin);
PlayerPanel.OnUsernameCopy += username => _clipboard.SetText(username);
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
// Kick command does not support GUIDs
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
PlayerPanel.OnWhitelistToggle += (id, whitelisted) =>
{
_console.ExecuteCommand(whitelisted ? $"whitelistremove \"{id}\"" : $"whitelistadd \"{id}\"");
};
PlayerPanel.OnFreezeAndMuteToggle += () => SendMessage(new PlayerPanelFreezeMessage(true));
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
}
public override void Opened()
{
PlayerPanel.OpenCentered();
}
public override void Closed()
{
PlayerPanel.Close();
}
public override void HandleState(EuiStateBase state)
{
if (state is not PlayerPanelEuiState s)
return;
PlayerPanel.TargetPlayer = s.Guid;
PlayerPanel.TargetUsername = s.Username;
PlayerPanel.SetUsername(s.Username);
PlayerPanel.SetPlaytime(s.Playtime);
PlayerPanel.SetBans(s.TotalBans, s.TotalRoleBans);
PlayerPanel.SetNotes(s.TotalNotes);
PlayerPanel.SetWhitelisted(s.Whitelisted);
PlayerPanel.SetSharedConnections(s.SharedConnections);
PlayerPanel.SetFrozen(s.CanFreeze, s.Frozen);
PlayerPanel.SetAhelp(s.CanAhelp);
PlayerPanel.SetButtons();
}
}

View File

@@ -1,21 +1,19 @@
using Content.Client.Administration.Managers;
using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
@@ -51,20 +49,10 @@ public sealed partial class ObjectsTab : Control
RefreshListButton.OnPressed += _ => RefreshObjectList();
var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection);
ObjectTypeOptions.SelectId((int) defaultSelection);
RefreshObjectList(defaultSelection);
}
private void TeleportTo(NetEntity nent)
{
_console.ExecuteCommand($"tpto {nent}");
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
}
public void RefreshObjectList()
{
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
@@ -128,9 +116,9 @@ public sealed partial class ObjectsTab : Control
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return;
var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
entry.OnTeleport += TeleportTo;
entry.OnDelete += Delete;
var entry = new ObjectsTabEntry(info.Name,
info.Entity,
new StyleBoxFlat { BackgroundColor = backgroundColor });
button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry);

View File

@@ -5,25 +5,13 @@
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="NameLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="EIDLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="TeleportButton"
Text="{Loc object-tab-entity-teleport}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="DeleteButton"
Text="{Loc object-tab-entity-delete}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
<Label Name="EIDLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</PanelContainer>

View File

@@ -1,5 +1,4 @@
using Content.Client.Administration.Managers;
using Robust.Client.AutoGenerated;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -11,30 +10,12 @@ public sealed partial class ObjectsTabEntry : PanelContainer
{
public NetEntity AssocEntity;
public Action<NetEntity>? OnTeleport;
public Action<NetEntity>? OnDelete;
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
{
RobustXamlLoader.Load(this);
AssocEntity = nent;
EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
TeleportButton.Disabled = !manager.CanCommand("tpto");
DeleteButton.Disabled = !manager.CanCommand("delete");
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
DeleteButton.OnPressed += _ =>
{
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
{
return;
}
OnDelete?.Invoke(nent);
};
}
}

View File

@@ -5,23 +5,17 @@
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="5"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="EntityIDLabel"
SizeFlagsStretchRatio="5"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-entity-id}"
MouseFilter="Pass"/>
<Label Name="EntityTeleportLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"/>
<Label Name="EntityDeleteLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"/>
</BoxContainer>
</Control>

View File

@@ -10,219 +10,220 @@ using Robust.Client.UserInterface.XAML;
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
[GenerateTypedNameReferences]
public sealed partial class PlayerTab : Control
namespace Content.Client.Administration.UI.Tabs.PlayerTab
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerMan = default!;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
private Header _headerClicked = Header.Username;
private bool _ascending = true;
private bool _showDisconnected;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public PlayerTab()
[GenerateTypedNameReferences]
public sealed partial class PlayerTab : Control
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerMan = default!;
_adminSystem = _entManager.System<AdminSystem>();
_adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
private Header _headerClicked = Header.Username;
private bool _ascending = true;
private bool _showDisconnected;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
ListHeader.OnHeaderClicked += HeaderClicked;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
private void OverlayEnabled()
{
OverlayButton.Pressed = true;
}
private void OverlayDisabled()
{
OverlayButton.Pressed = false;
}
private void OverlayButtonPressed(ButtonEventArgs args)
{
if (args.Button.Pressed)
public PlayerTab()
{
_adminSystem.AdminOverlayOn();
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_adminSystem = _entManager.System<AdminSystem>();
_adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
else
#region Antag Overlay
private void OverlayEnabled()
{
_adminSystem.AdminOverlayOff();
OverlayButton.Pressed = true;
}
}
#endregion
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
private void OverlayDisabled()
{
_adminSystem.PlayerListChanged -= RefreshPlayerList;
_adminSystem.OverlayEnabled -= OverlayEnabled;
_adminSystem.OverlayDisabled -= OverlayDisabled;
OverlayButton.OnPressed -= OverlayButtonPressed;
ListHeader.OnHeaderClicked -= HeaderClicked;
OverlayButton.Pressed = false;
}
}
#region ListContainer
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false;
if (!_showDisconnected && !info.Connected)
return false;
if (IsAllLower(filter))
private void OverlayButtonPressed(ButtonEventArgs args)
{
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
if (args.Button.Pressed)
{
_adminSystem.AdminOverlayOn();
}
else
{
_adminSystem.AdminOverlayOff();
}
}
#endregion
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_adminSystem.PlayerListChanged -= RefreshPlayerList;
_adminSystem.OverlayEnabled -= OverlayEnabled;
_adminSystem.OverlayDisabled -= OverlayDisabled;
OverlayButton.OnPressed -= OverlayButtonPressed;
ListHeader.OnHeaderClicked -= HeaderClicked;
}
}
#region ListContainer
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false;
}
else
{
if (!playerString.Contains(filter))
if (!_showDisconnected && !info.Connected)
return false;
if (IsAllLower(filter))
{
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
return false;
}
else
{
if (!playerString.Contains(filter))
return false;
}
return true;
}
return true;
}
private bool IsAllLower(string input)
{
foreach (var c in input)
private bool IsAllLower(string input)
{
if (char.IsLetter(c) && !char.IsLower(c))
return false;
foreach (var c in input)
{
if (char.IsLetter(c) && !char.IsLower(c))
return false;
}
return true;
}
return true;
}
#endregion
#endregion
#region Header
#region Header
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
private int Compare(PlayerInfo x, PlayerInfo y)
{
if (!_ascending)
private void UpdateHeaderSymbols()
{
(x, y) = (y, x);
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
return _headerClicked switch
private int Compare(PlayerInfo x, PlayerInfo y)
{
Header.Username => Compare(x.Username, y.Username),
Header.Character => Compare(x.CharacterName, y.CharacterName),
Header.Job => Compare(x.StartingJob, y.StartingJob),
Header.Antagonist => x.Antag.CompareTo(y.Antag),
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};
}
if (!_ascending)
{
(x, y) = (y, x);
}
private int Compare(string x, string y)
{
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
private void HeaderClicked(Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
return _headerClicked switch
{
Header.Username => Compare(x.Username, y.Username),
Header.Character => Compare(x.CharacterName, y.CharacterName),
Header.Job => Compare(x.StartingJob, y.StartingJob),
Header.Antagonist => x.Antag.CompareTo(y.Antag),
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};
}
RefreshPlayerList(_adminSystem.PlayerList);
private int Compare(string x, string y)
{
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
private void HeaderClicked(Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
RefreshPlayerList(_adminSystem.PlayerList);
}
#endregion
}
#endregion
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
}
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;

View File

@@ -93,6 +93,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
public void AlertClicked(ProtoId<AlertPrototype> alertType)
{
RaisePredictiveEvent(new ClickAlertEvent(alertType));
RaiseNetworkEvent(new ClickAlertEvent(alertType));
}
}

View File

@@ -31,7 +31,7 @@ public sealed partial class AnomalyScannerMenu : FancyWindow
msg.PushNewline();
var time = NextPulseTime.Value - _timing.CurTime;
var timestring = $"{time.Minutes:00}:{time.Seconds:00}";
msg.AddMarkupOrThrow(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
msg.AddMarkup(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
}
TextDisplay.SetMarkup(msg.ToMarkup());

View File

@@ -1,10 +0,0 @@
using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations;
namespace Content.Client.Atmos.EntitySystems;
[UsedImplicitly]
public sealed class GasMinerSystem : SharedGasMinerSystem
{
}

View File

@@ -16,8 +16,6 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasAnalyzerWindow : DefaultWindow
{
private NetEntity _currentEntity = NetEntity.Invalid;
public GasAnalyzerWindow()
{
RobustXamlLoader.Load(this);
@@ -57,13 +55,6 @@ namespace Content.Client.Atmos.UI
// Device Tab
if (msg.NodeGasMixes.Length > 1)
{
if (_currentEntity != msg.DeviceUid)
{
// when we get new device data switch to the device tab
CTabContainer.CurrentTab = 0;
_currentEntity = msg.DeviceUid;
}
CTabContainer.SetTabVisible(0, true);
CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
// Set up Grid
@@ -152,7 +143,6 @@ namespace Content.Client.Atmos.UI
CTabContainer.SetTabVisible(0, false);
CTabContainer.CurrentTab = 1;
minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
_currentEntity = NetEntity.Invalid;
}
MinSize = minSize;

View File

@@ -57,7 +57,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
/// </summary>
private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f));
private readonly Dictionary<Entity<AmbientSoundComponent>, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
private readonly Dictionary<AmbientSoundComponent, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
private readonly Dictionary<string, int> _playingCount = new();
public bool OverlayEnabled
@@ -107,7 +107,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args)
{
if (!_playingSounds.Remove((uid, component), out var sound))
if (!_playingSounds.Remove(component, out var sound))
return;
_audio.Stop(sound.Stream);
@@ -120,13 +120,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
{
_ambienceVolume = SharedAudioSystem.GainToVolume(value);
foreach (var (ent, values) in _playingSounds)
foreach (var (comp, values) in _playingSounds)
{
if (values.Stream == null)
continue;
var stream = values.Stream;
_audio.SetVolume(stream, _params.Volume + ent.Comp.Volume + _ambienceVolume);
_audio.SetVolume(stream, _params.Volume + comp.Volume + _ambienceVolume);
}
}
private void SetCooldown(float value) => _cooldown = value;
@@ -165,7 +165,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (_gameTiming.CurTime < _targetTime)
return;
_targetTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_cooldown);
_targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
var player = _playerManager.LocalEntity;
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
@@ -190,7 +190,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
private readonly struct QueryState
{
public readonly Dictionary<string, List<(float Importance, Entity<AmbientSoundComponent>)>> SourceDict = new();
public readonly Dictionary<string, List<(float Importance, AmbientSoundComponent)>> SourceDict = new();
public readonly Vector2 MapPos;
public readonly TransformComponent Player;
public readonly SharedTransformSystem TransformSystem;
@@ -224,11 +224,11 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (ambientComp.Sound is SoundPathSpecifier path)
key = path.Path.ToString();
else
key = ((SoundCollectionSpecifier)ambientComp.Sound).Collection ?? string.Empty;
key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
// Prioritize far away & loud sounds.
var importance = range * (ambientComp.Volume + 32);
state.SourceDict.GetOrNew(key).Add((importance, (value.Uid, ambientComp)));
state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
return true;
}
@@ -242,18 +242,16 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
var mapPos = _xformSystem.GetMapCoordinates(playerXform);
// Remove out-of-range ambiences
foreach (var (ent, sound) in _playingSounds)
foreach (var (comp, sound) in _playingSounds)
{
//var entity = comp.Owner;
var owner = ent.Owner;
var comp = ent.Comp;
var entity = comp.Owner;
if (comp.Enabled &&
// Don't keep playing sounds that have changed since.
sound.Sound == comp.Sound &&
query.TryGetComponent(owner, out var xform) &&
query.TryGetComponent(entity, out var xform) &&
xform.MapID == playerXform.MapID &&
!metaQuery.GetComponent(owner).EntityPaused)
!metaQuery.GetComponent(entity).EntityPaused)
{
// TODO: This is just trydistance for coordinates.
var distance = (xform.ParentUid == playerXform.ParentUid)
@@ -265,7 +263,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
}
_audio.Stop(sound.Stream);
_playingSounds.Remove(ent);
_playingSounds.Remove(comp);
_playingCount[sound.Path] -= 1;
if (_playingCount[sound.Path] == 0)
_playingCount.Remove(sound.Path);
@@ -280,7 +278,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
// Add in range ambiences
foreach (var (key, sourceList) in state.SourceDict)
foreach (var (key, sources) in state.SourceDict)
{
if (_playingSounds.Count >= _maxAmbientCount)
break;
@@ -288,14 +286,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound)
continue;
sourceList.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
sources.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
foreach (var (_, sourceEntity) in sourceList)
foreach (var (_, comp) in sources)
{
var uid = sourceEntity.Owner;
var comp = sourceEntity.Comp;
var uid = comp.Owner;
if (_playingSounds.ContainsKey(sourceEntity) ||
if (_playingSounds.ContainsKey(comp) ||
metaQuery.GetComponent(uid).EntityPaused)
continue;
@@ -306,7 +303,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
.WithMaxDistance(comp.Range);
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
_playingSounds[comp] = (stream.Value.Entity, comp.Sound, key);
playingCount++;
if (_playingSounds.Count >= _maxAmbientCount)

View File

@@ -5,70 +5,71 @@ using Content.Shared.Chat;
using Robust.Client.Console;
using Robust.Shared.Utility;
namespace Content.Client.Chat.Managers;
internal sealed class ChatManager : IChatManager
namespace Content.Client.Chat.Managers
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
private ISawmill _sawmill = default!;
public void Initialize()
internal sealed class ChatManager : IChatManager
{
_sawmill = Logger.GetSawmill("chat");
_sawmill.Level = LogLevel.Info;
}
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
public void SendMessage(string text, ChatSelectChannel channel)
{
var str = text.ToString();
switch (channel)
private ISawmill _sawmill = default!;
public void Initialize()
{
case ChatSelectChannel.Console:
// run locally
_consoleHost.ExecuteCommand(text);
break;
_sawmill = Logger.GetSawmill("chat");
_sawmill.Level = LogLevel.Info;
}
case ChatSelectChannel.LOOC:
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
break;
public void SendMessage(string text, ChatSelectChannel channel)
{
var str = text.ToString();
switch (channel)
{
case ChatSelectChannel.Console:
// run locally
_consoleHost.ExecuteCommand(text);
break;
case ChatSelectChannel.OOC:
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.LOOC:
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Admin:
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.OOC:
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Emotes:
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Admin:
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
break;
case ChatSelectChannel.Dead:
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
goto case ChatSelectChannel.Local;
case ChatSelectChannel.Emotes:
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
break;
if (_adminMgr.HasFlag(AdminFlags.Admin))
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
else
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
break;
case ChatSelectChannel.Dead:
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
goto case ChatSelectChannel.Local;
// TODO sepearate radio and say into separate commands.
case ChatSelectChannel.Radio:
case ChatSelectChannel.Local:
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
break;
if (_adminMgr.HasFlag(AdminFlags.Admin))
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
else
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
break;
case ChatSelectChannel.Whisper:
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
break;
// TODO sepearate radio and say into separate commands.
case ChatSelectChannel.Radio:
case ChatSelectChannel.Local:
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
break;
default:
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
case ChatSelectChannel.Whisper:
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
break;
default:
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
}
}
}
}

View File

@@ -16,7 +16,6 @@ namespace Content.Client.Chat.UI
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
private readonly SharedTransformSystem _transformSystem;
public enum SpeechType : byte
{
@@ -84,7 +83,6 @@ namespace Content.Client.Chat.UI
{
IoCManager.InjectDependencies(this);
_senderEntity = senderEntity;
_transformSystem = _entityManager.System<SharedTransformSystem>();
// Use text clipping so new messages don't overlap old ones being pushed up.
RectClipContent = true;
@@ -142,7 +140,7 @@ namespace Content.Client.Chat.UI
}
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
var worldPos = _transformSystem.GetWorldPosition(xform) + offset;
var worldPos = xform.WorldPosition + offset;
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);

View File

@@ -1,5 +1,4 @@
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -10,15 +9,11 @@ namespace Content.Client.Chemistry.UI
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
{
private IEntityManager _entManager;
private EntityUid _owner;
[ViewVariables]
private TransferAmountWindow? _window;
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_owner = owner;
_entManager = IoCManager.Resolve<IEntityManager>();
}
protected override void Open()
@@ -26,9 +21,6 @@ namespace Content.Client.Chemistry.UI
base.Open();
_window = this.CreateWindow<TransferAmountWindow>();
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
{
if (int.TryParse(_window.AmountLineEdit.Text, out var i))

View File

@@ -6,10 +6,6 @@
<BoxContainer Orientation="Horizontal">
<LineEdit Name="AmountLineEdit" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc 'ui-transfer-amount-line-edit-placeholder'}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="MinimumAmount" Access="Public" HorizontalExpand="True" />
<Label Name="MaximumAmount" Access="Public" />
</BoxContainer>
<Button Name="ApplyButton" Access="Public" Text="{Loc 'ui-transfer-amount-apply'}"/>
</BoxContainer>
</DefaultWindow>

View File

@@ -8,29 +8,9 @@ namespace Content.Client.Chemistry.UI
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
{
private int _max = Int32.MaxValue;
private int _min = 1;
public TransferAmountWindow()
{
RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
}
}
}

View File

@@ -2,8 +2,6 @@ using Content.Client.Items.Systems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Hands;
using Content.Shared.Item;
using Content.Shared.Rounding;
@@ -22,7 +20,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
base.Initialize();
SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals);
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetEquipmentVisualsEvent>(OnGetClothingVisuals);
}
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
@@ -177,41 +174,4 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
args.Layers.Add((key, layer));
}
}
private void OnGetClothingVisuals(Entity<SolutionContainerVisualsComponent> ent, ref GetEquipmentVisualsEvent args)
{
if (ent.Comp.EquippedFillBaseName == null)
return;
if (!TryComp<AppearanceComponent>(ent, out var appearance))
return;
if (!TryComp<ClothingComponent>(ent, out var clothing))
return;
if (!AppearanceSystem.TryGetData<float>(ent, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
var closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, ent.Comp.EquippedMaxFillLevels + 1);
if (closestFillSprite > 0)
{
var layer = new PrototypeLayerData();
var equippedPrefix = clothing.EquippedPrefix == null ? $"equipped-{args.Slot}" : $" {clothing.EquippedPrefix}-equipped-{args.Slot}";
var key = equippedPrefix + ent.Comp.EquippedFillBaseName + closestFillSprite;
// Make sure the sprite state is valid so we don't show a big red error message
// This saves us from having to make fill level sprites for every possible slot the item could be in (including pockets).
if (!TryComp<SpriteComponent>(ent, out var sprite) || sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(key, out _))
return;
layer.State = key;
if (ent.Comp.ChangeColor && AppearanceSystem.TryGetData<Color>(ent, SolutionContainerVisuals.Color, out var color, appearance))
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}

View File

@@ -1,17 +1,148 @@
namespace Content.Client.Clickable;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using static Robust.Client.GameObjects.SpriteComponent;
using Direction = Robust.Shared.Maths.Direction;
[RegisterComponent]
public sealed partial class ClickableComponent : Component
namespace Content.Client.Clickable
{
[DataField] public DirBoundData? Bounds;
[DataDefinition]
public sealed partial class DirBoundData
[RegisterComponent]
public sealed partial class ClickableComponent : Component
{
[DataField] public Box2 All;
[DataField] public Box2 North;
[DataField] public Box2 South;
[DataField] public Box2 East;
[DataField] public Box2 West;
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
[DataField("bounds")] public DirBoundData? Bounds;
/// <summary>
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
/// </summary>
/// <param name="worldPos">The world position that was clicked.</param>
/// <param name="drawDepth">
/// The draw depth for the sprite that captured the click.
/// </param>
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(SpriteComponent sprite, TransformComponent transform, EntityQuery<TransformComponent> xformQuery, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
{
if (!sprite.Visible)
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
drawDepth = sprite.DrawDepth;
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound(sprite, relativeRotation, localPos))
return true;
// Next check each individual sprite layer using automatically computed click maps.
foreach (var spriteLayer in sprite.AllLayers)
{
// TODO: Move this to a system and also use SpriteSystem.IsVisible instead.
if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null)
{
continue;
}
// Check the layer's texture, if it has one
if (layer.Texture != null)
{
// Convert to image coordinates
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
return true;
}
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
continue;
var dir = Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
// This **can** differ from the dir defined before, but can also just be the same.
if (sprite.EnableDirectionOverride)
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
dir = dir.OffsetRsiDir(layer.DirOffset);
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
return true;
}
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
public bool CheckDirBound(SpriteComponent sprite, Angle relativeRotation, Vector2 localPos)
{
if (Bounds == null)
return false;
// These explicit bounds only work for either 1 or 4 directional sprites.
// This would be the orientation of a 4-directional sprite.
var direction = relativeRotation.GetCardinalDir();
var modLocalPos = sprite.NoRotation
? localPos
: direction.ToAngle().RotateVec(localPos);
// First, check the bounding box that is valid for all orientations
if (Bounds.All.Contains(modLocalPos))
return true;
// Next, get and check the appropriate bounding box for the current sprite orientation
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
{
Direction.East => Bounds.East,
Direction.North => Bounds.North,
Direction.South => Bounds.South,
Direction.West => Bounds.West,
_ => throw new InvalidOperationException()
};
return boundsForDir.Contains(modLocalPos);
}
[DataDefinition]
public sealed partial class DirBoundData
{
[DataField("all")] public Box2 All;
[DataField("north")] public Box2 North;
[DataField("south")] public Box2 South;
[DataField("east")] public Box2 East;
[DataField("west")] public Box2 West;
}
}
}

View File

@@ -1,168 +0,0 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
namespace Content.Client.Clickable;
/// <summary>
/// Handles click detection for sprites.
/// </summary>
public sealed class ClickableSystem : EntitySystem
{
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
[Dependency] private readonly SharedTransformSystem _transforms = default!;
[Dependency] private readonly SpriteSystem _sprites = default!;
private EntityQuery<ClickableComponent> _clickableQuery;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_clickableQuery = GetEntityQuery<ClickableComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
/// <summary>
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
/// </summary>
/// <param name="worldPos">The world position that was clicked.</param>
/// <param name="drawDepth">
/// The draw depth for the sprite that captured the click.
/// </param>
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(Entity<ClickableComponent?, SpriteComponent, TransformComponent?> entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
{
if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false))
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp3))
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
var sprite = entity.Comp2;
var transform = entity.Comp3;
if (!sprite.Visible)
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
drawDepth = sprite.DrawDepth;
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = _transforms.GetWorldPositionRotation(transform);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
var cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound((entity.Owner, entity.Comp1, entity.Comp2), relativeRotation, localPos))
return true;
// Next check each individual sprite layer using automatically computed click maps.
foreach (var spriteLayer in sprite.AllLayers)
{
if (spriteLayer is not SpriteComponent.Layer layer || !_sprites.IsVisible(layer))
{
continue;
}
// Check the layer's texture, if it has one
if (layer.Texture != null)
{
// Convert to image coordinates
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
return true;
}
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
continue;
var dir = SpriteComponent.Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
// This **can** differ from the dir defined before, but can also just be the same.
if (sprite.EnableDirectionOverride)
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
dir = dir.OffsetRsiDir(layer.DirOffset);
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
return true;
}
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
public bool CheckDirBound(Entity<ClickableComponent, SpriteComponent> entity, Angle relativeRotation, Vector2 localPos)
{
var clickable = entity.Comp1;
var sprite = entity.Comp2;
if (clickable.Bounds == null)
return false;
// These explicit bounds only work for either 1 or 4 directional sprites.
// This would be the orientation of a 4-directional sprite.
var direction = relativeRotation.GetCardinalDir();
var modLocalPos = sprite.NoRotation
? localPos
: direction.ToAngle().RotateVec(localPos);
// First, check the bounding box that is valid for all orientations
if (clickable.Bounds.All.Contains(modLocalPos))
return true;
// Next, get and check the appropriate bounding box for the current sprite orientation
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
{
Direction.East => clickable.Bounds.East,
Direction.North => clickable.Bounds.North,
Direction.South => clickable.Bounds.South,
Direction.West => clickable.Bounds.West,
_ => throw new InvalidOperationException()
};
return boundsForDir.Contains(modLocalPos);
}
}

View File

@@ -1,6 +0,0 @@
using Content.Shared.Clothing;
namespace Content.Client.Clothing.Systems;
/// <inheritdoc/>
public sealed class CursedMaskSystem : SharedCursedMaskSystem;

View File

@@ -1,4 +1,3 @@
using Content.Client.Actions;
using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Shared.Administration;
@@ -62,3 +61,27 @@ public sealed class LoadActionsCommand : LocalizedCommands
}
}
}
[AnyCommand]
public sealed class LoadMappingActionsCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
public const string CommandName = "loadmapacts";
public override string Command => CommandName;
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
try
{
_entitySystemManager.GetEntitySystem<MappingSystem>().LoadMappingActions();
}
catch
{
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
}
}
}

View File

@@ -30,7 +30,7 @@ public sealed class HideMechanismsCommand : LocalizedCommands
sprite.ContainerOccluded = false;
var tempParent = uid;
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
while (containerSys.TryGetContainingContainer(tempParent, out var container))
{
if (!container.ShowContents)
{

View File

@@ -1,9 +1,6 @@
using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Client.Markers;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Shared.Console;
namespace Content.Client.Commands;
@@ -13,7 +10,6 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
public override string Command => "mappingclientsidesetup";
@@ -25,8 +21,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
_lightManager.Enabled = false;
shell.ExecuteCommand("showsubfloorforever");
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
shell.ExecuteCommand(ShowSubFloorForever.CommandName);
shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
}
}
}

View File

@@ -130,7 +130,7 @@ namespace Content.Client.Communications.UI
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
var infoText = Loc.GetString($"comms-console-menu-time-remaining",
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
("time", diff.TotalSeconds.ToString(CultureInfo.CurrentCulture)));
CountdownLabel.SetMessage(infoText);
}
}

View File

@@ -2,7 +2,6 @@ using System.Numerics;
using System.Threading;
using Content.Client.CombatMode;
using Content.Client.Gameplay;
using Content.Client.Mapping;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
@@ -17,7 +16,7 @@ namespace Content.Client.ContextMenu.UI
/// <remarks>
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
/// </remarks>
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnStateEntered<MappingState>, IOnStateExited<MappingState>
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
{
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
@@ -43,51 +42,18 @@ namespace Content.Client.ContextMenu.UI
public Action<ContextMenuElement>? OnSubMenuOpened;
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
private bool _setup;
public void OnStateEntered(GameplayState state)
{
Setup();
}
public void OnStateExited(GameplayState state)
{
Shutdown();
}
public void OnStateEntered(MappingState state)
{
Setup();
}
public void OnStateExited(MappingState state)
{
Shutdown();
}
public void Setup()
{
if (_setup)
return;
_setup = true;
RootMenu = new(this, null);
RootMenu.OnPopupHide += Close;
Menus.Push(RootMenu);
}
public void Shutdown()
public void OnStateExited(GameplayState state)
{
if (!_setup)
return;
_setup = false;
Close();
RootMenu.OnPopupHide -= Close;
RootMenu.Dispose();
RootMenu = default!;
}
/// <summary>

View File

@@ -51,7 +51,7 @@ public sealed class CrewManifestSection : BoxContainer
title.SetMessage(entry.JobTitle);
if (prototypeManager.TryIndex<JobIconPrototype>(entry.JobIcon, out var jobIcon))
if (prototypeManager.TryIndex<StatusIconPrototype>(entry.JobIcon, out var jobIcon))
{
var icon = new TextureRect()
{

View File

@@ -7,12 +7,10 @@ using Content.Shared.Security;
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.CriminalRecords;
@@ -38,6 +36,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
public Action<SecurityStatus, string>? OnDialogConfirmed;
private uint _maxLength;
private bool _isPopulating;
private bool _access;
private uint? _selectedKey;
private CriminalRecord? _selectedRecord;
@@ -75,7 +74,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
RecordListing.OnItemSelected += args =>
{
if (RecordListing[args.ItemIndex].Metadata is not uint cast)
if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
return;
OnKeySelected?.Invoke(cast);
@@ -83,7 +82,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
RecordListing.OnItemDeselected += _ =>
{
OnKeySelected?.Invoke(null);
if (!_isPopulating)
OnKeySelected?.Invoke(null);
};
FilterType.OnItemSelected += eventArgs =>
@@ -133,8 +133,13 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
FilterType.SelectId((int)_currentFilterType);
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
PopulateRecordListing(state.RecordListing);
// set up the records listing panel
RecordListing.Clear();
var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0;
NoRecords.Visible = !hasRecords;
if (hasRecords)
PopulateRecordListing(state.RecordListing!);
// set up the selected person's record
var selected = _selectedKey != null;
@@ -162,59 +167,19 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
}
}
private void PopulateRecordListing(Dictionary<uint, string>? listing)
private void PopulateRecordListing(Dictionary<uint, string> listing)
{
if (listing == null)
{
RecordListing.Clear();
return;
}
_isPopulating = true;
var entries = listing.ToList();
entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal));
// `entries` now contains the definitive list of items which should be in
// our list of records and is in the order we want to present those items.
// Walk through the existing items in RecordListing and in the updated listing
// in parallel to synchronize the items in RecordListing with `entries`.
int i = RecordListing.Count - 1;
int j = entries.Count - 1;
while(i >= 0 && j >= 0)
foreach (var (key, name) in listing)
{
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
if (strcmp == 0)
{
// This item exists in both RecordListing and `entries`. Nothing to do.
i--;
j--;
}
else if (strcmp > 0)
{
// Item exists in RecordListing, but not in `entries`. Remove it.
RecordListing.RemoveAt(i);
i--;
}
else if (strcmp < 0)
{
// A new entry which doesn't exist in RecordListing. Create it.
RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
j--;
}
var item = RecordListing.AddItem(name);
item.Metadata = key;
item.Selected = key == _selectedKey;
}
_isPopulating = false;
// Any remaining items in RecordListing don't exist in `entries`, so remove them
while (i >= 0)
{
RecordListing.RemoveAt(i);
i--;
}
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
while (j >= 0)
{
RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
j--;
}
RecordListing.SortItemsByText();
}
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
@@ -246,7 +211,10 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private void FilterListingOfRecords(string text = "")
{
OnFiltersChanged?.Invoke(_currentFilterType, text);
if (!_isPopulating)
{
OnFiltersChanged?.Invoke(_currentFilterType, text);
}
}
private void SetStatus(SecurityStatus status)

View File

@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays;
@@ -17,7 +16,7 @@ public sealed class DecalPlacementOverlay : Overlay
private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
{
@@ -25,7 +24,6 @@ public sealed class DecalPlacementOverlay : Overlay
_placement = placement;
_transform = transform;
_sprite = sprite;
ZIndex = 1000;
}
protected override void Draw(in OverlayDrawArgs args)
@@ -57,7 +55,7 @@ public sealed class DecalPlacementOverlay : Overlay
if (snap)
{
localPos = localPos.Floored() + grid.TileSizeHalfVector;
localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
}
// Nothing uses snap cardinals so probably don't need preview?

View File

@@ -1,80 +0,0 @@
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Drowsiness;
public sealed class DrowsinessOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
private readonly ShaderInstance _drowsinessShader;
public float CurrentPower = 0.0f;
private const float PowerDivisor = 250.0f;
private const float Intensity = 0.2f; // for adjusting the visual scale
private float _visualScale = 0; // between 0 and 1
public DrowsinessOverlay()
{
IoCManager.InjectDependencies(this);
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
}
protected override void FrameUpdate(FrameEventArgs args)
{
var playerEntity = _playerManager.LocalEntity;
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
return;
var curTime = _timing.CurTime;
var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
}
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;
_visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f);
return _visualScale > 0;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;
var handle = args.WorldHandle;
_drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_drowsinessShader.SetParameter("Strength", _visualScale * Intensity);
handle.UseShader(_drowsinessShader);
handle.DrawRect(args.WorldBounds, Color.White);
handle.UseShader(null);
}
}

View File

@@ -1,53 +0,0 @@
using Content.Shared.Drowsiness;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
namespace Content.Client.Drowsiness;
public sealed class DrowsinessSystem : SharedDrowsinessSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private DrowsinessOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DrowsinessComponent, ComponentInit>(OnDrowsinessInit);
SubscribeLocalEvent<DrowsinessComponent, ComponentShutdown>(OnDrowsinessShutdown);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
_overlayMan.AddOverlay(_overlay);
}
private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid)
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
}

View File

@@ -16,19 +16,15 @@ public sealed class ExplosionOverlay : Overlay
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly SharedTransformSystem _transformSystem;
private SharedAppearanceSystem _appearance;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private ShaderInstance _shader;
public ExplosionOverlay(SharedAppearanceSystem appearanceSystem)
public ExplosionOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _proto.Index<ShaderPrototype>("unshaded").Instance();
_transformSystem = _entMan.System<SharedTransformSystem>();
_appearance = appearanceSystem;
}
protected override void Draw(in OverlayDrawArgs args)
@@ -37,14 +33,15 @@ public sealed class ExplosionOverlay : Overlay
drawHandle.UseShader(_shader);
var xforms = _entMan.GetEntityQuery<TransformComponent>();
var query = _entMan.EntityQueryEnumerator<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent>();
var query = _entMan
.EntityQuery<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent, AppearanceComponent>(true);
while (query.MoveNext(out var uid, out var visuals, out var textures))
foreach (var (visuals, textures, appearance) in query)
{
if (visuals.Epicenter.MapId != args.MapId)
continue;
if (!_appearance.TryGetData(uid, ExplosionAppearanceData.Progress, out int index))
if (!appearance.TryGetData(ExplosionAppearanceData.Progress, out int index))
continue;
index = Math.Min(index, visuals.Intensity.Count - 1);
@@ -70,7 +67,7 @@ public sealed class ExplosionOverlay : Overlay
continue;
var xform = xforms.GetComponent(gridId);
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(xform, xforms);
var (_, _, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xforms);
gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2);
drawHandle.SetTransform(worldMatrix);

View File

@@ -20,7 +20,6 @@ public sealed class ExplosionOverlaySystem : EntitySystem
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!;
[Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
@@ -29,7 +28,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentInit>(OnExplosionInit);
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentRemove>(OnCompRemove);
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentHandleState>(OnExplosionHandleState);
_overlayMan.AddOverlay(new ExplosionOverlay(_appearance));
_overlayMan.AddOverlay(new ExplosionOverlay());
}
private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent component, ref ComponentHandleState args)

View File

@@ -1,8 +0,0 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion.EntitySystems;
public sealed class ExplosionSystem : SharedExplosionSystem
{
}

View File

@@ -14,7 +14,6 @@ public sealed class PuddleOverlay : Overlay
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private readonly PuddleDebugOverlaySystem _debugOverlaySystem;
private readonly SharedTransformSystem _transformSystem;
private readonly Color _heavyPuddle = new(0, 255, 255, 50);
private readonly Color _mediumPuddle = new(0, 150, 255, 50);
@@ -30,7 +29,6 @@ public sealed class PuddleOverlay : Overlay
_debugOverlaySystem = _entitySystemManager.GetEntitySystem<PuddleDebugOverlaySystem>();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
_transformSystem = _entityManager.System<SharedTransformSystem>();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -58,7 +56,7 @@ public sealed class PuddleOverlay : Overlay
continue;
var gridXform = xformQuery.GetComponent(gridId);
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
var (_, _, worldMatrix, invWorldMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
gridBounds = invWorldMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
drawHandle.SetTransform(worldMatrix);
@@ -91,7 +89,7 @@ public sealed class PuddleOverlay : Overlay
continue;
var gridXform = xformQuery.GetComponent(gridId);
var (_, _, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridXform, xformQuery);
var (_, _, matrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(xformQuery);
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(mapGrid.TileSize * 2);
foreach (var debugOverlayData in _debugOverlaySystem.GetData(gridId))

View File

@@ -54,16 +54,10 @@ namespace Content.Client.Forensics
}
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
foreach (var dna in msg.TouchDNAs)
foreach (var dna in msg.DNAs)
{
text.AppendLine(dna);
}
foreach (var dna in msg.SolutionDNAs)
{
if (msg.TouchDNAs.Contains(dna))
continue;
text.AppendLine(dna);
}
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-residues"));
foreach (var residue in msg.Residues)

View File

@@ -2,7 +2,6 @@ using System.Linq;
using System.Numerics;
using Content.Client.Clickable;
using Content.Client.UserInterface;
using Content.Client.Viewport;
using Content.Shared.Input;
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
@@ -14,13 +13,11 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Console;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using YamlDotNet.Serialization.TypeInspectors;
namespace Content.Client.Gameplay
{
@@ -101,15 +98,7 @@ namespace Content.Client.Gameplay
public EntityUid? GetClickedEntity(MapCoordinates coordinates)
{
return GetClickedEntity(coordinates, _eyeManager.CurrentEye);
}
public EntityUid? GetClickedEntity(MapCoordinates coordinates, IEye? eye)
{
if (eye == null)
return null;
var first = GetClickableEntities(coordinates, eye).FirstOrDefault();
var first = GetClickableEntities(coordinates).FirstOrDefault();
return first.IsValid() ? first : null;
}
@@ -121,20 +110,6 @@ namespace Content.Client.Gameplay
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates)
{
return GetClickableEntities(coordinates, _eyeManager.CurrentEye);
}
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates, IEye? eye)
{
/*
* TODO:
* 1. Stuff like MeleeWeaponSystem need an easy way to hook into viewport specific entities / entities under mouse
* 2. Cleanup the mess around InteractionOutlineSystem + below the keybind click detection.
*/
if (eye == null)
return Array.Empty<EntityUid>();
// Find all the entities intersecting our click
var spriteTree = _entityManager.EntitySysManager.GetEntitySystem<SpriteTreeSystem>();
var entities = spriteTree.QueryAabb(coordinates.MapId, Box2.CenteredAround(coordinates.Position, new Vector2(1, 1)));
@@ -142,12 +117,15 @@ namespace Content.Client.Gameplay
// Check the entities against whether or not we can click them
var foundEntities = new List<(EntityUid, int, uint, float)>(entities.Count);
var clickQuery = _entityManager.GetEntityQuery<ClickableComponent>();
var clickables = _entityManager.System<ClickableSystem>();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
// TODO: Smelly
var eye = _eyeManager.CurrentEye;
foreach (var entity in entities)
{
if (clickQuery.TryGetComponent(entity.Uid, out var component) &&
clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
component.CheckClick(entity.Component, entity.Transform, xformQuery, coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
{
foundEntities.Add((entity.Uid, drawDepthClicked, renderOrder, bottom));
}
@@ -210,15 +188,7 @@ namespace Content.Client.Gameplay
if (args.Viewport is IViewportControl vp && kArgs.PointerLocation.IsValid)
{
var mousePosWorld = vp.PixelToMap(kArgs.PointerLocation.Position);
if (vp is ScalingViewport svp)
{
entityToClick = GetClickedEntity(mousePosWorld, svp.Eye);
}
else
{
entityToClick = GetClickedEntity(mousePosWorld);
}
entityToClick = GetClickedEntity(mousePosWorld);
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
grid.MapToGrid(mousePosWorld) :

View File

@@ -22,7 +22,7 @@ public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface
_ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage;
}
private void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
public void SendGhostRoleRadioMessage(ProtoId<GhostRolePrototype> protoId)
{
SendMessage(new GhostRoleRadioMessage(protoId));
}

View File

@@ -1,6 +1,7 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Ghost.Roles;
using Content.Shared.Ghost.Roles.Components;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -32,7 +33,7 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
private void RefreshUI()
{
// The main control that will contain all the clickable options
// The main control that will contain all of the clickable options
var main = FindControl<RadialContainer>("Main");
// The purpose of this radial UI is for ghost role radios that allow you to select
@@ -69,7 +70,7 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
if (_prototypeManager.TryIndex(ghostRoleProto.IconPrototype, out var iconProto))
entProtoView.SetPrototype(iconProto);
else
entProtoView.SetPrototype(ghostRoleProto.EntityPrototype);
entProtoView.SetPrototype(comp.Prototype);
button.AddChild(entProtoView);
main.AddChild(button);

View File

@@ -1,5 +1,4 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
HorizontalExpand="True"
@@ -27,18 +26,5 @@
VerticalAlignment="Center"
Access="Public"
Visible="False"/>
<!-- CP14 random reactions begin -->
<BoxContainer Name="RandomVariations"
Orientation="Vertical"
VerticalExpand="True"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<controls:SplitBar MinHeight="10">
</controls:SplitBar>
<Label Name="RandomVariationsLabel" Text="{Loc 'cp14-guidebook-random-variations-title'}" Visible="False"/>
<controls:SplitBar MinHeight="10">
</controls:SplitBar>
</BoxContainer>
<!-- CP14 random reactions end -->
</BoxContainer>
</BoxContainer>

View File

@@ -1,6 +1,5 @@
using System.Linq;
using Content.Client.Message;
using Content.Client.UserInterface.Controls; // CP14 random reactions
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Atmos.Prototypes;
using Content.Shared.Chemistry.Components;
@@ -38,36 +37,13 @@ public sealed partial class GuideReagentReaction : BoxContainer, ISearchableCont
var reactantsLabel = ReactantsLabel;
SetReagents(prototype.Reactants, ref reactantsLabel, protoMan);
var productLabel = ProductsLabel;
var products = new Dictionary<string, FixedPoint2>(prototype._products); // CP14 random reactions
var products = new Dictionary<string, FixedPoint2>(prototype.Products);
foreach (var (reagent, reactantProto) in prototype.Reactants)
{
if (reactantProto.Catalyst)
products.Add(reagent, reactantProto.Amount);
}
SetReagents(products, ref productLabel, protoMan);
// CP14 random reagents begin
foreach (var randomVariation in prototype.Cp14RandomProducts)
{
// If there aren't any variations, this label will be not visible
RandomVariationsLabel.Visible = true;
var randomProductLabel = new RichTextLabel {
HorizontalAlignment=HAlignment.Left,
VerticalAlignment=VAlignment.Center,
};
var randomProducts = new Dictionary<string, FixedPoint2>(randomVariation);
RandomVariations.AddChild(randomProductLabel);
RandomVariations.AddChild(new SplitBar
{
MinHeight = 10,
});
foreach (var (reagent, reactantProto) in prototype.Reactants)
{
if (reactantProto.Catalyst)
randomProducts.Add(reagent, reactantProto.Amount);
}
SetReagents(randomProducts, ref randomProductLabel, protoMan);
}
// CP14 random reagents end
var mixingCategories = new List<MixingCategoryPrototype>();
if (prototype.MixingCategories != null)

View File

@@ -1,41 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Margin="5 5 5 5"
MinHeight="200">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="White" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BackgroundColor="DarkRed" BorderColor="Black" />
</PanelContainer.PanelOverride>
<Label Margin="5" StyleClasses="bold" Text="{Loc 'guidebook-parser-error'}" />
</PanelContainer>
<OutputPanel Margin="5" MinHeight="75" VerticalExpand="True" Name="Original">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BorderColor="Gray"
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
</OutputPanel.StyleBoxOverride>
</OutputPanel>
<Collapsible Margin="5" MinHeight="75" VerticalExpand="True">
<CollapsibleHeading Title="{Loc 'guidebook-error-message' }" />
<CollapsibleBody VerticalExpand="True">
<OutputPanel Name="Error" VerticalExpand="True" MinHeight="100">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
</OutputPanel.StyleBoxOverride>
</OutputPanel>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -1,23 +0,0 @@
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Guidebook.Controls;
[UsedImplicitly] [GenerateTypedNameReferences]
public sealed partial class GuidebookError : BoxContainer
{
public GuidebookError()
{
RobustXamlLoader.Load(this);
}
public GuidebookError(string original, string? error) : this()
{
Original.AddText(original);
if (error is not null)
Error.AddText(error);
}
}

View File

@@ -4,10 +4,12 @@ using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using Content.Client.UserInterface.Systems.Info;
using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
@@ -16,18 +18,15 @@ namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
private readonly ISawmill _sawmill;
public GuidebookWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = Logger.GetSawmill("Guidebook");
Tree.OnSelectedItemChanged += OnSelectionChanged;
@@ -37,20 +36,6 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
};
}
public void HandleClick(string link)
{
if (!_entries.TryGetValue(link, out var entry))
return;
if (Tree.TryGetIndexFromMetadata(entry, out var index))
{
Tree.ExpandParentEntries(index.Value);
Tree.SetSelectedIndex(index);
}
else
ShowGuide(entry);
}
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
@@ -86,9 +71,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
{
// The guidebook will automatically display the in-guidebook error if it fails
_sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
}
}
@@ -140,10 +124,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
entry.Children = sortedChildren;
}
entries.ExceptWith(entry.Children);
}
rootEntries = entries.ToList();
}
@@ -153,26 +135,22 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
}
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null,
ProtoId<GuideEntryPrototype>? forcedRoot = null)
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
{
Tree.Clear();
HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();
var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots))
{
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter
AddEntry(entry.Id, parent, addedEntries);
}
Tree.SetAllExpanded(true);
}
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id,
TreeItem? parent,
HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
@@ -202,6 +180,22 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
return item;
}
public void HandleClick(string link)
{
if (!_entries.TryGetValue(link, out var entry))
return;
if (Tree.TryGetIndexFromMetadata(entry, out var index))
{
Tree.ExpandParentEntries(index.Value);
Tree.SetSelectedIndex(index);
}
else
{
ShowGuide(entry);
}
}
private void HandleFilter()
{
var emptySearch = SearchBar.Text.Trim().Length == 0;
@@ -215,5 +209,6 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
element.SetHiddenState(true, SearchBar.Text.Trim());
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using Content.Client.Guidebook.Controls;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin;
@@ -8,7 +7,6 @@ using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
using Robust.Shared.Utility;
using static Pidgin.Parser;
namespace Content.Client.Guidebook;
@@ -24,10 +22,8 @@ public sealed partial class DocumentParsingManager
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
private Parser<char, Control> _controlParser = default!;
private ISawmill _sawmill = default!;
private Parser<char, Control> _tagParser = default!;
private Parser<char, Control> _controlParser = default!;
public Parser<char, IEnumerable<Control>> ControlParser = default!;
public void Initialize()
@@ -36,8 +32,7 @@ public sealed partial class DocumentParsingManager
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
.Before(SkipWhitespaces);
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser).Before(SkipWhitespaces);
foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
{
@@ -45,8 +40,6 @@ public sealed partial class DocumentParsingManager
}
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
_sawmill = Logger.GetSawmill("Guidebook");
}
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
@@ -75,57 +68,37 @@ public sealed partial class DocumentParsingManager
}
catch (Exception e)
{
_sawmill.Error($"Encountered error while generating markup controls: {e}");
control.AddChild(new GuidebookError(text, e.ToStringBetter()));
if (log)
Logger.Error($"Encountered error while generating markup controls: {e}");
return false;
}
return true;
}
private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox)
{
return Map(
(args, controls) =>
{
try
{
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
if (!tag.TryParseTag(args, out var control))
{
_sawmill.Error($"Failed to parse {tagId} args");
return new GuidebookError(args.ToString() ?? tagId, $"Failed to parse {tagId} args");
}
private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox) => Map(
(args, controls) =>
{
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
if (!tag.TryParseTag(args, out var control))
{
Logger.Error($"Failed to parse {tagId} args");
return new Control();
}
foreach (var child in controls)
{
control.AddChild(child);
}
return control;
}
catch (Exception e)
{
var output = args.Aggregate(string.Empty,
(current, pair) => current + $"{pair.Key}=\"{pair.Value}\" ");
_sawmill.Error($"Tag: {tagId} \n Arguments: {output}/>");
return new GuidebookError($"Tag: {tagId}\nArguments: {output}", e.ToString());
}
},
ParseTagArgs(tagId),
TagContentParser(tagId))
.Labelled($"{tagId} control");
}
foreach (var child in controls)
{
control.AddChild(child);
}
return control;
},
ParseTagArgs(tagId),
TagContentParser(tagId)).Labelled($"{tagId} control");
// Parse a bunch of controls until we encounter a matching closing tag.
private Parser<char, IEnumerable<Control>> TagContentParser(string tag)
{
return OneOf(
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
);
}
private Parser<char, IEnumerable<Control>> TagContentParser(string tag) =>
OneOf(
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
);
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using Content.Client.Guidebook.Controls;
using Pidgin;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -15,142 +14,92 @@ public sealed partial class DocumentParsingManager
{
private const string ListBullet = " ";
// Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
private static readonly Parser<char, char> TryEscapedChar = Try(Char('\\')
.Then(OneOf(
Try(Char('<')),
Try(Char('>')),
Try(Char('\\')),
Try(Char('-')),
Try(Char('=')),
Try(Char('"')),
Try(Char(' ')),
Try(Char('n')).ThenReturn('\n'),
Try(Char('t')).ThenReturn('\t')
)));
#region Text Parsing
#region Basic Text Parsing
// Try look for an escaped character. If found, skip the escaping slash and return the character.
private static readonly Parser<char, char> TryEscapedChar = Try(Char('\\').Then(OneOf(
Try(Char('<')),
Try(Char('>')),
Try(Char('\\')),
Try(Char('-')),
Try(Char('=')),
Try(Char('"')),
Try(Char(' ')),
Try(Char('n')).ThenReturn('\n'),
Try(Char('t')).ThenReturn('\t')
)));
private static readonly Parser<char, Unit> SkipNewline = Whitespace.SkipUntil(Char('\n'));
private static readonly Parser<char, char> TrySingleNewlineToSpace =
Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
private static readonly Parser<char, char> TrySingleNewlineToSpace = Try(SkipNewline).Then(SkipWhitespaces).ThenReturn(' ');
private static readonly Parser<char, char> TextChar = OneOf(
TryEscapedChar, // consume any backslashed being used to escape text
TrySingleNewlineToSpace, // turn single newlines into spaces
Any // just return the character.
);
private static readonly Parser<char, char> QuotedTextChar = OneOf(TryEscapedChar, Any);
private static readonly Parser<char, string> QuotedText =
Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
private static readonly Parser<char, Unit> TryStartList =
Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartParagraph =
Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryLookTextEnd =
Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
private static readonly Parser<char, string> TextParser =
TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
private static readonly Parser<char, Control> TextControlParser = Try(Map<char, string, Control>(text =>
{
var rt = new RichTextLabel
{
HorizontalExpand = true,
Margin = new Thickness(0, 0, 0, 15.0f)
};
var msg = new FormattedMessage();
// THANK YOU RICHTEXT VERY COOL
// (text doesn't default to white).
msg.PushColor(Color.White);
// If the parsing fails, don't throw an error and instead make an inline error message
string? error;
if (!msg.TryAddMarkup(text, out error))
{
Logger.GetSawmill("Guidebook").Error("Failed to parse RichText in Guidebook");
return new GuidebookError(text, error);
}
msg.Pop();
rt.SetMessage(msg);
return rt;
},
TextParser)
.Cast<Control>())
.Labelled("richtext");
private static readonly Parser<char, Control> HeaderControlParser = Try(Char('#'))
.Then(SkipWhitespaces.Then(Map(text => new Label
{
Text = text,
StyleClasses = { "LabelHeadingBigger" }
},
AnyCharExcept('\n').AtLeastOnceString())
.Cast<Control>()))
.Labelled("header");
private static readonly Parser<char, Control> SubHeaderControlParser = Try(String("##"))
.Then(SkipWhitespaces.Then(Map(text => new Label
{
Text = text,
StyleClasses = { "LabelHeading" }
},
AnyCharExcept('\n').AtLeastOnceString())
.Cast<Control>()))
.Labelled("subheader");
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
private static readonly Parser<char, Control> ListControlParser = Try(Char('-'))
.Then(SkipWhitespaces)
.Then(Map(
control => new BoxContainer
{
Children = { new Label { Text = ListBullet, VerticalAlignment = VAlignment.Top }, control },
Orientation = LayoutOrientation.Horizontal
},
TextControlParser)
.Cast<Control>())
.Labelled("list");
#region Text Parsing
#region Basic Text Parsing
// Try look for an escaped character. If found, skip the escaping slash and return the character.
);
// like TextChar, but not skipping whitespace around newlines
private static readonly Parser<char, char> QuotedTextChar = OneOf(TryEscapedChar, Any);
// Quoted text
private static readonly Parser<char, string> QuotedText = Char('"').Then(QuotedTextChar.Until(Try(Char('"'))).Select(string.Concat)).Labelled("quoted text");
#endregion
#region rich text-end markers
private static readonly Parser<char, Unit> TryStartList = Try(SkipNewline.Then(SkipWhitespaces).Then(Char('-'))).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartTag = Try(Char('<')).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryStartParagraph = Try(SkipNewline.Then(SkipNewline)).Then(SkipWhitespaces);
private static readonly Parser<char, Unit> TryLookTextEnd = Lookahead(OneOf(TryStartTag, TryStartList, TryStartParagraph, Try(Whitespace.SkipUntil(End))));
#endregion
// parses text characters until it hits a text-end
private static readonly Parser<char, string> TextParser = TextChar.AtLeastOnceUntil(TryLookTextEnd).Select(string.Concat);
private static readonly Parser<char, Control> TextControlParser = Try(Map(text =>
{
var rt = new RichTextLabel()
{
HorizontalExpand = true,
Margin = new Thickness(0, 0, 0, 15.0f),
};
var msg = new FormattedMessage();
// THANK YOU RICHTEXT VERY COOL
// (text doesn't default to white).
msg.PushColor(Color.White);
msg.AddMarkup(text);
msg.Pop();
rt.SetMessage(msg);
return rt;
}, TextParser).Cast<Control>()).Labelled("richtext");
#endregion
#region Headers
private static readonly Parser<char, Control> HeaderControlParser = Try(Char('#')).Then(SkipWhitespaces.Then(Map(text => new Label()
{
Text = text,
StyleClasses = { "LabelHeadingBigger" }
}, AnyCharExcept('\n').AtLeastOnceString()).Cast<Control>())).Labelled("header");
private static readonly Parser<char, Control> SubHeaderControlParser = Try(String("##")).Then(SkipWhitespaces.Then(Map(text => new Label()
{
Text = text,
StyleClasses = { "LabelHeading" }
}, AnyCharExcept('\n').AtLeastOnceString()).Cast<Control>())).Labelled("subheader");
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
#endregion
#region Tag Parsing
// Parser that consumes a - and then just parses normal rich text with some prefix text (a bullet point).
private static readonly Parser<char, Control> ListControlParser = Try(Char('-')).Then(SkipWhitespaces).Then(Map(
control => new BoxContainer()
{
Children = { new Label() { Text = ListBullet, VerticalAlignment = VAlignment.Top, }, control },
Orientation = LayoutOrientation.Horizontal,
}, TextControlParser).Cast<Control>()).Labelled("list");
#region Tag Parsing
// closing brackets for tags
private static readonly Parser<char, Unit> TagEnd = Char('>').Then(SkipWhitespaces);
private static readonly Parser<char, Unit> ImmediateTagEnd = String("/>").Then(SkipWhitespaces);
@@ -158,24 +107,20 @@ public sealed partial class DocumentParsingManager
private static readonly Parser<char, Unit> TryLookTagEnd = Lookahead(OneOf(Try(TagEnd), Try(ImmediateTagEnd)));
//parse tag argument key. any normal text character up until we hit a "="
private static readonly Parser<char, string> TagArgKey =
LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
private static readonly Parser<char, string> TagArgKey = LetterOrDigit.Until(Char('=')).Select(string.Concat).Labelled("tag argument key");
// parser for a singular tag argument. Note that each TryQuoteOrChar will consume a whole quoted block before the Until() looks for whitespace
private static readonly Parser<char, (string, string)> TagArgParser =
Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
private static readonly Parser<char, (string, string)> TagArgParser = Map((key, value) => (key, value), TagArgKey, QuotedText).Before(SkipWhitespaces);
// parser for all tag arguments
private static readonly Parser<char, IEnumerable<(string, string)>> TagArgsParser =
TagArgParser.Until(TryLookTagEnd);
private static readonly Parser<char, IEnumerable<(string, string)>> TagArgsParser = TagArgParser.Until(TryLookTagEnd);
// parser for an opening tag.
private static readonly Parser<char, string> TryOpeningTag =
Try(Char('<'))
.Then(SkipWhitespaces)
.Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
.Select(string.Concat)
.Labelled("opening tag");
.Then(SkipWhitespaces)
.Then(TextChar.Until(OneOf(Whitespace.SkipAtLeastOnce(), TryLookTagEnd)))
.Select(string.Concat).Labelled($"opening tag");
private static Parser<char, Dictionary<string, string>> ParseTagArgs(string tag)
{
@@ -193,6 +138,5 @@ public sealed partial class DocumentParsingManager
.Then(TagEnd)
.Labelled($"closing {tag} tag");
}
#endregion
}

View File

@@ -283,7 +283,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
for (var i = 0; i < PointsUsed; i++)
{
SlotSelector.AddItem(Loc.GetString("marking-slot", ("number", $"{i + 1}")), i);
SlotSelector.AddItem($"Slot {i + 1}", i);
if (i == _slot)
{

View File

@@ -16,8 +16,6 @@ namespace Content.Client.IconSmoothing
[UsedImplicitly]
public sealed partial class IconSmoothSystem : EntitySystem
{
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
private readonly Queue<EntityUid> _dirtyEntities = new();
private readonly Queue<EntityUid> _anchorChangedEntities = new();
@@ -48,7 +46,7 @@ namespace Content.Client.IconSmoothing
if (xform.Anchored)
{
component.LastPosition = TryComp<MapGridComponent>(xform.GridUid, out var grid)
? (xform.GridUid.Value, _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates))
? (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates))
: (null, new Vector2i(0, 0));
DirtyNeighbours(uid, component);
@@ -153,12 +151,9 @@ namespace Content.Client.IconSmoothing
Vector2i pos;
EntityUid entityUid;
if (transform.Anchored && TryComp<MapGridComponent>(transform.GridUid, out var grid))
{
entityUid = transform.GridUid.Value;
pos = _mapSystem.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates);
pos = grid.CoordinatesToTile(transform.Coordinates);
}
else
{
@@ -169,22 +164,21 @@ namespace Content.Client.IconSmoothing
if (!TryComp(gridId, out grid))
return;
entityUid = gridId;
pos = oldPos;
}
// Yes, we updates ALL smoothing entities surrounding us even if they would never smooth with us.
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 0)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 0)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, 1)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, -1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)));
if (comp.Mode is IconSmoothingMode.Corners or IconSmoothingMode.NoSprite or IconSmoothingMode.Diagonal)
{
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 1)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, -1)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 1)));
DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, -1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, -1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 1)));
DirtyEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, -1)));
}
}
@@ -212,7 +206,7 @@ namespace Content.Client.IconSmoothing
IconSmoothComponent? smooth = null)
{
TransformComponent? xform;
Entity<MapGridComponent>? gridEntity = null;
MapGridComponent? grid = null;
// The generation check prevents updating an entity multiple times per tick.
// As it stands now, it's totally possible for something to get queued twice.
@@ -229,20 +223,17 @@ namespace Content.Client.IconSmoothing
{
var directions = DirectionFlag.None;
if (TryComp(xform.GridUid, out MapGridComponent? grid))
if (TryComp(xform.GridUid, out grid))
{
var gridUid = xform.GridUid.Value;
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
var pos = grid.TileIndicesFor(xform.Coordinates);
gridEntity = (gridUid, grid);
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery))
directions |= DirectionFlag.North;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery))
directions |= DirectionFlag.South;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery))
directions |= DirectionFlag.East;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery))
directions |= DirectionFlag.West;
}
@@ -266,11 +257,7 @@ namespace Content.Client.IconSmoothing
if (xform.Anchored)
{
if (TryComp(xform.GridUid, out MapGridComponent? grid))
{
gridEntity = (xform.GridUid.Value, grid);
}
else
if (!TryComp(xform.GridUid, out grid))
{
Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
return;
@@ -280,31 +267,28 @@ namespace Content.Client.IconSmoothing
switch (smooth.Mode)
{
case IconSmoothingMode.Corners:
CalculateNewSpriteCorners(gridEntity, smooth, spriteEnt, xform, smoothQuery);
CalculateNewSpriteCorners(grid, smooth, spriteEnt, xform, smoothQuery);
break;
case IconSmoothingMode.CardinalFlags:
CalculateNewSpriteCardinal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
CalculateNewSpriteCardinal(grid, smooth, spriteEnt, xform, smoothQuery);
break;
case IconSmoothingMode.Diagonal:
CalculateNewSpriteDiagonal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
CalculateNewSpriteDiagonal(grid, smooth, spriteEnt, xform, smoothQuery);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void CalculateNewSpriteDiagonal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth,
private void CalculateNewSpriteDiagonal(MapGridComponent? grid, IconSmoothComponent smooth,
Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
{
if (gridEntity == null)
if (grid == null)
{
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}0");
return;
}
var gridUid = gridEntity.Value.Owner;
var grid = gridEntity.Value.Comp;
var neighbors = new Vector2[]
{
new(1, 0),
@@ -312,14 +296,14 @@ namespace Content.Client.IconSmoothing
new(0, -1),
};
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
var pos = grid.TileIndicesFor(xform.Coordinates);
var rotation = xform.LocalRotation;
var matching = true;
for (var i = 0; i < neighbors.Length; i++)
{
var neighbor = (Vector2i)rotation.RotateVec(neighbors[i]);
matching = matching && MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos + neighbor), smoothQuery);
var neighbor = (Vector2i) rotation.RotateVec(neighbors[i]);
matching = matching && MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos + neighbor), smoothQuery);
}
if (matching)
@@ -332,30 +316,27 @@ namespace Content.Client.IconSmoothing
}
}
private void CalculateNewSpriteCardinal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
private void CalculateNewSpriteCardinal(MapGridComponent? grid, IconSmoothComponent smooth, Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
{
var dirs = CardinalConnectDirs.None;
if (gridEntity == null)
if (grid == null)
{
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int) dirs}");
return;
}
var gridUid = gridEntity.Value.Owner;
var grid = gridEntity.Value.Comp;
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
var pos = grid.TileIndicesFor(xform.Coordinates);
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery))
dirs |= CardinalConnectDirs.North;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery))
dirs |= CardinalConnectDirs.South;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery))
dirs |= CardinalConnectDirs.East;
if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
if (MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery))
dirs |= CardinalConnectDirs.West;
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int) dirs}");
var directions = DirectionFlag.None;
@@ -386,11 +367,11 @@ namespace Content.Client.IconSmoothing
return false;
}
private void CalculateNewSpriteCorners(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> spriteEnt, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
private void CalculateNewSpriteCorners(MapGridComponent? grid, IconSmoothComponent smooth, Entity<SpriteComponent> spriteEnt, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
{
var (cornerNE, cornerNW, cornerSW, cornerSE) = gridEntity == null
var (cornerNE, cornerNW, cornerSW, cornerSE) = grid == null
? (CornerFill.None, CornerFill.None, CornerFill.None, CornerFill.None)
: CalculateCornerFill(gridEntity.Value, smooth, xform, smoothQuery);
: CalculateCornerFill(grid, smooth, xform, smoothQuery);
// TODO figure out a better way to set multiple sprite layers.
// This will currently re-calculate the sprite bounding box 4 times.
@@ -398,10 +379,10 @@ namespace Content.Client.IconSmoothing
// At the very least each event currently only queues a sprite for updating.
// Oh god sprite component is a mess.
var sprite = spriteEnt.Comp;
sprite.LayerSetState(CornerLayers.NE, $"{smooth.StateBase}{(int)cornerNE}");
sprite.LayerSetState(CornerLayers.SE, $"{smooth.StateBase}{(int)cornerSE}");
sprite.LayerSetState(CornerLayers.SW, $"{smooth.StateBase}{(int)cornerSW}");
sprite.LayerSetState(CornerLayers.NW, $"{smooth.StateBase}{(int)cornerNW}");
sprite.LayerSetState(CornerLayers.NE, $"{smooth.StateBase}{(int) cornerNE}");
sprite.LayerSetState(CornerLayers.SE, $"{smooth.StateBase}{(int) cornerSE}");
sprite.LayerSetState(CornerLayers.SW, $"{smooth.StateBase}{(int) cornerSW}");
sprite.LayerSetState(CornerLayers.NW, $"{smooth.StateBase}{(int) cornerNW}");
var directions = DirectionFlag.None;
@@ -420,20 +401,17 @@ namespace Content.Client.IconSmoothing
CalculateEdge(spriteEnt, directions, sprite);
}
private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(Entity<MapGridComponent> gridEntity, IconSmoothComponent smooth, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(MapGridComponent grid, IconSmoothComponent smooth, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
{
var gridUid = gridEntity.Owner;
var grid = gridEntity.Comp;
var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
var n = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery);
var ne = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthEast)), smoothQuery);
var e = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery);
var se = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthEast)), smoothQuery);
var s = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery);
var sw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthWest)), smoothQuery);
var w = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery);
var nw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthWest)), smoothQuery);
var pos = grid.TileIndicesFor(xform.Coordinates);
var n = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.North)), smoothQuery);
var ne = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.NorthEast)), smoothQuery);
var e = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.East)), smoothQuery);
var se = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.SouthEast)), smoothQuery);
var s = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.South)), smoothQuery);
var sw = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.SouthWest)), smoothQuery);
var w = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.West)), smoothQuery);
var nw = MatchingEntity(smooth, grid.GetAnchoredEntitiesEnumerator(pos.Offset(Direction.NorthWest)), smoothQuery);
// ReSharper disable InconsistentNaming
var cornerNE = CornerFill.None;

View File

@@ -74,9 +74,6 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.OpenBackpack);
human.AddFunction(ContentKeyFunctions.OpenBelt);
human.AddFunction(ContentKeyFunctions.MouseMiddle);
human.AddFunction(ContentKeyFunctions.RotateObjectClockwise);
human.AddFunction(ContentKeyFunctions.RotateObjectCounterclockwise);
human.AddFunction(ContentKeyFunctions.FlipObject);
human.AddFunction(ContentKeyFunctions.ArcadeUp);
human.AddFunction(ContentKeyFunctions.ArcadeDown);
human.AddFunction(ContentKeyFunctions.ArcadeLeft);

View File

@@ -206,7 +206,7 @@ namespace Content.Client.Instruments.UI
var container = _entManager.System<SharedContainerSystem>();
// If we're a handheld instrument, we might be in a container. Get it just in case.
container.TryGetContainingContainer((Entity, null, null), out var conMan);
container.TryGetContainingContainer(Entity, out var conMan);
// If the instrument is handheld and we're not holding it, we return.
if (instrument.Handheld && (conMan == null || conMan.Owner != localEntity))

View File

@@ -42,7 +42,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
// how often to recheck possible targets (prevents calling expensive
// check logic each update)
@@ -90,7 +89,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
/// </summary>
private bool _isReplaying;
public float Deadzone;
private float _deadzone;
private DragState _state = DragState.NotDragging;
@@ -122,7 +121,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
private void SetDeadZone(float deadZone)
{
Deadzone = deadZone;
_deadzone = deadZone;
}
public override void Shutdown()
@@ -212,7 +211,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
_draggedEntity = entity;
_state = DragState.MouseDown;
_mouseDownScreenPos = args.ScreenCoordinates;
_mouseDownScreenPos = _inputManager.MouseScreenPosition;
_mouseDownTime = 0;
// don't want anything else to process the click,
@@ -240,13 +239,8 @@ public sealed class DragDropSystem : SharedDragDropSystem
if (TryComp<SpriteComponent>(_draggedEntity, out var draggedSprite))
{
var screenPos = _inputManager.MouseScreenPosition;
// No _draggedEntity in null window (Happens in tests)
if (!screenPos.IsValid)
return;
// pop up drag shadow under mouse
var mousePos = _eyeManager.PixelToMap(screenPos);
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
_dragShadow = EntityManager.SpawnEntity("dragshadow", mousePos);
var dragSprite = Comp<SpriteComponent>(_dragShadow.Value);
dragSprite.CopyFrom(draggedSprite);
@@ -523,9 +517,6 @@ public sealed class DragDropSystem : SharedDragDropSystem
if (dropEv2.Handled)
return dropEv2.CanDrop;
if (dropEv.Handled && dropEv.CanDrop)
return true;
return null;
}
@@ -539,7 +530,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
case DragState.MouseDown:
{
var screenPos = _inputManager.MouseScreenPosition;
if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > Deadzone)
if ((_mouseDownScreenPos!.Value.Position - screenPos.Position).Length() > _deadzone)
{
StartDrag();
}
@@ -560,7 +551,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
if (Exists(_dragShadow))
{
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
_transformSystem.SetWorldPosition(_dragShadow.Value, mousePos.Position);
Transform(_dragShadow.Value).WorldPosition = mousePos.Position;
}
}
}

View File

@@ -4,23 +4,23 @@ using Content.Client.Chat.Managers;
using Content.Client.Clickable;
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.Fullscreen;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Launcher;
using Content.Client.Mapping;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Replay;
using Content.Client.Screenshot;
using Content.Client.Fullscreen;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Administration.Logs;
using Content.Client.Guidebook;
using Content.Client.Lobby;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
{
internal static class ClientContentIoC
@@ -49,7 +49,6 @@ namespace Content.Client.IoC
collection.Register<DocumentParsingManager>();
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
collection.Register<MappingManager>();
collection.Register<DebugMonitorManager>();
}
}

View File

@@ -43,7 +43,7 @@ public sealed class ItemSystem : SharedItemSystem
public override void VisualsChanged(EntityUid uid)
{
// if the item is in a container, it might be equipped to hands or inventory slots --> update visuals.
if (Container.TryGetContainingContainer((uid, null, null), out var container))
if (Container.TryGetContainingContainer(uid, out var container))
RaiseLocalEvent(container.Owner, new VisualsChangedEvent(GetNetEntity(uid), container.ID));
}

View File

@@ -2,11 +2,9 @@ using System.Linq;
using System.Numerics;
using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Client.Console;
@@ -28,7 +26,6 @@ namespace Content.Client.LateJoin
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
public event Action<(NetEntity, string)> SelectedId;
@@ -257,7 +254,7 @@ namespace Content.Client.LateJoin
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
if (!_jobRequirements.IsAllowed(prototype, out var reason))
{
jobButton.Disabled = true;

View File

@@ -22,29 +22,21 @@ public sealed class LatheSystem : SharedLatheSystem
if (args.Sprite == null)
return;
// Lathe specific stuff
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component))
{
if (args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
component.RunningState != null &&
component.IdleState != null)
{
var state = isRunning ? component.RunningState : component.IdleState;
args.Sprite.LayerSetState(runningLayer, state);
}
}
if (_appearance.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component) &&
args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out var powerLayer))
{
args.Sprite.LayerSetVisible(powerLayer, powered);
}
if (component.UnlitIdleState != null &&
component.UnlitRunningState != null)
{
var state = isRunning ? component.UnlitRunningState : component.UnlitIdleState;
args.Sprite.LayerSetState(powerLayer, state);
}
// Lathe specific stuff
if (_appearance.TryGetData<bool>(uid, LatheVisuals.IsRunning, out var isRunning, args.Component) &&
args.Sprite.LayerMapTryGet(LatheVisualLayers.IsRunning, out var runningLayer) &&
component.RunningState != null &&
component.IdleState != null)
{
var state = isRunning ? component.RunningState : component.IdleState;
args.Sprite.LayerSetAnimationTime(runningLayer, 0f);
args.Sprite.LayerSetState(runningLayer, state);
}
}

View File

@@ -100,9 +100,11 @@
Margin="5 0 0 0"
Text="{Loc 'lathe-menu-fabricating-message'}">
</Label>
<BoxContainer Name="FabricatingDisplayContainer"
HorizontalAlignment="Left"
Margin="100 0 0 0"/>
<EntityPrototypeView
Name="FabricatingEntityProto"
HorizontalAlignment="Left"
Margin="100 0 0 0"
/>
<Label
Name="NameLabel"
RectClipContent="True"

View File

@@ -1,15 +1,18 @@
using System.Buffers;
using System.Linq;
using System.Text;
using Content.Client.Materials;
using Content.Shared.Lathe;
using Content.Shared.Lathe.Prototypes;
using Content.Shared.Materials;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.ResourceManagement;
using Robust.Client.Graphics;
using Robust.Shared.Prototypes;
namespace Content.Client.Lathe.UI;
@@ -73,16 +76,6 @@ public sealed partial class LatheMenu : DefaultWindow
MaterialsList.SetOwner(Entity);
}
protected override void Opened()
{
base.Opened();
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComp))
{
AmountLineEdit.SetText(latheComp.DefaultProductionAmount.ToString());
}
}
/// <summary>
/// Populates the list of all the recipes
/// </summary>
@@ -99,7 +92,7 @@ public sealed partial class LatheMenu : DefaultWindow
if (SearchBar.Text.Trim().Length != 0)
{
if (_lathe.GetRecipeName(recipe).ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
if (proto.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
recipesToShow.Add(proto);
}
else
@@ -111,15 +104,19 @@ public sealed partial class LatheMenu : DefaultWindow
if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0)
quantity = 1;
var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
var sortedRecipesToShow = recipesToShow.OrderBy(p => p.Name);
RecipeList.Children.Clear();
_entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
foreach (var prototype in sortedRecipesToShow)
{
EntityPrototype? recipeProto = null;
if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto))
recipeProto = entityProto;
var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe);
var control = new RecipeControl(_lathe, prototype, () => GenerateTooltipText(prototype), canProduce, GetRecipeDisplayControl(prototype));
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, recipeProto);
control.OnButtonPressed += s =>
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -135,9 +132,9 @@ public sealed partial class LatheMenu : DefaultWindow
StringBuilder sb = new();
var multiplier = _entityManager.GetComponent<LatheComponent>(Entity).MaterialUseMultiplier;
foreach (var (id, amount) in prototype.Materials)
foreach (var (id, amount) in prototype.RequiredMaterials)
{
if (!_prototypeManager.TryIndex(id, out var proto))
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
continue;
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier);
@@ -166,9 +163,8 @@ public sealed partial class LatheMenu : DefaultWindow
sb.AppendLine(tooltipText);
}
var desc = _lathe.GetRecipeDescription(prototype);
if (!string.IsNullOrWhiteSpace(desc))
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", desc)));
if (!string.IsNullOrWhiteSpace(prototype.Description))
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
// Remove last newline
if (sb.Length > 0)
@@ -226,10 +222,13 @@ public sealed partial class LatheMenu : DefaultWindow
var queuedRecipeBox = new BoxContainer();
queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
var queuedRecipeProto = new EntityPrototypeView();
queuedRecipeBox.AddChild(queuedRecipeProto);
if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
queuedRecipeProto.SetPrototype(entityProto);
var queuedRecipeLabel = new Label();
queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
queuedRecipeLabel.Text = $"{idx}. {recipe.Name}";
queuedRecipeBox.AddChild(queuedRecipeLabel);
QueueList.AddChild(queuedRecipeBox);
idx++;
@@ -242,29 +241,10 @@ public sealed partial class LatheMenu : DefaultWindow
if (recipe == null)
return;
FabricatingDisplayContainer.Children.Clear();
FabricatingDisplayContainer.AddChild(GetRecipeDisplayControl(recipe));
if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
FabricatingEntityProto.SetPrototype(entityProto);
NameLabel.Text = _lathe.GetRecipeName(recipe);
}
public Control GetRecipeDisplayControl(LatheRecipePrototype recipe)
{
if (recipe.Icon != null)
{
var textRect = new TextureRect();
textRect.Texture = _spriteSystem.Frame0(recipe.Icon);
return textRect;
}
if (recipe.Result is { } result)
{
var entProtoView = new EntityPrototypeView();
entProtoView.SetPrototype(result);
return entProtoView;
}
return new Control();
NameLabel.Text = $"{recipe.Name}";
}
private void OnItemSelected(OptionButton.ItemSelectedEventArgs obj)

View File

@@ -5,8 +5,8 @@
Margin="0"
StyleClasses="ButtonSquare">
<BoxContainer Orientation="Horizontal">
<BoxContainer
Name="RecipeDisplayContainer"
<EntityPrototypeView
Name="RecipePrototype"
Margin="0 0 4 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"

View File

@@ -1,7 +1,10 @@
using Content.Shared.Research.Prototypes;
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;
namespace Content.Client.Lathe.UI;
@@ -11,12 +14,13 @@ public sealed partial class RecipeControl : Control
public Action<string>? OnButtonPressed;
public Func<string> TooltipTextSupplier;
public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl)
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, EntityPrototype? entityPrototype = null)
{
RobustXamlLoader.Load(this);
RecipeName.Text = latheSystem.GetRecipeName(recipe);
RecipeDisplayContainer.AddChild(displayControl);
RecipeName.Text = recipe.Name;
if (entityPrototype != null)
RecipePrototype.SetPrototype(entityPrototype);
Button.Disabled = !canProduce;
TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip;

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Shared.Light.Components;
using JetBrains.Annotations;
using Robust.Client.Animations;
@@ -68,7 +68,7 @@ namespace Content.Client.Light.Components
if (MinDuration > 0)
{
MaxTime = (float)_random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
MaxTime = (float) _random.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
}
else
{
@@ -192,11 +192,11 @@ namespace Content.Client.Light.Components
{
if (interpolateValue < 0.5f)
{
ApplyInterpolation(StartValue, EndValue, interpolateValue * 2);
ApplyInterpolation(StartValue, EndValue, interpolateValue*2);
}
else
{
ApplyInterpolation(EndValue, StartValue, (interpolateValue - 0.5f) * 2);
ApplyInterpolation(EndValue, StartValue, (interpolateValue-0.5f)*2);
}
}
else
@@ -238,9 +238,9 @@ namespace Content.Client.Light.Components
public override void OnInitialize()
{
_randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
_randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
_randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float)_random.NextDouble());
_randomValue1 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
_randomValue2 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
_randomValue3 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
}
public override void OnStart()
@@ -258,7 +258,7 @@ namespace Content.Client.Light.Components
}
_randomValue3 = _randomValue4;
_randomValue4 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
_randomValue4 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble());
}
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
@@ -362,7 +362,7 @@ namespace Content.Client.Light.Components
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public const string KeyPrefix = nameof(LightBehaviourComponent);
private const string KeyPrefix = nameof(LightBehaviourComponent);
public sealed class AnimationContainer
{
@@ -387,7 +387,7 @@ namespace Content.Client.Light.Components
public readonly List<AnimationContainer> Animations = new();
[ViewVariables(VVAccess.ReadOnly)]
public Dictionary<string, object> OriginalPropertyValues = new();
private Dictionary<string, object> _originalPropertyValues = new();
void ISerializationHooks.AfterDeserialization()
{
@@ -397,12 +397,155 @@ namespace Content.Client.Light.Components
{
var animation = new Animation()
{
AnimationTracks = { behaviour }
AnimationTracks = {behaviour}
};
Animations.Add(new AnimationContainer(key, animation, behaviour));
key++;
}
}
/// <summary>
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
/// </summary>
private void CopyLightSettings(EntityUid uid, string property)
{
if (_entMan.TryGetComponent(uid, out PointLightComponent? light))
{
var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
if (propertyValue != null)
{
_originalPropertyValues.Add(property, propertyValue);
}
}
else
{
Logger.Warning($"{_entMan.GetComponent<MetaDataComponent>(uid).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
}
}
/// <summary>
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
/// If specified light behaviours are already animating, calling this does nothing.
/// Multiple light behaviours can have the same ID.
/// </summary>
public void StartLightBehaviour(string id = "")
{
var uid = Owner;
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
{
return;
}
var animations = _entMan.System<AnimationPlayerSystem>();
foreach (var container in Animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (!animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
{
CopyLightSettings(uid, container.LightBehaviour.Property);
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
animations.Play(uid, animation, container.Animation, KeyPrefix + container.Key);
}
}
}
}
/// <summary>
/// If any light behaviour with the specified ID is animating, then stop it.
/// If no ID is specified then all light behaviours will be stopped.
/// Multiple light behaviours can have the same ID.
/// </summary>
/// <param name="id"></param>
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
{
var uid = Owner;
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
{
return;
}
var toRemove = new List<AnimationContainer>();
var animations = _entMan.System<AnimationPlayerSystem>();
foreach (var container in Animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key))
{
animations.Stop(uid, animation, KeyPrefix + container.Key);
}
if (removeBehaviour)
{
toRemove.Add(container);
}
}
}
foreach (var container in toRemove)
{
Animations.Remove(container);
}
if (resetToOriginalSettings && _entMan.TryGetComponent(uid, out PointLightComponent? light))
{
foreach (var (property, value) in _originalPropertyValues)
{
AnimationHelper.SetAnimatableProperty(light, property, value);
}
}
_originalPropertyValues.Clear();
}
/// <summary>
/// Checks if at least one behaviour is running.
/// </summary>
/// <returns>Whether at least one behaviour is running, false if none is.</returns>
public bool HasRunningBehaviours()
{
var uid = Owner;
if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation))
{
return false;
}
var animations = _entMan.System<AnimationPlayerSystem>();
return Animations.Any(container => animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key));
}
/// <summary>
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
/// </summary>
public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
{
var key = 0;
while (Animations.Any(x => x.Key == key))
{
key++;
}
var animation = new Animation()
{
AnimationTracks = {behaviour}
};
behaviour.Initialize(Owner, _random, _entMan);
var container = new AnimationContainer(key, animation, behaviour);
Animations.Add(container);
if (playImmediately)
{
StartLightBehaviour(behaviour.ID);
}
}
}
}

View File

@@ -11,7 +11,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!;
public override void Initialize()
{
@@ -33,11 +32,11 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
if (AppearanceSystem.TryGetData<string>(uid, ExpendableLightVisuals.Behavior, out var lightBehaviourID, args.Component)
&& TryComp<LightBehaviourComponent>(uid, out var lightBehaviour))
{
_lightBehavior.StopLightBehaviour((uid, lightBehaviour));
lightBehaviour.StopLightBehaviour();
if (!string.IsNullOrEmpty(lightBehaviourID))
{
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), lightBehaviourID);
lightBehaviour.StartLightBehaviour(lightBehaviourID);
}
else if (TryComp<PointLightComponent>(uid, out var light))
{

View File

@@ -1,9 +1,7 @@
using System.Linq;
using Content.Client.Light.Components;
using Robust.Client.GameObjects;
using Robust.Client.Animations;
using Robust.Shared.Random;
using Robust.Shared.Animations;
namespace Content.Client.Light.EntitySystems;
@@ -38,163 +36,23 @@ public sealed class LightBehaviorSystem : EntitySystem
}
}
private void OnLightStartup(Entity<LightBehaviourComponent> entity, ref ComponentStartup args)
private void OnLightStartup(EntityUid uid, LightBehaviourComponent component, ComponentStartup args)
{
// TODO: Do NOT ensure component here. And use eventbus events instead...
EnsureComp<AnimationPlayerComponent>(entity);
EnsureComp<AnimationPlayerComponent>(uid);
foreach (var container in entity.Comp.Animations)
foreach (var container in component.Animations)
{
container.LightBehaviour.Initialize(entity, _random, EntityManager);
container.LightBehaviour.Initialize(uid, _random, EntityManager);
}
// we need to initialize all behaviours before starting any
foreach (var container in entity.Comp.Animations)
foreach (var container in component.Animations)
{
if (container.LightBehaviour.Enabled)
{
StartLightBehaviour(entity, container.LightBehaviour.ID);
component.StartLightBehaviour(container.LightBehaviour.ID);
}
}
}
/// <summary>
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
/// </summary>
private void CopyLightSettings(Entity<LightBehaviourComponent> entity, string property)
{
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
{
var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
if (propertyValue != null)
{
entity.Comp.OriginalPropertyValues.Add(property, propertyValue);
}
}
else
{
Log.Warning($"{EntityManager.GetComponent<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
}
}
/// <summary>
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
/// If specified light behaviours are already animating, calling this does nothing.
/// Multiple light behaviours can have the same ID.
/// </summary>
public void StartLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "")
{
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return;
}
foreach (var container in entity.Comp.Animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (!_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
{
CopyLightSettings(entity, container.LightBehaviour.Property);
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
_player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key);
}
}
}
}
/// <summary>
/// If any light behaviour with the specified ID is animating, then stop it.
/// If no ID is specified then all light behaviours will be stopped.
/// Multiple light behaviours can have the same ID.
/// </summary>
/// <param name="id"></param>
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
public void StopLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
{
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return;
}
var comp = entity.Comp;
var toRemove = new List<LightBehaviourComponent.AnimationContainer>();
foreach (var container in comp.Animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key))
{
_player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key);
}
if (removeBehaviour)
{
toRemove.Add(container);
}
}
}
foreach (var container in toRemove)
{
comp.Animations.Remove(container);
}
if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light))
{
foreach (var (property, value) in comp.OriginalPropertyValues)
{
AnimationHelper.SetAnimatableProperty(light, property, value);
}
}
comp.OriginalPropertyValues.Clear();
}
/// <summary>
/// Checks if at least one behaviour is running.
/// </summary>
/// <returns>Whether at least one behaviour is running, false if none is.</returns>
public bool HasRunningBehaviours(Entity<LightBehaviourComponent> entity)
{
//var uid = Owner;
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return false;
}
return entity.Comp.Animations.Any(container => _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key));
}
/// <summary>
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
/// </summary>
public void AddNewLightBehaviour(Entity<LightBehaviourComponent> entity, LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
{
var key = 0;
var comp = entity.Comp;
while (comp.Animations.Any(x => x.Key == key))
{
key++;
}
var animation = new Animation()
{
AnimationTracks = { behaviour }
};
behaviour.Initialize(entity.Owner, _random, EntityManager);
var container = new LightBehaviourComponent.AnimationContainer(key, animation, behaviour);
comp.Animations.Add(container);
if (playImmediately)
{
StartLightBehaviour(entity, behaviour.ID);
}
}
}

View File

@@ -3,15 +3,15 @@ using Content.Client.Light.Components;
using Content.Shared.Light;
using Content.Shared.Light.Components;
using Content.Shared.Toggleable;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Content.Client.Light.EntitySystems;
using Robust.Shared.Animations;
namespace Content.Client.Light;
public sealed class HandheldLightSystem : SharedHandheldLightSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly LightBehaviorSystem _lightBehavior = default!;
public override void Initialize()
{
@@ -41,9 +41,9 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
if (TryComp<LightBehaviourComponent>(uid, out var lightBehaviour))
{
// Reset any running behaviour to reset the animated properties back to the original value, to avoid conflicts between resets
if (_lightBehavior.HasRunningBehaviours((uid, lightBehaviour)))
if (lightBehaviour.HasRunningBehaviours())
{
_lightBehavior.StopLightBehaviour((uid, lightBehaviour), resetToOriginalSettings: true);
lightBehaviour.StopLightBehaviour(resetToOriginalSettings: true);
}
if (!enabled)
@@ -56,10 +56,10 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
case HandheldLightPowerStates.FullPower:
break; // We just needed to reset all behaviours
case HandheldLightPowerStates.LowPower:
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.RadiatingBehaviourId);
lightBehaviour.StartLightBehaviour(component.RadiatingBehaviourId);
break;
case HandheldLightPowerStates.Dying:
_lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.BlinkingBehaviourId);
lightBehaviour.StartLightBehaviour(component.BlinkingBehaviourId);
break;
}
}

View File

@@ -43,7 +43,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor;
@@ -273,7 +272,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_logManager,
_playerManager,
_prototypeManager,
_resourceCache,
_requirements,
_markings);
@@ -366,7 +364,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
_spawn.EquipStartingGear(uid, loadoutProto);
_spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
}
}
}
@@ -389,51 +387,36 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
// TODO: Need some way to apply starting gear to an entity and replace existing stuff coz holy fucking shit dude.
// TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
foreach (var slot in slots)
{
// Try startinggear first
if (_prototypeManager.TryIndex(loadoutProto.StartingGear, out var loadoutGear))
var itemType = loadoutGear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
var itemType = ((IEquipmentLoadout) loadoutGear).GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
EntityManager.DeleteEntity(unequippedItem.Value);
}
else
if (itemType != string.Empty)
{
var itemType = ((IEquipmentLoadout) loadoutProto).GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
}
}
if (!_prototypeManager.TryIndex(job.StartingGear, out var gear))
if (job.StartingGear == null)
return;
var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
foreach (var slot in slots)
{
var itemType = ((IEquipmentLoadout) gear).GetGear(slot.Name);
var itemType = gear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{

View File

@@ -38,8 +38,6 @@
<Button Name="ResetButton" Disabled="True" Text="{Loc 'humanoid-profile-editor-reset-button'}"/>
<Button Name="ImportButton" Text="{Loc 'humanoid-profile-editor-import-button'}"/>
<Button Name="ExportButton" Text="{Loc 'humanoid-profile-editor-export-button'}"/>
<Button Name="ExportImageButton" Text="{Loc 'humanoid-profile-editor-export-image-button'}"/>
<Button Name="OpenImagesButton" Text="{Loc 'humanoid-profile-editor-open-image-button'}"/>
</BoxContainer>
</ui:HighlightedContainer>
</BoxContainer>

View File

@@ -6,7 +6,6 @@ using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Sprite;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._CP14.Humanoid;
@@ -29,7 +28,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -46,7 +44,6 @@ namespace Content.Client.Lobby.UI
private readonly IFileDialogManager _dialogManager;
private readonly IPlayerManager _playerManager;
private readonly IPrototypeManager _prototypeManager;
private readonly IResourceManager _resManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private readonly LobbyUIController _controller;
@@ -58,7 +55,6 @@ namespace Content.Client.Lobby.UI
private LoadoutWindow? _loadoutWindow;
private bool _exporting;
private bool _imaging;
/// <summary>
/// If we're attempting to save.
@@ -112,7 +108,6 @@ namespace Content.Client.Lobby.UI
ILogManager logManager,
IPlayerManager playerManager,
IPrototypeManager prototypeManager,
IResourceManager resManager,
JobRequirementsManager requirements,
MarkingManager markings)
{
@@ -125,7 +120,6 @@ namespace Content.Client.Lobby.UI
_prototypeManager = prototypeManager;
_markingManager = markings;
_preferencesManager = preferencesManager;
_resManager = resManager;
_requirements = requirements;
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
@@ -139,16 +133,6 @@ namespace Content.Client.Lobby.UI
ExportProfile();
};
ExportImageButton.OnPressed += args =>
{
ExportImage();
};
OpenImagesButton.OnPressed += args =>
{
_resManager.UserData.OpenOsWindow(ContentSpriteSystem.Exports);
};
ResetButton.OnPressed += args =>
{
SetProfile((HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, _preferencesManager.Preferences?.SelectedCharacterIndex);
@@ -441,6 +425,7 @@ namespace Content.Client.Lobby.UI
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
UpdateSpeciesGuidebookIcon();
ReloadPreview();
IsDirty = false;
}
@@ -650,7 +635,7 @@ namespace Content.Client.Lobby.UI
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
if (!_requirements.CheckRoleTime(requirements, out var reason))
{
selector.LockRequirements(reason);
Profile = Profile?.WithAntagPreference(antag.ID, false);
@@ -713,12 +698,11 @@ namespace Content.Client.Lobby.UI
_entManager.DeleteEntity(PreviewDummy);
PreviewDummy = EntityUid.Invalid;
if (Profile == null || !_prototypeManager.HasIndex(Profile.Species))
if (Profile == null || !_prototypeManager.HasIndex<SpeciesPrototype>(Profile.Species))
return;
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
SpriteView.SetEntity(PreviewDummy);
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, Profile.Name);
}
/// <summary>
@@ -904,7 +888,7 @@ namespace Content.Client.Lobby.UI
icon.Texture = jobIcon.Icon.Frame0();
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
if (!_requirements.IsAllowed(job, out var reason))
{
selector.LockRequirements(reason);
}
@@ -1155,17 +1139,6 @@ namespace Content.Client.Lobby.UI
_loadoutWindow?.Dispose();
_loadoutWindow = null;
}
protected override void EnteredTree()
{
base.EnteredTree();
ReloadPreview();
}
protected override void ExitedTree()
{
base.ExitedTree();
_entManager.DeleteEntity(PreviewDummy);
PreviewDummy = EntityUid.Invalid;
}
@@ -1226,11 +1199,6 @@ namespace Content.Client.Lobby.UI
{
Profile = Profile?.WithName(newName);
SetDirty();
if (!IsDirty)
return;
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, newName);
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
@@ -1575,19 +1543,6 @@ namespace Content.Client.Lobby.UI
UpdateNameEdit();
}
private async void ExportImage()
{
if (_imaging)
return;
var dir = SpriteView.OverrideDirection ?? Direction.South;
// I tried disabling the button but it looks sorta goofy as it only takes a frame or two to save
_imaging = true;
await _entManager.System<ContentSpriteSystem>().Export(PreviewDummy, dir, includeId: false);
_imaging = false;
}
private async void ImportProfile()
{
if (_exporting || CharacterSlot == null || Profile == null)

View File

@@ -1,8 +0,0 @@
<mapping:MappingActionsButton
xmlns="https://spacestation14.io"
xmlns:mapping="clr-namespace:Content.Client.Mapping"
StyleClasses="ButtonSquare" ToggleMode="True" SetSize="32 32" Margin="0 0 5 0"
TooltipDelay="0">
<TextureRect Name="Texture" Access="Public" Stretch="Scale" SetSize="16 16"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</mapping:MappingActionsButton>

View File

@@ -1,15 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Mapping;
[GenerateTypedNameReferences]
public sealed partial class MappingActionsButton : Button
{
public MappingActionsButton()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -1,4 +0,0 @@
<mapping:MappingDoNotMeasure
xmlns="https://spacestation14.io"
xmlns:mapping="clr-namespace:Content.Client.Mapping">
</mapping:MappingDoNotMeasure>

View File

@@ -1,21 +0,0 @@
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Mapping;
[GenerateTypedNameReferences]
public sealed partial class MappingDoNotMeasure : Control
{
public MappingDoNotMeasure()
{
RobustXamlLoader.Load(this);
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
return Vector2.Zero;
}
}

View File

@@ -1,69 +0,0 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Mapping;
using Robust.Client.UserInterface;
using Robust.Shared.Network;
namespace Content.Client.Mapping;
public sealed class MappingManager : IPostInjectInit
{
[Dependency] private readonly IFileDialogManager _file = default!;
[Dependency] private readonly IClientNetManager _net = default!;
private Stream? _saveStream;
private MappingMapDataMessage? _mapData;
public void PostInject()
{
_net.RegisterNetMessage<MappingSaveMapMessage>();
_net.RegisterNetMessage<MappingSaveMapErrorMessage>(OnSaveError);
_net.RegisterNetMessage<MappingMapDataMessage>(OnMapData);
}
private void OnSaveError(MappingSaveMapErrorMessage message)
{
_saveStream?.DisposeAsync();
_saveStream = null;
}
private async void OnMapData(MappingMapDataMessage message)
{
if (_saveStream == null)
{
_mapData = message;
return;
}
await _saveStream.WriteAsync(Encoding.ASCII.GetBytes(message.Yml));
await _saveStream.DisposeAsync();
_saveStream = null;
_mapData = null;
}
public async Task SaveMap()
{
if (_saveStream != null)
await _saveStream.DisposeAsync();
var request = new MappingSaveMapMessage();
_net.ClientSendMessage(request);
var path = await _file.SaveFile();
if (path is not { fileStream: var stream })
return;
if (_mapData != null)
{
await stream.WriteAsync(Encoding.ASCII.GetBytes(_mapData.Yml));
_mapData = null;
await stream.FlushAsync();
await stream.DisposeAsync();
return;
}
_saveStream = stream;
}
}

View File

@@ -1,84 +0,0 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using static Content.Client.Mapping.MappingState;
namespace Content.Client.Mapping;
public sealed class MappingOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
// 1 off in case something else uses these colors since we use them to compare
private static readonly Color PickColor = new(1, 255, 0);
private static readonly Color DeleteColor = new(255, 1, 0);
private readonly Dictionary<EntityUid, Color> _oldColors = new();
private readonly MappingState _state;
private readonly ShaderInstance _shader;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public MappingOverlay(MappingState state)
{
IoCManager.InjectDependencies(this);
_state = state;
_shader = _prototypes.Index<ShaderPrototype>("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
{
foreach (var (id, color) in _oldColors)
{
if (!_entities.TryGetComponent(id, out SpriteComponent? sprite))
continue;
if (sprite.Color == DeleteColor || sprite.Color == PickColor)
sprite.Color = color;
}
_oldColors.Clear();
if (_player.LocalEntity == null)
return;
var handle = args.WorldHandle;
handle.UseShader(_shader);
switch (_state.State)
{
case CursorState.Pick:
{
if (_state.GetHoveredEntity() is { } entity &&
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
{
_oldColors[entity] = sprite.Color;
sprite.Color = PickColor;
}
break;
}
case CursorState.Delete:
{
if (_state.GetHoveredEntity() is { } entity &&
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
{
_oldColors[entity] = sprite.Color;
sprite.Color = DeleteColor;
}
break;
}
}
handle.UseShader(null);
}
}

View File

@@ -1,39 +0,0 @@
using Content.Shared.Decals;
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Client.Mapping;
/// <summary>
/// Used to represent a button's data in the mapping editor.
/// </summary>
public sealed class MappingPrototype
{
/// <summary>
/// The prototype instance, if any.
/// Can be one of <see cref="EntityPrototype"/>, <see cref="ContentTileDefinition"/> or <see cref="DecalPrototype"/>
/// If null, this is a top-level button (such as Entities, Tiles or Decals)
/// </summary>
public readonly IPrototype? Prototype;
/// <summary>
/// The text to display on the UI for this button.
/// </summary>
public readonly string Name;
/// <summary>
/// Which other prototypes (buttons) this one is nested inside of.
/// </summary>
public List<MappingPrototype>? Parents;
/// <summary>
/// Which other prototypes (buttons) are nested inside this one.
/// </summary>
public List<MappingPrototype>? Children;
public MappingPrototype(IPrototype? prototype, string name)
{
Prototype = prototype;
Name = name;
}
}

View File

@@ -1,21 +0,0 @@
<mapping:MappingPrototypeList
xmlns="https://spacestation14.io"
xmlns:mapping="clr-namespace:Content.Client.Mapping">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Button Name="CollapseAllButton" Access="Public" Text="-" SetSize="48 48"
StyleClasses="ButtonSquare" ToolTip="Collapse All" TooltipDelay="0" />
<LineEdit Name="SearchBar" SetHeight="48" HorizontalExpand="True" Access="Public" />
<Button Name="ClearSearchButton" Access="Public" Text="X" SetSize="48 48"
StyleClasses="ButtonSquare" />
</BoxContainer>
<ScrollContainer Name="ScrollContainer" Access="Public" VerticalExpand="True"
ReserveScrollbarSpace="True">
<BoxContainer Name="PrototypeList" Access="Public" Orientation="Vertical" />
<PrototypeListContainer Name="SearchList" Access="Public" Visible="False" />
</ScrollContainer>
<mapping:MappingDoNotMeasure Visible="False">
<mapping:MappingSpawnButton Name="MeasureButton" Access="Public" />
</mapping:MappingDoNotMeasure>
</BoxContainer>
</mapping:MappingPrototypeList>

View File

@@ -1,170 +0,0 @@
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Mapping;
[GenerateTypedNameReferences]
public sealed partial class MappingPrototypeList : Control
{
private (int start, int end) _lastIndices;
private readonly List<MappingPrototype> _prototypes = new();
private readonly List<Texture> _insertTextures = new();
private readonly List<MappingPrototype> _search = new();
public MappingSpawnButton? Selected;
public Action<IPrototype, List<Texture>>? GetPrototypeData;
public event Action<MappingSpawnButton, IPrototype?>? SelectionChanged;
public event Action<MappingSpawnButton, ButtonToggledEventArgs>? CollapseToggled;
public MappingPrototypeList()
{
RobustXamlLoader.Load(this);
MeasureButton.Measure(Vector2Helpers.Infinity);
ScrollContainer.OnScrolled += UpdateSearch;
OnResized += UpdateSearch;
}
public void UpdateVisible(List<MappingPrototype> prototypes)
{
_prototypes.Clear();
PrototypeList.DisposeAllChildren();
_prototypes.AddRange(prototypes);
Selected = null;
ScrollContainer.SetScrollValue(new Vector2(0, 0));
foreach (var prototype in _prototypes)
{
Insert(PrototypeList, prototype, true);
}
}
public MappingSpawnButton Insert(Container list, MappingPrototype mapping, bool includeChildren)
{
var prototype = mapping.Prototype;
_insertTextures.Clear();
if (prototype != null)
GetPrototypeData?.Invoke(prototype, _insertTextures);
var button = new MappingSpawnButton { Prototype = mapping };
button.Label.Text = mapping.Name;
if (_insertTextures.Count > 0)
{
button.Texture.Textures.AddRange(_insertTextures);
button.Texture.InvalidateMeasure();
}
else
{
button.Texture.Visible = false;
}
if (prototype != null && button.Prototype == Selected?.Prototype)
{
Selected = button;
button.Button.Pressed = true;
}
list.AddChild(button);
button.Button.OnToggled += _ => SelectionChanged?.Invoke(button, prototype);
if (includeChildren && mapping.Children?.Count > 0)
{
button.CollapseButton.Visible = true;
button.CollapseButton.OnToggled += args => CollapseToggled?.Invoke(button, args);
}
else
{
button.CollapseButtonWrapper.Visible = false;
button.CollapseButton.Visible = false;
}
return button;
}
public void Search(List<MappingPrototype> prototypes)
{
_search.Clear();
SearchList.DisposeAllChildren();
_lastIndices = (0, -1);
_search.AddRange(prototypes);
SearchList.TotalItemCount = _search.Count;
ScrollContainer.SetScrollValue(new Vector2(0, 0));
UpdateSearch();
}
/// <summary>
/// Constructs a virtual list where not all buttons exist at one time, since there may be thousands of them.
/// </summary>
private void UpdateSearch()
{
if (!SearchList.Visible)
return;
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
var offset = Math.Max(-SearchList.Position.Y, 0);
var startIndex = (int) Math.Floor(offset / height);
SearchList.ItemOffset = startIndex;
var (prevStart, prevEnd) = _lastIndices;
var endIndex = startIndex - 1;
var spaceUsed = -height;
// calculate how far down we are scrolled
while (spaceUsed < SearchList.Parent!.Height)
{
spaceUsed += height;
endIndex += 1;
}
endIndex = Math.Min(endIndex, _search.Count - 1);
// nothing changed in terms of which buttons are visible now and before
if (endIndex == prevEnd && startIndex == prevStart)
return;
_lastIndices = (startIndex, endIndex);
// remove previously seen but now unseen buttons from the top
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
{
var control = SearchList.GetChild(0);
SearchList.RemoveChild(control);
}
// remove previously seen but now unseen buttons from the bottom
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
{
var control = SearchList.GetChild(SearchList.ChildCount - 1);
SearchList.RemoveChild(control);
}
// insert buttons that can now be seen, from the start
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
{
Insert(SearchList, _search[i], false).SetPositionInParent(0);
}
// insert buttons that can now be seen, from the end
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
{
Insert(SearchList, _search[i], false);
}
}
}

View File

@@ -1,85 +0,0 @@
<mapping:MappingScreen
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:mapping="clr-namespace:Content.Client.Mapping"
VerticalExpand="False"
VerticalAlignment="Bottom"
HorizontalAlignment="Center">
<controls:RecordedSplitContainer Name="ScreenContainer" HorizontalExpand="True"
VerticalExpand="True" SplitWidth="0"
StretchDirection="TopLeft">
<BoxContainer Orientation="Vertical" VerticalExpand="True" Name="SpawnContainer" MinWidth="200" SetWidth="600">
<mapping:MappingPrototypeList Name="Prototypes" Access="Public" VerticalExpand="True" />
<BoxContainer Name="DecalContainer" Access="Public" Orientation="Horizontal"
Visible="False">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<ColorSelectorSliders Name="DecalColorPicker" IsAlphaVisible="True" />
<Button Name="DecalPickerOpen" Text="{Loc decal-placer-window-palette}"
StyleClasses="ButtonSquare" />
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<CheckBox Name="DecalEnableAuto" Margin="0 0 0 10"
Text="{Loc decal-placer-window-enable-auto}" />
<CheckBox Name="DecalEnableSnap"
Text="{Loc decal-placer-window-enable-snap}" />
<CheckBox Name="DecalEnableCleanable"
Text="{Loc decal-placer-window-enable-cleanable}" />
<BoxContainer Name="DecalSpinBoxContainer" Orientation="Horizontal">
<Label Text="{Loc decal-placer-window-rotation}" Margin="0 0 0 1" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc decal-placer-window-zindex}" Margin="0 0 0 1" />
<SpinBox Name="DecalZIndexSpinBox" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
<BoxContainer Name="EntityContainer" Access="Public" Orientation="Horizontal"
Visible="False">
<Button Name="EntityReplaceButton" Access="Public" ToggleMode="True"
SetHeight="48"
StyleClasses="ButtonSquare" Text="{Loc 'mapping-replace'}" HorizontalExpand="True" />
<OptionButton Name="EntityPlacementMode" Access="Public"
SetHeight="48"
StyleClasses="ButtonSquare" TooltipDelay="0"
ToolTip="{Loc entity-spawn-window-override-menu-tooltip}"
HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="EraseEntityButton" Access="Public" HorizontalExpand="True"
SetHeight="48"
ToggleMode="True" Text="{Loc 'mapping-erase-entity'}" StyleClasses="ButtonSquare" />
<Button Name="EraseDecalButton" Access="Public" HorizontalExpand="True"
SetHeight="48"
ToggleMode="True" Text="{Loc 'mapping-erase-decal'}" StyleClasses="ButtonSquare" />
</BoxContainer>
<widgets:ChatBox Visible="False" />
</BoxContainer>
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
<controls:MainViewport Name="MainViewport"/>
<hotbar:HotbarGui Name="Hotbar" />
<PanelContainer Name="Actions" VerticalExpand="True" HorizontalExpand="True"
MaxHeight="48">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#222222AA" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Horizontal" Margin="15 10">
<mapping:MappingActionsButton
Name="Add" Access="Public" Disabled="True" ToolTip="" Visible="False" />
<mapping:MappingActionsButton Name="Fill" Access="Public"
ToolTip="" Visible="False" />
<mapping:MappingActionsButton Name="Grab" Access="Public"
ToolTip="" Visible="False" />
<mapping:MappingActionsButton Name="Move" Access="Public"
ToolTip="" Visible="False" />
<mapping:MappingActionsButton Name="Pick" Access="Public"
ToolTip="Pick (Hold 5)" />
<mapping:MappingActionsButton Name="Delete" Access="Public"
ToolTip="Delete (Hold 6)" />
</BoxContainer>
</PanelContainer>
</LayoutContainer>
</controls:RecordedSplitContainer>
</mapping:MappingScreen>

View File

@@ -1,197 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Client.Decals;
using Content.Client.Decals.UI;
using Content.Client.UserInterface.Screens;
using Content.Client.UserInterface.Systems.Chat.Widgets;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Mapping;
[GenerateTypedNameReferences]
public sealed partial class MappingScreen : InGameScreen
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public DecalPlacementSystem DecalSystem = default!;
private PaletteColorPicker? _picker;
private ProtoId<DecalPrototype>? _id;
private Color _decalColor = Color.White;
private float _decalRotation;
private bool _decalSnap;
private int _decalZIndex;
private bool _decalCleanable;
private bool _decalAuto;
public override ChatBox ChatBox => GetWidget<ChatBox>()!;
public event Func<MappingSpawnButton, bool>? IsDecalVisible;
public MappingScreen()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
AutoscaleMaxResolution = new Vector2i(1080, 770);
SetAnchorPreset(ScreenContainer, LayoutPreset.Wide);
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
SetAnchorPreset(SpawnContainer, LayoutPreset.Wide);
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
SetAnchorAndMarginPreset(Actions, LayoutPreset.TopWide, margin: 5);
ScreenContainer.OnSplitResizeFinished += () =>
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
var rotationSpinBox = new FloatSpinBox(90.0f, 0)
{
HorizontalExpand = true
};
DecalSpinBoxContainer.AddChild(rotationSpinBox);
DecalColorPicker.OnColorChanged += OnDecalColorPicked;
DecalPickerOpen.OnPressed += OnDecalPickerOpenPressed;
rotationSpinBox.OnValueChanged += args =>
{
_decalRotation = args.Value;
UpdateDecal();
};
DecalEnableAuto.OnToggled += args =>
{
_decalAuto = args.Pressed;
if (_id is { } id)
SelectDecal(id);
};
DecalEnableSnap.OnToggled += args =>
{
_decalSnap = args.Pressed;
UpdateDecal();
};
DecalEnableCleanable.OnToggled += args =>
{
_decalCleanable = args.Pressed;
UpdateDecal();
};
DecalZIndexSpinBox.ValueChanged += args =>
{
_decalZIndex = args.Value;
UpdateDecal();
};
for (var i = 0; i < EntitySpawnWindow.InitOpts.Length; i++)
{
EntityPlacementMode.AddItem(EntitySpawnWindow.InitOpts[i], i);
}
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
}
private void OnDecalColorPicked(Color color)
{
_decalColor = color;
DecalColorPicker.Color = color;
UpdateDecal();
}
private void OnDecalPickerOpenPressed(ButtonEventArgs obj)
{
if (_picker == null)
{
_picker = new PaletteColorPicker();
_picker.OpenToLeft();
_picker.PaletteList.OnItemSelected += args =>
{
var color = ((Color?) args.ItemList.GetSelected().First().Metadata)!.Value;
OnDecalColorPicked(color);
};
return;
}
if (_picker.IsOpen)
_picker.Close();
else
_picker.Open();
}
private void UpdateDecal()
{
if (_id is not { } id)
return;
DecalSystem.UpdateDecalInfo(id, _decalColor, _decalRotation, _decalSnap, _decalZIndex, _decalCleanable);
}
public void SelectDecal(string decalId)
{
if (!_prototype.TryIndex<DecalPrototype>(decalId, out var decal))
return;
_id = decalId;
if (_decalAuto)
{
_decalColor = Color.White;
_decalCleanable = decal.DefaultCleanable;
_decalSnap = decal.DefaultSnap;
DecalColorPicker.Color = _decalColor;
DecalEnableCleanable.Pressed = _decalCleanable;
DecalEnableSnap.Pressed = _decalSnap;
}
UpdateDecal();
RefreshList();
}
private void RefreshList()
{
foreach (var control in Prototypes.Children)
{
if (control is not MappingSpawnButton button ||
button.Prototype?.Prototype is not DecalPrototype)
{
continue;
}
foreach (var child in button.Children)
{
if (child is not MappingSpawnButton { Prototype.Prototype: DecalPrototype } childButton)
{
continue;
}
childButton.Texture.Modulate = _decalColor;
childButton.Visible = IsDecalVisible?.Invoke(childButton) ?? true;
}
}
}
public override void SetChatSize(Vector2 size)
{
ScreenContainer.DesiredSplitCenter = size.X;
ScreenContainer.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
}
public void UnPressActionsExcept(Control except)
{
Add.Pressed = Add == except;
Fill.Pressed = Fill == except;
Grab.Pressed = Grab == except;
Move.Pressed = Move == except;
Pick.Pressed = Pick == except;
Delete.Pressed = Delete == except;
}
}

View File

@@ -1,26 +0,0 @@
<mapping:MappingSpawnButton
xmlns="https://spacestation14.io"
xmlns:mapping="clr-namespace:Content.Client.Mapping">
<BoxContainer Orientation="Vertical">
<Control>
<Button Name="Button" Access="Public" ToggleMode="True" StyleClasses="ButtonSquare" />
<BoxContainer Orientation="Horizontal">
<LayeredTextureRect Name="Texture" Access="Public" MinSize="48 48"
HorizontalAlignment="Center" VerticalAlignment="Center"
Stretch="KeepAspectCentered" CanShrink="True" />
<Control SetSize="48 48" Access="Public" Name="CollapseButtonWrapper">
<Button Name="CollapseButton" Access="Public" Text="▶"
ToggleMode="True" StyleClasses="ButtonSquare" SetSize="48 48" />
</Control>
<Label Name="Label" Access="Public"
VAlign="Center"
VerticalExpand="True"
MinHeight="48"
Margin="5 0"
HorizontalExpand="True" ClipText="True" />
</BoxContainer>
</Control>
<BoxContainer Name="ChildrenPrototypes" Access="Public" Orientation="Vertical"
Margin="24 0 0 0" />
</BoxContainer>
</mapping:MappingSpawnButton>

View File

@@ -1,16 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Mapping;
[GenerateTypedNameReferences]
public sealed partial class MappingSpawnButton : Control
{
public MappingPrototype? Prototype;
public MappingSpawnButton()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -1,936 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Managers;
using Content.Client.ContextMenu.UI;
using Content.Client.Decals;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Client.Verbs;
using Content.Shared.Administration;
using Content.Shared.Decals;
using Content.Shared.Input;
using Content.Shared.Maps;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Placement;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static System.StringComparison;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.LineEdit;
using static Robust.Client.UserInterface.Controls.OptionButton;
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
namespace Content.Client.Mapping;
public sealed class MappingState : GameplayStateBase
{
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entityNetwork = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly MappingManager _mapping = default!;
[Dependency] private readonly IOverlayManager _overlays = default!;
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IResourceCache _resources = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private EntityMenuUIController _entityMenuController = default!;
private DecalPlacementSystem _decal = default!;
private SpriteSystem _sprite = default!;
private TransformSystem _transform = default!;
private VerbSystem _verbs = default!;
private readonly ISawmill _sawmill;
private readonly GameplayStateLoadController _loadController;
private bool _setup;
private readonly List<MappingPrototype> _allPrototypes = new();
private readonly Dictionary<IPrototype, MappingPrototype> _allPrototypesDict = new();
private readonly Dictionary<Type, Dictionary<string, MappingPrototype>> _idDict = new();
private readonly List<MappingPrototype> _prototypes = new();
private (TimeSpan At, MappingSpawnButton Button)? _lastClicked;
private Control? _scrollTo;
private bool _updatePlacement;
private bool _updateEraseDecal;
private MappingScreen Screen => (MappingScreen) UserInterfaceManager.ActiveScreen!;
private MainViewport Viewport => UserInterfaceManager.ActiveScreen!.GetWidget<MainViewport>()!;
public CursorState State { get; set; }
public MappingState()
{
IoCManager.InjectDependencies(this);
_sawmill = _log.GetSawmill("mapping");
_loadController = UserInterfaceManager.GetUIController<GameplayStateLoadController>();
}
protected override void Startup()
{
EnsureSetup();
base.Startup();
UserInterfaceManager.LoadScreen<MappingScreen>();
_loadController.LoadScreen();
var context = _input.Contexts.GetContext("common");
context.AddFunction(ContentKeyFunctions.MappingUnselect);
context.AddFunction(ContentKeyFunctions.SaveMap);
context.AddFunction(ContentKeyFunctions.MappingEnablePick);
context.AddFunction(ContentKeyFunctions.MappingEnableDelete);
context.AddFunction(ContentKeyFunctions.MappingPick);
context.AddFunction(ContentKeyFunctions.MappingRemoveDecal);
context.AddFunction(ContentKeyFunctions.MappingCancelEraseDecal);
context.AddFunction(ContentKeyFunctions.MappingOpenContextMenu);
Screen.DecalSystem = _decal;
Screen.Prototypes.SearchBar.OnTextChanged += OnSearch;
Screen.Prototypes.CollapseAllButton.OnPressed += OnCollapseAll;
Screen.Prototypes.ClearSearchButton.OnPressed += OnClearSearch;
Screen.Prototypes.GetPrototypeData += OnGetData;
Screen.Prototypes.SelectionChanged += OnSelected;
Screen.Prototypes.CollapseToggled += OnCollapseToggled;
Screen.Pick.OnPressed += OnPickPressed;
Screen.Delete.OnPressed += OnDeletePressed;
Screen.EntityReplaceButton.OnToggled += OnEntityReplacePressed;
Screen.EntityPlacementMode.OnItemSelected += OnEntityPlacementSelected;
Screen.EraseEntityButton.OnToggled += OnEraseEntityPressed;
Screen.EraseDecalButton.OnToggled += OnEraseDecalPressed;
_placement.PlacementChanged += OnPlacementChanged;
CommandBinds.Builder
.Bind(ContentKeyFunctions.MappingUnselect, new PointerInputCmdHandler(HandleMappingUnselect, outsidePrediction: true))
.Bind(ContentKeyFunctions.SaveMap, new PointerInputCmdHandler(HandleSaveMap, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingEnablePick, new PointerStateInputCmdHandler(HandleEnablePick, HandleDisablePick, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingEnableDelete, new PointerStateInputCmdHandler(HandleEnableDelete, HandleDisableDelete, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingPick, new PointerInputCmdHandler(HandlePick, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingRemoveDecal, new PointerInputCmdHandler(HandleEditorCancelPlace, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingCancelEraseDecal, new PointerInputCmdHandler(HandleCancelEraseDecal, outsidePrediction: true))
.Bind(ContentKeyFunctions.MappingOpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu, outsidePrediction: true))
.Register<MappingState>();
_overlays.AddOverlay(new MappingOverlay(this));
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
Screen.Prototypes.UpdateVisible(_prototypes);
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
{
if (!obj.WasModified<EntityPrototype>() &&
!obj.WasModified<ContentTileDefinition>() &&
!obj.WasModified<DecalPrototype>())
{
return;
}
ReloadPrototypes();
}
private bool HandleOpenContextMenu(in PointerInputCmdArgs args)
{
Deselect();
var coords = args.Coordinates.ToMap(_entityManager, _transform);
if (_verbs.TryGetEntityMenuEntities(coords, out var entities))
_entityMenuController.OpenRootMenu(entities);
return true;
}
protected override void Shutdown()
{
CommandBinds.Unregister<MappingState>();
Screen.Prototypes.SearchBar.OnTextChanged -= OnSearch;
Screen.Prototypes.CollapseAllButton.OnPressed -= OnCollapseAll;
Screen.Prototypes.ClearSearchButton.OnPressed -= OnClearSearch;
Screen.Prototypes.GetPrototypeData -= OnGetData;
Screen.Prototypes.SelectionChanged -= OnSelected;
Screen.Prototypes.CollapseToggled -= OnCollapseToggled;
Screen.Pick.OnPressed -= OnPickPressed;
Screen.Delete.OnPressed -= OnDeletePressed;
Screen.EntityReplaceButton.OnToggled -= OnEntityReplacePressed;
Screen.EntityPlacementMode.OnItemSelected -= OnEntityPlacementSelected;
Screen.EraseEntityButton.OnToggled -= OnEraseEntityPressed;
Screen.EraseDecalButton.OnToggled -= OnEraseDecalPressed;
_placement.PlacementChanged -= OnPlacementChanged;
_prototypeManager.PrototypesReloaded -= OnPrototypesReloaded;
UserInterfaceManager.ClearWindows();
_loadController.UnloadScreen();
UserInterfaceManager.UnloadScreen();
var context = _input.Contexts.GetContext("common");
context.RemoveFunction(ContentKeyFunctions.MappingUnselect);
context.RemoveFunction(ContentKeyFunctions.SaveMap);
context.RemoveFunction(ContentKeyFunctions.MappingEnablePick);
context.RemoveFunction(ContentKeyFunctions.MappingEnableDelete);
context.RemoveFunction(ContentKeyFunctions.MappingPick);
context.RemoveFunction(ContentKeyFunctions.MappingRemoveDecal);
context.RemoveFunction(ContentKeyFunctions.MappingCancelEraseDecal);
context.RemoveFunction(ContentKeyFunctions.MappingOpenContextMenu);
_overlays.RemoveOverlay<MappingOverlay>();
base.Shutdown();
}
private void EnsureSetup()
{
if (_setup)
return;
_setup = true;
_entityMenuController = UserInterfaceManager.GetUIController<EntityMenuUIController>();
_decal = _entityManager.System<DecalPlacementSystem>();
_sprite = _entityManager.System<SpriteSystem>();
_transform = _entityManager.System<TransformSystem>();
_verbs = _entityManager.System<VerbSystem>();
ReloadPrototypes();
}
private void ReloadPrototypes()
{
var entities = new MappingPrototype(null, Loc.GetString("mapping-entities")) { Children = new List<MappingPrototype>() };
_prototypes.Add(entities);
var mappings = new Dictionary<string, MappingPrototype>();
foreach (var entity in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
Register(entity, entity.ID, entities);
}
Sort(mappings, entities);
mappings.Clear();
var tiles = new MappingPrototype(null, Loc.GetString("mapping-tiles")) { Children = new List<MappingPrototype>() };
_prototypes.Add(tiles);
foreach (var tile in _prototypeManager.EnumeratePrototypes<ContentTileDefinition>())
{
Register(tile, tile.ID, tiles);
}
Sort(mappings, tiles);
mappings.Clear();
var decals = new MappingPrototype(null, Loc.GetString("mapping-decals")) { Children = new List<MappingPrototype>() };
_prototypes.Add(decals);
foreach (var decal in _prototypeManager.EnumeratePrototypes<DecalPrototype>())
{
Register(decal, decal.ID, decals);
}
Sort(mappings, decals);
mappings.Clear();
}
private void Sort(Dictionary<string, MappingPrototype> prototypes, MappingPrototype topLevel)
{
static int Compare(MappingPrototype a, MappingPrototype b)
{
return string.Compare(a.Name, b.Name, OrdinalIgnoreCase);
}
topLevel.Children ??= new List<MappingPrototype>();
foreach (var prototype in prototypes.Values)
{
if (prototype.Parents == null && prototype != topLevel)
{
prototype.Parents = new List<MappingPrototype> { topLevel };
topLevel.Children.Add(prototype);
}
prototype.Parents?.Sort(Compare);
prototype.Children?.Sort(Compare);
}
topLevel.Children.Sort(Compare);
}
private MappingPrototype? Register<T>(T? prototype, string id, MappingPrototype topLevel) where T : class, IPrototype, IInheritingPrototype
{
{
if (prototype == null &&
_prototypeManager.TryIndex(id, out prototype) &&
prototype is EntityPrototype entity)
{
if (entity.HideSpawnMenu || entity.Abstract)
prototype = null;
}
}
if (prototype == null)
{
if (!_prototypeManager.TryGetMapping(typeof(T), id, out var node))
{
_sawmill.Error($"No {nameof(T)} found with id {id}");
return null;
}
var ids = _idDict.GetOrNew(typeof(T));
if (ids.TryGetValue(id, out var mapping))
{
return mapping;
}
else
{
var name = node.TryGet("name", out ValueDataNode? nameNode)
? nameNode.Value
: id;
if (node.TryGet("suffix", out ValueDataNode? suffix))
name = $"{name} [{suffix.Value}]";
mapping = new MappingPrototype(prototype, name);
_allPrototypes.Add(mapping);
ids.Add(id, mapping);
if (node.TryGet("parent", out ValueDataNode? parentValue))
{
var parent = Register<T>(null, parentValue.Value, topLevel);
if (parent != null)
{
mapping.Parents ??= new List<MappingPrototype>();
mapping.Parents.Add(parent);
parent.Children ??= new List<MappingPrototype>();
parent.Children.Add(mapping);
}
}
else if (node.TryGet("parent", out SequenceDataNode? parentSequence))
{
foreach (var parentNode in parentSequence.Cast<ValueDataNode>())
{
var parent = Register<T>(null, parentNode.Value, topLevel);
if (parent != null)
{
mapping.Parents ??= new List<MappingPrototype>();
mapping.Parents.Add(parent);
parent.Children ??= new List<MappingPrototype>();
parent.Children.Add(mapping);
}
}
}
else
{
topLevel.Children ??= new List<MappingPrototype>();
topLevel.Children.Add(mapping);
mapping.Parents ??= new List<MappingPrototype>();
mapping.Parents.Add(topLevel);
}
return mapping;
}
}
else
{
var ids = _idDict.GetOrNew(typeof(T));
if (ids.TryGetValue(id, out var mapping))
{
return mapping;
}
else
{
var entity = prototype as EntityPrototype;
var name = entity?.Name ?? prototype.ID;
if (!string.IsNullOrWhiteSpace(entity?.EditorSuffix))
name = $"{name} [{entity.EditorSuffix}]";
mapping = new MappingPrototype(prototype, name);
_allPrototypes.Add(mapping);
_allPrototypesDict.Add(prototype, mapping);
ids.Add(prototype.ID, mapping);
}
if (prototype.Parents == null)
{
topLevel.Children ??= new List<MappingPrototype>();
topLevel.Children.Add(mapping);
mapping.Parents ??= new List<MappingPrototype>();
mapping.Parents.Add(topLevel);
return mapping;
}
foreach (var parentId in prototype.Parents)
{
var parent = Register<T>(null, parentId, topLevel);
if (parent != null)
{
mapping.Parents ??= new List<MappingPrototype>();
mapping.Parents.Add(parent);
parent.Children ??= new List<MappingPrototype>();
parent.Children.Add(mapping);
}
}
return mapping;
}
}
private void OnPlacementChanged(object? sender, EventArgs e)
{
_updatePlacement = true;
}
protected override void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
{
if (args.Viewport == null)
base.OnKeyBindStateChanged(new ViewportBoundKeyEventArgs(args.KeyEventArgs, Viewport.Viewport));
else
base.OnKeyBindStateChanged(args);
}
private void OnSearch(LineEditEventArgs args)
{
if (string.IsNullOrEmpty(args.Text))
{
Screen.Prototypes.PrototypeList.Visible = true;
Screen.Prototypes.SearchList.Visible = false;
return;
}
var matches = new List<MappingPrototype>();
foreach (var prototype in _allPrototypes)
{
if (prototype.Name.Contains(args.Text, OrdinalIgnoreCase))
matches.Add(prototype);
}
matches.Sort(static (a, b) => string.Compare(a.Name, b.Name, OrdinalIgnoreCase));
Screen.Prototypes.PrototypeList.Visible = false;
Screen.Prototypes.SearchList.Visible = true;
Screen.Prototypes.Search(matches);
}
private void OnCollapseAll(ButtonEventArgs args)
{
foreach (var child in Screen.Prototypes.PrototypeList.Children)
{
if (child is not MappingSpawnButton button)
continue;
Collapse(button);
}
Screen.Prototypes.ScrollContainer.SetScrollValue(new Vector2(0, 0));
}
private void OnClearSearch(ButtonEventArgs obj)
{
Screen.Prototypes.SearchBar.Text = string.Empty;
OnSearch(new LineEditEventArgs(Screen.Prototypes.SearchBar, string.Empty));
}
private void OnGetData(IPrototype prototype, List<Texture> textures)
{
switch (prototype)
{
case EntityPrototype entity:
textures.AddRange(SpriteComponent.GetPrototypeTextures(entity, _resources).Select(t => t.Default));
break;
case DecalPrototype decal:
textures.Add(_sprite.Frame0(decal.Sprite));
break;
case ContentTileDefinition tile:
if (tile.Sprite?.ToString() is { } sprite)
textures.Add(_resources.GetResource<TextureResource>(sprite).Texture);
break;
}
}
private void OnSelected(MappingPrototype mapping)
{
if (mapping.Prototype == null)
return;
var chain = new Stack<MappingPrototype>();
chain.Push(mapping);
var parent = mapping.Parents?.FirstOrDefault();
while (parent != null)
{
chain.Push(parent);
parent = parent.Parents?.FirstOrDefault();
}
_lastClicked = null;
Control? last = null;
var children = Screen.Prototypes.PrototypeList.Children;
foreach (var prototype in chain)
{
foreach (var child in children)
{
if (child is MappingSpawnButton button &&
button.Prototype == prototype)
{
UnCollapse(button);
OnSelected(button, prototype.Prototype);
children = button.ChildrenPrototypes.Children;
last = child;
break;
}
}
}
if (last != null && Screen.Prototypes.PrototypeList.Visible)
_scrollTo = last;
}
private void OnSelected(MappingSpawnButton button, IPrototype? prototype)
{
var time = _timing.CurTime;
if (prototype is DecalPrototype)
Screen.SelectDecal(prototype.ID);
// Double-click functionality if it's collapsible.
if (_lastClicked is { } lastClicked &&
lastClicked.Button == button &&
lastClicked.At > time - TimeSpan.FromSeconds(0.333) &&
string.IsNullOrEmpty(Screen.Prototypes.SearchBar.Text) &&
button.CollapseButton.Visible)
{
button.CollapseButton.Pressed = !button.CollapseButton.Pressed;
ToggleCollapse(button);
button.Button.Pressed = true;
Screen.Prototypes.Selected = button;
_lastClicked = null;
return;
}
// Toggle if it's the same button (at least if we just unclicked it).
if (!button.Button.Pressed && button.Prototype?.Prototype != null && _lastClicked?.Button == button)
{
_lastClicked = null;
Deselect();
return;
}
_lastClicked = (time, button);
if (button.Prototype == null)
return;
if (Screen.Prototypes.Selected is { } oldButton &&
oldButton != button)
{
Deselect();
}
Screen.EntityContainer.Visible = false;
Screen.DecalContainer.Visible = false;
switch (prototype)
{
case EntityPrototype entity:
{
var placementId = Screen.EntityPlacementMode.SelectedId;
var placement = new PlacementInformation
{
PlacementOption = placementId > 0 ? EntitySpawnWindow.InitOpts[placementId] : entity.PlacementMode,
EntityType = entity.ID,
IsTile = false
};
Screen.EntityContainer.Visible = true;
_decal.SetActive(false);
_placement.BeginPlacing(placement);
break;
}
case DecalPrototype decal:
_placement.Clear();
_decal.SetActive(true);
_decal.UpdateDecalInfo(decal.ID, Color.White, 0, true, 0, false);
Screen.DecalContainer.Visible = true;
break;
case ContentTileDefinition tile:
{
var placement = new PlacementInformation
{
PlacementOption = "AlignTileAny",
TileType = tile.TileId,
IsTile = true
};
_decal.SetActive(false);
_placement.BeginPlacing(placement);
break;
}
default:
_placement.Clear();
break;
}
Screen.Prototypes.Selected = button;
button.Button.Pressed = true;
}
private void Deselect()
{
if (Screen.Prototypes.Selected is { } selected)
{
selected.Button.Pressed = false;
Screen.Prototypes.Selected = null;
if (selected.Prototype?.Prototype is DecalPrototype)
{
_decal.SetActive(false);
Screen.DecalContainer.Visible = false;
}
if (selected.Prototype?.Prototype is EntityPrototype)
{
_placement.Clear();
}
if (selected.Prototype?.Prototype is ContentTileDefinition)
{
_placement.Clear();
}
}
}
private void OnCollapseToggled(MappingSpawnButton button, ButtonToggledEventArgs args)
{
ToggleCollapse(button);
}
private void OnPickPressed(ButtonEventArgs args)
{
if (args.Button.Pressed)
EnablePick();
else
DisablePick();
}
private void OnDeletePressed(ButtonEventArgs obj)
{
if (obj.Button.Pressed)
EnableDelete();
else
DisableDelete();
}
private void OnEntityReplacePressed(ButtonToggledEventArgs args)
{
_placement.Replacement = args.Pressed;
}
private void OnEntityPlacementSelected(ItemSelectedEventArgs args)
{
Screen.EntityPlacementMode.SelectId(args.Id);
if (_placement.CurrentMode != null)
{
var placement = new PlacementInformation
{
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
EntityType = _placement.CurrentPermission!.EntityType,
TileType = _placement.CurrentPermission.TileType,
Range = 2,
IsTile = _placement.CurrentPermission.IsTile,
};
_placement.BeginPlacing(placement);
}
}
private void OnEraseEntityPressed(ButtonEventArgs args)
{
if (args.Button.Pressed == _placement.Eraser)
return;
if (args.Button.Pressed)
EnableEraser();
else
DisableEraser();
}
private void OnEraseDecalPressed(ButtonToggledEventArgs args)
{
_placement.Clear();
Deselect();
Screen.EraseEntityButton.Pressed = false;
_updatePlacement = true;
_updateEraseDecal = args.Pressed;
}
private void EnableEraser()
{
if (_placement.Eraser)
return;
_placement.Clear();
_placement.ToggleEraser();
Screen.EntityPlacementMode.Disabled = true;
Screen.EraseDecalButton.Pressed = false;
Deselect();
}
private void DisableEraser()
{
if (!_placement.Eraser)
return;
_placement.ToggleEraser();
Screen.EntityPlacementMode.Disabled = false;
}
private void EnablePick()
{
Screen.UnPressActionsExcept(Screen.Pick);
State = CursorState.Pick;
}
private void DisablePick()
{
Screen.Pick.Pressed = false;
State = CursorState.None;
}
private void EnableDelete()
{
Screen.UnPressActionsExcept(Screen.Delete);
State = CursorState.Delete;
EnableEraser();
}
private void DisableDelete()
{
Screen.Delete.Pressed = false;
State = CursorState.None;
DisableEraser();
}
private bool HandleMappingUnselect(in PointerInputCmdArgs args)
{
if (Screen.Prototypes.Selected is not { Prototype.Prototype: DecalPrototype })
return false;
Deselect();
return true;
}
private bool HandleSaveMap(in PointerInputCmdArgs args)
{
#if FULL_RELEASE
return false;
#endif
if (!_admin.IsAdmin(true) || !_admin.HasFlag(AdminFlags.Host))
return false;
SaveMap();
return true;
}
private bool HandleEnablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
EnablePick();
return true;
}
private bool HandleDisablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
DisablePick();
return true;
}
private bool HandleEnableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
EnableDelete();
return true;
}
private bool HandleDisableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
DisableDelete();
return true;
}
private bool HandlePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (State != CursorState.Pick)
return false;
MappingPrototype? button = null;
// Try and get tile under it
// TODO: Separate mode for decals.
if (!uid.IsValid())
{
var mapPos = _transform.ToMapCoordinates(coords);
if (_mapMan.TryFindGridAt(mapPos, out var gridUid, out var grid) &&
_entityManager.System<SharedMapSystem>().TryGetTileRef(gridUid, grid, coords, out var tileRef) &&
_allPrototypesDict.TryGetValue(tileRef.GetContentTileDefinition(), out button))
{
OnSelected(button);
return true;
}
}
if (button == null)
{
if (uid == EntityUid.Invalid ||
_entityManager.GetComponentOrNull<MetaDataComponent>(uid) is not { EntityPrototype: { } prototype } ||
!_allPrototypesDict.TryGetValue(prototype, out button))
{
// we always block other input handlers if pick mode is enabled
// this makes you not accidentally place something in space because you
// miss-clicked while holding down the pick hotkey
return true;
}
// Selected an entity
OnSelected(button);
// Match rotation
_placement.Direction = _entityManager.GetComponent<TransformComponent>(uid).LocalRotation.GetDir();
}
return true;
}
private bool HandleEditorCancelPlace(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (!Screen.EraseDecalButton.Pressed)
return false;
_entityNetwork.SendSystemNetworkMessage(new RequestDecalRemovalEvent(_entityManager.GetNetCoordinates(coords)));
return true;
}
private bool HandleCancelEraseDecal(in PointerInputCmdArgs args)
{
if (!Screen.EraseDecalButton.Pressed)
return false;
Screen.EraseDecalButton.Pressed = false;
return true;
}
private async void SaveMap()
{
await _mapping.SaveMap();
}
private void ToggleCollapse(MappingSpawnButton button)
{
if (button.CollapseButton.Pressed)
{
if (button.Prototype?.Children != null)
{
foreach (var child in button.Prototype.Children)
{
Screen.Prototypes.Insert(button.ChildrenPrototypes, child, true);
}
}
button.CollapseButton.Label.Text = "▼";
}
else
{
button.ChildrenPrototypes.DisposeAllChildren();
button.CollapseButton.Label.Text = "▶";
}
}
private void Collapse(MappingSpawnButton button)
{
if (!button.CollapseButton.Pressed)
return;
button.CollapseButton.Pressed = false;
ToggleCollapse(button);
}
private void UnCollapse(MappingSpawnButton button)
{
if (button.CollapseButton.Pressed)
return;
button.CollapseButton.Pressed = true;
ToggleCollapse(button);
}
public EntityUid? GetHoveredEntity()
{
if (UserInterfaceManager.CurrentlyHovered is not IViewportControl viewport ||
_input.MouseScreenPosition is not { IsValid: true } position)
{
return null;
}
var mapPos = viewport.PixelToMap(position.Position);
return GetClickedEntity(mapPos);
}
public override void FrameUpdate(FrameEventArgs e)
{
if (_updatePlacement)
{
_updatePlacement = false;
if (!_placement.IsActive && _decal.GetActiveDecal().Decal == null)
Deselect();
Screen.EraseEntityButton.Pressed = _placement.Eraser;
Screen.EraseDecalButton.Pressed = _updateEraseDecal;
Screen.EntityPlacementMode.Disabled = _placement.Eraser;
}
if (_scrollTo is not { } scrollTo)
return;
// this is not ideal but we wait until the control's height is computed to use
// its position to scroll to
if (scrollTo.Height > 0 && Screen.Prototypes.PrototypeList.Visible)
{
var y = scrollTo.GlobalPosition.Y - Screen.Prototypes.ScrollContainer.Height / 2 + scrollTo.Height;
var scroll = Screen.Prototypes.ScrollContainer;
scroll.SetScrollValue(scroll.GetScrollValue() + new Vector2(0, y));
_scrollTo = null;
}
}
// TODO this doesn't handle pressing down multiple state hotkeys at the moment
public enum CursorState
{
None,
Pick,
Delete
}
}

View File

@@ -13,6 +13,7 @@ public sealed partial class MappingSystem : EntitySystem
{
[Dependency] private readonly IPlacementManager _placementMan = default!;
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
/// <summary>
@@ -25,6 +26,8 @@ public sealed partial class MappingSystem : EntitySystem
/// </summary>
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
public string DefaultMappingActions = "/mapping_actions.yml";
public override void Initialize()
{
base.Initialize();
@@ -33,6 +36,11 @@ public sealed partial class MappingSystem : EntitySystem
SubscribeLocalEvent<StartPlacementActionEvent>(OnStartPlacementAction);
}
public void LoadMappingActions()
{
_actionsSystem.LoadActionAssignments(DefaultMappingActions, false);
}
/// <summary>
/// This checks if the placement manager is currently active, and attempts to copy the placement information for
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd

View File

@@ -18,7 +18,6 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
public bool Enabled { get; set; }
@@ -63,11 +62,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
if (_dragging == null) return;
if (_lastMousePosition != null && TryComp(_dragging.Value, out TransformComponent? xform) &&
TryComp<PhysicsComponent>(_dragging.Value, out _) &&
TryComp<PhysicsComponent>(_dragging.Value, out var body) &&
xform.MapID == _lastMousePosition.Value.MapId)
{
var tickTime = _gameTiming.TickPeriod;
var distance = _lastMousePosition.Value.Position - _transformSystem.GetWorldPosition(xform);
var distance = _lastMousePosition.Value.Position - xform.WorldPosition;
RaiseNetworkEvent(new GridDragVelocityRequest()
{
Grid = GetNetEntity(_dragging.Value),

View File

@@ -1,27 +0,0 @@
using Content.Shared.Conveyor;
using Content.Shared.Materials;
using Robust.Client.GameObjects;
namespace Content.Client.Materials;
public sealed class RecyclerVisualizerSystem : VisualizerSystem<RecyclerVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, RecyclerVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null || !args.Sprite.LayerMapTryGet(RecyclerVisualLayers.Main, out var layer))
return;
AppearanceSystem.TryGetData<ConveyorState>(uid, ConveyorVisuals.State, out var running);
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Bloody, out var bloody);
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Broken, out var broken);
var activityState = running == ConveyorState.Off ? 0 : 1;
if (broken) //breakage overrides activity
activityState = 2;
var bloodyKey = bloody ? component.BloodyKey : string.Empty;
var state = $"{component.BaseKey}{activityState}{bloodyKey}";
args.Sprite.LayerSetState(layer, state);
}
}

View File

@@ -1,17 +0,0 @@
namespace Content.Client.Materials;
[RegisterComponent]
public sealed partial class RecyclerVisualsComponent : Component
{
/// <summary>
/// Key appended to state string if bloody.
/// </summary>
[DataField]
public string BloodyKey = "bld";
/// <summary>
/// Base key for the visual state.
/// </summary>
[DataField]
public string BaseKey = "grinder-o";
}

View File

@@ -257,7 +257,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
mainContainer.AddChild(jobContainer);
// Job icon
if (_prototypeManager.TryIndex<JobIconPrototype>(sensor.JobIcon, out var proto))
if (_prototypeManager.TryIndex<StatusIconPrototype>(sensor.JobIcon, out var proto))
{
var jobIcon = new TextureRect()
{

View File

@@ -64,15 +64,15 @@ public sealed class JetpackSystem : SharedJetpackSystem
private void CreateParticles(EntityUid uid)
{
var uidXform = Transform(uid);
// Don't show particles unless the user is moving.
if (Container.TryGetContainingContainer((uid, uidXform, null), out var container) &&
if (Container.TryGetContainingContainer(uid, out var container) &&
TryComp<PhysicsComponent>(container.Owner, out var body) &&
body.LinearVelocity.LengthSquared() < 1f)
{
return;
}
var uidXform = Transform(uid);
var coordinates = uidXform.Coordinates;
var gridUid = _transform.GetGrid(coordinates);

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