diff --git a/Content.Client/Guidebook/Components/GuideHelpComponent.cs b/Content.Client/Guidebook/Components/GuideHelpComponent.cs
index a0124c5a7b..f333c873d6 100644
--- a/Content.Client/Guidebook/Components/GuideHelpComponent.cs
+++ b/Content.Client/Guidebook/Components/GuideHelpComponent.cs
@@ -1,23 +1,34 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-namespace Content.Client.Guidebook;
+namespace Content.Client.Guidebook.Components;
///
/// This component stores a reference to a guidebook that contains information relevant to this entity.
///
[RegisterComponent]
+[Access(typeof(GuidebookSystem))]
public sealed class GuideHelpComponent : Component
{
///
- /// What guides to include show when opening the guidebook. The first entry will be used to select the currently
- /// selected guidebook.
+ /// What guides to include show when opening the guidebook. The first entry will be used to select the currently
+ /// selected guidebook.
///
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)]
+ [ViewVariables]
public List Guides = new();
///
- /// Whether or not to automatically include the children of the given guides.
+ /// Whether or not to automatically include the children of the given guides.
///
[DataField("includeChildren")]
+ [ViewVariables(VVAccess.ReadWrite)]
public bool IncludeChildren = true;
+
+ ///
+ /// Whether or not to open the UI when interacting with the entity while on hand.
+ /// Mostly intended for books
+ ///
+ [DataField("openOnActivation")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool OpenOnActivation;
}
diff --git a/Content.Client/Guidebook/Components/GuidebookControlsTestComponent.cs b/Content.Client/Guidebook/Components/GuidebookControlsTestComponent.cs
index a2935011cd..21c6c0365e 100644
--- a/Content.Client/Guidebook/Components/GuidebookControlsTestComponent.cs
+++ b/Content.Client/Guidebook/Components/GuidebookControlsTestComponent.cs
@@ -1,4 +1,4 @@
-namespace Content.Client.Guidebook;
+namespace Content.Client.Guidebook.Components;
///
/// This is used for the guidebook monkey.
diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs
index 74991547cb..e6272f440c 100644
--- a/Content.Client/Guidebook/GuidebookSystem.cs
+++ b/Content.Client/Guidebook/GuidebookSystem.cs
@@ -1,8 +1,7 @@
using System.Linq;
-using Content.Client.Guidebook.Controls;
+using Content.Client.Guidebook.Components;
using Content.Client.Light;
using Content.Client.Verbs;
-using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Light.Component;
using Content.Shared.Speech;
@@ -10,10 +9,7 @@ using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Client.Player;
-using Robust.Shared.Input;
-using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook;
@@ -24,25 +20,21 @@ namespace Content.Client.Guidebook;
public sealed class GuidebookSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly VerbSystem _verbSystem = default!;
[Dependency] private readonly RgbLightControllerSystem _rgbLightControllerSystem = default!;
+ [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = default!;
- private GuidebookWindow _guideWindow = default!;
+ public event Action, List?, string?, bool, string?>? OnGuidebookOpen;
public const string GuideEmbedTag = "GuideEmbeded";
///
public override void Initialize()
{
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.OpenGuidebook,
- new PointerInputCmdHandler(HandleOpenGuidebook))
- .Register();
- _guideWindow = new GuidebookWindow();
-
SubscribeLocalEvent>(OnGetVerbs);
+ SubscribeLocalEvent(OnInteract);
+
SubscribeLocalEvent(OnGuidebookControlsTestInteractHand);
SubscribeLocalEvent(OnGuidebookControlsTestActivateInWorld);
SubscribeLocalEvent>(
@@ -58,12 +50,21 @@ public sealed class GuidebookSystem : EntitySystem
{
Text = Loc.GetString("guide-help-verb"),
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
- Act = () => OpenGuidebook(component.Guides, includeChildren: component.IncludeChildren, selected: component.Guides[0]),
+ Act = () => OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]),
ClientExclusive = true,
CloseMenu = true
});
}
+ private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInWorldEvent args)
+ {
+ if (!component.OpenOnActivation || component.Guides.Count == 0 || _tags.HasTag(uid, GuideEmbedTag))
+ return;
+
+ OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]);
+ args.Handled = true;
+ }
+
private void OnGuidebookControlsTestGetAlternateVerbs(EntityUid uid, GuidebookControlsTestComponent component, GetVerbsEvent args)
{
args.Verbs.Add(new AlternativeVerb()
@@ -81,8 +82,8 @@ public sealed class GuidebookSystem : EntitySystem
{
Act = () =>
{
- var light = EnsureComp(uid); // RGB demands this.
- light.Enabled = false;
+ EnsureComp(uid); // RGB demands this.
+ _pointLightSystem.SetEnabled(uid, false);
var rgb = EnsureComp(uid);
var sprite = EnsureComp(uid);
@@ -143,103 +144,4 @@ public sealed class GuidebookSystem : EntitySystem
var activateMsg = new InteractHandEvent(user, activated);
RaiseLocalEvent(activated, activateMsg, true);
}
-
- private bool HandleOpenGuidebook(in PointerInputCmdHandler.PointerInputCmdArgs args)
- {
- if (args.State != BoundKeyState.Down)
- return false;
-
- OpenGuidebook();
- return true;
- }
-
- ///
- /// Opens the guidebook.
- ///
- /// What guides should be shown. If not specified, this will instead raise a and automatically include all guide prototypes.
- /// A list of guides that should form the base of the table of contents. If not specified,
- /// this will automatically simply be a list of all guides that have no parent.
- /// This forces a singular guide to contain all other guides. This guide will
- /// contain its own children, in addition to what would normally be the root guides if this were not
- /// specified.
- /// Whether or not to automatically include child entries. If false, this will ONLY
- /// show the specified entries
- /// The guide whose contents should be displayed when the guidebook is opened
- public bool OpenGuidebook(
- Dictionary? guides = null,
- List? rootEntries = null,
- string? forceRoot = null,
- bool includeChildren = true,
- string? selected = null)
- {
- _guideWindow.OpenCenteredRight();
-
- if (guides == null)
- {
- var ev = new GetGuidesEvent()
- {
- Guides = _prototypeManager.EnumeratePrototypes().ToDictionary(x => x.ID, x => (GuideEntry) x)
- };
- RaiseLocalEvent(ev);
- guides = ev.Guides;
- }
- else if (includeChildren)
- {
- var oldGuides = guides;
- guides = new(oldGuides);
- foreach (var guide in oldGuides.Values)
- {
- RecursivelyAddChildren(guide, guides);
- }
- }
-
- _guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
-
- return true;
- }
-
- public bool OpenGuidebook(
- List guideList,
- List? rootEntries = null,
- string? forceRoot = null,
- bool includeChildren = true,
- string? selected = null)
- {
- Dictionary guides = new();
- foreach (var guideId in guideList)
- {
- if (!_prototypeManager.TryIndex(guideId, out var guide))
- {
- Logger.Error($"Encountered unknown guide prototype: {guideId}");
- continue;
- }
- guides.Add(guideId, guide);
- }
-
- return OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
- }
-
- private void RecursivelyAddChildren(GuideEntry guide, Dictionary guides)
- {
- foreach (var childId in guide.Children)
- {
- if (guides.ContainsKey(childId))
- continue;
-
- 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;
- }
-
- guides.Add(childId, child);
- RecursivelyAddChildren(child, guides);
- }
- }
-}
-
-public sealed class GetGuidesEvent : EntityEventArgs
-{
- public Dictionary Guides { get; init; } = new();
}
diff --git a/Content.Client/Info/LinkBanner.cs b/Content.Client/Info/LinkBanner.cs
index 4d8059df89..a30aa41376 100644
--- a/Content.Client/Info/LinkBanner.cs
+++ b/Content.Client/Info/LinkBanner.cs
@@ -1,5 +1,6 @@
using Content.Client.Changelog;
using Content.Client.UserInterface.Systems.EscapeMenu;
+using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -34,6 +35,14 @@ namespace Content.Client.Info
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
+ var guidebookController = UserInterfaceManager.GetUIController();
+ var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };
+ guidebookButton.OnPressed += _ =>
+ {
+ guidebookController.ToggleGuidebook();
+ };
+ buttons.AddChild(guidebookButton);
+
var changelogButton = new ChangelogButton();
changelogButton.OnPressed += args => UserInterfaceManager.GetUIController().ToggleWindow();
buttons.AddChild(changelogButton);
diff --git a/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs b/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs
index 1e96cdc045..56792c2992 100644
--- a/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs
+++ b/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs
@@ -1,12 +1,10 @@
using Content.Client.Gameplay;
-using Content.Client.Guidebook;
-using Content.Client.Info;
using Content.Client.UserInterface.Controls;
+using Content.Client.UserInterface.Systems.Guidebook;
using Content.Client.UserInterface.Systems.Info;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Client.Console;
-using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration;
@@ -26,7 +24,7 @@ public sealed class EscapeUIController : UIController, IOnStateEntered
{
- _guidebook?.OpenGuidebook();
+ _guidebook.ToggleGuidebook();
};
// Hide wiki button if we don't have a link for it.
diff --git a/Content.Client/UserInterface/Systems/Guidebook/GuidebookUIController.cs b/Content.Client/UserInterface/Systems/Guidebook/GuidebookUIController.cs
new file mode 100644
index 0000000000..66376df317
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Guidebook/GuidebookUIController.cs
@@ -0,0 +1,206 @@
+using System.Linq;
+using Content.Client.Gameplay;
+using Content.Client.Guidebook;
+using Content.Client.Guidebook.Controls;
+using Content.Client.Lobby;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Input;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+using Robust.Shared.Input.Binding;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.UserInterface.Systems.Guidebook;
+
+public sealed class GuidebookUIController : UIController, IOnStateEntered, IOnStateEntered, IOnStateExited, IOnStateExited, IOnSystemChanged
+{
+ [UISystemDependency] private readonly GuidebookSystem _guidebookSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private GuidebookWindow? _guideWindow;
+ private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull()?.GuidebookButton;
+
+ public void OnStateEntered(LobbyState state)
+ {
+ HandleStateEntered();
+ }
+
+ public void OnStateEntered(GameplayState state)
+ {
+ HandleStateEntered();
+ }
+
+ private void HandleStateEntered()
+ {
+ DebugTools.Assert(_guideWindow == null);
+
+ // setup window
+ _guideWindow = UIManager.CreateWindow();
+ _guideWindow.OnClose += OnWindowClosed;
+ _guideWindow.OnOpen += OnWindowOpen;
+
+ // setup keybinding
+ CommandBinds.Builder
+ .Bind(ContentKeyFunctions.OpenGuidebook,
+ InputCmdHandler.FromDelegate(_ => ToggleGuidebook()))
+ .Register();
+ }
+
+ public void OnStateExited(LobbyState state)
+ {
+ HandleStateExited();
+ }
+
+ public void OnStateExited(GameplayState state)
+ {
+ HandleStateExited();
+ }
+
+ private void HandleStateExited()
+ {
+ if (_guideWindow == null)
+ return;
+
+ _guideWindow.OnClose -= OnWindowClosed;
+ _guideWindow.OnOpen -= OnWindowOpen;
+
+ // shutdown
+ _guideWindow.Dispose();
+ _guideWindow = null;
+ CommandBinds.Unregister();
+ }
+
+ public void OnSystemLoaded(GuidebookSystem system)
+ {
+ _guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
+ }
+
+ public void OnSystemUnloaded(GuidebookSystem system)
+ {
+ _guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
+ }
+
+ internal void UnloadButton()
+ {
+ if (GuidebookButton == null)
+ return;
+
+ GuidebookButton.OnPressed -= GuidebookButtonOnPressed;
+ }
+
+ internal void LoadButton()
+ {
+ if (GuidebookButton == null)
+ return;
+
+ GuidebookButton.OnPressed += GuidebookButtonOnPressed;
+ }
+
+ private void GuidebookButtonOnPressed(ButtonEventArgs obj)
+ {
+ ToggleGuidebook();
+ }
+
+ private void OnWindowClosed()
+ {
+ if (GuidebookButton != null)
+ GuidebookButton.Pressed = false;
+ }
+
+ private void OnWindowOpen()
+ {
+ if (GuidebookButton != null)
+ GuidebookButton.Pressed = true;
+ }
+
+ ///
+ /// Opens the guidebook.
+ ///
+ /// What guides should be shown. If not specified, this will instead list all the entries
+ /// A list of guides that should form the base of the table of contents. If not specified,
+ /// this will automatically simply be a list of all guides that have no parent.
+ /// This forces a singular guide to contain all other guides. This guide will
+ /// contain its own children, in addition to what would normally be the root guides if this were not
+ /// specified.
+ /// Whether or not to automatically include child entries. If false, this will ONLY
+ /// show the specified entries
+ /// The guide whose contents should be displayed when the guidebook is opened
+ public void ToggleGuidebook(
+ Dictionary? guides = null,
+ List? rootEntries = null,
+ string? forceRoot = null,
+ bool includeChildren = true,
+ string? selected = null)
+ {
+ if (_guideWindow == null)
+ return;
+
+ if (_guideWindow.IsOpen)
+ {
+ _guideWindow.Close();
+ return;
+ }
+
+ if (GuidebookButton != null)
+ GuidebookButton.Pressed = !_guideWindow.IsOpen;
+
+ if (guides == null)
+ {
+ guides = _prototypeManager.EnumeratePrototypes()
+ .ToDictionary(x => x.ID, x => (GuideEntry) x);
+ }
+ else if (includeChildren)
+ {
+ var oldGuides = guides;
+ guides = new(oldGuides);
+ foreach (var guide in oldGuides.Values)
+ {
+ RecursivelyAddChildren(guide, guides);
+ }
+ }
+
+ _guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
+ _guideWindow.OpenCenteredRight();
+ }
+
+ public void ToggleGuidebook(
+ List guideList,
+ List? rootEntries = null,
+ string? forceRoot = null,
+ bool includeChildren = true,
+ string? selected = null)
+ {
+ Dictionary guides = new();
+ foreach (var guideId in guideList)
+ {
+ if (!_prototypeManager.TryIndex(guideId, out var guide))
+ {
+ Logger.Error($"Encountered unknown guide prototype: {guideId}");
+ continue;
+ }
+ guides.Add(guideId, guide);
+ }
+
+ ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
+ }
+
+ private void RecursivelyAddChildren(GuideEntry guide, Dictionary guides)
+ {
+ foreach (var childId in guide.Children)
+ {
+ if (guides.ContainsKey(childId))
+ continue;
+
+ 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;
+ }
+
+ guides.Add(childId, child);
+ RecursivelyAddChildren(child, guides);
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs
index 9779dbc582..b399e83fc6 100644
--- a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs
+++ b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs
@@ -1,4 +1,3 @@
-using Content.Client.Gameplay;
using Content.Client.UserInterface.Systems.Actions;
using Content.Client.UserInterface.Systems.Admin;
using Content.Client.UserInterface.Systems.Bwoink;
@@ -6,6 +5,7 @@ using Content.Client.UserInterface.Systems.Character;
using Content.Client.UserInterface.Systems.Crafting;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Client.UserInterface.Systems.Gameplay;
+using Content.Client.UserInterface.Systems.Guidebook;
using Content.Client.UserInterface.Systems.Inventory;
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Client.UserInterface.Systems.Sandbox;
@@ -23,6 +23,7 @@ public sealed class GameTopMenuBarUIController : UIController
[Dependency] private readonly AHelpUIController _ahelp = default!;
[Dependency] private readonly ActionUIController _action = default!;
[Dependency] private readonly SandboxUIController _sandbox = default!;
+ [Dependency] private readonly GuidebookUIController _guidebook = default!;
private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull();
@@ -38,6 +39,7 @@ public sealed class GameTopMenuBarUIController : UIController
public void UnloadButtons()
{
_escape.UnloadButton();
+ _guidebook.UnloadButton();
_inventory.UnloadButton();
_admin.UnloadButton();
_character.UnloadButton();
@@ -50,6 +52,7 @@ public sealed class GameTopMenuBarUIController : UIController
public void LoadButtons()
{
_escape.LoadButton();
+ _guidebook.LoadButton();
_inventory.LoadButton();
_admin.LoadButton();
_character.LoadButton();
diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml
index 9ba11fec45..83b8d010d3 100644
--- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml
+++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml
@@ -23,6 +23,16 @@
HorizontalExpand="True"
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenRight}"
/>
+