Merge remote-tracking branch 'upstream/master' into ed-13-06-2024-upstream2

# Conflicts:
#	Content.Client/Guidebook/GuidebookSystem.cs
#	Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs
#	Content.Shared/Guidebook/GuideEntry.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Resources/Prototypes/Accents/word_replacements.yml
#	Resources/Prototypes/Maps/arenas.yml
#	Resources/Prototypes/Maps/atlas.yml
#	Resources/Prototypes/Maps/bagel.yml
#	Resources/Prototypes/Maps/box.yml
#	Resources/Prototypes/Maps/cluster.yml
#	Resources/Prototypes/Maps/core.yml
#	Resources/Prototypes/Maps/debug.yml
#	Resources/Prototypes/Maps/europa.yml
#	Resources/Prototypes/Maps/fland.yml
#	Resources/Prototypes/Maps/marathon.yml
#	Resources/Prototypes/Maps/meta.yml
#	Resources/Prototypes/Maps/oasis.yml
#	Resources/Prototypes/Maps/omega.yml
#	Resources/Prototypes/Maps/origin.yml
#	Resources/Prototypes/Maps/packed.yml
#	Resources/Prototypes/Maps/reach.yml
#	Resources/Prototypes/Maps/saltern.yml
#	Resources/Prototypes/Maps/train.yml
#	Resources/Prototypes/lobbyscreens.yml
This commit is contained in:
Ed
2024-06-13 12:35:00 +03:00
475 changed files with 23765 additions and 12166 deletions

View File

@@ -6,7 +6,8 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
<TabContainer Name="MasterTabContainer">
<adminTab:AdminTab />
<adminbusTab:AdminbusTab />
@@ -14,6 +15,7 @@
<tabs:RoundTab />
<tabs:ServerTab />
<panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" />
<baby:BabyJailTab Name="BabyJailControl" Access="Public" />
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
</TabContainer>

View File

@@ -21,8 +21,12 @@ namespace Content.Client.Administration.UI
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
/*
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-baby-jail-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(8, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing)

View File

@@ -0,0 +1,6 @@
<controls:BabyJailStatusWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
Title="{Loc admin-ui-baby-jail-window-title}">
<RichTextLabel Name="MessageLabel" Access="Public" />
</controls:BabyJailStatusWindow>

View File

@@ -0,0 +1,21 @@
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
[GenerateTypedNameReferences]
public sealed partial class BabyJailStatusWindow : FancyWindow
{
public BabyJailStatusWindow()
{
RobustXamlLoader.Load(this);
MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
}
}

View File

@@ -0,0 +1,26 @@
<controls:BabyJailTab
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Margin="4">
<BoxContainer Orientation="Vertical">
<cc:CommandButton Name="EnabledButton" Command="babyjail" ToggleMode="True"
Text="{Loc admin-ui-baby-jail-disabled}"
ToolTip="{Loc admin-ui-baby-jail-tooltip}" />
<cc:CommandButton Name="ShowReasonButton" Command="babyjail_show_reason"
ToggleMode="True" Text="{Loc admin-ui-baby-jail-show-reason}"
ToolTip="{Loc admin-ui-baby-jail-show-reason-tooltip}" />
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-account-age}" MinWidth="175" />
<LineEdit Name="MaxAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-overall-minutes}" MinWidth="175" />
<LineEdit Name="MaxOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:BabyJailTab>

View File

@@ -0,0 +1,75 @@
using Content.Shared.Administration.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
[GenerateTypedNameReferences]
public sealed partial class BabyJailTab : Control
{
[Dependency] private readonly IConsoleHost _console = default!;
private string _maxAccountAge;
private string _maxOverallMinutes;
public BabyJailTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
_maxOverallMinutes = MaxOverallMinutes.Text;
}
private void SendMaxAccountAge(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxAccountAge ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_account_age {minutes}");
}
private void SendMaxOverallMinutes(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxOverallMinutes ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
}
public void UpdateStatus(BabyJailStatus status)
{
EnabledButton.Pressed = status.Enabled;
EnabledButton.Text = Loc.GetString(status.Enabled
? "admin-ui-baby-jail-enabled"
: "admin-ui-baby-jail-disabled"
);
EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
ShowReasonButton.Pressed = status.ShowReason;
MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
_maxOverallMinutes = MaxOverallMinutes.Text;
}
}

View File

@@ -1,15 +1,21 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
Text="{Loc Object type:}" />
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
</BoxContainer>
<cc:HSeparator/>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ObjectList">
</BoxContainer>
</ScrollContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<ot:ObjectsTabHeader Name="ListHeader"/>
<cc:HSeparator/>
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
</BoxContainer>
</BoxContainer>
</Control>

View File

@@ -1,5 +1,7 @@
using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
@@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List<ObjectsTabEntry> _objects = new();
private List<ObjectsTabSelection> _selections = new();
private readonly List<ObjectsTabSelection> _selections = new();
private bool _ascending = false; // Set to false for descending order by default
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
// Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
// OR
// I can do this.
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate;
public ObjectsTab()
{
@@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
RefreshObjectList();
// Set initial selection and refresh the list to apply the initial sort order
var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
RefreshObjectList(defaultSelection); // Refresh the list with the default selection
// Initialize the next update time
_nextUpdate = TimeSpan.Zero;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
return;
_nextUpdate = _timing.CurTime + _updateFrequency;
RefreshObjectList();
}
@@ -81,32 +107,70 @@ public sealed partial class ObjectsTab : Control
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
foreach (var control in _objects)
entities.Sort((a, b) =>
{
ObjectList.RemoveChild(control);
var valueA = GetComparableValue(a, _headerClicked);
var valueB = GetComparableValue(b, _headerClicked);
return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
});
var listData = new List<ObjectsListData>();
for (int index = 0; index < entities.Count; index++)
{
var info = entities[index];
listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
}
_objects.Clear();
foreach (var (name, nent) in entities)
{
var ctrl = new ObjectsTabEntry(name, nent);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
}
SearchList.PopulateList(listData);
}
protected override void FrameUpdate(FrameEventArgs args)
private void GenerateButton(ListData data, ListContainerButton button)
{
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return;
// I do not care for precision.
_nextUpdate = _timing.CurTime + _updateFrequency;
var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
button.ToolTip = $"{info.Name}, {info.Entity}";
button.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
button.AddChild(entry);
}
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not ObjectsListData { FilteringString: var filteringString })
return false;
// If the filter is empty, do not filter out any entries
if (string.IsNullOrEmpty(filter))
return true;
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
}
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
{
return header switch
{
ObjectsTabHeader.Header.ObjectName => entity.Name,
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
_ => entity.Name
};
}
private void HeaderClicked(ObjectsTabHeader.Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
RefreshObjectList();
}
@@ -118,3 +182,4 @@ public sealed partial class ObjectsTab : Control
}
}
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData;

View File

@@ -1,6 +1,6 @@
<ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel"/>
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
@@ -14,4 +14,4 @@
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</ContainerButton>
</PanelContainer>

View File

@@ -1,19 +1,21 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabEntry : ContainerButton
public sealed partial class ObjectsTabEntry : PanelContainer
{
public NetEntity AssocEntity;
public ObjectsTabEntry(string name, NetEntity nent)
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
{
RobustXamlLoader.Load(this);
AssocEntity = nent;
EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
}
}

View File

@@ -0,0 +1,21 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="EntityIDLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-entity-id}"
MouseFilter="Pass"/>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,86 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
{
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabHeader : Control
{
public event Action<Header>? OnHeaderClicked;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
public ObjectsTabHeader()
{
RobustXamlLoader.Load(this);
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
}
public Label GetHeader(Header header)
{
return header switch
{
Header.ObjectName => ObjectNameLabel,
Header.EntityID => EntityIDLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
};
}
public void ResetHeaderText()
{
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
}
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
{
ResetHeaderText();
var arrow = ascending ? ArrowUp : ArrowDown;
GetHeader(headerClicked).Text += $" {arrow}";
}
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
{
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
OnHeaderClicked?.Invoke(header);
args.Handle();
}
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.ObjectName);
}
private void EntityIDClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.EntityID);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
}
}
public enum Header
{
ObjectName,
EntityID
}
}
}

View File

@@ -31,12 +31,12 @@
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-account-age}" MinWidth="175" />
<LineEdit Name="MinAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-hours}" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-overall-hours}" MinWidth="175" />
<LineEdit Name="MinOverallHours" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-hours}" />
<Label Text="{Loc admin-ui-panic-bunker-min-overall-minutes}" MinWidth="175" />
<LineEdit Name="MinOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>

View File

@@ -12,7 +12,7 @@ public sealed partial class PanicBunkerTab : Control
[Dependency] private readonly IConsoleHost _console = default!;
private string _minAccountAge;
private string _minOverallHours;
private string _minOverallMinutes;
public PanicBunkerTab()
{
@@ -25,9 +25,9 @@ public sealed partial class PanicBunkerTab : Control
MinAccountAge.OnFocusExit += args => SendMinAccountAge(args.Text);
_minAccountAge = MinAccountAge.Text;
MinOverallHours.OnTextEntered += args => SendMinOverallHours(args.Text);
MinOverallHours.OnFocusExit += args => SendMinOverallHours(args.Text);
_minOverallHours = MinOverallHours.Text;
MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text);
MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text);
_minOverallMinutes = MinOverallMinutes.Text;
}
private void SendMinAccountAge(string text)
@@ -42,16 +42,16 @@ public sealed partial class PanicBunkerTab : Control
_console.ExecuteCommand($"panicbunker_min_account_age {minutes}");
}
private void SendMinOverallHours(string text)
private void SendMinOverallMinutes(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _minOverallHours ||
!int.TryParse(text, out var hours))
text == _minOverallMinutes ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"panicbunker_min_overall_hours {hours}");
_console.ExecuteCommand($"panicbunker_min_overall_minutes {minutes}");
}
public void UpdateStatus(PanicBunkerStatus status)
@@ -68,10 +68,10 @@ public sealed partial class PanicBunkerTab : Control
CountDeadminnedButton.Pressed = status.CountDeadminnedAdmins;
ShowReasonButton.Pressed = status.ShowReason;
MinAccountAge.Text = status.MinAccountAgeHours.ToString();
MinAccountAge.Text = status.MinAccountAgeMinutes.ToString();
_minAccountAge = MinAccountAge.Text;
MinOverallHours.Text = status.MinOverallHours.ToString();
_minOverallHours = MinOverallHours.Text;
MinOverallMinutes.Text = status.MinOverallMinutes.ToString();
_minOverallMinutes = MinOverallMinutes.Text;
}
}

View File

@@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
@@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
_players = players;
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
var sortedPlayers = new List<PlayerInfo>(players);
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();

View File

@@ -27,6 +27,7 @@ namespace Content.Client.Construction
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -195,9 +196,8 @@ namespace Content.Client.Construction
if (GhostPresent(loc))
return false;
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))

View File

@@ -8,7 +8,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
</BoxContainer>
<Control MinHeight="10"/>

View File

@@ -23,7 +23,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
private readonly ItemSlotsSystem _itemSlots;
private readonly FlatpackSystem _flatpack;
private readonly MaterialStorageSystem _materialStorage;
private readonly SpriteSystem _spriteSystem;
private readonly EntityUid _owner;
@@ -31,7 +30,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
private EntityUid? _currentBoard = EntityUid.Invalid;
private EntityUid? _machinePreview;
public event Action? PackButtonPressed;
@@ -43,7 +41,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
_itemSlots = _entityManager.System<ItemSlotsSystem>();
_flatpack = _entityManager.System<FlatpackSystem>();
_materialStorage = _entityManager.System<MaterialStorageSystem>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_owner = uid;
@@ -57,17 +54,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
{
base.FrameUpdate(args);
if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
{
_machinePreview = null;
MachineSprite.SetEntity(_machinePreview);
}
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
MachineBoardComponent? machineBoardComp = null;
if (flatpacker.Packing)
{
PackButton.Disabled = true;
@@ -75,11 +65,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
else if (_currentBoard != null)
{
Dictionary<string, int> cost;
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
machineBoardComp.Prototype is not null)
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp))
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
}
@@ -87,9 +76,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
if (_currentBoard == itemSlot.Item)
return;
if (_machinePreview != null)
_entityManager.DeleteEntity(_machinePreview);
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = _currentBoard != null;
InsertLabel.Visible = _currentBoard == null;
@@ -99,35 +85,32 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
string? prototype = null;
Dictionary<string, int>? cost = null;
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var newMachineBoardComp))
{
prototype = machineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
prototype = newMachineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
}
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
{
prototype = computerBoard.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
}
if (prototype is not null && cost is not null)
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
_machinePreview = _entityManager.Spawn(proto.ID);
_spriteSystem.ForceUpdate(_machinePreview.Value);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
}
}
else
{
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
PackButton.Disabled = true;
}
MachineSprite.SetEntity(_machinePreview);
}
private string GetCostString(Dictionary<string, int> costs)
@@ -149,7 +132,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
("amount", amountText),
("material", Loc.GetString(matProto.Name)));
msg.AddMarkup(text);
msg.TryAddMarkup(text, out _);
if (i != orderedCosts.Length - 1)
msg.PushNewline();
@@ -157,12 +140,4 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
return msg.ToMarkup();
}
public override void Close()
{
base.Close();
_entityManager.DeleteEntity(_machinePreview);
_machinePreview = null;
}
}

View File

@@ -2,11 +2,9 @@ using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Eui;
using Content.Client.Flash;
using Content.Client.Fullscreen;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Info;
using Content.Client.Input;
using Content.Client.IoC;
using Content.Client.Launcher;
@@ -53,7 +51,6 @@ namespace Content.Client.Entry
[Dependency] private readonly IScreenshotHook _screenshotHook = default!;
[Dependency] private readonly FullscreenHook _fullscreenHook = default!;
[Dependency] private readonly ChangelogManager _changelogManager = default!;
[Dependency] private readonly RulesManager _rulesManager = default!;
[Dependency] private readonly ViewportManager _viewportManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
@@ -126,7 +123,6 @@ namespace Content.Client.Entry
_screenshotHook.Initialize();
_fullscreenHook.Initialize();
_changelogManager.Initialize();
_rulesManager.Initialize();
_viewportManager.Initialize();
_ghostKick.Initialize();
_extendedDisconnectInformation.Initialize();

View File

@@ -4,10 +4,12 @@ using Content.Client.Lobby;
using Content.Client.RoundEnd;
using Content.Shared.GameTicking;
using Content.Shared.GameWindow;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.GameTicking.Managers
{
@@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
private Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> _jobsAvailable = new();
private Dictionary<NetEntity, string> _stationNames = new();
[ViewVariables] public bool AreWeReady { get; private set; }
@@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
public event Action? InfoBlobUpdated;
public event Action? LobbyStatusUpdated;
public event Action? LobbyLateJoinStatusUpdated;
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? LobbyJobsAvailableUpdated;
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>>? LobbyJobsAvailableUpdated;
public override void Initialize()
{
@@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers
// reading the console. E.g., logs like this one could leak the nuke station/grid:
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
#if !DEBUG
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
EntityManager.System<SharedMapSystem>().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
#endif
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Content.Shared.Guidebook;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Components;
@@ -13,9 +14,8 @@ public sealed partial class GuideHelpComponent : Component
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
/// selected guidebook.
/// </summary>
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)]
[ViewVariables]
public List<string> Guides = new();
[DataField(required: true)]
public List<ProtoId<GuideEntryPrototype>> Guides = new();
/// <summary>
/// Whether or not to automatically include the children of the given guides.

View File

@@ -2,7 +2,7 @@
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="750 700"
SetSize="900 700"
MinSize="100 200"
Resizable="True"
Title="{Loc 'guidebook-window-title'}">
@@ -18,12 +18,16 @@
Name="SearchBar"
PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
HorizontalExpand="True"
Margin="0 5 10 5">
Margin="0 5 10 5">
</LineEdit>
</BoxContainer>
<BoxContainer Access="Internal" Name="ReturnContainer" Orientation="Horizontal" HorizontalAlignment="Right" Visible="False">
<Button Name="HomeButton" Text="{Loc 'ui-rules-button-home'}" Margin="0 0 10 0"/>
</BoxContainer>
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<Control>
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False">
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>

View File

@@ -1,15 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using JetBrains.Annotations;
using Content.Client.UserInterface.Systems.Info;
using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
@@ -19,7 +21,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
private Dictionary<string, GuideEntry> _entries = new();
private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
public GuidebookWindow()
{
@@ -37,7 +39,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
{
ShowGuide(entry);
var isRulesEntry = entry.RuleEntry;
ReturnContainer.Visible = isRulesEntry;
HomeButton.OnPressed += _ => ShowGuide(entry);
}
else
ClearSelectedGuide();
}
@@ -69,10 +77,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
}
public void UpdateGuides(
Dictionary<string, GuideEntry> entries,
List<string>? rootEntries = null,
string? forceRoot = null,
string? selected = null)
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> entries,
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
ProtoId<GuideEntryPrototype>? forceRoot = null,
ProtoId<GuideEntryPrototype>? selected = null)
{
_entries = entries;
RepopulateTree(rootEntries, forceRoot);
@@ -98,11 +106,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
}
}
private IEnumerable<GuideEntry> GetSortedEntries(List<string>? rootEntries)
private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototype>>? rootEntries)
{
if (rootEntries == null)
{
HashSet<string> entries = new(_entries.Keys);
HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
foreach (var entry in _entries.Values)
{
if (entry.Children.Count > 0)
@@ -111,7 +119,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
.Select(childId => _entries[childId])
.OrderBy(childEntry => childEntry.Priority)
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
.Select(childEntry => childEntry.Id)
.Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
.ToList();
entry.Children = sortedChildren;
@@ -127,13 +135,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
}
private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
{
Tree.Clear();
HashSet<string> addedEntries = new();
HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, 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
@@ -142,17 +150,23 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
Tree.SetAllExpanded(true);
}
private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
if (!addedEntries.Add(id))
{
// TODO GUIDEBOOK Maybe allow duplicate entries?
// E.g., for adding medicine under both chemicals & the chemist job
Logger.Error($"Adding duplicate guide entry: {id}");
return null;
}
var rulesProto = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
if (entry.RuleEntry && entry.Id != rulesProto.Id)
return null;
var item = Tree.AddItem(parent);
item.Metadata = entry;
var name = Loc.GetString(entry.Name);

View File

@@ -1,7 +1,10 @@
using System.Linq;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin;
using Robust.Client.UserInterface;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
using static Pidgin.Parser;
@@ -13,7 +16,9 @@ namespace Content.Client.Guidebook;
/// </summary>
public sealed partial class DocumentParsingManager
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
@@ -37,6 +42,21 @@ public sealed partial class DocumentParsingManager
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
}
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
{
if (!_prototype.TryIndex(entryId, out var entry))
return false;
using var file = _resourceManager.ContentFileReadText(entry.Text);
return TryAddMarkup(control, file.ReadToEnd(), log);
}
public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
{
using var file = _resourceManager.ContentFileReadText(entry.Text);
return TryAddMarkup(control, file.ReadToEnd(), log);
}
public bool TryAddMarkup(Control control, string text, bool log = true)
{
try

View File

@@ -1,51 +0,0 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook;
[Virtual]
public class GuideEntry
{
/// <summary>
/// The file containing the contents of this guide.
/// </summary>
[DataField("text", required: true)] public ResPath Text = default!;
/// <summary>
/// The unique id for this guide.
/// </summary>
[IdDataField]
public string Id = default!;
/// <summary>
/// The name of this guide. This gets localized.
/// </summary>
[DataField("name", required: true)] public string Name = default!;
/// <summary>
/// The "children" of this guide for when guides are shown in a tree / table of contents.
/// </summary>
[DataField("children", customTypeSerializer:typeof(PrototypeIdListSerializer<GuideEntryPrototype>))]
public List<string> Children = new();
/// <summary>
/// Enable filtering of items.
/// </summary>
[DataField("filterEnabled")] public bool FilterEnabled = default!;
/// <summary>
/// Priority for sorting top-level guides when shown in a tree / table of contents.
/// If the guide is the child of some other guide, the order simply determined by the order of children in <see cref="Children"/>.
/// </summary>
[DataField("priority")] public int Priority = 0;
[DataField]
public bool CrystallPunkAllowed = false;
}
[Prototype("guideEntry")]
public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
{
public string ID => Id;
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Client.Guidebook.Components;
using Content.Client.Light;
using Content.Client.Verbs;
using Content.Shared.Guidebook;
using Content.Shared.Interaction;
using Content.Shared.Light.Components;
using Content.Shared.Speech;
@@ -31,10 +32,14 @@ public sealed class GuidebookSystem : EntitySystem
[Dependency] private readonly RgbLightControllerSystem _rgbLightControllerSystem = default!;
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; //CrystallPunk guidebook filter
public event Action<List<string>, List<string>?, string?, bool, string?>? OnGuidebookOpen;
public event Action<List<ProtoId<GuideEntryPrototype>>,
List<ProtoId<GuideEntryPrototype>>?,
ProtoId<GuideEntryPrototype>?,
bool,
ProtoId<GuideEntryPrototype>?>? OnGuidebookOpen;
public const string GuideEmbedTag = "GuideEmbeded";
private EntityUid _defaultUser;
@@ -99,7 +104,7 @@ public sealed class GuidebookSystem : EntitySystem
});
}
public void OpenHelp(List<string> guides)
public void OpenHelp(List<ProtoId<GuideEntryPrototype>> guides)
{
OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
}

View File

@@ -1,25 +0,0 @@
using Content.Shared.Info;
using Robust.Shared.Log;
namespace Content.Client.Info;
public sealed class InfoSystem : EntitySystem
{
public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules.");
[Dependency] private readonly RulesManager _rules = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<RulesMessage>(OnRulesReceived);
Log.Debug("Requested server info.");
RaiseNetworkEvent(new RequestRulesMessage());
}
private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs)
{
Log.Debug("Received server rules.");
Rules = message;
_rules.UpdateRules();
}
}

View File

@@ -1,10 +1,8 @@
using System.Numerics;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
namespace Content.Client.Info
@@ -12,7 +10,6 @@ namespace Content.Client.Info
public sealed class RulesAndInfoWindow : DefaultWindow
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly RulesManager _rules = default!;
public RulesAndInfoWindow()
{
@@ -22,8 +19,14 @@ namespace Content.Client.Info
var rootContainer = new TabContainer();
var rulesList = new Info();
var tutorialList = new Info();
var rulesList = new RulesControl
{
Margin = new Thickness(10)
};
var tutorialList = new Info
{
Margin = new Thickness(10)
};
rootContainer.AddChild(rulesList);
rootContainer.AddChild(tutorialList);
@@ -31,7 +34,6 @@ namespace Content.Client.Info
TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules"));
TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial"));
AddSection(rulesList, _rules.RulesSection());
PopulateTutorial(tutorialList);
Contents.AddChild(rootContainer);

View File

@@ -1,6 +1,18 @@
<BoxContainer xmlns="https://spacestation14.io"
Name="InfoContainer"
Orientation="Vertical"
Margin="2 2 0 0"
SeparationOverride="10"
Access="Public"/>
<BoxContainer xmlns="https://spacestation14.io" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<Control HorizontalExpand="True" VerticalExpand="True" HorizontalAlignment="Stretch">
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="RulesContainer" VerticalExpand="True" Margin="0 0 5 0"/>
</ScrollContainer>
<BoxContainer Margin="0 0 15 0" HorizontalExpand="True" HorizontalAlignment="Right">
<Button Name="BackButton"
Text="{Loc 'ui-rules-button-back'}"
HorizontalAlignment="Right"
VerticalAlignment="Top"/>
<Control MinWidth="5"/>
<Button Name="HomeButton"
Text="{Loc 'ui-rules-button-home'}"
HorizontalAlignment="Right"
VerticalAlignment="Top"/>
</BoxContainer>
</Control>
</BoxContainer>

View File

@@ -1,22 +1,54 @@
using System.IO;
using Content.Shared.CCVar;
using Content.Client.Guidebook;
using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.Systems.Info;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client.Info;
[GenerateTypedNameReferences]
public sealed partial class RulesControl : BoxContainer
public sealed partial class RulesControl : BoxContainer, ILinkClickHandler
{
[Dependency] private readonly RulesManager _rules = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
private string? _currentEntry;
private readonly Stack<string> _priorEntries = new();
public RulesControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
AddChild(_rules.RulesSection());
SetGuide();
HomeButton.OnPressed += _ => SetGuide();
BackButton.OnPressed += _ => SetGuide(_priorEntries.Pop(), false);
}
public void HandleClick(string link)
{
SetGuide(link);
}
private void SetGuide(ProtoId<GuideEntryPrototype>? entry = null, bool addToPrior = true)
{
var coreEntry = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
entry ??= coreEntry;
Scroll.SetScrollValue(default);
RulesContainer.Children.Clear();
if (!_parsingMan.TryAddMarkup(RulesContainer, entry.Value))
return;
if (addToPrior && _currentEntry != null)
_priorEntries.Push(_currentEntry);
_currentEntry = entry.Value;
HomeButton.Visible = entry.Value != coreEntry.Id;
BackButton.Visible = _priorEntries.Count != 0 && _priorEntries.Peek() != entry.Value;
}
}

View File

@@ -1,105 +0,0 @@
using Content.Client.Lobby;
using Content.Client.Gameplay;
using Content.Shared.CCVar;
using Content.Shared.Info;
using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
namespace Content.Client.Info;
public sealed class RulesManager : SharedRulesManager
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
private InfoSection rulesSection = new InfoSection("", "", false);
private bool _shouldShowRules = false;
private RulesPopup? _activePopup;
public void Initialize()
{
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>(OnShouldShowRules);
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
_stateManager.OnStateChanged += OnStateChanged;
_consoleHost.RegisterCommand("fuckrules", "", "", (_, _, _) =>
{
OnAcceptPressed();
});
}
private void OnShouldShowRules(ShouldShowRulesPopupMessage message)
{
_shouldShowRules = true;
}
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
{
ShowRules(message.PopupTime);
}
private void OnStateChanged(StateChangedEventArgs args)
{
if (args.NewState is not (GameplayState or LobbyState))
return;
if (!_shouldShowRules)
return;
_shouldShowRules = false;
ShowRules(_configManager.GetCVar(CCVars.RulesWaitTime));
}
private void ShowRules(float time)
{
if (_activePopup != null)
return;
_activePopup = new RulesPopup
{
Timer = time
};
_activePopup.OnQuitPressed += OnQuitPressed;
_activePopup.OnAcceptPressed += OnAcceptPressed;
_userInterfaceManager.WindowRoot.AddChild(_activePopup);
LayoutContainer.SetAnchorPreset(_activePopup, LayoutContainer.LayoutPreset.Wide);
}
private void OnQuitPressed()
{
_consoleHost.ExecuteCommand("quit");
}
private void OnAcceptPressed()
{
_netManager.ClientSendMessage(new RulesAcceptedMessage());
_activePopup?.Orphan();
_activePopup = null;
}
public void UpdateRules()
{
var rules = _sysMan.GetEntitySystem<InfoSystem>().Rules;
rulesSection.SetText(rules.Title, rules.Text, true);
}
public Control RulesSection()
{
rulesSection = new InfoSection("", "", false);
UpdateRules();
return rulesSection;
}
}

View File

@@ -5,20 +5,20 @@
MouseFilter="Stop">
<parallax:ParallaxControl />
<Control VerticalExpand="True"
MaxWidth="650">
MaxWidth="800"
MaxHeight="900">
<PanelContainer StyleClasses="windowPanel" />
<ScrollContainer HScrollEnabled="False">
<BoxContainer Orientation="Vertical" SeparationOverride="10">
<info:RulesControl />
<BoxContainer Orientation="Vertical" SeparationOverride="10" Margin="10 10 5 10">
<info:RulesControl/>
<Label Name="WaitLabel" />
<BoxContainer Orientation="Horizontal">
<Button Name="AcceptButton"
Text="{Loc 'ui-rules-accept'}"
Disabled="True" />
<Button Name="QuitButton"
StyleClasses="Caution"
Text="{Loc 'ui-escape-quit'}" />
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</Control>
</Control>

View File

@@ -1,9 +1,7 @@
using System;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
namespace Content.Client.Info;

View File

@@ -4,7 +4,6 @@ using Content.Client.Chat.Managers;
using Content.Client.Clickable;
using Content.Client.Eui;
using Content.Client.GhostKick;
using Content.Client.Info;
using Content.Client.Launcher;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
@@ -41,7 +40,6 @@ namespace Content.Client.IoC
collection.Register<EuiManager, EuiManager>();
collection.Register<IVoteManager, VoteManager>();
collection.Register<ChangelogManager, ChangelogManager>();
collection.Register<RulesManager, RulesManager>();
collection.Register<ViewportManager, ViewportManager>();
collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
collection.Register<GhostKickManager>();

View File

@@ -290,7 +290,7 @@ namespace Content.Client.LateJoin
}
}
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
{
foreach (var stationEntries in updatedJobs)
{
@@ -337,10 +337,10 @@ namespace Content.Client.LateJoin
public Label JobLabel { get; }
public string JobId { get; }
public string JobLocalisedName { get; }
public uint? Amount { get; private set; }
public int? Amount { get; private set; }
private bool _initialised = false;
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
{
JobLabel = jobLabel;
JobId = jobId;
@@ -350,7 +350,7 @@ namespace Content.Client.LateJoin
_initialised = true;
}
public void RefreshLabel(uint? amount)
public void RefreshLabel(int? amount)
{
if (Amount == amount && _initialised)
{

View File

@@ -207,8 +207,11 @@ namespace Content.Client.Light
public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity<RgbLightControllerComponent> rgb)
{
var delta = (float)(curTime - offset).TotalSeconds;
var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f);
var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1;
return Color.FromHsv(new Vector4(
(float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1),
MathF.Abs(hue),
1.0f,
1.0f,
1.0f

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Lobby.UI;
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor;
@@ -232,6 +234,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_requirements,
_markings);
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
_characterSetup.CloseButton.OnPressed += _ =>
@@ -302,7 +306,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
{
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
}
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)

View File

@@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton
.LoadProfileEntity(humanoid, null, true);
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
if (highPriorityJob != null)
if (highPriorityJob != default)
{
var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
description = $"{description}\n{jobName}";
}
}

View File

@@ -1,7 +1,6 @@
using System.IO;
using System.Linq;
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles;
@@ -13,13 +12,13 @@ using Content.Shared._CP14.Humanoid;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
using Content.Shared.Guidebook;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
@@ -97,6 +96,8 @@ namespace Content.Client.Lobby.UI
[ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species";
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
private ISawmill _sawmill;
public HumanoidProfileEditor(
@@ -372,6 +373,7 @@ namespace Content.Client.Lobby.UI
{
PreferenceUnavailableButton.SelectId(args.Id);
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
SetDirty();
};
_jobCategories = new Dictionary<string, BoxContainer>();
@@ -616,13 +618,15 @@ namespace Content.Client.Lobby.UI
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
selector.OnOpenGuidebook += OnOpenGuidebook;
var title = Loc.GetString(antag.Name);
var description = Loc.GetString(antag.Objective);
selector.Setup(items, title, 250, description);
selector.Setup(items, title, 250, description, guides: antag.Guides);
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
if (!_requirements.CheckRoleTime(requirements, out var reason))
{
selector.LockRequirements(reason);
Profile = Profile?.WithAntagPreference(antag.ID, false);
@@ -754,6 +758,10 @@ namespace Content.Client.Lobby.UI
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
// TODO GUIDEBOOK
// make the species guide book a field on the species prototype.
// I.e., do what jobs/antags do.
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var page = DefaultSpeciesGuidebook;
@@ -762,10 +770,10 @@ namespace Content.Client.Lobby.UI
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
{
var dict = new Dictionary<string, GuideEntry>();
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
dict.Add(DefaultSpeciesGuidebook, guideRoot);
//TODO: Don't close the guidebook if its already open, just go to the correct page
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
guidebookController.OpenGuidebook(dict, includeChildren:true, selected: page);
}
}
@@ -860,6 +868,7 @@ namespace Content.Client.Lobby.UI
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
selector.OnOpenGuidebook += OnOpenGuidebook;
var icon = new TextureRect
{
@@ -868,7 +877,7 @@ namespace Content.Client.Lobby.UI
};
var jobIcon = _prototypeManager.Index(job.Icon);
icon.Texture = jobIcon.Icon.Frame0();
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
if (!_requirements.IsAllowed(job, out var reason))
{

View File

@@ -1,9 +1,14 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Orientation="Horizontal">
<Label Name="TitleLabel"
Margin="5 0"
MouseFilter="Stop"/>
<BoxContainer Name="OptionsContainer"
SetWidth="400"/>
<Label Name="TitleLabel"
Margin="5 0"
MouseFilter="Stop"/>
<!--21 was the height of OptionsContainer at the time that this button was added. So I am limiting the texture to 21x21-->
<Control SetSize="21 21">
<TextureButton Name="Help" StyleClasses="HelpButton"/>
</Control>
<BoxContainer Name="OptionsContainer"
SetWidth="400"/>
</BoxContainer>

View File

@@ -1,10 +1,12 @@
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Lobby.UI.Roles;
@@ -17,8 +19,10 @@ public sealed partial class RequirementsSelector : BoxContainer
{
private readonly RadioOptions<int> _options;
private readonly StripeBack _lockStripe;
private List<ProtoId<GuideEntryPrototype>>? _guides;
public event Action<int>? OnSelected;
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
public int Selected => _options.SelectedId;
@@ -60,18 +64,33 @@ public sealed partial class RequirementsSelector : BoxContainer
requirementsLabel
}
};
Help.OnPressed += _ =>
{
if (_guides != null)
OnOpenGuidebook?.Invoke(_guides);
};
}
/// <summary>
/// Actually adds the controls.
/// </summary>
public void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
public void Setup(
(string, int)[] items,
string title,
int titleSize,
string? description,
TextureRect? icon = null,
List<ProtoId<GuideEntryPrototype>>? guides = null)
{
foreach (var (text, value) in items)
{
_options.AddItem(Loc.GetString(text), value);
}
Help.Visible = guides != null;
_guides = guides;
TitleLabel.Text = title;
TitleLabel.MinSize = new Vector2(titleSize, 0f);
TitleLabel.ToolTip = description;

View File

@@ -0,0 +1,20 @@
using Content.Shared.MapText;
using Robust.Client.Graphics;
namespace Content.Client.MapText;
[RegisterComponent]
public sealed partial class MapTextComponent : SharedMapTextComponent
{
/// <summary>
/// The font that gets cached on component init or state changes
/// </summary>
[ViewVariables]
public VectorFont? CachedFont;
/// <summary>
/// The text currently being displayed. This is either <see cref="SharedMapTextComponent.Text"/> or the
/// localized text <see cref="SharedMapTextComponent.LocText"/> or
/// </summary>
public string CachedText = string.Empty;
}

View File

@@ -0,0 +1,85 @@
using System.Numerics;
using Content.Shared.MapText;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client.MapText;
/// <summary>
/// Draws map text as an overlay
/// </summary>
public sealed class MapTextOverlay : Overlay
{
private readonly IConfigurationManager _configManager;
private readonly IEntityManager _entManager;
private readonly IUserInterfaceManager _uiManager;
private readonly SharedTransformSystem _transform;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public MapTextOverlay(
IConfigurationManager configManager,
IEntityManager entManager,
IUserInterfaceManager uiManager,
SharedTransformSystem transform,
IResourceCache resourceCache,
IPrototypeManager prototypeManager)
{
_configManager = configManager;
_entManager = entManager;
_uiManager = uiManager;
_transform = transform;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.ViewportControl == null)
return;
args.DrawingHandle.SetTransform(Matrix3x2.Identity);
var scale = _configManager.GetCVar(CVars.DisplayUIScale);
if (scale == 0f)
scale = _uiManager.DefaultUIScale;
DrawWorld(args.ScreenHandle, args, scale);
args.DrawingHandle.UseShader(null);
}
private void DrawWorld(DrawingHandleScreen handle, OverlayDrawArgs args, float scale)
{
if ( args.ViewportControl == null)
return;
var matrix = args.ViewportControl.GetWorldToScreenMatrix();
var query = _entManager.AllEntityQueryEnumerator<MapTextComponent>();
// Enlarge bounds to try prevent pop-in due to large text.
var bounds = args.WorldBounds.Enlarged(2);
while(query.MoveNext(out var uid, out var mapText))
{
var mapPos = _transform.GetMapCoordinates(uid);
if (mapPos.MapId != args.MapId)
continue;
if (!bounds.Contains(mapPos.Position))
continue;
if (mapText.CachedFont == null)
continue;
var pos = Vector2.Transform(mapPos.Position, matrix) + mapText.Offset;
var dimensions = handle.GetDimensions(mapText.CachedFont, mapText.CachedText, scale);
handle.DrawString(mapText.CachedFont, pos - dimensions / 2f, mapText.CachedText, scale, mapText.Color);
}
}
}

View File

@@ -0,0 +1,81 @@
using Content.Shared.MapText;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Configuration;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.MapText;
/// <inheritdoc/>
public sealed class MapTextSystem : SharedMapTextSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
private MapTextOverlay _overlay = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<MapTextComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<MapTextComponent, ComponentHandleState>(HandleCompState);
_overlay = new MapTextOverlay(_configManager, EntityManager, _uiManager, _transform, _resourceCache, _prototypeManager);
_overlayManager.AddOverlay(_overlay);
// TODO move font prototype to robust.shared, then use ProtoId<FontPrototype>
DebugTools.Assert(_prototypeManager.HasIndex<FontPrototype>(SharedMapTextComponent.DefaultFont));
}
private void OnComponentStartup(Entity<MapTextComponent> ent, ref ComponentStartup args)
{
CacheText(ent.Comp);
// TODO move font prototype to robust.shared, then use ProtoId<FontPrototype>
DebugTools.Assert(_prototypeManager.HasIndex<FontPrototype>(ent.Comp.FontId));
}
private void HandleCompState(Entity<MapTextComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not MapTextComponentState state)
return;
ent.Comp.Text = state.Text;
ent.Comp.LocText = state.LocText;
ent.Comp.Color = state.Color;
ent.Comp.FontId = state.FontId;
ent.Comp.FontSize = state.FontSize;
ent.Comp.Offset = state.Offset;
CacheText(ent.Comp);
}
private void CacheText(MapTextComponent component)
{
component.CachedFont = null;
component.CachedText = string.IsNullOrWhiteSpace(component.Text)
? Loc.GetString(component.LocText)
: component.Text;
if (!_prototypeManager.TryIndex<FontPrototype>(component.FontId, out var fontPrototype))
{
component.CachedText = Loc.GetString("map-text-font-error");
component.Color = Color.Red;
if(_prototypeManager.TryIndex<FontPrototype>(SharedMapTextComponent.DefaultFont, out var @default))
component.CachedFont = new VectorFont(_resourceCache.GetResource<FontResource>(@default.Path), 14);
return;
}
var fontResource = _resourceCache.GetResource<FontResource>(fontPrototype.Path);
component.CachedFont = new VectorFont(fontResource, component.FontSize);
}
}

View File

@@ -87,6 +87,6 @@ public sealed partial class MaterialStorageControl : ScrollContainer
}
_currentMaterials = mats;
NoMatsLabel.Visible = ChildCount == 1;
NoMatsLabel.Visible = MaterialList.ChildCount == 1;
}
}

View File

@@ -21,8 +21,16 @@ namespace Content.Client.PDA
protected override void Open()
{
base.Open();
if (_menu == null)
CreateMenu();
_menu?.OpenCenteredLeft();
}
private void CreateMenu()
{
_menu = new PdaMenu();
_menu.OpenCenteredLeft();
_menu.OnClose += Close;
_menu.FlashLightToggleButton.OnToggled += _ =>
{

View File

@@ -106,7 +106,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
if (player == null)
return true;
return CheckRoleTime(job.Requirements, out reason);
return CheckRoleTime(job, out reason);
}
public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
{
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
return CheckRoleTime(reqs, out reason);
}
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)

View File

@@ -9,6 +9,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.Laws.Ui;
@@ -27,6 +28,8 @@ public sealed partial class LawDisplay : Control
var identifier = law.LawIdentifierOverride ?? $"{law.Order}";
var lawIdentifier = Loc.GetString("laws-ui-law-header", ("id", identifier));
var lawDescription = Loc.GetString(law.LawString);
var lawIdentifierPlaintext = FormattedMessage.RemoveMarkupPermissive(lawIdentifier);
var lawDescriptionPlaintext = FormattedMessage.RemoveMarkupPermissive(lawDescription);
LawNumberLabel.SetMarkup(lawIdentifier);
LawLabel.SetMessage(lawDescription);
@@ -46,7 +49,7 @@ public sealed partial class LawDisplay : Control
localButton.OnPressed += _ =>
{
_chatManager.SendMessage($"{lawIdentifier}: {lawDescription}", ChatSelectChannel.Local);
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Local);
};
LawAnnouncementButtons.AddChild(localButton);
@@ -73,9 +76,9 @@ public sealed partial class LawDisplay : Control
switch (radioChannel)
{
case SharedChatSystem.CommonChannel:
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
default:
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
}
};

View File

@@ -78,6 +78,8 @@ namespace Content.Client.Stylesheets
public const string StyleClassLabelSmall = "LabelSmall";
public const string StyleClassButtonBig = "ButtonBig";
public const string StyleClassButtonHelp = "HelpButton";
public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution";
public const string StyleClassPopupMessageMedium = "PopupMessageMedium";
@@ -1346,6 +1348,10 @@ namespace Content.Client.Stylesheets
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
}),
Element<TextureButton>()
.Class(StyleClassButtonHelp)
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
// Labels ---
Element<Label>().Class(StyleClassLabelBig)
.Prop(Label.StylePropertyFont, notoSans16),

View File

@@ -1,6 +1,7 @@
using System.Numerics;
using Content.Client.Guidebook;
using Content.Client.Guidebook.Components;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -32,8 +33,8 @@ namespace Content.Client.UserInterface.Controls
set => WindowTitle.Text = value;
}
private List<string>? _helpGuidebookIds;
public List<string>? HelpGuidebookIds
private List<ProtoId<GuideEntryPrototype>>? _helpGuidebookIds;
public List<ProtoId<GuideEntryPrototype>>? HelpGuidebookIds
{
get => _helpGuidebookIds;
set

View File

@@ -14,6 +14,8 @@ public sealed class ScrambleTag : IMarkupTag
{
[Dependency] private readonly IGameTiming _timing = default!;
private const int MaxScrambleLength = 32;
public string Name => "scramble";
public string TextBefore(MarkupNode node)
@@ -29,8 +31,9 @@ public sealed class ScrambleTag : IMarkupTag
var seed = (int) (_timing.CurTime.TotalMilliseconds / rate);
var rand = new Random(seed + node.GetHashCode());
var charOptions = chars.ToCharArray();
var realLength = MathF.Min(length.Value, MaxScrambleLength);
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
for (var i = 0; i < realLength; i++)
{
var index = rand.Next() % charOptions.Length;
sb.Append(charOptions[index]);

View File

@@ -289,6 +289,10 @@ public sealed class ActionButton : Control, IEntityControl
{
if (_action.IconOn != null)
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
else if (_action.Icon != null)
SetActionIcon(_spriteSys.Frame0(_action.Icon));
else
SetActionIcon(null);
if (_action.BackgroundOn != null)
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);

View File

@@ -3,6 +3,7 @@ using Content.Client.Administration.Systems;
using Content.Client.Administration.UI;
using Content.Client.Administration.UI.Tabs.ObjectsTab;
using Content.Client.Administration.UI.Tabs.PanicBunkerTab;
using Content.Client.Administration.UI.Tabs.BabyJailTab;
using Content.Client.Administration.UI.Tabs.PlayerTab;
using Content.Client.Gameplay;
using Content.Client.Lobby;
@@ -37,11 +38,13 @@ public sealed class AdminUIController : UIController,
private AdminMenuWindow? _window;
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AdminButton;
private PanicBunkerStatus? _panicBunker;
private BabyJailStatus? _babyJail;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<PanicBunkerChangedEvent>(OnPanicBunkerUpdated);
SubscribeNetworkEvent<BabyJailChangedEvent>(OnBabyJailUpdated);
}
private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEventArgs args)
@@ -56,6 +59,18 @@ public sealed class AdminUIController : UIController,
}
}
private void OnBabyJailUpdated(BabyJailChangedEvent msg, EntitySessionEventArgs args)
{
var showDialog = _babyJail == null && msg.Status.Enabled;
_babyJail = msg.Status;
_window?.BabyJailControl.UpdateStatus(msg.Status);
if (showDialog)
{
UIManager.CreateWindow<BabyJailStatusWindow>().OpenCentered();
}
}
public void OnStateEntered(GameplayState state)
{
EnsureWindow();
@@ -101,6 +116,13 @@ public sealed class AdminUIController : UIController,
if (_panicBunker != null)
_window.PanicBunkerControl.UpdateStatus(_panicBunker);
/*
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
if (_babyJail != null)
_window.BabyJailControl.UpdateStatus(_babyJail);
_window.PlayerTabControl.OnEntryKeyBindDown += PlayerTabEntryKeyBindDown;
_window.ObjectsTabControl.OnEntryKeyBindDown += ObjectsTabEntryKeyBindDown;
_window.OnOpen += OnWindowOpen;
@@ -198,9 +220,12 @@ public sealed class AdminUIController : UIController,
args.Handle();
}
private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args)
private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data)
{
var uid = entry.AssocEntity;
if (data is not ObjectsListData { Info: var info })
return;
var uid = info.Entity;
var function = args.Function;
if (function == EngineKeyFunctions.UIClick)

View File

@@ -3,10 +3,15 @@ using Content.Client.Gameplay;
using Content.Client.Guidebook;
using Content.Client.Guidebook.Controls;
using Content.Client.Lobby;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.UserInterface.Controls;
using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Content.Shared.Input;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration;
using static Robust.Client.UserInterface.Controls.BaseButton;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
@@ -18,21 +23,25 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
{
[UISystemDependency] private readonly GuidebookSystem _guidebookSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
private const int PlaytimeOpenGuidebook = 60;
private GuidebookWindow? _guideWindow;
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
public void OnStateEntered(LobbyState state)
{
HandleStateEntered();
HandleStateEntered(state);
}
public void OnStateEntered(GameplayState state)
{
HandleStateEntered();
HandleStateEntered(state);
}
private void HandleStateEntered()
private void HandleStateEntered(State state)
{
DebugTools.Assert(_guideWindow == null);
@@ -41,6 +50,14 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
_guideWindow.OnClose += OnWindowClosed;
_guideWindow.OnOpen += OnWindowOpen;
if (state is LobbyState &&
_jobRequirements.FetchOverallPlaytime() < TimeSpan.FromMinutes(PlaytimeOpenGuidebook))
{
OpenGuidebook();
_guideWindow.RecenterWindow(new(0.5f, 0.5f));
_guideWindow.SetPositionFirst();
}
// setup keybinding
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenGuidebook,
@@ -74,12 +91,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
public void OnSystemLoaded(GuidebookSystem system)
{
_guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
_guidebookSystem.OnGuidebookOpen += OpenGuidebook;
}
public void OnSystemUnloaded(GuidebookSystem system)
{
_guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
_guidebookSystem.OnGuidebookOpen -= OpenGuidebook;
}
internal void UnloadButton()
@@ -103,10 +120,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
ToggleGuidebook();
}
public void ToggleGuidebook()
{
if (_guideWindow == null)
return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
}
else
{
OpenGuidebook();
}
}
private void OnWindowClosed()
{
if (GuidebookButton != null)
GuidebookButton.Pressed = false;
if (_guideWindow != null)
_guideWindow.ReturnContainer.Visible = false;
}
private void OnWindowOpen()
@@ -127,30 +163,25 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
/// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
/// show the specified entries</param>
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
public void ToggleGuidebook(
Dictionary<string, GuideEntry>? guides = null,
List<string>? rootEntries = null,
string? forceRoot = null,
public void OpenGuidebook(
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>? guides = null,
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
ProtoId<GuideEntryPrototype>? forceRoot = null,
bool includeChildren = true,
string? selected = null)
ProtoId<GuideEntryPrototype>? selected = null)
{
if (_guideWindow == null)
return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
return;
}
if (GuidebookButton != null)
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
selected ??= _configuration.GetCVar(CCVars.DefaultGuide);
if (guides == null)
{
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
.ToDictionary(x => x.ID, x => (GuideEntry) x);
.ToDictionary(x => new ProtoId<GuideEntryPrototype>(x.ID), x => (GuideEntry) x);
}
else if (includeChildren)
{
@@ -171,17 +202,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
_guideWindow.OpenCenteredRight();
}
public void ToggleGuidebook(
List<string> guideList,
List<string>? rootEntries = null,
string? forceRoot = null,
public void OpenGuidebook(
List<ProtoId<GuideEntryPrototype>> guideList,
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
ProtoId<GuideEntryPrototype>? forceRoot = null,
bool includeChildren = true,
string? selected = null)
ProtoId<GuideEntryPrototype>? selected = null)
{
Dictionary<string, GuideEntry> guides = new();
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides = new();
foreach (var guideId in guideList)
{
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
if (!_prototypeManager.TryIndex(guideId, out var guide))
{
Logger.Error($"Encountered unknown guide prototype: {guideId}");
continue;
@@ -189,17 +220,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
guides.Add(guideId, guide);
}
ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
}
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
public void CloseGuidebook()
{
if (_guideWindow == null)
return;
if (_guideWindow.IsOpen)
{
UIManager.ClickSound();
_guideWindow.Close();
}
}
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides)
{
foreach (var childId in guide.Children)
{
if (guides.ContainsKey(childId))
continue;
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
if (!_prototypeManager.TryIndex(childId, out var child))
{
Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
continue;

View File

@@ -1,13 +1,49 @@
using Content.Client.Gameplay;
using Content.Client.Gameplay;
using Content.Client.Info;
using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Content.Shared.Info;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Client.UserInterface.Systems.Info;
public sealed class InfoUIController : UIController, IOnStateExited<GameplayState>
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private RulesPopup? _rulesPopup;
private RulesAndInfoWindow? _infoWindow;
public override void Initialize()
{
base.Initialize();
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
_consoleHost.RegisterCommand("fuckrules",
"",
"",
(_, _, _) =>
{
OnAcceptPressed();
});
}
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
{
ShowRules(message.PopupTime);
}
public void OnStateExited(GameplayState state)
{
if (_infoWindow == null)
@@ -17,12 +53,46 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
_infoWindow = null;
}
private void ShowRules(float time)
{
if (_rulesPopup != null)
return;
_rulesPopup = new RulesPopup
{
Timer = time
};
_rulesPopup.OnQuitPressed += OnQuitPressed;
_rulesPopup.OnAcceptPressed += OnAcceptPressed;
UIManager.WindowRoot.AddChild(_rulesPopup);
LayoutContainer.SetAnchorPreset(_rulesPopup, LayoutContainer.LayoutPreset.Wide);
}
private void OnQuitPressed()
{
_consoleHost.ExecuteCommand("quit");
}
private void OnAcceptPressed()
{
_netManager.ClientSendMessage(new RulesAcceptedMessage());
_rulesPopup?.Orphan();
_rulesPopup = null;
}
public GuideEntryPrototype GetCoreRuleEntry()
{
var guide = _cfg.GetCVar(CCVars.RulesFile);
var guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(guide);
return guideEntryPrototype;
}
public void OpenWindow()
{
if (_infoWindow == null || _infoWindow.Disposed)
{
_infoWindow = UIManager.CreateWindow<RulesAndInfoWindow>();
}
_infoWindow?.OpenCentered();
}