]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
technology auto guidebook (#21029)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Mon, 16 Oct 2023 21:51:58 +0000 (17:51 -0400)
committerGitHub <noreply@github.com>
Mon, 16 Oct 2023 21:51:58 +0000 (16:51 -0500)
* technology auto guidebook

* boo-womp

* boo-womp II

14 files changed:
Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml [new file with mode: 0644]
Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml.cs [new file with mode: 0644]
Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml [new file with mode: 0644]
Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml.cs [new file with mode: 0644]
Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs
Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs
Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
Content.Shared/Research/Prototypes/TechnologyPrototype.cs
Content.Shared/Research/Systems/SharedResearchSystem.cs
Resources/Locale/en-US/guidebook/guides.ftl
Resources/Locale/en-US/research/components/research-console-component.ftl
Resources/Prototypes/Guidebook/science.yml
Resources/ServerInfo/Guidebook/Science/Science.xml
Resources/ServerInfo/Guidebook/Science/Technologies.xml [new file with mode: 0644]

diff --git a/Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml b/Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml
new file mode 100644 (file)
index 0000000..0878951
--- /dev/null
@@ -0,0 +1,4 @@
+<BoxContainer xmlns="https://spacestation14.io"
+              Orientation="Vertical">
+    <BoxContainer Name="DisciplineContainer" Orientation="Vertical"/>
+</BoxContainer>
diff --git a/Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideTechDisciplineEmbed.xaml.cs
new file mode 100644 (file)
index 0000000..88d264c
--- /dev/null
@@ -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;
+    }
+}
diff --git a/Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml b/Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml
new file mode 100644 (file)
index 0000000..6d96f19
--- /dev/null
@@ -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>
diff --git a/Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideTechnologyEmbed.xaml.cs
new file mode 100644 (file)
index 0000000..d61cc2d
--- /dev/null
@@ -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
+        };
+    }
+}
index 8b1a583c24a322f2e9addfcfffb43104da33cadf..5af1159c935184a277cbc73748b736f441b50af0 100644 (file)
@@ -2,6 +2,7 @@
 using Robust.Client.AutoGenerated;
 using Robust.Client.GameObjects;
 using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
@@ -15,10 +16,13 @@ public sealed partial class MiniTechnologyCardControl : Control
     {
         RobustXamlLoader.Load(this);
 
-        var discipline = prototypeManager.Index<TechDisciplinePrototype>(technology.Discipline);
+        var discipline = prototypeManager.Index(technology.Discipline);
         Background.ModulateSelfOverride = discipline.Color;
         Texture.Texture = spriteSys.Frame0(technology.Icon);
         NameLabel.SetMessage(Loc.GetString(technology.Name));
-        Main.ToolTip = description.ToString();
+
+        var tooltip = new Tooltip();
+        tooltip.SetMessage(description);
+        Main.TooltipSupplier = _ => tooltip;
     }
 }
index c2b23f7341dedaa364753ab7c0189c4650fbc3a4..a20509202fdeea7038878db60dab8b0fd4dbb327 100644 (file)
@@ -27,7 +27,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
     private readonly TechnologyDatabaseComponent? _technologyDatabase;
     private readonly ResearchSystem _research;
     private readonly SpriteSystem _sprite;
-    private readonly AccessReaderSystem _accessReader = default!;
+    private readonly AccessReaderSystem _accessReader;
 
     public readonly EntityUid Entity;
 
@@ -55,7 +55,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
 
         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);
         }
 
@@ -74,7 +74,7 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
         foreach (var techId in _technologyDatabase.CurrentTechnologyCards)
         {
             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);
             TechnologyCardsContainer.AddChild(cardControl);
         }
@@ -82,37 +82,11 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
         foreach (var unlocked in _technologyDatabase.UnlockedTechnologies)
         {
             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);
         }
     }
 
-    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)
     {
         var amountMsg = new FormattedMessage();
index d886493e3f3f68926fe26c0a8f9e7eb239243985..176b2b68bc9468ae16ec0866207fc4fc654e2364 100644 (file)
@@ -72,7 +72,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
         var tier = int.Parse(weightedRandom.Pick(_random));
 
         //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>())
         {
             if (tech.Tier != tier)
index ec1ca029c71fbf7c696715bd01e172b4541182b1..38000677a136375f7225cd481c4930c8a9a93b99 100644 (file)
@@ -1,6 +1,4 @@
 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;
 
 namespace Content.Shared.Research.Prototypes;
@@ -19,57 +17,57 @@ public sealed class TechnologyPrototype : IPrototype
     /// The name of the technology.
     /// Supports locale strings
     /// </summary>
-    [DataField("name", required: true)]
-    public string Name = string.Empty;
+    [DataField(required: true)]
+    public LocId Name = string.Empty;
 
     /// <summary>
     /// An icon used to visually represent the technology in UI.
     /// </summary>
-    [DataField("icon", required: true)]
+    [DataField(required: true)]
     public SpriteSpecifier Icon = default!;
 
     /// <summary>
     /// What research discipline this technology belongs to.
     /// </summary>
-    [DataField("discipline", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<TechDisciplinePrototype>))]
-    public string Discipline = default!;
+    [DataField(required: true)]
+    public ProtoId<TechDisciplinePrototype> Discipline;
 
     /// <summary>
     /// What tier research is this?
     /// The tier governs how much lower-tier technology
     /// needs to be unlocked before this one.
     /// </summary>
-    [DataField("tier", required: true)]
+    [DataField(required: true)]
     public int Tier;
 
     /// <summary>
     /// Hidden tech is not ever available at the research console.
     /// </summary>
-    [DataField("hidden")]
+    [DataField]
     public bool Hidden;
 
     /// <summary>
     /// How much research is needed to unlock.
     /// </summary>
-    [DataField("cost")]
+    [DataField]
     public int Cost = 10000;
 
     /// <summary>
     /// A list of <see cref="TechnologyPrototype"/>s that need to be unlocked in order to unlock this technology.
     /// </summary>
-    [DataField("technologyPrerequisites", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))]
-    public IReadOnlyList<string> TechnologyPrerequisites = new List<string>();
+    [DataField]
+    public List<ProtoId<TechnologyPrototype>> TechnologyPrerequisites = new();
 
     /// <summary>
     /// A list of <see cref="LatheRecipePrototype"/>s that are unlocked by this technology
     /// </summary>
-    [DataField("recipeUnlocks", customTypeSerializer: typeof(PrototypeIdListSerializer<LatheRecipePrototype>))]
-    public IReadOnlyList<string> RecipeUnlocks = new List<string>();
+    [DataField]
+    public List<ProtoId<LatheRecipePrototype>> RecipeUnlocks = new();
 
     /// <summary>
     /// A list of non-standard effects that are done when this technology is unlocked.
     /// </summary>
-    [DataField("genericUnlocks")]
+    [DataField]
     public IReadOnlyList<GenericUnlock> GenericUnlocks = new List<GenericUnlock>();
 }
 
@@ -80,13 +78,13 @@ public partial record struct GenericUnlock()
     /// What event is raised when this is unlocked?
     /// Used for doing non-standard logic.
     /// </summary>
-    [DataField("purchaseEvent")]
+    [DataField]
     public object? PurchaseEvent = null;
 
     /// <summary>
     /// A player facing tooltip for what the unlock does.
     /// Supports locale strings.
     /// </summary>
-    [DataField("unlockDescription")]
+    [DataField]
     public string UnlockDescription = string.Empty;
 }
index e0cc937b004e9f0f50f5f70d84b6daa7b55f0fb1..12f27d0b9c920fdfcf4d333cb46b20b3e3a8310a 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Research.Components;
 using Content.Shared.Research.Prototypes;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
+using Robust.Shared.Utility;
 
 namespace Content.Shared.Research.Systems;
 
@@ -40,7 +41,7 @@ public abstract class SharedResearchSystem : EntitySystem
 
             component.CurrentTechnologyCards.Add(selected.ID);
         }
-        Dirty(component);
+        Dirty(uid, component);
     }
 
     public List<TechnologyPrototype> GetAvailableTechnologies(EntityUid uid, TechnologyDatabaseComponent? component = null)
@@ -142,6 +143,59 @@ public abstract class SharedResearchSystem : EntitySystem
         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>
     ///     Returns whether a technology is unlocked on this database or not.
     /// </summary>
index 9fc69839c08d37acbce309dd0a0a6ada896322f5..8893db9b23093ea6b94bd482f8073c33c86d8685 100644 (file)
@@ -34,6 +34,7 @@ guide-entry-botanicals = Botanicals
 guide-entry-cloning = Cloning
 guide-entry-cryogenics = Cryogenics
 guide-entry-science = Science
+guide-entry-technologies = Technologies
 guide-entry-anomalous-research = Anomalous Research
 guide-entry-scanners-and-vessels = Scanners and Vessels
 guide-entry-ape = A.P.E.
index 1063e6ebcb202baf62d2a3e96f0069a7084840da..196983efcd43ec7d6a7d3dd5fc87b8bdb6bc3c90 100644 (file)
@@ -14,5 +14,7 @@ research-console-cost = Cost: [color=orchid]{$amount}[/color]
 research-console-unlocks-list-start = Unlocks:
 research-console-unlocks-list-entry = - [color=yellow]{$name}[/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!
index d9a3d442b446c30887e6d25963a2aa53b5d97b30..03840ec15ee1d1dd4036b6a69d0e6bacbcb30099 100644 (file)
@@ -3,11 +3,18 @@
   name: guide-entry-science
   text: "/ServerInfo/Guidebook/Science/Science.xml"
   children:
+  - Technologies
   - AnomalousResearch
   - Xenoarchaeology
   - Robotics
   - MachineUpgrading
 
+- type: guideEntry
+  id: Technologies
+  name: guide-entry-technologies
+  text: "/ServerInfo/Guidebook/Science/Technologies.xml"
+  filterEnabled: True
+
 - type: guideEntry
   id: AnomalousResearch
   name: guide-entry-anomalous-research
index a32112877fb469f6fc5e397295ceef34a8f7e539..c886daf4d53174f8c80a2e7b890e20427ec6f387 100644 (file)
@@ -10,7 +10,9 @@ Science, often called Research and Development, is a job made up of both generat
 </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.
 
-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
 Technologies are spread over 5 different Disciplines:
diff --git a/Resources/ServerInfo/Guidebook/Science/Technologies.xml b/Resources/ServerInfo/Guidebook/Science/Technologies.xml
new file mode 100644 (file)
index 0000000..7f0feac
--- /dev/null
@@ -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>