Compare commits
1 Commits
ed-28-08-2
...
TheShuEd-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3f222e7d |
14
.github/FUNDING.yml
vendored
14
.github/FUNDING.yml
vendored
@@ -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
6
.vscode/tasks.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
}
|
||||
};
|
||||
|
||||
OnOpen += () =>
|
||||
{
|
||||
Bwoink.ChannelSelector.StopFiltering();
|
||||
Bwoink.PopulateList();
|
||||
};
|
||||
OnOpen += () => Bwoink.PopulateList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -93,6 +93,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
public void AlertClicked(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
RaisePredictiveEvent(new ClickAlertEvent(alertType));
|
||||
RaiseNetworkEvent(new ClickAlertEvent(alertType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasMinerSystem : SharedGasMinerSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Client.Clothing.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class CursedMaskSystem : SharedCursedMaskSystem;
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
|
||||
namespace Content.Client.Explosion.EntitySystems;
|
||||
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) :
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<mapping:MappingDoNotMeasure
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
</mapping:MappingDoNotMeasure>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user