technology auto guidebook (#21029)

* technology auto guidebook

* boo-womp

* boo-womp II
This commit is contained in:
Nemanja
2023-10-16 17:51:58 -04:00
committed by GitHub
parent fedc7c6957
commit fd994511a7
14 changed files with 296 additions and 52 deletions

View File

@@ -0,0 +1,4 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical">
<BoxContainer Name="DisciplineContainer" Orientation="Vertical"/>
</BoxContainer>

View File

@@ -0,0 +1,60 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Research.Prototypes;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
/// <summary>
/// Control for embedding all the technologies in a discipline into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideTechDisciplineEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public GuideTechDisciplineEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MouseFilter = MouseFilterMode.Stop;
}
public GuideTechDisciplineEmbed(string group) : this()
{
var prototypes = _prototype.EnumeratePrototypes<TechnologyPrototype>()
.Where(p => p.Discipline.Equals(group)).OrderBy(p => p.Tier).ThenBy(p => Loc.GetString(p.Name));
foreach (var tech in prototypes)
{
var embed = new GuideTechnologyEmbed(tech);
DisciplineContainer.AddChild(embed);
}
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Discipline", out var group))
{
Logger.Error("Technology discipline embed tag is missing discipline argument");
return false;
}
var prototypes = _prototype.EnumeratePrototypes<TechnologyPrototype>()
.Where(p => p.Discipline.Equals(group)).OrderBy(p => p.Tier).ThenBy(p => Loc.GetString(p.Name));
foreach (var tech in prototypes)
{
var embed = new GuideTechnologyEmbed(tech);
DisciplineContainer.AddChild(embed);
}
control = this;
return true;
}
}

View File

@@ -0,0 +1,22 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Orientation="Vertical"
Margin="5 5 5 5">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#777777"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<PanelContainer Name="DisciplineColorBackground" HorizontalExpand="True" VerticalExpand="False" MinHeight="15"/>
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<RichTextLabel Name="NameLabel" VerticalAlignment="Bottom" HorizontalExpand="True"/>
<TextureRect Name="TechTexture" VerticalAlignment="Center" HorizontalAlignment="Right"/>
</BoxContainer>
<customControls:HSeparator Margin="10 5 10 10"/>
<RichTextLabel Name="DescriptionLabel"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -0,0 +1,93 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Client.Research;
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Research.Prototypes;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
/// <summary>
/// Control for embedding a research technology into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideTechnologyEmbed : BoxContainer, IDocumentTag, ISearchableControl
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ResearchSystem _research;
private readonly SpriteSystem _sprite;
public GuideTechnologyEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_research = _systemManager.GetEntitySystem<ResearchSystem>();
_sprite = _systemManager.GetEntitySystem<SpriteSystem>();
MouseFilter = MouseFilterMode.Stop;
}
public GuideTechnologyEmbed(string technology) : this()
{
GenerateControl(_prototype.Index<TechnologyPrototype>(technology));
}
public GuideTechnologyEmbed(TechnologyPrototype technology) : this()
{
GenerateControl(technology);
}
public bool CheckMatchesSearch(string query)
{
return this.ChildrenContainText(query);
}
public void SetHiddenState(bool state, string query)
{
Visible = CheckMatchesSearch(query) ? state : !state;
}
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Technology", out var id))
{
Logger.Error("Technology embed tag is missing technology prototype argument");
return false;
}
if (!_prototype.TryIndex<TechnologyPrototype>(id, out var technology))
{
Logger.Error($"Specified technology prototype \"{id}\" is not a valid technology prototype");
return false;
}
GenerateControl(technology);
control = this;
return true;
}
private void GenerateControl(TechnologyPrototype technology)
{
var discipline = _prototype.Index(technology.Discipline);
NameLabel.SetMarkup($"[bold]{Loc.GetString(technology.Name)}[/bold]");
DescriptionLabel.SetMessage(_research.GetTechnologyDescription(technology, includePrereqs: true, disciplinePrototype: discipline));
TechTexture.Texture = _sprite.Frame0(technology.Icon);
DisciplineColorBackground.PanelOverride = new StyleBoxFlat
{
BackgroundColor = discipline.Color
};
}
}

View File

@@ -2,6 +2,7 @@
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -15,10 +16,13 @@ public sealed partial class MiniTechnologyCardControl : Control
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
var discipline = prototypeManager.Index<TechDisciplinePrototype>(technology.Discipline); var discipline = prototypeManager.Index(technology.Discipline);
Background.ModulateSelfOverride = discipline.Color; Background.ModulateSelfOverride = discipline.Color;
Texture.Texture = spriteSys.Frame0(technology.Icon); Texture.Texture = spriteSys.Frame0(technology.Icon);
NameLabel.SetMessage(Loc.GetString(technology.Name)); NameLabel.SetMessage(Loc.GetString(technology.Name));
Main.ToolTip = description.ToString();
var tooltip = new Tooltip();
tooltip.SetMessage(description);
Main.TooltipSupplier = _ => tooltip;
} }
} }

View File

@@ -27,7 +27,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
private readonly TechnologyDatabaseComponent? _technologyDatabase; private readonly TechnologyDatabaseComponent? _technologyDatabase;
private readonly ResearchSystem _research; private readonly ResearchSystem _research;
private readonly SpriteSystem _sprite; private readonly SpriteSystem _sprite;
private readonly AccessReaderSystem _accessReader = default!; private readonly AccessReaderSystem _accessReader;
public readonly EntityUid Entity; public readonly EntityUid Entity;
@@ -55,7 +55,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
foreach (var tech in allTech) foreach (var tech in allTech)
{ {
var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false)); var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech));
AvailableCardsContainer.AddChild(mini); AvailableCardsContainer.AddChild(mini);
} }
@@ -74,7 +74,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
foreach (var techId in _technologyDatabase.CurrentTechnologyCards) foreach (var techId in _technologyDatabase.CurrentTechnologyCards)
{ {
var tech = _prototype.Index<TechnologyPrototype>(techId); var tech = _prototype.Index<TechnologyPrototype>(techId);
var cardControl = new TechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech), state.Points, hasAccess); var cardControl = new TechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech, includeTier: false), state.Points, hasAccess);
cardControl.OnPressed += () => OnTechnologyCardPressed?.Invoke(techId); cardControl.OnPressed += () => OnTechnologyCardPressed?.Invoke(techId);
TechnologyCardsContainer.AddChild(cardControl); TechnologyCardsContainer.AddChild(cardControl);
} }
@@ -82,37 +82,11 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
foreach (var unlocked in _technologyDatabase.UnlockedTechnologies) foreach (var unlocked in _technologyDatabase.UnlockedTechnologies)
{ {
var tech = _prototype.Index<TechnologyPrototype>(unlocked); var tech = _prototype.Index<TechnologyPrototype>(unlocked);
var cardControl = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false)); var cardControl = new MiniTechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech, false));
UnlockedCardsContainer.AddChild(cardControl); UnlockedCardsContainer.AddChild(cardControl);
} }
} }
public FormattedMessage GetTechnologyDescription(TechnologyPrototype technology, bool includeCost = true)
{
var description = new FormattedMessage();
if (includeCost)
{
description.AddMarkup(Loc.GetString("research-console-cost", ("amount", technology.Cost)));
description.PushNewline();
}
description.AddMarkup(Loc.GetString("research-console-unlocks-list-start"));
foreach (var recipe in technology.RecipeUnlocks)
{
var recipeProto = _prototype.Index<LatheRecipePrototype>(recipe);
description.PushNewline();
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry",
("name",recipeProto.Name)));
}
foreach (var generic in technology.GenericUnlocks)
{
description.PushNewline();
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry-generic",
("name", Loc.GetString(generic.UnlockDescription))));
}
return description;
}
public void UpdateInformationPanel(ResearchConsoleBoundInterfaceState state) public void UpdateInformationPanel(ResearchConsoleBoundInterfaceState state)
{ {
var amountMsg = new FormattedMessage(); var amountMsg = new FormattedMessage();

View File

@@ -72,7 +72,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
var tier = int.Parse(weightedRandom.Pick(_random)); var tier = int.Parse(weightedRandom.Pick(_random));
//get a list of every distinct recipe in all the technologies. //get a list of every distinct recipe in all the technologies.
var techs = new List<string>(); var techs = new List<ProtoId<LatheRecipePrototype>>();
foreach (var tech in _prototype.EnumeratePrototypes<TechnologyPrototype>()) foreach (var tech in _prototype.EnumeratePrototypes<TechnologyPrototype>())
{ {
if (tech.Tier != tier) if (tech.Tier != tier)

View File

@@ -1,6 +1,4 @@
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Shared.Research.Prototypes; namespace Content.Shared.Research.Prototypes;
@@ -19,57 +17,57 @@ public sealed class TechnologyPrototype : IPrototype
/// The name of the technology. /// The name of the technology.
/// Supports locale strings /// Supports locale strings
/// </summary> /// </summary>
[DataField("name", required: true)] [DataField(required: true)]
public string Name = string.Empty; public LocId Name = string.Empty;
/// <summary> /// <summary>
/// An icon used to visually represent the technology in UI. /// An icon used to visually represent the technology in UI.
/// </summary> /// </summary>
[DataField("icon", required: true)] [DataField(required: true)]
public SpriteSpecifier Icon = default!; public SpriteSpecifier Icon = default!;
/// <summary> /// <summary>
/// What research discipline this technology belongs to. /// What research discipline this technology belongs to.
/// </summary> /// </summary>
[DataField("discipline", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<TechDisciplinePrototype>))] [DataField(required: true)]
public string Discipline = default!; public ProtoId<TechDisciplinePrototype> Discipline;
/// <summary> /// <summary>
/// What tier research is this? /// What tier research is this?
/// The tier governs how much lower-tier technology /// The tier governs how much lower-tier technology
/// needs to be unlocked before this one. /// needs to be unlocked before this one.
/// </summary> /// </summary>
[DataField("tier", required: true)] [DataField(required: true)]
public int Tier; public int Tier;
/// <summary> /// <summary>
/// Hidden tech is not ever available at the research console. /// Hidden tech is not ever available at the research console.
/// </summary> /// </summary>
[DataField("hidden")] [DataField]
public bool Hidden; public bool Hidden;
/// <summary> /// <summary>
/// How much research is needed to unlock. /// How much research is needed to unlock.
/// </summary> /// </summary>
[DataField("cost")] [DataField]
public int Cost = 10000; public int Cost = 10000;
/// <summary> /// <summary>
/// A list of <see cref="TechnologyPrototype"/>s that need to be unlocked in order to unlock this technology. /// A list of <see cref="TechnologyPrototype"/>s that need to be unlocked in order to unlock this technology.
/// </summary> /// </summary>
[DataField("technologyPrerequisites", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))] [DataField]
public IReadOnlyList<string> TechnologyPrerequisites = new List<string>(); public List<ProtoId<TechnologyPrototype>> TechnologyPrerequisites = new();
/// <summary> /// <summary>
/// A list of <see cref="LatheRecipePrototype"/>s that are unlocked by this technology /// A list of <see cref="LatheRecipePrototype"/>s that are unlocked by this technology
/// </summary> /// </summary>
[DataField("recipeUnlocks", customTypeSerializer: typeof(PrototypeIdListSerializer<LatheRecipePrototype>))] [DataField]
public IReadOnlyList<string> RecipeUnlocks = new List<string>(); public List<ProtoId<LatheRecipePrototype>> RecipeUnlocks = new();
/// <summary> /// <summary>
/// A list of non-standard effects that are done when this technology is unlocked. /// A list of non-standard effects that are done when this technology is unlocked.
/// </summary> /// </summary>
[DataField("genericUnlocks")] [DataField]
public IReadOnlyList<GenericUnlock> GenericUnlocks = new List<GenericUnlock>(); public IReadOnlyList<GenericUnlock> GenericUnlocks = new List<GenericUnlock>();
} }
@@ -80,13 +78,13 @@ public partial record struct GenericUnlock()
/// What event is raised when this is unlocked? /// What event is raised when this is unlocked?
/// Used for doing non-standard logic. /// Used for doing non-standard logic.
/// </summary> /// </summary>
[DataField("purchaseEvent")] [DataField]
public object? PurchaseEvent = null; public object? PurchaseEvent = null;
/// <summary> /// <summary>
/// A player facing tooltip for what the unlock does. /// A player facing tooltip for what the unlock does.
/// Supports locale strings. /// Supports locale strings.
/// </summary> /// </summary>
[DataField("unlockDescription")] [DataField]
public string UnlockDescription = string.Empty; public string UnlockDescription = string.Empty;
} }

View File

@@ -3,6 +3,7 @@ using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes; using Content.Shared.Research.Prototypes;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Shared.Research.Systems; namespace Content.Shared.Research.Systems;
@@ -40,7 +41,7 @@ public abstract class SharedResearchSystem : EntitySystem
component.CurrentTechnologyCards.Add(selected.ID); component.CurrentTechnologyCards.Add(selected.ID);
} }
Dirty(component); Dirty(uid, component);
} }
public List<TechnologyPrototype> GetAvailableTechnologies(EntityUid uid, TechnologyDatabaseComponent? component = null) public List<TechnologyPrototype> GetAvailableTechnologies(EntityUid uid, TechnologyDatabaseComponent? component = null)
@@ -142,6 +143,59 @@ public abstract class SharedResearchSystem : EntitySystem
return tier - 1; return tier - 1;
} }
public FormattedMessage GetTechnologyDescription(
TechnologyPrototype technology,
bool includeCost = true,
bool includeTier = true,
bool includePrereqs = false,
TechDisciplinePrototype? disciplinePrototype = null)
{
var description = new FormattedMessage();
if (includeTier)
{
disciplinePrototype ??= PrototypeManager.Index(technology.Discipline);
description.AddMarkup(Loc.GetString("research-console-tier-discipline-info",
("tier", technology.Tier), ("color", disciplinePrototype.Color), ("discipline", Loc.GetString(disciplinePrototype.Name))));
description.PushNewline();
}
if (includeCost)
{
description.AddMarkup(Loc.GetString("research-console-cost", ("amount", technology.Cost)));
description.PushNewline();
}
if (includePrereqs && technology.TechnologyPrerequisites.Any())
{
description.AddMarkup(Loc.GetString("research-console-prereqs-list-start"));
foreach (var recipe in technology.TechnologyPrerequisites)
{
var techProto = PrototypeManager.Index(recipe);
description.PushNewline();
description.AddMarkup(Loc.GetString("research-console-prereqs-list-entry",
("text", Loc.GetString(techProto.Name))));
}
description.PushNewline();
}
description.AddMarkup(Loc.GetString("research-console-unlocks-list-start"));
foreach (var recipe in technology.RecipeUnlocks)
{
var recipeProto = PrototypeManager.Index(recipe);
description.PushNewline();
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry",
("name",recipeProto.Name)));
}
foreach (var generic in technology.GenericUnlocks)
{
description.PushNewline();
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry-generic",
("text", Loc.GetString(generic.UnlockDescription))));
}
return description;
}
/// <summary> /// <summary>
/// Returns whether a technology is unlocked on this database or not. /// Returns whether a technology is unlocked on this database or not.
/// </summary> /// </summary>

View File

@@ -34,6 +34,7 @@ guide-entry-botanicals = Botanicals
guide-entry-cloning = Cloning guide-entry-cloning = Cloning
guide-entry-cryogenics = Cryogenics guide-entry-cryogenics = Cryogenics
guide-entry-science = Science guide-entry-science = Science
guide-entry-technologies = Technologies
guide-entry-anomalous-research = Anomalous Research guide-entry-anomalous-research = Anomalous Research
guide-entry-scanners-and-vessels = Scanners and Vessels guide-entry-scanners-and-vessels = Scanners and Vessels
guide-entry-ape = A.P.E. guide-entry-ape = A.P.E.

View File

@@ -14,5 +14,7 @@ research-console-cost = Cost: [color=orchid]{$amount}[/color]
research-console-unlocks-list-start = Unlocks: research-console-unlocks-list-start = Unlocks:
research-console-unlocks-list-entry = - [color=yellow]{$name}[/color] research-console-unlocks-list-entry = - [color=yellow]{$name}[/color]
research-console-unlocks-list-entry-generic = - [color=green]{$text}[/color] research-console-unlocks-list-entry-generic = - [color=green]{$text}[/color]
research-console-prereqs-list-start = Requires:
research-console-prereqs-list-entry = - [color=orchid]{$text}[/color]
research-console-no-access-popup = No access! research-console-no-access-popup = No access!

View File

@@ -3,11 +3,18 @@
name: guide-entry-science name: guide-entry-science
text: "/ServerInfo/Guidebook/Science/Science.xml" text: "/ServerInfo/Guidebook/Science/Science.xml"
children: children:
- Technologies
- AnomalousResearch - AnomalousResearch
- Xenoarchaeology - Xenoarchaeology
- Robotics - Robotics
- MachineUpgrading - MachineUpgrading
- type: guideEntry
id: Technologies
name: guide-entry-technologies
text: "/ServerInfo/Guidebook/Science/Technologies.xml"
filterEnabled: True
- type: guideEntry - type: guideEntry
id: AnomalousResearch id: AnomalousResearch
name: guide-entry-anomalous-research name: guide-entry-anomalous-research

View File

@@ -10,7 +10,9 @@ Science, often called Research and Development, is a job made up of both generat
</Box> </Box>
The most important thing inside your department is the R&D server, which stores unlocked technologies, and the R&D computer, which allows you to unlock technologies. The most important thing inside your department is the R&D server, which stores unlocked technologies, and the R&D computer, which allows you to unlock technologies.
Each technology costs [color=#a4885c]Research Points[/color] and unlocks recipes at lathes. Some technologies will also have prerequesites you have to unlock before you can research them. Each technology costs [color=#a4885c]Research Points[/color] and unlocks recipes at lathes. Some technologies will also have prerequisites you have to unlock before you can research them.
Information about the different technologies can be viewed [textlink="on the technology guidebook page" link="Technologies"].
## Disciplines ## Disciplines
Technologies are spread over 5 different Disciplines: Technologies are spread over 5 different Disciplines:

View File

@@ -0,0 +1,23 @@
<Document>
# Technologies
All technologies have a cost and a tier requirement in order to be researched. Unlocking them adds a variety of recipes that can be printed at various lathes.
The different technologies and their respective discipline are listed below.
## Industrial
<GuideTechDisciplineEmbed Discipline="Industrial"/>
## Biochemical
<GuideTechDisciplineEmbed Discipline="Biochemical"/>
## Arsenal
<GuideTechDisciplineEmbed Discipline="Arsenal"/>
## Experimental
<GuideTechDisciplineEmbed Discipline="Experimental"/>
## Civilian Services
<GuideTechDisciplineEmbed Discipline="CivilianServices"/>
</Document>