-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+using Content.Shared.Guidebook;
+using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Components;
/// 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.
-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.Shared.Guidebook;
using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.ContentPack;
+using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
[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()
{
}
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);
}
}
- 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)
.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;
.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))
{
AddEntry(entry.Id, parent, addedEntries);
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;
}
using System.Linq;
using Content.Client.Guidebook.Richtext;
+using Content.Shared.Guidebook;
using Pidgin;
using Robust.Client.UserInterface;
using Robust.Shared.ContentPack;
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;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
[Dependency] private readonly TagSystem _tags = default!;
- 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;
});
}
- public void OpenHelp(List<string> guides)
+ public void OpenHelp(List<ProtoId<GuideEntryPrototype>> guides)
{
OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
}
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.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using System.Linq;
+using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Lobby.UI;
[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;
_requirements,
_markings);
+ _profileEditor.OnOpenGuidebook += _guide.OpenHelp;
+
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
_characterSetup.CloseButton.OnPressed += _ =>
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;
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;
[ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species";
+ public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
+
private ISawmill _sawmill;
public HumanoidProfileEditor(
{
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))
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;
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);
}
}
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
+ selector.OnOpenGuidebook += OnOpenGuidebook;
var icon = new TextureRect
{
};
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))
{
<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>
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;
{
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;
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;
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";
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),
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;
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
using Content.Client.Guidebook.Controls;
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
+using Content.Shared.Guidebook;
using Content.Shared.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
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()
ToggleGuidebook();
}
+ public void ToggleGuidebook()
+ {
+ if (_guideWindow == null)
+ return;
+
+ if (_guideWindow.IsOpen)
+ {
+ UIManager.ClickSound();
+ _guideWindow.Close();
+ }
+ else
+ {
+ OpenGuidebook();
+ }
+ }
+
private void OnWindowClosed()
{
if (GuidebookButton != null)
/// <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);
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)
{
_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;
guides.Add(guideId, guide);
}
- ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
+ OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
+ }
+
+ public void CloseGuidebook()
+ {
+ if (_guideWindow == null)
+ return;
+
+ if (_guideWindow.IsOpen)
+ {
+ UIManager.ClickSound();
+ _guideWindow.Close();
+ }
}
- private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
+ 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;
using System.Globalization;
using Content.Client.Gameplay;
-using Content.Client.Guidebook;
using Content.Client.Info;
using Content.Shared.Administration.Managers;
using Content.Shared.CCVar;
+using Content.Shared.Guidebook;
using Content.Shared.Info;
using Robust.Client;
using Robust.Client.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using System.Linq;
+using Content.Shared.Guidebook;
namespace Content.IntegrationTests.Tests.Guidebook;
factory.RegisterIgnore(IgnoredComponents.List);
prototypes.RegisterIgnore("parallax");
- prototypes.RegisterIgnore("guideEntry");
ServerContentIoC.Register();
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Utility;
-namespace Content.Client.Guidebook;
+namespace Content.Shared.Guidebook;
+
+[Prototype("guideEntry")]
+public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
+{
+ public string ID => Id;
+}
[Virtual]
public class GuideEntry
/// <summary>
/// The file containing the contents of this guide.
/// </summary>
- [DataField("text", required: true)] public ResPath Text = default!;
+ [DataField(required: true)] public ResPath Text = default!;
/// <summary>
/// The unique id for this guide.
/// <summary>
/// The name of this guide. This gets localized.
/// </summary>
- [DataField("name", required: true)] public string Name = default!;
+ [DataField(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();
+ [DataField]
+ public List<ProtoId<GuideEntryPrototype>> Children = new();
/// <summary>
/// Enable filtering of items.
/// </summary>
- [DataField("filterEnabled")] public bool FilterEnabled = default!;
+ [DataField] 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;
-}
-
-[Prototype("guideEntry")]
-public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
-{
- public string ID => Id;
+ [DataField] public int Priority = 0;
}
+using Content.Shared.Guidebook;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
/// </summary>
[DataField("requirements")]
public HashSet<JobRequirement>? Requirements;
+
+ /// <summary>
+ /// Optional list of guides associated with this antag. If the guides are opened, the first entry in this list
+ /// will be used to select the currently selected guidebook.
+ /// </summary>
+ [DataField]
+ public List<ProtoId<GuideEntryPrototype>>? Guides;
}
using Content.Shared.Access;
+using Content.Shared.Guidebook;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
[DataField]
public bool Whitelisted;
+
+ /// <summary>
+ /// Optional list of guides associated with this role. If the guides are opened, the first entry in this list
+ /// will be used to select the currently selected guidebook.
+ /// </summary>
+ [DataField]
+ public List<ProtoId<GuideEntryPrototype>>? Guides;
}
/// <summary>
name: guide-entry-chemist
text: "/ServerInfo/Guidebook/Medical/Chemist.xml"
children:
- - Medicine
+ # - Medicine
+ # Duplicate guide entries are currently not supported
+ # TODO GUIDEBOOK Maybe allow duplicate entries?
- Botanicals
- AdvancedBrute
antagonist: true
setPreference: false
objective: roles-antag-space-ninja-objective
+ guides: [ SpaceNinja ]
#Ninja Gear
- type: startingGear
- Screwdriver
- Wirecutter
- Welder
- - Multitool
\ No newline at end of file
+ - Multitool
requirements:
- !type:OverallPlaytimeRequirement
time: 18000 # 5h
+ guides: [ NuclearOperatives ]
- type: antag
id: NukeopsMedic
- !type:OverallPlaytimeRequirement
time: 18000 # 5h
- !type:RoleTimeRequirement
- role: JobChemist
+ role: JobChemist
time: 10800 # 3h
+ guides: [ NuclearOperatives ]
- type: antag
id: NukeopsCommander
department: Security
time: 18000 # 5h
# should be changed to nukie playtime when thats tracked (wyci)
+ guides: [ NuclearOperatives ]
#Nuclear Operative Gear
- type: startingGear
id: SyndicateLoneOperativeGearFull
parent: SyndicateOperativeGearFull
equipment:
- pocket2: BaseUplinkRadio60TC
\ No newline at end of file
+ pocket2: BaseUplinkRadio60TC
antagonist: true
setPreference: true
objective: roles-antag-rev-head-objective
+ guides: [ Revolutionaries ]
- type: antag
id: Rev
antagonist: true
setPreference: false
objective: roles-antag-rev-objective
+ guides: [ Revolutionaries ]
- type: startingGear
id: HeadRevGear
storage:
back:
- Flash
- - ClothingEyesGlassesSunglasses
\ No newline at end of file
+ - ClothingEyesGlassesSunglasses
antagonist: true
setPreference: true
objective: roles-antag-syndicate-agent-objective
+ guides: [ Traitors ]
# Syndicate Operative Outfit - Monkey
- type: startingGear
ears: ClothingHeadsetAltSyndicate
gloves: ClothingHandsGlovesCombat
pocket1: BaseUplinkRadio40TC
- id: SyndiPDA
\ No newline at end of file
+ id: SyndiPDA
antagonist: true
setPreference: true
objective: roles-antag-initial-infected-objective
+ guides: [ Zombies ]
- type: antag
id: Zombie
antagonist: true
setPreference: false
objective: roles-antag-zombie-objective
+ guides: [ Zombies ]