diff --git a/Content.Client/Research/UI/MiniTechnologyCardControl.xaml b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml
new file mode 100644
index 0000000000..2808995524
--- /dev/null
+++ b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs
new file mode 100644
index 0000000000..8b1a583c24
--- /dev/null
+++ b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Research.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Research.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class MiniTechnologyCardControl : Control
+{
+ public MiniTechnologyCardControl(TechnologyPrototype technology, IPrototypeManager prototypeManager, SpriteSystem spriteSys, FormattedMessage description)
+ {
+ RobustXamlLoader.Load(this);
+
+ 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();
+ }
+}
diff --git a/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs b/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs
index c9a3fb4379..97556c931d 100644
--- a/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs
+++ b/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs
@@ -4,91 +4,63 @@ using Content.Shared.Research.Systems;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
-namespace Content.Client.Research.UI
-{
- [UsedImplicitly]
- public sealed class ResearchConsoleBoundUserInterface : BoundUserInterface
- {
- public int Points { get; private set; }
- public int PointsPerSecond { get; private set; }
- private ResearchConsoleMenu? _consoleMenu;
- private TechnologyDatabaseComponent? _technologyDatabase;
- private readonly IEntityManager _entityManager;
- private readonly SharedResearchSystem _research;
+namespace Content.Client.Research.UI;
- public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+[UsedImplicitly]
+public sealed class ResearchConsoleBoundUserInterface : BoundUserInterface
+{
+
+ private ResearchConsoleMenu? _consoleMenu;
+
+
+ public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ {
+ SendMessage(new ConsoleServerSyncMessage());
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ var owner = Owner.Owner;
+
+ _consoleMenu = new ResearchConsoleMenu(owner);
+
+ _consoleMenu.OnTechnologyCardPressed += id =>
+ {
+ SendMessage(new ConsoleUnlockTechnologyMessage(id));
+ };
+
+ _consoleMenu.OnServerButtonPressed += () =>
+ {
+ SendMessage(new ConsoleServerSelectionMessage());
+ };
+
+ _consoleMenu.OnSyncButtonPressed += () =>
{
SendMessage(new ConsoleServerSyncMessage());
- _entityManager = IoCManager.Resolve();
- _research = _entityManager.System();
- }
+ };
- protected override void Open()
- {
- base.Open();
+ _consoleMenu.OnClose += Close;
- if (!_entityManager.TryGetComponent(Owner.Owner, out _technologyDatabase))
- return;
+ _consoleMenu.OpenCentered();
+ }
- _consoleMenu = new ResearchConsoleMenu(this);
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
- _consoleMenu.OnClose += Close;
+ if (state is not ResearchConsoleBoundInterfaceState castState)
+ return;
+ _consoleMenu?.UpdatePanels(castState);
+ _consoleMenu?.UpdateInformationPanel(castState);
+ }
- _consoleMenu.ServerSyncButton.OnPressed += (_) =>
- {
- SendMessage(new ConsoleServerSyncMessage());
- };
-
- _consoleMenu.ServerSelectionButton.OnPressed += (_) =>
- {
- SendMessage(new ConsoleServerSelectionMessage());
- };
-
- _consoleMenu.UnlockButton.OnPressed += (_) =>
- {
- if (_consoleMenu.TechnologySelected != null)
- {
- SendMessage(new ConsoleUnlockTechnologyMessage(_consoleMenu.TechnologySelected.ID));
- }
- };
-
- _consoleMenu.OpenCentered();
- }
-
- public bool IsTechnologyUnlocked(TechnologyPrototype technology)
- {
- if (_technologyDatabase == null)
- return false;
-
- return _research.IsTechnologyUnlocked(_technologyDatabase.Owner, technology, _technologyDatabase);
- }
-
- public bool CanUnlockTechnology(TechnologyPrototype technology)
- {
- if (_technologyDatabase == null)
- return false;
-
- return _research.ArePrerequesitesUnlocked(_technologyDatabase.Owner, technology, _technologyDatabase);
- }
-
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- base.UpdateState(state);
-
- var castState = (ResearchConsoleBoundInterfaceState)state;
- Points = castState.Points;
- PointsPerSecond = castState.PointsPerSecond;
- // We update the user interface here.
- _consoleMenu?.PopulatePoints();
- _consoleMenu?.Populate();
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
- _consoleMenu?.Dispose();
- }
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+ _consoleMenu?.Dispose();
}
}
diff --git a/Content.Client/Research/UI/ResearchConsoleMenu.xaml b/Content.Client/Research/UI/ResearchConsoleMenu.xaml
index df892b8820..a332458de3 100644
--- a/Content.Client/Research/UI/ResearchConsoleMenu.xaml
+++ b/Content.Client/Research/UI/ResearchConsoleMenu.xaml
@@ -1,87 +1,101 @@
-
+
-
-
-
-
-
+ VerticalExpand="False"
+ MinHeight="85"
+ Margin="10">
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+ VerticalExpand="True">
-
-
-
+ HorizontalExpand="True"
+ SizeFlagsStretchRatio="2"
+ Margin="10 0 10 10"
+ MinWidth="175">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+ HorizontalExpand="True"
+ SizeFlagsStretchRatio="3"
+ Margin="0 0 10 10">
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs b/Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs
index bc7470e198..16ae456a93 100644
--- a/Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs
+++ b/Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs
@@ -1,195 +1,167 @@
-using System.Collections.Generic;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
-using Robust.Client.Graphics;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Client.Utility;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
-namespace Content.Client.Research.UI
+namespace Content.Client.Research.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class ResearchConsoleMenu : FancyWindow
{
- [GenerateTypedNameReferences]
- public sealed partial class ResearchConsoleMenu : DefaultWindow
+ public Action? OnTechnologyCardPressed;
+ public Action? OnServerButtonPressed;
+ public Action? OnSyncButtonPressed;
+
+ [Dependency] private readonly IEntityManager _entity = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ private readonly TechnologyDatabaseComponent? _technologyDatabase;
+ private readonly ResearchSystem _research;
+ private readonly SpriteSystem _sprite;
+
+ public readonly EntityUid Entity;
+
+ public ResearchConsoleMenu(EntityUid entity)
{
- public ResearchConsoleBoundUserInterface Owner { get; }
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
- private readonly List _unlockedTechnologyPrototypes = new();
- private readonly List _unlockableTechnologyPrototypes = new();
- private readonly List _futureTechnologyPrototypes = new();
+ _research = _entity.System();
+ _sprite = _entity.System();
+ Entity = entity;
- public TechnologyPrototype? TechnologySelected;
+ ServerButton.OnPressed += _ => OnServerButtonPressed?.Invoke();
+ SyncButton.OnPressed += _ => OnSyncButtonPressed?.Invoke();
- public ResearchConsoleMenu(ResearchConsoleBoundUserInterface owner)
+ _entity.TryGetComponent(entity, out _technologyDatabase);
+ }
+
+ public void UpdatePanels(ResearchConsoleBoundInterfaceState state)
+ {
+ var allTech = _research.GetAvailableTechnologies(Entity);
+ AvailableCardsContainer.Children.Clear();
+ TechnologyCardsContainer.Children.Clear();
+ UnlockedCardsContainer.Children.Clear();
+
+ foreach (var tech in allTech)
{
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
+ var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false));
+ AvailableCardsContainer.AddChild(mini);
+ }
- Owner = owner;
+ if (_technologyDatabase == null)
+ return;
- UnlockedTechnologies.OnItemSelected += UnlockedTechnologySelected;
- UnlockableTechnologies.OnItemSelected += UnlockableTechnologySelected;
- FutureTechnologies.OnItemSelected += FutureTechnologySelected;
+ // i can't figure out the spacing so here you go
+ TechnologyCardsContainer.AddChild(new Control
+ {
+ MinHeight = 10
+ });
+ foreach (var techId in _technologyDatabase.CurrentTechnologyCards)
+ {
+ var tech = _prototype.Index(techId);
+ var cardControl = new TechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech), state.Points);
+ cardControl.OnPressed += () => OnTechnologyCardPressed?.Invoke(techId);
+ TechnologyCardsContainer.AddChild(cardControl);
+ }
- PointLabel.Text = Loc.GetString("research-console-menu-research-points-text", ("points", 0));
- PointsPerSecondLabel.Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", 0));
+ foreach (var unlocked in _technologyDatabase.UnlockedTechnologies)
+ {
+ var tech = _prototype.Index(unlocked);
+ var cardControl = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false));
+ UnlockedCardsContainer.AddChild(cardControl);
+ }
+ }
- UnlockButton.Text = Loc.GetString("research-console-menu-server-unlock-button");
+ 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(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))));
+ }
- UnlockButton.OnPressed += _ =>
+ return description;
+ }
+
+ public void UpdateInformationPanel(ResearchConsoleBoundInterfaceState state)
+ {
+ var amountMsg = new FormattedMessage();
+ amountMsg.AddMarkup(Loc.GetString("research-console-menu-research-points-text",
+ ("points", state.Points)));
+ ResearchAmountLabel.SetMessage(amountMsg);
+
+ if (_technologyDatabase == null)
+ return;
+
+ var disciplineText = Loc.GetString("research-discipline-none");
+ var disciplineColor = Color.Gray;
+ if (_technologyDatabase.MainDiscipline != null)
+ {
+ var discipline = _prototype.Index(_technologyDatabase.MainDiscipline);
+ disciplineText = Loc.GetString(discipline.Name);
+ disciplineColor = discipline.Color;
+ }
+
+ var msg = new FormattedMessage();
+ msg.AddMarkup(Loc.GetString("research-console-menu-main-discipline",
+ ("name", disciplineText), ("color", disciplineColor)));
+ MainDisciplineLabel.SetMessage(msg);
+
+ TierDisplayContainer.Children.Clear();
+ foreach (var disciplineId in _technologyDatabase.SupportedDisciplines)
+ {
+ var discipline = _prototype.Index(disciplineId);
+ var tier = _research.GetHighestDisciplineTier(_technologyDatabase, discipline);
+
+ // don't show tiers with no available tech
+ if (tier == 0)
+ continue;
+
+ // i'm building the small-ass control here to spare me some mild annoyance in making a new file
+ var texture = new TextureRect
{
- CleanSelectedTechnology();
+ TextureScale = ( 2, 2 ),
+ VerticalAlignment = VAlignment.Center
};
+ var label = new RichTextLabel();
+ texture.Texture = _sprite.Frame0(discipline.Icon);
+ label.SetMessage(Loc.GetString("research-console-tier-info-small", ("tier", tier)));
- Populate();
- }
-
- ///
- /// Cleans the selected technology controls to blank.
- ///
- private void CleanSelectedTechnology()
- {
- UnlockButton.Disabled = true;
- TechnologyIcon.Texture = Texture.Transparent;
- TechnologyName.Text = string.Empty;
- TechnologyDescription.Text = string.Empty;
- TechnologyRequirements.Text = string.Empty;
- }
-
- ///
- /// Called when an unlocked technology is selected.
- ///
- private void UnlockedTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
- {
- TechnologySelected = _unlockedTechnologyPrototypes[obj.ItemIndex];
-
- UnlockButton.Disabled = true;
-
- PopulateSelectedTechnology();
- }
-
- ///
- /// Called when an unlockable technology is selected.
- ///
- private void UnlockableTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
- {
- TechnologySelected = _unlockableTechnologyPrototypes[obj.ItemIndex];
-
- UnlockButton.Disabled = Owner.Points < TechnologySelected.RequiredPoints;
-
- PopulateSelectedTechnology();
- }
-
- ///
- /// Called when a future technology is selected
- ///
- private void FutureTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
- {
- TechnologySelected = _futureTechnologyPrototypes[obj.ItemIndex];
-
- UnlockButton.Disabled = true;
-
- PopulateSelectedTechnology();
- }
-
- ///
- /// Populate all technologies in the ItemLists.
- ///
- public void PopulateItemLists()
- {
- UnlockedTechnologies.Clear();
- UnlockableTechnologies.Clear();
- FutureTechnologies.Clear();
-
- _unlockedTechnologyPrototypes.Clear();
- _unlockableTechnologyPrototypes.Clear();
- _futureTechnologyPrototypes.Clear();
-
- var prototypeMan = IoCManager.Resolve();
-
- // For now, we retrieve all technologies. In the future, this should be changed.
- foreach (var tech in prototypeMan.EnumeratePrototypes())
+ var control = new BoxContainer
{
- var techName = GetTechName(tech);
- if (Owner.IsTechnologyUnlocked(tech))
+ Children =
{
- UnlockedTechnologies.AddItem(techName, tech.Icon.Frame0());
- _unlockedTechnologyPrototypes.Add(tech);
+ texture,
+ label,
+ new Control
+ {
+ MinWidth = 10
+ }
}
- else if (Owner.CanUnlockTechnology(tech))
- {
- UnlockableTechnologies.AddItem(techName, tech.Icon.Frame0());
- _unlockableTechnologyPrototypes.Add(tech);
- }
- else
- {
- FutureTechnologies.AddItem(techName, tech.Icon.Frame0());
- _futureTechnologyPrototypes.Add(tech);
- }
- }
- }
-
- private string GetTechName(TechnologyPrototype prototype)
- {
- if (prototype.Name is { } name)
- return Loc.GetString(name);
-
- return prototype.ID;
- }
-
- ///
- /// Fills the selected technology controls with details.
- ///
- public void PopulateSelectedTechnology()
- {
- if (TechnologySelected == null)
- {
- TechnologyName.Text = string.Empty;
- TechnologyDescription.Text = string.Empty;
- TechnologyRequirements.Text = string.Empty;
- return;
- }
-
- TechnologyIcon.Texture = TechnologySelected.Icon.Frame0();
- TechnologyName.Text = GetTechName(TechnologySelected);
- var desc = Loc.GetString(TechnologySelected.Description);
- TechnologyDescription.Text = desc + $"\n{TechnologySelected.RequiredPoints} " + Loc.GetString("research-console-menu-research-points-text" ,("points", Owner.Points)).ToLowerInvariant();
- TechnologyRequirements.Text = Loc.GetString("research-console-tech-requirements-none");
-
- var prototypeMan = IoCManager.Resolve();
-
- for (var i = 0; i < TechnologySelected.RequiredTechnologies.Count; i++)
- {
- var requiredId = TechnologySelected.RequiredTechnologies[i];
- if (!prototypeMan.TryIndex(requiredId, out TechnologyPrototype? prototype)) continue;
- var protoName = GetTechName(prototype);
- if (i == 0)
- TechnologyRequirements.Text = Loc.GetString("research-console-tech-requirements-prototype-name", ("prototypeName", protoName));
- else
- TechnologyRequirements.Text += $", {protoName}";
- }
- }
-
- ///
- /// Updates the research point labels.
- ///
- public void PopulatePoints()
- {
- PointLabel.Text = Loc.GetString("research-console-menu-research-points-text", ("points", Owner.Points));
- PointsPerSecondLabel.Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", Owner.PointsPerSecond));
- }
-
- ///
- /// Updates the whole user interface.
- ///
- public void Populate()
- {
- PopulatePoints();
- PopulateSelectedTechnology();
- PopulateItemLists();
+ };
+ TierDisplayContainer.AddChild(control);
}
}
}
+
diff --git a/Content.Client/Research/UI/TechnologyCardControl.xaml b/Content.Client/Research/UI/TechnologyCardControl.xaml
new file mode 100644
index 0000000000..e8d02015e6
--- /dev/null
+++ b/Content.Client/Research/UI/TechnologyCardControl.xaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
diff --git a/Content.Client/Research/UI/TechnologyCardControl.xaml.cs b/Content.Client/Research/UI/TechnologyCardControl.xaml.cs
new file mode 100644
index 0000000000..76e30354c6
--- /dev/null
+++ b/Content.Client/Research/UI/TechnologyCardControl.xaml.cs
@@ -0,0 +1,36 @@
+using Content.Shared.Research.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Research.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class TechnologyCardControl : Control
+{
+ public Action? OnPressed;
+
+ public TechnologyCardControl(TechnologyPrototype technology, IPrototypeManager prototypeManager, SpriteSystem spriteSys, FormattedMessage description, int points)
+ {
+ RobustXamlLoader.Load(this);
+
+ var discipline = prototypeManager.Index(technology.Discipline);
+ Background.ModulateSelfOverride = discipline.Color;
+
+ DisciplineTexture.Texture = spriteSys.Frame0(discipline.Icon);
+ TechnologyNameLabel.Text = Loc.GetString(technology.Name);
+ var message = new FormattedMessage();
+ message.AddMarkup(Loc.GetString("research-console-tier-discipline-info",
+ ("tier", technology.Tier), ("color", discipline.Color), ("discipline", Loc.GetString(discipline.Name))));
+ TierLabel.SetMessage(message);
+ UnlocksLabel.SetMessage(description);
+
+ TechnologyTexture.Texture = spriteSys.Frame0(technology.Icon);
+
+ ResearchButton.Disabled = points < technology.Cost;
+ ResearchButton.OnPressed += _ => OnPressed?.Invoke();
+ }
+}
diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs
index a377b74340..5097132a86 100644
--- a/Content.Client/Stylesheets/StyleNano.cs
+++ b/Content.Client/Stylesheets/StyleNano.cs
@@ -1351,6 +1351,10 @@ namespace Content.Client.Stylesheets
.Prop(Label.StylePropertyFont, notoSans12)
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#111111")),
+ Element().Class("LabelSubText")
+ .Prop(Label.StylePropertyFont, notoSans10)
+ .Prop(Label.StylePropertyFontColor, Color.DarkGray),
+
Element().Class("PaperLineEdit")
.Prop(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
diff --git a/Content.IntegrationTests/Tests/ResearchTest.cs b/Content.IntegrationTests/Tests/ResearchTest.cs
index 7462af4daa..e38698ebaa 100644
--- a/Content.IntegrationTests/Tests/ResearchTest.cs
+++ b/Content.IntegrationTests/Tests/ResearchTest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Content.Shared.Lathe;
using Content.Shared.Research.Prototypes;
@@ -10,6 +11,40 @@ namespace Content.IntegrationTests.Tests;
[TestFixture]
public sealed class ResearchTest
{
+ [Test]
+ public async Task DisciplineValidTierPrerequesitesTest()
+ {
+ await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings {NoClient = true});
+ var server = pairTracker.Pair.Server;
+
+ var protoManager = server.ResolveDependency();
+
+ await server.WaitAssertion(() =>
+ {
+ var allTechs = protoManager.EnumeratePrototypes().ToList();
+
+ foreach (var discipline in protoManager.EnumeratePrototypes())
+ {
+ foreach (var tech in allTechs)
+ {
+ if (tech.Discipline != discipline.ID)
+ continue;
+
+ // we ignore these, anyways
+ if (tech.Tier == 1)
+ continue;
+
+ Assert.That(tech.Tier, Is.GreaterThan(0), $"Technology {tech} has invalid tier {tech.Tier}.");
+
+ Assert.That(discipline.TierPrerequisites.ContainsKey(tech.Tier),
+ $"Discipline {discipline.ID} does not have a TierPrerequisites definition for tier {tech.Tier}");
+ }
+ }
+ });
+
+ await pairTracker.CleanReturnAsync();
+ }
+
[Test]
public async Task AllTechPrintableTest()
{
@@ -40,14 +75,13 @@ public sealed class ResearchTest
foreach (var recipe in lathe.DynamicRecipes)
{
- if (!latheTechs.Contains(recipe))
- latheTechs.Add(recipe);
+ latheTechs.Add(recipe);
}
}
foreach (var tech in protoManager.EnumeratePrototypes())
{
- foreach (var recipe in tech.UnlockedRecipes)
+ foreach (var recipe in tech.RecipeUnlocks)
{
Assert.That(latheTechs, Does.Contain(recipe), $"Recipe \"{recipe}\" cannot be unlocked on any lathes.");
}
diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs
index f71c618c1a..2cdbc8a87f 100644
--- a/Content.Server/Lathe/LatheSystem.cs
+++ b/Content.Server/Lathe/LatheSystem.cs
@@ -194,7 +194,7 @@ namespace Content.Server.Lathe
if (uid != args.Lathe || !TryComp(uid, out var latheComponent) || latheComponent.DynamicRecipes == null)
return;
- args.Recipes = args.Recipes.Union(component.RecipeIds.Where(r => latheComponent.DynamicRecipes.Contains(r))).ToList();
+ args.Recipes = args.Recipes.Union(component.UnlockedRecipes.Where(r => latheComponent.DynamicRecipes.Contains(r))).ToList();
}
private void OnMaterialAmountChanged(EntityUid uid, LatheComponent component, ref MaterialAmountChangedEvent args)
diff --git a/Content.Server/Research/Disk/ResearchDiskSystem.cs b/Content.Server/Research/Disk/ResearchDiskSystem.cs
index 9d257c89da..d32c49ce6f 100644
--- a/Content.Server/Research/Disk/ResearchDiskSystem.cs
+++ b/Content.Server/Research/Disk/ResearchDiskSystem.cs
@@ -28,7 +28,7 @@ namespace Content.Server.Research.Disk
if (!TryComp(args.Target, out var server))
return;
- _research.AddPointsToServer(server.Owner, component.Points, server);
+ _research.ModifyServerPoints(args.Target.Value, component.Points, server);
_popupSystem.PopupEntity(Loc.GetString("research-disk-inserted", ("points", component.Points)), args.Target.Value, args.User);
EntityManager.QueueDeleteEntity(uid);
}
@@ -39,7 +39,7 @@ namespace Content.Server.Research.Disk
return;
component.Points = _prototype.EnumeratePrototypes()
- .Sum(tech => tech.RequiredPoints);
+ .Sum(tech => tech.Cost);
}
}
}
diff --git a/Content.Server/Research/Systems/ResearchSystem.Client.cs b/Content.Server/Research/Systems/ResearchSystem.Client.cs
index d1041fb1c3..0416c42d87 100644
--- a/Content.Server/Research/Systems/ResearchSystem.Client.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.Client.cs
@@ -105,8 +105,10 @@ public sealed partial class ResearchSystem
/// The server's ResearchServerComponent. Null if false
/// The client's Researchclient component
/// If the server was successfully retrieved.
- public bool TryGetClientServer(EntityUid uid, [NotNullWhen(returnValue: true)] out EntityUid? server,
- [NotNullWhen(returnValue: true)] out ResearchServerComponent? serverComponent, ResearchClientComponent? component = null)
+ public bool TryGetClientServer(EntityUid uid,
+ [NotNullWhen(returnValue: true)] out EntityUid? server,
+ [NotNullWhen(returnValue: true)] out ResearchServerComponent? serverComponent,
+ ResearchClientComponent? component = null)
{
server = null;
serverComponent = null;
@@ -117,11 +119,10 @@ public sealed partial class ResearchSystem
if (component.Server == null)
return false;
- if (!TryComp(component.Server, out var sc))
+ if (!TryComp(component.Server, out serverComponent))
return false;
server = component.Server;
- serverComponent = sc;
return true;
}
}
diff --git a/Content.Server/Research/Systems/ResearchSystem.Console.cs b/Content.Server/Research/Systems/ResearchSystem.Console.cs
index 9e2f29dafc..fce7303062 100644
--- a/Content.Server/Research/Systems/ResearchSystem.Console.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.Console.cs
@@ -33,18 +33,17 @@ public sealed partial class ResearchSystem
ResearchConsoleBoundInterfaceState state;
- if (TryGetClientServer(uid, out var server, out var serverComponent, clientComponent))
+ if (TryGetClientServer(uid, out _, out var serverComponent, clientComponent))
{
var points = clientComponent.ConnectedToServer ? serverComponent.Points : 0;
- var pointsPerSecond = clientComponent.ConnectedToServer ? PointsPerSecond(server.Value, serverComponent) : 0;
- state = new ResearchConsoleBoundInterfaceState(points, pointsPerSecond);
+ state = new ResearchConsoleBoundInterfaceState(points);
}
else
{
- state = new ResearchConsoleBoundInterfaceState(default, default);
+ state = new ResearchConsoleBoundInterfaceState(default);
}
- _uiSystem.TrySetUiState(component.Owner, ResearchConsoleUiKey.Key, state);
+ _uiSystem.TrySetUiState(uid, ResearchConsoleUiKey.Key, state);
}
private void OnPointsChanged(EntityUid uid, ResearchConsoleComponent component, ref ResearchServerPointsChangedEvent args)
diff --git a/Content.Server/Research/Systems/ResearchSystem.Server.cs b/Content.Server/Research/Systems/ResearchSystem.Server.cs
index d7a9c5fafb..8d37e58f18 100644
--- a/Content.Server/Research/Systems/ResearchSystem.Server.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.Server.cs
@@ -49,7 +49,7 @@ public sealed partial class ResearchSystem
if (!CanRun(uid))
return;
- AddPointsToServer(uid, PointsPerSecond(uid, component) * time, component);
+ ModifyServerPoints(uid, GetPointsPerSecond(uid, component) * time, component);
}
///
@@ -60,15 +60,14 @@ public sealed partial class ResearchSystem
///
///
/// Whether or not to dirty the server component after registration
- /// Whether or not the client was successfully registered to the server
- public bool RegisterClient(EntityUid client, EntityUid server, ResearchClientComponent? clientComponent = null,
+ public void RegisterClient(EntityUid client, EntityUid server, ResearchClientComponent? clientComponent = null,
ResearchServerComponent? serverComponent = null, bool dirtyServer = true)
{
if (!Resolve(client, ref clientComponent) || !Resolve(server, ref serverComponent))
- return false;
+ return;
if (serverComponent.Clients.Contains(client))
- return false;
+ return;
serverComponent.Clients.Add(client);
clientComponent.Server = server;
@@ -78,7 +77,6 @@ public sealed partial class ResearchSystem
var ev = new ResearchRegistrationChangedEvent(server);
RaiseLocalEvent(client, ref ev);
- return true;
}
///
@@ -130,7 +128,7 @@ public sealed partial class ResearchSystem
///
///
///
- public int PointsPerSecond(EntityUid uid, ResearchServerComponent? component = null)
+ public int GetPointsPerSecond(EntityUid uid, ResearchServerComponent? component = null)
{
var points = 0;
@@ -154,7 +152,7 @@ public sealed partial class ResearchSystem
/// The server
/// The amount of points being added
///
- public void AddPointsToServer(EntityUid uid, int points, ResearchServerComponent? component = null)
+ public void ModifyServerPoints(EntityUid uid, int points, ResearchServerComponent? component = null)
{
if (points == 0)
return;
diff --git a/Content.Server/Research/Systems/ResearchSystem.Technology.cs b/Content.Server/Research/Systems/ResearchSystem.Technology.cs
index 85057dafc8..f18a0ca33f 100644
--- a/Content.Server/Research/Systems/ResearchSystem.Technology.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.Technology.cs
@@ -1,5 +1,6 @@
using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
+using JetBrains.Annotations;
namespace Content.Server.Research.Systems;
@@ -13,13 +14,16 @@ public sealed partial class ResearchSystem
if (!Resolve(primaryUid, ref primaryDb) || !Resolve(otherUid, ref otherDb))
return;
- primaryDb.TechnologyIds = otherDb.TechnologyIds;
- primaryDb.RecipeIds = otherDb.RecipeIds;
+ primaryDb.MainDiscipline = otherDb.MainDiscipline;
+ primaryDb.CurrentTechnologyCards = otherDb.CurrentTechnologyCards;
+ primaryDb.SupportedDisciplines = otherDb.SupportedDisciplines;
+ primaryDb.UnlockedTechnologies = otherDb.UnlockedTechnologies;
+ primaryDb.UnlockedRecipes = otherDb.UnlockedRecipes;
Dirty(primaryDb);
var ev = new TechnologyDatabaseModifiedEvent();
- RaiseLocalEvent(primaryDb.Owner, ref ev);
+ RaiseLocalEvent(primaryUid, ref ev);
}
///
@@ -28,16 +32,15 @@ public sealed partial class ResearchSystem
/// syncs against the research server, and the server against the local database.
///
/// Whether it could sync or not
- public bool SyncClientWithServer(EntityUid uid, TechnologyDatabaseComponent? databaseComponent = null, ResearchClientComponent? clientComponent = null)
+ public void SyncClientWithServer(EntityUid uid, TechnologyDatabaseComponent? databaseComponent = null, ResearchClientComponent? clientComponent = null)
{
if (!Resolve(uid, ref databaseComponent, ref clientComponent, false))
- return false;
+ return;
if (!TryComp(clientComponent.Server, out var serverDatabase))
- return false;
+ return;
Sync(uid, clientComponent.Server.Value, databaseComponent, serverDatabase);
- return true;
}
///
@@ -45,45 +48,49 @@ public sealed partial class ResearchSystem
///
/// If the technology was successfully added
public bool UnlockTechnology(EntityUid client, string prototypeid, ResearchClientComponent? component = null,
- TechnologyDatabaseComponent? databaseComponent = null)
+ TechnologyDatabaseComponent? clientDatabase = null)
{
- if (!_prototypeManager.TryIndex(prototypeid, out var prototype))
- {
- Logger.Error("invalid technology prototype");
+ if (!PrototypeManager.TryIndex(prototypeid, out var prototype))
return false;
- }
- return UnlockTechnology(client, prototype, component, databaseComponent);
+
+ return UnlockTechnology(client, prototype, component, clientDatabase);
}
///
/// Tries to add a technology to a database, checking if it is able to
///
/// If the technology was successfully added
- public bool UnlockTechnology(EntityUid client, TechnologyPrototype prototype, ResearchClientComponent? component = null,
- TechnologyDatabaseComponent? databaseComponent = null)
+ public bool UnlockTechnology(EntityUid client,
+ TechnologyPrototype prototype,
+ ResearchClientComponent? component = null,
+ TechnologyDatabaseComponent? clientDatabase = null)
{
- if (!Resolve(client, ref component, ref databaseComponent, false))
+ if (!Resolve(client, ref component, ref clientDatabase, false))
return false;
- if (!CanUnlockTechnology(client, prototype, databaseComponent))
+ if (!TryGetClientServer(client, out var serverEnt, out _, component))
return false;
- if (component.Server is not { } server)
+ if (!CanServerUnlockTechnology(client, prototype, clientDatabase, component))
return false;
- AddTechnology(server, prototype.ID);
- AddPointsToServer(server, -prototype.RequiredPoints);
+
+ AddTechnology(serverEnt.Value, prototype);
+ TrySetMainDiscipline(prototype, serverEnt.Value);
+ ModifyServerPoints(serverEnt.Value, -prototype.Cost);
+ UpdateTechnologyCards(serverEnt.Value);
return true;
}
///
/// Adds a technology to the database without checking if it could be unlocked.
///
+ [PublicAPI]
public void AddTechnology(EntityUid uid, string technology, TechnologyDatabaseComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
- if (!_prototypeManager.TryIndex(technology, out var prototype))
+ if (!PrototypeManager.TryIndex(technology, out var prototype))
return;
AddTechnology(uid, prototype, component);
}
@@ -96,12 +103,19 @@ public sealed partial class ResearchSystem
if (!Resolve(uid, ref component))
return;
- component.TechnologyIds.Add(technology.ID);
- foreach (var unlock in technology.UnlockedRecipes)
+ //todo this needs to support some other stuff, too
+ foreach (var generic in technology.GenericUnlocks)
{
- if (component.RecipeIds.Contains(unlock))
+ if (generic.PurchaseEvent != null)
+ RaiseLocalEvent(generic.PurchaseEvent);
+ }
+
+ component.UnlockedTechnologies.Add(technology.ID);
+ foreach (var unlock in technology.RecipeUnlocks)
+ {
+ if (component.UnlockedRecipes.Contains(unlock))
continue;
- component.RecipeIds.Add(unlock);
+ component.UnlockedRecipes.Add(unlock);
}
Dirty(component);
@@ -113,17 +127,16 @@ public sealed partial class ResearchSystem
/// Adds a lathe recipe to the specified technology database
/// without checking if it can be unlocked.
///
- public void AddLatheRecipe(EntityUid uid, string recipe, TechnologyDatabaseComponent? component = null, bool dirty = true)
+ public void AddLatheRecipe(EntityUid uid, string recipe, TechnologyDatabaseComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
- if (component.RecipeIds.Contains(recipe))
+ if (component.UnlockedRecipes.Contains(recipe))
return;
- component.RecipeIds.Add(recipe);
- if (dirty)
- Dirty(component);
+ component.UnlockedRecipes.Add(recipe);
+ Dirty(component);
var ev = new TechnologyDatabaseModifiedEvent();
RaiseLocalEvent(uid, ref ev);
@@ -134,34 +147,36 @@ public sealed partial class ResearchSystem
/// taking parent technologies into account.
///
/// Whether it could be unlocked or not
- public bool CanUnlockTechnology(EntityUid uid, string technology, TechnologyDatabaseComponent? database = null, ResearchClientComponent? client = null)
+ public bool CanServerUnlockTechnology(EntityUid uid,
+ TechnologyPrototype technology,
+ TechnologyDatabaseComponent? database = null,
+ ResearchClientComponent? client = null)
{
- if (!_prototypeManager.TryIndex(technology, out var prototype))
- return false;
- return CanUnlockTechnology(uid, prototype, database, client);
- }
- ///
- /// Returns whether a technology can be unlocked on this database,
- /// taking parent technologies into account.
- ///
- /// Whether it could be unlocked or not
- public bool CanUnlockTechnology(EntityUid uid, TechnologyPrototype technology, TechnologyDatabaseComponent? database = null, ResearchClientComponent? client = null)
- {
- if (!Resolve(uid, ref database, ref client))
+ if (!Resolve(uid, ref client, ref database, false))
return false;
- if (!TryGetClientServer(uid, out _, out var serverComponent, client))
+ if (!TryGetClientServer(uid, out _, out var serverComp, client))
return false;
- if (serverComponent.Points < technology.RequiredPoints)
+ if (!IsTechnologyAvailable(database, technology))
return false;
- if (IsTechnologyUnlocked(uid, technology, database))
+ if (technology.Cost > serverComp.Points)
return false;
- if (!ArePrerequesitesUnlocked(uid, technology, database))
- return false;
return true;
}
+
+ private void OnDatabaseRegistrationChanged(EntityUid uid, TechnologyDatabaseComponent component, ref ResearchRegistrationChangedEvent args)
+ {
+ if (args.Server != null)
+ return;
+ component.MainDiscipline = null;
+ component.CurrentTechnologyCards = new List();
+ component.SupportedDisciplines = new List();
+ component.UnlockedTechnologies = new List();
+ component.UnlockedRecipes = new List();
+ Dirty(component);
+ }
}
diff --git a/Content.Server/Research/Systems/ResearchSystem.cs b/Content.Server/Research/Systems/ResearchSystem.cs
index 0e1630b788..92e17df7f9 100644
--- a/Content.Server/Research/Systems/ResearchSystem.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.cs
@@ -4,7 +4,6 @@ using Content.Shared.Research.Components;
using Content.Shared.Research.Systems;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
-using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Research.Systems
@@ -13,7 +12,6 @@ namespace Content.Server.Research.Systems
public sealed partial class ResearchSystem : SharedResearchSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize()
@@ -23,6 +21,8 @@ namespace Content.Server.Research.Systems
InitializeConsole();
InitializeSource();
InitializeServer();
+
+ SubscribeLocalEvent(OnDatabaseRegistrationChanged);
}
///
diff --git a/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs b/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs
index 341a8d489b..b784c37d0b 100644
--- a/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs
+++ b/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs
@@ -51,7 +51,7 @@ public sealed class DiskConsoleSystem : EntitySystem
if (serverComp.Points < component.PricePerDisk)
return;
- _research.AddPointsToServer(server.Value, -component.PricePerDisk, serverComp);
+ _research.ModifyServerPoints(server.Value, -component.PricePerDisk, serverComp);
_audio.PlayPvs(component.PrintSound, uid);
var printing = EnsureComp(uid);
diff --git a/Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs b/Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
index 2f4bce403e..e0b10257ac 100644
--- a/Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
+++ b/Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
@@ -38,12 +38,11 @@ public sealed class TechnologyDiskSystem : EntitySystem
{
foreach (var recipe in component.Recipes)
{
- _research.AddLatheRecipe(target, recipe, database, false);
+ _research.AddLatheRecipe(target, recipe, database);
}
- Dirty(database);
}
_popup.PopupEntity(Loc.GetString("tech-disk-inserted"), target, args.User);
- EntityManager.DeleteEntity(uid);
+ Del(uid);
args.Handled = true;
}
@@ -71,7 +70,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
var allTechs = new List();
foreach (var tech in _prototype.EnumeratePrototypes())
{
- allTechs.AddRange(tech.UnlockedRecipes);
+ allTechs.AddRange(tech.RecipeUnlocks);
}
allTechs = allTechs.Distinct().ToList();
@@ -79,7 +78,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
var allUnlocked = new List();
foreach (var database in EntityQuery())
{
- allUnlocked.AddRange(database.RecipeIds);
+ allUnlocked.AddRange(database.UnlockedRecipes);
}
allUnlocked = allUnlocked.Distinct().ToList();
diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
index 8989df0ec1..d4375f95e1 100644
--- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
+++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
@@ -361,7 +361,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
if (pointValue == 0)
return;
- _research.AddPointsToServer(server.Value, pointValue, serverComponent);
+ _research.ModifyServerPoints(server.Value, pointValue, serverComponent);
_artifact.AdjustConsumedPoints(artifact.Value, pointValue);
_audio.PlayPvs(component.DestroySound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
diff --git a/Content.Shared/Research/Components/ResearchServerComponent.cs b/Content.Shared/Research/Components/ResearchServerComponent.cs
index 220c6ff412..763b5d9059 100644
--- a/Content.Shared/Research/Components/ResearchServerComponent.cs
+++ b/Content.Shared/Research/Components/ResearchServerComponent.cs
@@ -2,73 +2,62 @@ using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Research.Components
+namespace Content.Shared.Research.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ResearchServerComponent : Component
{
- [RegisterComponent, NetworkedComponent]
- public sealed class ResearchServerComponent : Component
- {
- ///
- /// The name of the server
- ///
- [DataField("servername"), ViewVariables(VVAccess.ReadWrite)]
- public string ServerName = "RDSERVER";
-
- ///
- /// The amount of points on the server.
- ///
- [DataField("points"), ViewVariables(VVAccess.ReadWrite)]
- public int Points;
-
- ///
- /// A unique numeric id representing the server
- ///
- [ViewVariables(VVAccess.ReadOnly)]
- public int Id;
-
- ///
- /// Entities connected to the server
- ///
- ///
- /// This is not safe to read clientside
- ///
- [ViewVariables(VVAccess.ReadOnly)]
- public List Clients = new();
-
- [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan NextUpdateTime = TimeSpan.Zero;
-
- [DataField("researchConsoleUpdateTime"), ViewVariables(VVAccess.ReadWrite)]
- public readonly TimeSpan ResearchConsoleUpdateTime = TimeSpan.FromSeconds(1);
- }
-
- [Serializable, NetSerializable]
- public sealed class ResearchServerState : ComponentState
- {
- public string ServerName;
- public int Points;
- public int Id;
- public ResearchServerState(string serverName, int points, int id)
- {
- ServerName = serverName;
- Points = points;
- Id = id;
- }
- }
+ ///
+ /// The name of the server
+ ///
+ [AutoNetworkedField]
+ [DataField("serverName"), ViewVariables(VVAccess.ReadWrite)]
+ public string ServerName = "RDSERVER";
///
- /// Event raised on a server's clients when the point value of the server is changed.
+ /// The amount of points on the server.
///
- ///
- ///
- ///
- [ByRefEvent]
- public readonly record struct ResearchServerPointsChangedEvent(EntityUid Server, int Total, int Delta);
+ [AutoNetworkedField]
+ [DataField("points"), ViewVariables(VVAccess.ReadWrite)]
+ public int Points;
///
- /// Event raised every second to calculate the amount of points added to the server.
+ /// A unique numeric id representing the server
///
- ///
- ///
- [ByRefEvent]
- public record struct ResearchServerGetPointsPerSecondEvent(EntityUid Server, int Points);
+ [AutoNetworkedField]
+ [ViewVariables(VVAccess.ReadOnly)]
+ public int Id;
+
+ ///
+ /// Entities connected to the server
+ ///
+ ///
+ /// This is not safe to read clientside
+ ///
+ [ViewVariables(VVAccess.ReadOnly)]
+ public List Clients = new();
+
+ [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan NextUpdateTime = TimeSpan.Zero;
+
+ [DataField("researchConsoleUpdateTime"), ViewVariables(VVAccess.ReadWrite)]
+ public readonly TimeSpan ResearchConsoleUpdateTime = TimeSpan.FromSeconds(1);
}
+
+///
+/// Event raised on a server's clients when the point value of the server is changed.
+///
+///
+///
+///
+[ByRefEvent]
+public readonly record struct ResearchServerPointsChangedEvent(EntityUid Server, int Total, int Delta);
+
+///
+/// Event raised every second to calculate the amount of points added to the server.
+///
+///
+///
+[ByRefEvent]
+public record struct ResearchServerGetPointsPerSecondEvent(EntityUid Server, int Points);
+
diff --git a/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs b/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs
index a8304e97fb..0e0fbbb4a9 100644
--- a/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs
+++ b/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs
@@ -35,11 +35,9 @@ namespace Content.Shared.Research.Components
public sealed class ResearchConsoleBoundInterfaceState : BoundUserInterfaceState
{
public int Points;
- public int PointsPerSecond;
- public ResearchConsoleBoundInterfaceState(int points, int pointsPerSecond)
+ public ResearchConsoleBoundInterfaceState(int points)
{
Points = points;
- PointsPerSecond = pointsPerSecond;
}
}
}
diff --git a/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs b/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
index 0970d3b077..28999087c6 100644
--- a/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
+++ b/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
@@ -1,48 +1,56 @@
using Content.Shared.Research.Prototypes;
+using Content.Shared.Research.Systems;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-namespace Content.Shared.Research.Components
-{
- [RegisterComponent, NetworkedComponent]
- public sealed class TechnologyDatabaseComponent : Component
- {
- ///
- /// The ids of all the technologies which have been unlocked.
- ///
- [DataField("technologyIds", customTypeSerializer: typeof(PrototypeIdListSerializer))]
- public List TechnologyIds = new();
+namespace Content.Shared.Research.Components;
- ///
- /// The ids of all the lathe recipes which have been unlocked.
- /// This is maintained alongside the TechnologyIds
- ///
- [DataField("recipeIds", customTypeSerializer: typeof(PrototypeIdListSerializer))]
- public List RecipeIds = new();
- }
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedResearchSystem)), AutoGenerateComponentState]
+public sealed partial class TechnologyDatabaseComponent : Component
+{
+ ///
+ /// A main discipline that locks out other discipline technology past a certain tier.
+ ///
+ [AutoNetworkedField]
+ [DataField("mainDiscipline", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string? MainDiscipline;
+
+ [AutoNetworkedField(true)]
+ [DataField("currentTechnologyCards")]
+ public List CurrentTechnologyCards = new();
///
- /// Event raised on the database whenever its
- /// technologies or recipes are modified.
+ /// Which research disciplines are able to be unlocked
///
- ///
- /// This event is forwarded from the
- /// server to all of it's clients.
- ///
- [ByRefEvent]
- public readonly record struct TechnologyDatabaseModifiedEvent;
+ [AutoNetworkedField(true)]
+ [DataField("supportedDisciplines", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List SupportedDisciplines = new();
- [Serializable, NetSerializable]
- public sealed class TechnologyDatabaseState : ComponentState
- {
- public List Technologies;
- public List Recipes;
+ ///
+ /// The ids of all the technologies which have been unlocked.
+ ///
+ [AutoNetworkedField(true)]
+ [DataField("unlockedTechnologies", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List UnlockedTechnologies = new();
- public TechnologyDatabaseState(List technologies, List recipes)
- {
- Technologies = technologies;
- Recipes = recipes;
- }
- }
+ ///
+ /// The ids of all the lathe recipes which have been unlocked.
+ /// This is maintained alongside the TechnologyIds
+ ///
+ /// todo: if you unlock all the recipes in a tech, it doesn't count as unlocking the tech. sadge
+ [AutoNetworkedField(true)]
+ [DataField("unlockedRecipes", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List UnlockedRecipes = new();
}
+
+///
+/// Event raised on the database whenever its
+/// technologies or recipes are modified.
+///
+///
+/// This event is forwarded from the
+/// server to all of it's clients.
+///
+[ByRefEvent]
+public readonly record struct TechnologyDatabaseModifiedEvent;
diff --git a/Content.Shared/Research/Prototypes/TechDisciplinePrototype.cs b/Content.Shared/Research/Prototypes/TechDisciplinePrototype.cs
new file mode 100644
index 0000000000..d5c7ad89af
--- /dev/null
+++ b/Content.Shared/Research/Prototypes/TechDisciplinePrototype.cs
@@ -0,0 +1,48 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Research.Prototypes;
+
+///
+/// This is a prototype for a research discipline, a category
+/// that governs how s are unlocked.
+///
+[Prototype("techDiscipline")]
+public sealed class TechDisciplinePrototype : IPrototype
+{
+ ///
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ ///
+ /// Player-facing name.
+ /// Supports locale strings.
+ ///
+ [DataField("name", required: true)]
+ public readonly string Name = string.Empty;
+
+ ///
+ /// A color used for UI
+ ///
+ [DataField("color", required: true)]
+ public readonly Color Color;
+
+ ///
+ /// An icon used to visually represent the discipline in UI.
+ ///
+ [DataField("icon")]
+ public readonly SpriteSpecifier Icon = default!;
+
+ ///
+ /// For each tier a discipline supports, what percentage
+ /// of the previous tier must be unlocked for it to become available
+ ///
+ [DataField("tierPrerequisites", required: true)]
+ public readonly Dictionary TierPrerequisites = new();
+
+ ///
+ /// Purchasing this tier of technology causes a server to become "locked" to this discipline.
+ ///
+ [DataField("lockoutTier")]
+ public readonly int LockoutTier = 3;
+}
diff --git a/Content.Shared/Research/Prototypes/TechnologyPrototype.cs b/Content.Shared/Research/Prototypes/TechnologyPrototype.cs
index 4c0ca81fda..8b04be7209 100644
--- a/Content.Shared/Research/Prototypes/TechnologyPrototype.cs
+++ b/Content.Shared/Research/Prototypes/TechnologyPrototype.cs
@@ -1,54 +1,92 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
+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
+namespace Content.Shared.Research.Prototypes;
+
+///
+/// This is a prototype for a technology that can be unlocked.
+///
+[Prototype("technology")]
+public sealed class TechnologyPrototype : IPrototype
{
- [NetSerializable, Serializable, Prototype("technology")]
- public sealed class TechnologyPrototype : IPrototype
- {
- ///
- /// The ID of this technology prototype.
- ///
- [ViewVariables]
- [IdDataField]
- public string ID { get; } = default!;
+ ///
+ [IdDataField]
+ public string ID { get; } = default!;
- ///
- /// The name this technology will have on user interfaces.
- ///
- [DataField("name")]
- public string? Name { get; private set; }
+ ///
+ /// The name of the technology.
+ /// Supports locale strings
+ ///
+ [DataField("name", required: true)]
+ public readonly string Name = string.Empty;
- ///
- /// An icon that represent this technology.
- ///
- [DataField("icon")]
- public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid;
+ ///
+ /// An icon used to visually represent the technology in UI.
+ ///
+ [DataField("icon", required: true)]
+ public readonly SpriteSpecifier Icon = default!;
- ///
- /// A short description of the technology.
- ///
- [DataField("description")]
- public string Description { get; private set; } = "";
+ ///
+ /// What research discipline this technology belongs to.
+ ///
+ [DataField("discipline", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public readonly string Discipline = default!;
- ///
- /// The required research points to unlock this technology.
- ///
- [DataField("requiredPoints")]
- public int RequiredPoints { get; }
+ ///
+ /// What tier research is this?
+ /// The tier governs how much lower-tier technology
+ /// needs to be unlocked before this one.
+ ///
+ [DataField("tier", required: true)]
+ public readonly int Tier;
- ///
- /// A list of technology IDs required to unlock this technology.
- ///
- [DataField("requiredTechnologies", customTypeSerializer: typeof(PrototypeIdListSerializer))]
- public List RequiredTechnologies { get; } = new();
+ ///
+ /// Hidden tech is not ever available at the research console.
+ ///
+ [DataField("hidden")]
+ public readonly bool Hidden;
- ///
- /// A list of recipe IDs this technology unlocks.
- ///
- [DataField("unlockedRecipes", customTypeSerializer:typeof(PrototypeIdListSerializer))]
- public List UnlockedRecipes { get; } = new();
- }
+ ///
+ /// How much research is needed to unlock.
+ ///
+ [DataField("cost")]
+ public readonly int Cost = 10000;
+
+ ///
+ /// A list of s that need to be unlocked in order to unlock this technology.
+ ///
+ [DataField("technologyPrerequisites", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public readonly IReadOnlyList TechnologyPrerequisites = new List();
+
+ ///
+ /// A list of s that are unlocked by this technology
+ ///
+ [DataField("recipeUnlocks", customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public readonly IReadOnlyList RecipeUnlocks = new List();
+
+ ///
+ /// A list of non-standard effects that are done when this technology is unlocked.
+ ///
+ [DataField("genericUnlocks")]
+ public readonly IReadOnlyList GenericUnlocks = new List();
+}
+
+[DataDefinition]
+public record struct GenericUnlock()
+{
+ ///
+ /// What event is raised when this is unlocked?
+ /// Used for doing non-standard logic.
+ ///
+ [DataField("purchaseEvent")]
+ public readonly object? PurchaseEvent = null;
+
+ ///
+ /// A player facing tooltip for what the unlock does.
+ /// Supports locale strings.
+ ///
+ [DataField("unlockDescription")]
+ public readonly string UnlockDescription = string.Empty;
}
diff --git a/Content.Shared/Research/Systems/SharedResearchSystem.cs b/Content.Shared/Research/Systems/SharedResearchSystem.cs
index 224535f7ad..fffbad847e 100644
--- a/Content.Shared/Research/Systems/SharedResearchSystem.cs
+++ b/Content.Shared/Research/Systems/SharedResearchSystem.cs
@@ -1,46 +1,145 @@
-using Content.Shared.Research.Components;
+using System.Linq;
+using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
-using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
namespace Content.Shared.Research.Systems;
public abstract class SharedResearchSystem : EntitySystem
{
+ [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnServerGetState);
- SubscribeLocalEvent(OnServerHandleState);
- SubscribeLocalEvent(OnTechnologyGetState);
- SubscribeLocalEvent(OnTechnologyHandleState);
+ SubscribeLocalEvent(OnMapInit);
}
- private void OnServerGetState(EntityUid uid, ResearchServerComponent component, ref ComponentGetState args)
+ private void OnMapInit(EntityUid uid, TechnologyDatabaseComponent component, MapInitEvent args)
{
- args.State = new ResearchServerState(component.ServerName, component.Points, component.Id);
+ UpdateTechnologyCards(uid, component);
}
- private void OnServerHandleState(EntityUid uid, ResearchServerComponent component, ref ComponentHandleState args)
+ public void UpdateTechnologyCards(EntityUid uid, TechnologyDatabaseComponent? component = null)
{
- if (args.Current is not ResearchServerState state)
+ if (!Resolve(uid, ref component))
return;
- component.ServerName = state.ServerName;
- component.Points = state.Points;
- component.Id = state.Id;
+
+ var availableTechnology = GetAvailableTechnologies(uid, component);
+ _random.Shuffle(availableTechnology);
+
+ component.CurrentTechnologyCards.Clear();
+ foreach (var discipline in component.SupportedDisciplines)
+ {
+ var selected = availableTechnology.FirstOrDefault(p => p.Discipline == discipline);
+ if (selected == null)
+ continue;
+
+ component.CurrentTechnologyCards.Add(selected.ID);
+ }
+ Dirty(component);
}
- private void OnTechnologyHandleState(EntityUid uid, TechnologyDatabaseComponent component, ref ComponentHandleState args)
+ public List GetAvailableTechnologies(EntityUid uid, TechnologyDatabaseComponent? component = null)
{
- if (args.Current is not TechnologyDatabaseState state)
- return;
- component.TechnologyIds = new (state.Technologies);
- component.RecipeIds = new(state.Recipes);
+ if (!Resolve(uid, ref component, false))
+ return new List();
+
+ var availableTechnologies = new List();
+ var disciplineTiers = GetDisciplineTiers(component);
+ foreach (var tech in PrototypeManager.EnumeratePrototypes())
+ {
+ if (IsTechnologyAvailable(component, tech, disciplineTiers))
+ availableTechnologies.Add(tech);
+ }
+
+ return availableTechnologies;
}
- private void OnTechnologyGetState(EntityUid uid, TechnologyDatabaseComponent component, ref ComponentGetState args)
+ public bool IsTechnologyAvailable(TechnologyDatabaseComponent component, TechnologyPrototype tech, Dictionary? disciplineTiers = null)
{
- args.State = new TechnologyDatabaseState(component.TechnologyIds, component.RecipeIds);
+ disciplineTiers ??= GetDisciplineTiers(component);
+
+ if (tech.Hidden)
+ return false;
+
+ if (!component.SupportedDisciplines.Contains(tech.Discipline))
+ return false;
+
+ if (tech.Tier > disciplineTiers[tech.Discipline])
+ return false;
+
+ if (component.UnlockedTechnologies.Contains(tech.ID))
+ return false;
+
+ foreach (var prereq in tech.TechnologyPrerequisites)
+ {
+ if (!component.UnlockedTechnologies.Contains(prereq))
+ return false;
+ }
+
+ return true;
+ }
+
+ public Dictionary GetDisciplineTiers(TechnologyDatabaseComponent component)
+ {
+ var tiers = new Dictionary();
+ foreach (var discipline in component.SupportedDisciplines)
+ {
+ tiers.Add(discipline, GetHighestDisciplineTier(component, discipline));
+ }
+
+ return tiers;
+ }
+
+ public int GetHighestDisciplineTier(TechnologyDatabaseComponent component, string disciplineId)
+ {
+ return GetHighestDisciplineTier(component, PrototypeManager.Index(disciplineId));
+ }
+
+ public int GetHighestDisciplineTier(TechnologyDatabaseComponent component, TechDisciplinePrototype techDiscipline)
+ {
+ var allTech = PrototypeManager.EnumeratePrototypes()
+ .Where(p => p.Discipline == techDiscipline.ID && !p.Hidden).ToList();
+ var allUnlocked = new List();
+ foreach (var recipe in component.UnlockedTechnologies)
+ {
+ var proto = PrototypeManager.Index(recipe);
+ if (proto.Discipline != techDiscipline.ID)
+ continue;
+ allUnlocked.Add(proto);
+ }
+
+ var highestTier = techDiscipline.TierPrerequisites.Keys.Max();
+ var tier = 2; //tier 1 is always given
+
+ // todo this might break if you have hidden technologies. i'm not sure
+
+ while (tier <= highestTier)
+ {
+ // we need to get the tech for the tier 1 below because that's
+ // what the percentage in TierPrerequisites is referring to.
+ var unlockedTierTech = allUnlocked.Where(p => p.Tier == tier - 1).ToList();
+ var allTierTech = allTech.Where(p => p.Discipline == techDiscipline.ID && p.Tier == tier - 1).ToList();
+
+ if (allTierTech.Count == 0)
+ break;
+
+ var percent = (float) unlockedTierTech.Count / allTierTech.Count;
+ if (percent < techDiscipline.TierPrerequisites[tier])
+ break;
+
+ if (tier >= techDiscipline.LockoutTier &&
+ component.MainDiscipline != null &&
+ techDiscipline.ID != component.MainDiscipline)
+ break;
+ tier++;
+ }
+
+ return tier - 1;
}
///
@@ -58,26 +157,18 @@ public abstract class SharedResearchSystem : EntitySystem
/// Whether it is unlocked or not
public bool IsTechnologyUnlocked(EntityUid uid, string technologyId, TechnologyDatabaseComponent? component = null)
{
- return Resolve(uid, ref component, false) && component.TechnologyIds.Contains(technologyId);
+ return Resolve(uid, ref component, false) && component.UnlockedTechnologies.Contains(technologyId);
}
- ///
- /// Returns whether or not all the prerequisite
- /// technologies for a technology are unlocked.
- ///
- ///
- ///
- ///
- /// Whether or not the prerequesites are present
- public bool ArePrerequesitesUnlocked(EntityUid uid, TechnologyPrototype prototype, TechnologyDatabaseComponent? component = null)
+ public void TrySetMainDiscipline(TechnologyPrototype prototype, EntityUid uid, TechnologyDatabaseComponent? component = null)
{
if (!Resolve(uid, ref component))
- return false;
- foreach (var technologyId in prototype.RequiredTechnologies)
- {
- if (!IsTechnologyUnlocked(uid, technologyId, component))
- return false;
- }
- return true;
+ return;
+
+ var discipline = PrototypeManager.Index(prototype.Discipline);
+ if (prototype.Tier < discipline.LockoutTier)
+ return;
+ component.MainDiscipline = prototype.Discipline;
+ Dirty(component);
}
}
diff --git a/Resources/Locale/en-US/prototypes/catalog/research/technologies.ftl b/Resources/Locale/en-US/prototypes/catalog/research/technologies.ftl
deleted file mode 100644
index 87b837181b..0000000000
--- a/Resources/Locale/en-US/prototypes/catalog/research/technologies.ftl
+++ /dev/null
@@ -1,105 +0,0 @@
-technologies-basic-research-technology = Basic research technology
-technologies-basic-research-technology-description = Nanotrasen basic research technologies.
-
-technologies-cleaning-technology = Cleaning technology
-technologies-cleaning-technology-description = Start to a shiny clean station.
-
-technologies-advanced-cleaning-technology = Advanced cleaning technology
-technologies-advanced-cleaning-technology-description = Advanced tools won't stop people from trashing the station, sadly.
-
-technologies-advanced-spray-technology = Advanced spray technology
-technologies-advanced-spray-technology-description = The newest ways to hose down the station. Filthy animals.
-
-technologies-foodbev-technology = Food and beverage technology
-technologies-food-and-beverage-technology-description = Robust service from better technology.
-
-technologies-biological-technology = Biological technology
-technologies-biological-technology-description = Investigations into the natural world.
-
-technologies-advanced-botany = Advanced botany
-technologies-advanced-botany-description = A better understanding of botany.
-
-technologies-virology = Virology
-technologies-virology-description = The secrets of the immune system.
-
-technologies-advanced-surgery = Advanced surgery
-technologies-advanced-surgery-description = Research new surgical procedures.
-
-technologies-chemistry-technology = Chemistry technology
-technologies-chemistry-technology-description = A crash course in chemistry.
-
-technologies-medical-machinery = Medical machinery
-technologies-medical-machinery-description = Machines any self-respecting medbay would need.
-
-technologies-advanced-life-support = Advanced life support systems
-technologies-advanced-life-support-description = The cutting edge of life and death.
-
-technologies-salvage-equipment = Salvage equipment
-technologies-salvage-equipment-description = Newer and faster resource collection.
-
-technologies-spacefaring = Spacefaring technology
-technologies-spacefaring-description = Able to bring you into the stars!
-
-technologies-surveillance = Surveillance technology
-technologies-surveillance-description = Retro-styled cameras straight from the year 1984!
-
-technologies-industrial-engineering = Industrial engineering
-technologies-industrial-engineering-description = A refresher course on modern engineering technology.
-
-technologies-rapid-upgrade = Rapid upgrade
-technologies-rapid-upgrade-description = The ability to quickly improve the station like never before.
-
-technologies-material-sheet-printing = Material sheet printing
-technologies-material-sheet-printing-description = Print those sheets!
-
-technologies-electromagnetic-theory = Electromagnetic theory
-technologies-electromagnetic-theory-description = Try not to fry yourself.
-
-technologies-electrical-engineering = Electrical engineering
-technologies-electrical-engineering-description = Machinery used to keep the station stable.
-
-technologies-advanced-atmospherics-technology = Advanced atmospherics technology
-technologies-advanced-atmospherics-technology-description = As if it can get more advanced.
-
-technologies-compact-power-technology = Compact power technology
-technologies-compact-power-technology-description = Power, but smaller.
-
-technologies-applied-musicology = Applied musicology
-technologies-applied-musicology-description = Bringing you the latest in audio-audio technology.
-
-technologies-basic-powercell-printing = Basic powercell printing
-technologies-basic-powercell-printing-description = Print standard powercells.
-
-technologies-advanced-powercell-printing = Advanced powercell printing
-technologies-advanced-powercell-printing-description = Print advanced powercells.
-
-technologies-super-powercell-printing = Super powercell printing
-technologies-super-powercell-printing-description = Print super powercells.
-
-technologies-scientific-technology = Scientific technology
-technologies-scientific-technology-description = The basics of a research team's supplies.
-
-technologies-anomaly-technology = Anomaly technology
-technologies-anomaly-technology-description = Machines for advanced anomaly containment.
-
-technologies-robotics-technology = Robotics technology
-technologies-robotics-technology-description = Parts needed for constructing mechanized friends.
-
-technologies-archaeology = Archeological equipment
-technologies-archaeology-description = Advanced equipment for uncovering the secrets of artifacts.
-
-technologies-ripley-technology = Exosuit: Ripley
-technologies-ripley-technology-description = The latest and greatest in mechanized cargo construction.
-
-technologies-clown-technology = Exosuit: H.O.N.K.
-technologies-clown-technology-description = Honk?!
-
-technologies-adv-parts-technology-description = Like the previous ones, but better!
-technologies-adv-parts-technology = Advanced parts technology
-
-technologies-super-parts-technology = Super parts technology
-technologies-super-parts-technology-description = New heights of machine performance.
-
-technologies-magboots-technology = Magboots technology
-technologies-magboots-technology-description = Magboots for a space escape from the assistant.
-
diff --git a/Resources/Locale/en-US/research/components/research-console-component.ftl b/Resources/Locale/en-US/research/components/research-console-component.ftl
index 6ea90313b2..bc53ad5d9d 100644
--- a/Resources/Locale/en-US/research/components/research-console-component.ftl
+++ b/Resources/Locale/en-US/research/components/research-console-component.ftl
@@ -1,13 +1,16 @@
## UI
research-console-menu-title = R&D Console
-research-console-menu-unlocked-technologies-label = Unlocked technologies
-research-console-menu-unlockable-technologies-label = Unlockable technologies
-research-console-menu-future-technologies-label = Future technologies
-research-console-menu-research-points-text = Research Points: {$points}
-research-console-menu-points-per-second-text = Points per Second {$pointsPerSecond}
+research-console-menu-research-points-text = Research: [color=orchid]{$points}[/color]
+research-console-menu-main-discipline = Main Discipline: [color={$color}]{$name}[/color]
research-console-menu-server-selection-button = Server list
research-console-menu-server-sync-button = Sync
-research-console-menu-server-unlock-button = Unlock
-research-console-tech-requirements-none = No technology requirements.
-research-console-tech-requirements-prototype-name = Requires: {$prototypeName}
+research-console-menu-server-research-button = Research
+research-console-available-text = Researchable Technologies
+research-console-unlocked-text = Unlocked Technologies
+research-console-tier-discipline-info = Tier {$tier}, [color={$color}]{$discipline}[/color]
+research-console-tier-info-small = : Tier {$tier}
+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]
diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl
new file mode 100644
index 0000000000..e6c9ae3102
--- /dev/null
+++ b/Resources/Locale/en-US/research/technologies.ftl
@@ -0,0 +1,51 @@
+research-discipline-none = None
+research-discipline-industrial = Industrial
+research-discipline-biochemical = Biochemical
+research-discipline-experimental = Experimental
+research-discipline-civilian-services = Civilian Services
+
+research-technology-salvage-equipment = Salvage Equipment
+research-technology-advanced-powercells = Advanced Powercells
+research-technology-compact-power = Compact Power
+research-technology-industrial-engineering = Industrial Engineering
+research-technology-power-generation = Power Generation
+research-technology-atmospheric-tech = Atmospherics
+research-technology-rapid-construction = Rapid Construction
+research-technology-shuttlecraft = Shuttlecraft
+research-technology-ripley-aplu = Ripley APLU
+research-technology-advanced-atmospherics = Advanced Atmospherics
+research-technology-super-powercells = Super Powercells
+research-technology-bluespace-storage = Bluespace Storage
+
+research-technology-chemistry = Chemistry
+research-technology-surgical-tools = Surgical Tools
+research-technology-biochemical-stasis = Biochemical Stasis
+research-technology-virology = Virology
+research-technology-cryogenics = Cryogenics
+research-technology-chemical-dispensary = Chemical Dispensary
+research-technology-crew-monitoring = Crew Monitoring
+research-technology-cloning = Cloning
+
+research-technology-basic-robotics = Basic Robotics
+research-technology-signalling-tech = Signalling Tech
+research-technology-basic-anomalous-research = Basic Anomalous Research
+research-technology-basic-xenoarcheology = Basic XenoArcheology
+research-technology-alternative-research = Alternative Research
+research-technology-magnets-tech = Localized Magnetism
+research-technology-advanced-parts = Advanced Parts
+research-technology-abnormal-artifact-manipulation = Abnormal Artifact Manipulation
+research-technology-mobile-anomaly-tech = Mobile Anomaly Tech
+research-technology-rped = Rapid Part Exchange
+research-technology-super-parts = Super Parts
+
+research-technology-janitorial-equipment = Janitorial Equipment
+research-technology-laundry-tech = Laundry Tech
+research-technology-basic-hydroponics = Basic Hydroponics
+research-technology-food-service = Food Service
+research-technology-advanced-entertainment = Advanced Entertainment
+research-technology-audio-visual-communication = A/V Communication
+research-technology-advanced-cleaning = Advanced Cleaning
+research-technology-meat-manipulation = Meat Manipulation
+research-technology-honk-mech = H.O.N.K. Mech
+research-technology-advanced-spray = Advanced Spray
+
diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml
deleted file mode 100644
index 69ce2a684f..0000000000
--- a/Resources/Prototypes/Catalog/Research/technologies.yml
+++ /dev/null
@@ -1,655 +0,0 @@
-# In order to make this list somewhat organized, please place
-# new technologies underneath their overarching "base" technology.
-
-# Base Technology
-
-- type: technology
- name: technologies-basic-research-technology
- id: BasicResearch
- description: technologies-basic-research-technology-description
- icon:
- sprite: Structures/Machines/server.rsi
- state: server-on
- requiredPoints: 2500
-
-# Cleaning Technology Tree
-
-- type: technology
- name: technologies-cleaning-technology
- id: CleaningTechnology
- description: technologies-cleaning-technology-description
- icon:
- sprite: Objects/Specific/Janitorial/janitorial.rsi
- state: mopbucket
- requiredPoints: 5000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - Bucket
- - MopItem
- - SprayBottle
-
-- type: technology
- name: technologies-advanced-cleaning-technology
- id: AdvancedCleaningTechnology
- description: technologies-advanced-cleaning-technology-description
- icon:
- sprite: Objects/Specific/Janitorial/advmop.rsi
- state: advmop
- requiredPoints: 5000
- requiredTechnologies:
- - CleaningTechnology
- unlockedRecipes:
- - AdvMopItem
- - MegaSprayBottle
-
-- type: technology
- id: AdvancedSprayTechnology
- name: technologies-advanced-spray-technology
- description: technologies-advanced-spray-technology-description
- icon:
- sprite: Objects/Weapons/Guns/Basic/spraynozzle.rsi
- state: icon
- requiredPoints: 7500
- requiredTechnologies:
- - AdvancedCleaningTechnology
- unlockedRecipes:
- - WeaponSprayNozzle
- - ClothingBackpackWaterTank
-
-# Food/Bev Service Technology Tree
-
-- type: technology
- name: technologies-foodbev-technology
- id: FoodBevTechnology
- description: technologies-food-and-beverage-technology-description
- icon:
- sprite: Objects/Weapons/Melee/cleaver.rsi
- state: butch
- requiredPoints: 5000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - ButchCleaver
- - KitchenKnife
- - MicrowaveMachineCircuitboard
- - BoozeDispenserMachineCircuitboard
- - SodaDispenserMachineCircuitboard
- - FatExtractorMachineCircuitboard
-
-# Biological Technology Tree
-
-- type: technology
- name: technologies-biological-technology
- id: BiologicalTechnology
- description: technologies-biological-technology-description
- icon:
- sprite: Structures/Furniture/potted_plants.rsi
- state: applebush
- requiredPoints: 10000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - HydroponicsToolScythe
- - HydroponicsToolHatchet
- - Shovel
- - MiniHoe
- - Spade
- - Clippers
- - ButchCleaver
- - KitchenKnife
- - MicrowaveMachineCircuitboard
-
-- type: technology
- name: technologies-advanced-botany
- id: AdvancedBotany
- description: technologies-advanced-botany-description
- icon:
- sprite: Objects/Specific/Hydroponics/potato.rsi
- state: seed
- requiredPoints: 15000
- requiredTechnologies:
- - BiologicalTechnology
- unlockedRecipes:
- - SeedExtractorMachineCircuitboard
- - HydroponicsTrayMachineCircuitboard
- - Vape
-
-- type: technology
- name: technologies-virology
- id: Virology
- description: technologies-virology-description
- icon:
- sprite: Clothing/Mask/sterile.rsi
- state: icon
- requiredPoints: 10000
- requiredTechnologies:
- - BiologicalTechnology
- unlockedRecipes:
- - VaccinatorMachineCircuitboard
- - DiagnoserMachineCircuitboard
-
-# Medical Technology Tree
-
-- type: technology
- name: technologies-chemistry-technology
- id: ChemistryTechnology
- description: technologies-chemistry-technology-description
- icon:
- sprite: Objects/Specific/Chemistry/beaker_large.rsi
- state: beakerlarge
- requiredPoints: 10000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - Beaker
- - LargeBeaker
- - CryostasisBeaker
- - Dropper
- - Syringe
- - ReagentGrinderMachineCircuitboard
- - HotplateMachineCircuitboard
- - PillCanister
- - ChemistryEmptyBottle01
- - ChemicalPayload
-
-- type: technology
- name: technologies-advanced-surgery
- id: AdvancedSugery
- description: technologies-advanced-surgery-description
- icon:
- sprite: Objects/Specific/Medical/Surgery/saw.rsi
- state: saw
- requiredPoints: 7500
- requiredTechnologies:
- - BiologicalTechnology
- unlockedRecipes:
- - Scalpel
- - Retractor
- - Cautery
- - Drill
- - Saw
- - Hemostat
-
-- type: technology
- name: technologies-medical-machinery
- id: MedicalMachinery
- description: technologies-medical-machinery-description
- icon:
- sprite: Structures/dispensers.rsi
- state: industrial-working
- requiredPoints: 10000
- requiredTechnologies:
- - BiologicalTechnology
- - ChemistryTechnology
- unlockedRecipes:
- - ChemMasterMachineCircuitboard
- - ChemDispenserMachineCircuitboard
- - HandheldCrewMonitor
- - BiomassReclaimerMachineCircuitboard
-
-- type: technology
- name: technologies-advanced-life-support
- id: AdvancedLifeSupport
- description: technologies-advanced-life-support-description
- icon:
- sprite: Structures/Machines/cloning.rsi
- state: pod_0
- requiredPoints: 20000
- requiredTechnologies:
- - MedicalMachinery
- - Virology
- unlockedRecipes:
- - CloningPodMachineCircuitboard
- - MedicalScannerMachineCircuitboard
- - StasisBedMachineCircuitboard
- - CloningConsoleComputerCircuitboard
- - CryoPodMachineCircuitboard
-
-# Security Technology Tree
-
-- type: technology
- name: technologies-surveillance
- id: SurveillanceTechnology
- description: technologies-surveillance-description
- icon:
- sprite: Structures/Wallmounts/camera.rsi
- state: cameracase
- requiredPoints: 12500
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - SurveillanceCameraRouterCircuitboard
- - SurveillanceCameraWirelessRouterCircuitboard
- - SurveillanceWirelessCameraMovableCircuitboard
- - SurveillanceWirelessCameraAnchoredCircuitboard
- - SurveillanceCameraMonitorCircuitboard
- - SurveillanceWirelessCameraMonitorCircuitboard
- - ComputerTelevisionCircuitboard
-
-#- type: technology
-# name: "ballistic technology"
-# id: BallisticTechnology
-# description: Just a fancy term for guns.
-# icon:
-# sprite: Objects/Weapons/Guns/Pistols/mk58.rsi
-# state: icon
-# requiredPoints: 15000
-# requiredTechnologies:
-# - SecurityTechnology
-#
-# - type: technology
-# name: "direct energy technology"
-# id: DirectEnergyTechnology
-# description: Basically laser guns.
-# icon:
-# sprite: Objects/Weapons/Guns/Battery/taser.rsi
-# state: icon
-# requiredPoints: 15000
-# requiredTechnologies:
-# - SecurityTechnology
-#
-# - type: technology
-# name: "explosives technology"
-# id: ExplosivesTechnology
-# description: Let's just start with grenades for now.
-# icon:
-# sprite: Objects/Weapons/Grenades/flashbang.rsi
-# state: icon
-# requiredPoints: 15000
-# requiredTechnologies:
-# - SecurityTechnology
-#
-# - type: technology
-# name: "armor technology"
-# id: ArmorTechnology
-# description: Basic protective gear for security personnel.
-# icon:
-# sprite: Clothing/OuterClothing/Vests/kevlar.rsi
-# state: icon
-# requiredPoints: 15000
-# requiredTechnologies:
-# - SecurityTechnology
-
-# Salvage Technology Tree
-
-- type: technology
- name: technologies-salvage-equipment
- id: SalvageEquipment
- description: technologies-salvage-equipment-description
- icon:
- sprite: Objects/Tools/handdrill.rsi
- state: handdrill
- requiredPoints: 7500
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - AppraisalTool
- - MiningDrill
- - OreProcessorMachineCircuitboard
-
-- type: technology
- name: technologies-spacefaring
- id: SpacefaringTechnology
- description: technologies-spacefaring-description
- icon:
- sprite: Structures/Shuttles/gyroscope.rsi
- state: base
- requiredPoints: 10000
- requiredTechnologies:
- - SalvageEquipment
- - IndustrialEngineering
- unlockedRecipes:
- - ShuttleConsoleCircuitboard
- - RadarConsoleCircuitboard
- - ThrusterMachineCircuitboard
- - GyroscopeMachineCircuitboard
-
-- type: technology
- name: technologies-ripley-technology
- id: RipleyTechnology
- description: technologies-ripley-technology-description
- icon:
- sprite: Objects/Specific/Mech/mecha.rsi
- state: ripley
- requiredPoints: 30000
- requiredTechnologies:
- - SalvageEquipment
- unlockedRecipes:
- - RipleyCentralElectronics
- - RipleyPeripheralsElectronics
- - MechEquipmentGrabber
- - RipleyHarness
- - RipleyLArm
- - RipleyRArm
- - RipleyLLeg
- - RipleyRLeg
-
-- type: technology
- name: technologies-clown-technology
- id: ClownTechnology
- description: technologies-clown-technology-description
- icon:
- sprite: Objects/Specific/Mech/mecha.rsi
- state: honker
- requiredPoints: 15000
- requiredTechnologies:
- - RipleyTechnology
- unlockedRecipes:
- - HonkerCentralElectronics
- - HonkerPeripheralsElectronics
- - HonkerTargetingElectronics
- - MechEquipmentHorn
- - HonkerHarness
- - HonkerLArm
- - HonkerRArm
- - HonkerLLeg
- - HonkerRLeg
-
-# Industrial Engineering Technology Tree
-
-- type: technology
- name: technologies-industrial-engineering
- id: IndustrialEngineering
- description: technologies-industrial-engineering-description
- icon:
- sprite: Structures/Machines/protolathe.rsi
- state: icon
- requiredPoints: 10000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - IntercomElectronics
- - ConveyorBeltAssembly
- - FlashlightLantern
- - FireExtinguisher
- - FirelockElectronics
- - DoorElectronics
- - AutolatheMachineCircuitboard
- - ProtolatheMachineCircuitboard
- - CircuitImprinterMachineCircuitboard
- - UniformPrinterMachineCircuitboard
- - AirAlarmElectronics
- - FireAlarmElectronics
- - MailingUnitElectronics
- - SignalTimerElectronics
-
-- type: technology
- name: technologies-material-sheet-printing
- id: Sheets
- description: technologies-material-sheet-printing-description
- icon: Objects/Materials/Sheets/researchicon.png
- requiredPoints: 5000
- requiredTechnologies:
- - IndustrialEngineering
- unlockedRecipes:
- - SheetSteel
- - SheetPlastic
- - SheetRGlass
- - SheetGlass1
- - MaterialReclaimerMachineCircuitboard
-
-# Electromagnetic Theory Technology Tree
-
-- type: technology
- name: technologies-electromagnetic-theory
- id: ElectromagneticTheory
- description: technologies-electromagnetic-theory-description
- icon:
- sprite: Structures/Power/apc.rsi
- state: base
- requiredPoints: 10000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - CableStack
- - CableMVStack
- - CableHVStack
- - LightBulb
- - LightTube
- - APCElectronics
- - SubstationMachineCircuitboard
- - Signaller
- - SignalTrigger
- - VoiceTrigger
- - TimerTrigger
- - TelecomServerCircuitboard
-
-- type: technology
- name: technologies-electrical-engineering
- id: ElectricalEngineering
- description: technologies-electrical-engineering-description
- icon:
- sprite: Structures/Power/Generation/Singularity/emitter.rsi
- state: emitter1
- requiredPoints: 15000
- requiredTechnologies:
- - ElectromagneticTheory
- - IndustrialEngineering
- unlockedRecipes:
- - PowerDrill
- - SMESMachineCircuitboard
- - PowerComputerCircuitboard
- - GeneratorPlasmaMachineCircuitboard
- - GeneratorUraniumMachineCircuitboard
- - SolarControlComputerCircuitboard
- - EmitterCircuitboard
-
-- type: technology
- name: technologies-advanced-atmospherics-technology
- id: AdvancedAtmosTechnology
- description: technologies-advanced-atmospherics-technology-description
- icon:
- sprite: Structures/Piping/Atmospherics/thermomachine.rsi
- state: freezerOff
- requiredPoints: 7500
- requiredTechnologies:
- - ElectricalEngineering
- unlockedRecipes:
- - HolofanProjector
- - ThermomachineFreezerMachineCircuitBoard
- - PortableScrubberMachineCircuitBoard
- - GasRecyclerMachineCircuitboard
-
-- type: technology
- name: technologies-rapid-upgrade
- id: RapidUpgrade
- description: technologies-rapid-upgrade-description
- icon:
- sprite: Objects/Specific/Research/rped.rsi
- state: icon
- requiredPoints: 10000
- requiredTechnologies:
- - ElectricalEngineering
- unlockedRecipes:
- - RCD
- - RCDAmmo
- - RPED
-
-- type: technology
- name: technologies-compact-power-technology
- id: CompactPowerTechnology
- description: technologies-compact-power-technology-description
- icon:
- sprite: Structures/Power/Generation/wallmount_generator.rsi
- state: panel
- requiredPoints: 5000
- requiredTechnologies:
- - ElectromagneticTheory
- unlockedRecipes:
- - WallmountSubstationElectronics
- - WallmountGeneratorElectronics
- - WallmountGeneratorAPUElectronics
-
-- type: technology
- name: technologies-basic-powercell-printing
- id: PowerCellBasic
- description: technologies-basic-powercell-printing-description
- icon:
- sprite: Objects/Power/power_cells.rsi
- state: small
- requiredPoints: 2500
- requiredTechnologies:
- - ElectromagneticTheory
- unlockedRecipes:
- - PowerCellSmall
-
-- type: technology
- name: technologies-advanced-powercell-printing
- id: PowerCellAdvanced
- description: technologies-advanced-powercell-printing-description
- icon:
- sprite: Objects/Power/power_cells.rsi
- state: medium
- requiredPoints: 5000
- requiredTechnologies:
- - PowerCellBasic
- unlockedRecipes:
- - PowerCellMedium
-
-- type: technology
- name: technologies-super-powercell-printing
- id: PowerCellSuper
- description: technologies-super-powercell-printing-description
- icon:
- sprite: Objects/Power/power_cells.rsi
- state: high
- requiredPoints: 7500
- requiredTechnologies:
- - PowerCellAdvanced
- unlockedRecipes:
- - PowerCellHigh
-
-# Entertainment Technology Tree
-
-- type: technology
- name: technologies-applied-musicology
- id: AppliedMusicology
- description: technologies-applied-musicology-description
- icon:
- sprite: Objects/Fun/Instruments/h_synthesizer.rsi
- state: icon
- requiredPoints: 5000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - SynthesizerInstrument
- - DawInstrumentMachineCircuitboard
-
-# Science Technology Tree
-
-- type: technology
- name: technologies-scientific-technology
- id: ScientificTechnology
- description: technologies-scientific-technology-description
- icon:
- sprite: Objects/Misc/stock_parts.rsi
- state: micro_mani
- requiredPoints: 5000
- requiredTechnologies:
- - BasicResearch
- unlockedRecipes:
- - TechDiskComputerCircuitboard
- - CapacitorStockPart
- - MatterBinStockPart
- - MicroManipulatorStockPart
- - NodeScanner
- - AnomalyScanner
- - AnomalyLocator
-
-- type: technology
- name: technologies-anomaly-technology
- id: AnomalyTechnology
- description: technologies-anomaly-technology-description
- icon:
- sprite: Structures/Machines/Anomaly/ape.rsi
- state: base
- requiredPoints: 10000
- requiredTechnologies:
- - ScientificTechnology
- unlockedRecipes:
- - AnomalyVesselCircuitboard
- - APECircuitboard
- - WeaponPistolCHIMP
- - CartridgeAnomalousParticleDelta
- - CartridgeAnomalousParticleEpsilon
- - CartridgeAnomalousParticleZeta
-
-- type: technology
- name: technologies-robotics-technology
- id: RoboticsTechnology
- description: technologies-robotics-technology-description
- icon:
- sprite: Mobs/Silicon/Bots/honkbot.rsi
- state: honkbot
- requiredPoints: 7500
- requiredTechnologies:
- - ScientificTechnology
- unlockedRecipes:
- - ProximitySensor
- - LeftArmBorg
- - LightHeadBorg
- - RightArmBorg
- - LeftLegBorg
- - RightLegBorg
- - Drone
- - ExosuitFabricatorMachineCircuitboard
-
-- type: technology
- name: technologies-archaeology
- id: ArchaeologicalEquipment
- description: technologies-archaeology-description
- icon:
- sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
- state: ano01
- requiredPoints: 10000
- requiredTechnologies:
- - ScientificTechnology
- unlockedRecipes:
- - AnalysisComputerCircuitboard
- - ArtifactAnalyzerMachineCircuitboard
- - TraversalDistorterMachineCircuitboard
-
-- type: technology
- name: technologies-adv-parts-technology
- id: AdvancedPartsTechnology
- description: technologies-adv-parts-technology-description
- icon:
- sprite: Objects/Misc/stock_parts.rsi
- state: adv_capacitor
- requiredPoints: 15000
- requiredTechnologies:
- - ScientificTechnology
- unlockedRecipes:
- - AdvancedCapacitorStockPart
- - AdvancedMatterBinStockPart
- - NanoManipulatorStockPart
-
-- type: technology
- name: technologies-magboots-technology
- id: MagbootsTechnology
- description: technologies-magboots-technology-description
- icon:
- sprite: Clothing/Shoes/Boots/magboots.rsi
- state: icon
- requiredPoints: 8500
- requiredTechnologies:
- - ScientificTechnology
- unlockedRecipes:
- - ClothingShoesBootsMag
-
-- type: technology
- name: technologies-super-parts-technology
- id: SuperPartsTechnology
- description: technologies-super-parts-technology-description
- icon:
- sprite: Objects/Misc/stock_parts.rsi
- state: super_capacitor
- requiredPoints: 20000
- requiredTechnologies:
- - AdvancedPartsTechnology
- - CompactPowerTechnology
- unlockedRecipes:
- - SuperCapacitorStockPart
- - SuperMatterBinStockPart
- - PicoManipulatorStockPart
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 23643b593f..50e847f54d 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -76,12 +76,37 @@
- Multitool
- NetworkConfigurator
- AirlockPainter
+ - FlashlightLantern
- CableStack
+ - CableMVStack
+ - CableHVStack
- HandheldGPSBasic
- TRayScanner
- GasAnalyzer
- UtilityBelt
- Pickaxe
+ - AppraisalTool
+ - SheetRGlass
+ - Beaker
+ - Syringe
+ - LightTube
+ - LightBulb
+ - Bucket
+ - SprayBottle
+ - PowerCellSmall
+ - MicroManipulatorStockPart
+ - MatterBinStockPart
+ - CapacitorStockPart
+ - ConveyorBeltAssembly
+ - IntercomElectronics
+ - FirelockElectronics
+ - DoorElectronics
+ - AirAlarmElectronics
+ - FireAlarmElectronics
+ - MailingUnitElectronics
+ - APCElectronics
+ - SMESMachineCircuitboard
+ - SubstationMachineCircuitboard
- type: StaticPrice
price: 800
@@ -159,29 +184,12 @@
idleState: icon
runningState: building
dynamicRecipes:
- - LightTube
- - LightBulb
- - SheetSteel #these sheet recipe costs don't scale with upgrades
- - SheetGlass1
- - SheetRGlass
- - SheetPlastic
- - CableStack
- - CableMVStack
- - CableHVStack
- PowerDrill
- MiningDrill
- - ConveyorBeltAssembly
- - AppraisalTool
- AnomalyScanner
- AnomalyLocator
- RCD
- RCDAmmo
- - HydroponicsToolScythe
- - HydroponicsToolHatchet
- - Clippers
- - MiniHoe
- - Shovel
- - Spade
- HandheldCrewMonitor
- Scalpel
- Retractor
@@ -198,42 +206,28 @@
- PillCanister
- ChemistryEmptyBottle01
- Drone
- - Flash
- - MicroManipulatorStockPart
- - MatterBinStockPart
- - CapacitorStockPart
- AdvancedCapacitorStockPart
- AdvancedMatterBinStockPart
- NanoManipulatorStockPart
- SuperCapacitorStockPart
- SuperMatterBinStockPart
- PicoManipulatorStockPart
- - FirelockElectronics
- - DoorElectronics
- - APCElectronics
- - AirAlarmElectronics
- - FireAlarmElectronics
- - IntercomElectronics
- - MailingUnitElectronics
- SignalTimerElectronics
- - Bucket
- MopItem
+ - Holoprojector
+ - Mousetrap
+ - LightReplacer
+ - TrashBag
- AdvMopItem
- WeaponSprayNozzle
- ClothingBackpackWaterTank
- - SprayBottle
- MegaSprayBottle
- - FireExtinguisher
- - KitchenKnife
- - ButchCleaver
- - FlashlightLantern
- TimerTrigger
- ChemicalPayload
- FlashPayload
- Signaller
- SignalTrigger
- VoiceTrigger
- - PowerCellSmall
- PowerCellMedium
- PowerCellHigh
- WeaponPistolCHIMP
@@ -245,7 +239,9 @@
- ClothingShoesBootsMag
- NodeScanner
- HolofanProjector
- - Vape
+ - ClothingBackpackHolding
+ - ClothingBackpackSatchelHolding
+ - ClothingBackpackDuffelHolding
- type: entity
parent: Protolathe
@@ -271,16 +267,7 @@
idleState: icon
runningState: building
dynamicRecipes:
- - FirelockElectronics
- - DoorElectronics
- - APCElectronics
- - AirAlarmElectronics
- - FireAlarmElectronics
- - MailingUnitElectronics
- - IntercomElectronics
- SignalTimerElectronics
- - SMESMachineCircuitboard
- - SubstationMachineCircuitboard
- ThermomachineFreezerMachineCircuitBoard
- PortableScrubberMachineCircuitBoard
- CloningPodMachineCircuitboard
@@ -492,16 +479,7 @@
- ClothingHandsGlovesLatex
- ClothingMaskSterile
- DiseaseSwab
- - Scalpel
- - Retractor
- - Cautery
- - Drill
- - Saw
- - Hemostat
- Beaker
- - LargeBeaker
- - CryostasisBeaker
- - Dropper
- Syringe
- Implanter
- PillCanister
@@ -518,6 +496,15 @@
dynamicRecipes:
- HandheldCrewMonitor
- ClothingHandsGlovesNitrile
+ - CryostasisBeaker
+ - LargeBeaker
+ - Dropper
+ - Scalpel
+ - Retractor
+ - Cautery
+ - Drill
+ - Saw
+ - Hemostat
- type: Machine
board: MedicalTechFabCircuitboard
diff --git a/Resources/Prototypes/Entities/Structures/Machines/research.yml b/Resources/Prototypes/Entities/Structures/Machines/research.yml
index 885e57c4ef..8f891b4001 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/research.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/research.yml
@@ -9,6 +9,11 @@
state: server
- type: ResearchServer
- type: TechnologyDatabase
+ supportedDisciplines:
+ - Industrial
+ - Biochemical
+ - Experimental
+ - CivilianServices
- type: ApcPowerReceiver
powerLoad: 200
priority: Low
diff --git a/Resources/Prototypes/Recipes/Lathes/devices.yml b/Resources/Prototypes/Recipes/Lathes/devices.yml
index 0fbfd7ccb4..24127c16bb 100644
--- a/Resources/Prototypes/Recipes/Lathes/devices.yml
+++ b/Resources/Prototypes/Recipes/Lathes/devices.yml
@@ -71,4 +71,31 @@
completetime: 5
materials:
Steel: 500
- Glass: 400
\ No newline at end of file
+ Glass: 400
+
+- type: latheRecipe
+ id: ClothingBackpackHolding
+ result: ClothingBackpackHolding
+ completetime: 5
+ materials:
+ Steel: 1500
+ Plastic: 750
+ Plasma: 1000
+
+- type: latheRecipe
+ id: ClothingBackpackSatchelHolding
+ result: ClothingBackpackSatchelHolding
+ completetime: 5
+ materials:
+ Steel: 1500
+ Plastic: 750
+ Plasma: 1000
+
+- type: latheRecipe
+ id: ClothingBackpackDuffelHolding
+ result: ClothingBackpackDuffelHolding
+ completetime: 5
+ materials:
+ Steel: 1500
+ Plastic: 750
+ Plasma: 1000
diff --git a/Resources/Prototypes/Recipes/Lathes/janitorial.yml b/Resources/Prototypes/Recipes/Lathes/janitorial.yml
index b1cd8db385..902569e4b0 100644
--- a/Resources/Prototypes/Recipes/Lathes/janitorial.yml
+++ b/Resources/Prototypes/Recipes/Lathes/janitorial.yml
@@ -56,6 +56,22 @@
Steel: 100
Glass: 500
+- type: latheRecipe
+ id: Mousetrap
+ result: Mousetrap
+ completetime: 1
+ materials:
+ Wood: 100
+ Steel: 50
+
+- type: latheRecipe
+ id: Holoprojector
+ result: Holoprojector
+ completetime: 3
+ materials:
+ Plastic: 250
+ Glass: 150
+
- type: latheRecipe
id: AdvMopItem
result: AdvMopItem
diff --git a/Resources/Prototypes/Research/biochemical.yml b/Resources/Prototypes/Research/biochemical.yml
new file mode 100644
index 0000000000..938ed8b065
--- /dev/null
+++ b/Resources/Prototypes/Research/biochemical.yml
@@ -0,0 +1,117 @@
+# Tier 1
+
+- type: technology
+ id: Chemistry
+ name: research-technology-chemistry
+ icon:
+ sprite: Objects/Specific/Chemistry/beaker_large.rsi
+ state: beakerlarge
+ discipline: Biochemical
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - LargeBeaker
+ - Dropper
+ - HotplateMachineCircuitboard
+ - ChemicalPayload
+
+- type: technology
+ id: SurgicalTools
+ name: research-technology-surgical-tools
+ icon:
+ sprite: Objects/Specific/Medical/Surgery/saw.rsi
+ state: saw
+ discipline: Biochemical
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - Scalpel
+ - Retractor
+ - Cautery
+ - Drill
+ - Saw
+ - Hemostat
+
+- type: technology
+ id: BiochemicalStasis
+ name: research-technology-biochemical-stasis
+ icon:
+ sprite: Structures/Machines/stasis_bed.rsi
+ state: icon
+ discipline: Biochemical
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - CryostasisBeaker
+ - StasisBedMachineCircuitboard
+
+- type: technology
+ id: Virology
+ name: research-technology-virology
+ icon:
+ sprite: Structures/Machines/diagnoser.rsi
+ state: icon
+ discipline: Biochemical
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - VaccinatorMachineCircuitboard
+ - DiagnoserMachineCircuitboard
+
+# Tier 2
+
+- type: technology
+ id: Cryopod
+ name: research-technology-cryogenics
+ icon:
+ sprite: Structures/Machines/cryogenics.rsi
+ state: pod-on
+ discipline: Biochemical
+ tier: 2
+ cost: 7500
+ recipeUnlocks:
+ - CryoPodMachineCircuitboard
+
+- type: technology
+ id: ChemicalDispensary
+ name: research-technology-chemical-dispensary
+ icon:
+ sprite: Structures/dispensers.rsi
+ state: industrial-working
+ discipline: Biochemical
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - ChemMasterMachineCircuitboard
+ - ChemDispenserMachineCircuitboard
+ technologyPrerequisites:
+ - Chemistry
+
+- type: technology
+ id: CrewMonitoring
+ name: research-technology-crew-monitoring
+ icon:
+ sprite: Objects/Specific/Medical/handheldcrewmonitor.rsi
+ state: scanner
+ discipline: Biochemical
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - HandheldCrewMonitor
+
+# Tier 3
+
+- type: technology
+ id: Cloning
+ name: research-technology-cloning
+ icon:
+ sprite: Structures/Machines/cloning.rsi
+ state: pod_0
+ discipline: Biochemical
+ tier: 3
+ cost: 15000
+ recipeUnlocks:
+ - CloningPodMachineCircuitboard
+ - MedicalScannerMachineCircuitboard
+ - CloningConsoleComputerCircuitboard
+ - BiomassReclaimerMachineCircuitboard
diff --git a/Resources/Prototypes/Research/civilianservices.yml b/Resources/Prototypes/Research/civilianservices.yml
new file mode 100644
index 0000000000..8dd40a4a9e
--- /dev/null
+++ b/Resources/Prototypes/Research/civilianservices.yml
@@ -0,0 +1,153 @@
+# Tier 1
+
+- type: technology
+ id: JanitorialEquipment
+ name: research-technology-janitorial-equipment
+ icon:
+ sprite: Objects/Specific/Janitorial/mop.rsi
+ state: mop
+ discipline: CivilianServices
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - MopItem
+ - Holoprojector
+ - Mousetrap
+ - LightReplacer
+ - TrashBag
+
+- type: technology
+ id: LaundryTech
+ name: research-technology-laundry-tech
+ icon:
+ sprite: Structures/Machines/uniform_printer.rsi
+ state: icon
+ discipline: CivilianServices
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - UniformPrinterMachineCircuitboard
+
+- type: technology
+ id: Hydroponics
+ name: research-technology-basic-hydroponics
+ icon:
+ sprite: Structures/Machines/seed_extractor.rsi
+ state: seedextractor
+ discipline: CivilianServices
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - SeedExtractorMachineCircuitboard
+ - HydroponicsTrayMachineCircuitboard
+
+- type: technology
+ id: FoodService
+ name: research-technology-food-service
+ icon:
+ sprite: Structures/Machines/juicer.rsi
+ state: juicer1
+ discipline: CivilianServices
+ tier: 1
+ cost: 7500
+ recipeUnlocks: #remove all of these once we have more kitchen equipment
+ - MicrowaveMachineCircuitboard
+ - ReagentGrinderMachineCircuitboard
+ - BoozeDispenserMachineCircuitboard
+ - SodaDispenserMachineCircuitboard
+
+- type: technology
+ id: AudioVisualCommunication
+ name: research-technology-audio-visual-communication
+ icon:
+ sprite: Structures/Wallmounts/camera.rsi
+ state: cameracase
+ discipline: CivilianServices
+ tier: 1
+ cost: 7500
+ recipeUnlocks:
+ - SurveillanceCameraRouterCircuitboard
+ - SurveillanceCameraWirelessRouterCircuitboard
+ - SurveillanceWirelessCameraMovableCircuitboard
+ - SurveillanceWirelessCameraAnchoredCircuitboard
+ - SurveillanceCameraMonitorCircuitboard
+ - SurveillanceWirelessCameraMonitorCircuitboard
+ - TelecomServerCircuitboard
+
+# Tier 2
+
+- type: technology
+ id: AdvancedCleaning
+ name: research-technology-advanced-cleaning
+ icon:
+ sprite: Objects/Specific/Janitorial/advmop.rsi
+ state: advmop
+ discipline: CivilianServices
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - AdvMopItem
+ - MegaSprayBottle
+ technologyPrerequisites:
+ - JanitorialEquipment
+
+- type: technology
+ id: MeatManipulation
+ name: research-technology-meat-manipulation
+ icon:
+ sprite: Structures/Machines/fat_sucker.rsi
+ state: display
+ discipline: CivilianServices
+ tier: 2
+ cost: 5000
+ recipeUnlocks:
+ - FatExtractorMachineCircuitboard
+
+- type: technology
+ id: HONKMech
+ name: research-technology-honk-mech
+ icon:
+ sprite: Objects/Specific/Mech/mecha.rsi
+ state: honker
+ discipline: CivilianServices
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - HonkerHarness
+ - HonkerLArm
+ - HonkerRArm
+ - HonkerLLeg
+ - HonkerRLeg
+ - HonkerCentralElectronics
+ - HonkerPeripheralsElectronics
+ - HonkerTargetingElectronics
+ - MechEquipmentHorn
+
+- type: technology
+ id: AdvancedEntertainment
+ name: research-technology-advanced-entertainment
+ icon:
+ sprite: Structures/Machines/computers.rsi
+ state: television
+ discipline: CivilianServices
+ tier: 2
+ cost: 7500
+ recipeUnlocks:
+ - ComputerTelevisionCircuitboard
+ - SynthesizerInstrument
+ - DawInstrumentMachineCircuitboard
+
+# Tier 3
+
+- type: technology
+ id: AdvancedSpray
+ name: research-technology-advanced-spray
+ icon:
+ sprite: Objects/Weapons/Guns/Basic/spraynozzle.rsi
+ state: icon
+ discipline: CivilianServices
+ tier: 3
+ cost: 15000
+ recipeUnlocks:
+ - WeaponSprayNozzle
+ - ClothingBackpackWaterTank
diff --git a/Resources/Prototypes/Research/disciplines.yml b/Resources/Prototypes/Research/disciplines.yml
new file mode 100644
index 0000000000..bd39059fbd
--- /dev/null
+++ b/Resources/Prototypes/Research/disciplines.yml
@@ -0,0 +1,47 @@
+- type: techDiscipline
+ id: Industrial
+ name: research-discipline-industrial
+ color: "#eeac34"
+ icon:
+ sprite: Interface/Misc/research_disciplines.rsi
+ state: industrial
+ tierPrerequisites:
+ 1: 0
+ 2: 0.75
+ 3: 0.75
+
+- type: techDiscipline
+ id: Biochemical
+ name: research-discipline-biochemical
+ color: "#449ae6"
+ icon:
+ sprite: Interface/Misc/research_disciplines.rsi
+ state: biochemical
+ tierPrerequisites:
+ 1: 0
+ 2: 0.75
+ 3: 0.75
+
+- type: techDiscipline
+ id: Experimental
+ name: research-discipline-experimental
+ color: "#9a6ef0"
+ icon:
+ sprite: Interface/Misc/research_disciplines.rsi
+ state: experimental
+ tierPrerequisites:
+ 1: 0
+ 2: 0.75
+ 3: 0.75
+
+- type: techDiscipline
+ id: CivilianServices
+ name: research-discipline-civilian-services
+ color: "#7ecd48"
+ icon:
+ sprite: Interface/Misc/research_disciplines.rsi
+ state: civilianservices
+ tierPrerequisites:
+ 1: 0
+ 2: 0.75
+ 3: 0.75
diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml
new file mode 100644
index 0000000000..edac88d2a4
--- /dev/null
+++ b/Resources/Prototypes/Research/experimental.yml
@@ -0,0 +1,162 @@
+# Tier 1
+
+- type: technology
+ id: BasicRobotics
+ name: research-technology-basic-robotics
+ icon:
+ sprite: Mobs/Silicon/drone.rsi
+ state: drone
+ discipline: Experimental
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - ProximitySensor
+ - LeftArmBorg
+ - LightHeadBorg
+ - RightArmBorg
+ - LeftLegBorg
+ - RightLegBorg
+ - Drone
+ - ExosuitFabricatorMachineCircuitboard
+
+- type: technology
+ id: SignallingTech
+ name: research-technology-signalling-tech
+ icon:
+ sprite: Objects/Devices/signaller.rsi
+ state: signaller
+ discipline: Experimental
+ tier: 1
+ cost: 7500
+ recipeUnlocks:
+ - Signaller
+ - SignalTrigger
+ - VoiceTrigger
+ - TimerTrigger
+ - SignalTimerElectronics
+
+- type: technology
+ id: BasicAnomalousResearch
+ name: research-technology-basic-anomalous-research
+ icon:
+ sprite: Objects/Specific/Research/anomalyscanner.rsi
+ state: icon
+ discipline: Experimental
+ tier: 1
+ cost: 7500
+ recipeUnlocks:
+ - AnomalyScanner
+ - AnomalyLocator
+ - APECircuitboard
+ - AnomalyVesselCircuitboard
+
+- type: technology
+ id: BasicXenoArcheology
+ name: research-technology-basic-xenoarcheology
+ icon:
+ sprite: Structures/Machines/artifact_analyzer.rsi
+ state: display
+ discipline: Experimental
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - NodeScanner
+ - AnalysisComputerCircuitboard
+ - ArtifactAnalyzerMachineCircuitboard
+
+- type: technology
+ id: AlternativeResearch
+ name: research-technology-alternative-research
+ icon:
+ sprite: Structures/Machines/tech_disk_printer.rsi
+ state: display
+ discipline: Experimental
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - TechDiskComputerCircuitboard
+
+- type: technology
+ id: MagnetsTech
+ name: research-technology-magnets-tech
+ icon:
+ sprite: Clothing/Shoes/Boots/magboots.rsi
+ state: icon
+ discipline: Experimental
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - ClothingShoesBootsMag
+
+# Tier 2
+
+- type: technology
+ id: AdvancedParts
+ name: research-technology-advanced-parts
+ icon:
+ sprite: Objects/Misc/stock_parts.rsi
+ state: advanced_matter_bin
+ discipline: Experimental
+ tier: 2
+ cost: 15000
+ recipeUnlocks:
+ - AdvancedCapacitorStockPart
+ - AdvancedMatterBinStockPart
+ - NanoManipulatorStockPart
+
+- type: technology
+ id: AbnormalArtifactManipulation
+ name: research-technology-abnormal-artifact-manipulation
+ icon:
+ sprite: Structures/Machines/traversal_distorter.rsi
+ state: display
+ discipline: Experimental
+ tier: 2
+ cost: 5000
+ recipeUnlocks:
+ - TraversalDistorterMachineCircuitboard
+
+- type: technology
+ id: MobileAnomalyTech
+ name: research-technology-mobile-anomaly-tech
+ icon:
+ sprite: Objects/Weapons/Guns/Revolvers/chimp.rsi
+ state: base
+ discipline: Experimental
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - WeaponPistolCHIMP
+ - CartridgeAnomalousParticleDelta
+ - CartridgeAnomalousParticleEpsilon
+ - CartridgeAnomalousParticleZeta
+ technologyPrerequisites:
+ - BasicAnomalousResearch
+
+- type: technology
+ id: RapidPartExchange
+ name: research-technology-rped
+ icon:
+ sprite: Objects/Specific/Research/rped.rsi
+ state: icon
+ discipline: Experimental
+ tier: 2
+ cost: 7500
+ recipeUnlocks:
+ - RPED
+
+# Tier 3
+
+- type: technology
+ id: SuperParts
+ name: research-technology-super-parts
+ icon:
+ sprite: Objects/Misc/stock_parts.rsi
+ state: super_matter_bin
+ discipline: Experimental
+ tier: 3
+ cost: 15000
+ recipeUnlocks:
+ - SuperCapacitorStockPart
+ - SuperMatterBinStockPart
+ - PicoManipulatorStockPart
diff --git a/Resources/Prototypes/Research/industrial.yml b/Resources/Prototypes/Research/industrial.yml
new file mode 100644
index 0000000000..803870f826
--- /dev/null
+++ b/Resources/Prototypes/Research/industrial.yml
@@ -0,0 +1,177 @@
+# Tier 1
+
+- type: technology
+ id: SalvageEquipment
+ name: research-technology-salvage-equipment
+ icon:
+ sprite: Objects/Tools/handdrill.rsi
+ state: handdrill
+ discipline: Industrial
+ tier: 1
+ cost: 7500
+ recipeUnlocks:
+ - PowerDrill #todo remove this once we have advanced tools
+ - MiningDrill
+ - OreProcessorMachineCircuitboard
+
+- type: technology
+ id: AdvancedPowercells
+ name: research-technology-advanced-powercells
+ icon:
+ sprite: Objects/Power/power_cells.rsi
+ state: medium
+ discipline: Industrial
+ tier: 1
+ cost: 5000
+ recipeUnlocks:
+ - PowerCellMedium
+
+- type: technology
+ id: CompactPower
+ name: research-technology-compact-power
+ icon:
+ sprite: Structures/Power/Generation/wallmount_generator.rsi
+ state: panel
+ discipline: Industrial
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - WallmountSubstationElectronics
+ - WallmountGeneratorElectronics
+ - WallmountGeneratorAPUElectronics
+
+- type: technology
+ id: IndustrialEngineering
+ name: research-technology-industrial-engineering
+ icon:
+ sprite: Structures/Machines/protolathe.rsi
+ state: icon
+ discipline: Industrial
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - ProtolatheMachineCircuitboard
+ - AutolatheMachineCircuitboard
+ - CircuitImprinterMachineCircuitboard
+ - MaterialReclaimerMachineCircuitboard
+
+- type: technology
+ id: PowerGeneration
+ name: research-technology-power-generation
+ icon:
+ sprite: Structures/Power/Generation/portable_generator.rsi
+ state: portgen0_1
+ discipline: Industrial
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - GeneratorPlasmaMachineCircuitboard
+ - GeneratorUraniumMachineCircuitboard
+ - PowerComputerCircuitboard #the actual solar panel itself should be in here
+ - SolarControlComputerCircuitboard
+ - EmitterCircuitboard
+
+- type: technology
+ id: AtmosphericTech
+ name: research-technology-atmospheric-tech
+ icon:
+ sprite: Structures/Piping/Atmospherics/thermomachine.rsi
+ state: freezerOff
+ discipline: Industrial
+ tier: 1
+ cost: 7500
+ recipeUnlocks:
+ - ThermomachineFreezerMachineCircuitBoard
+ - GasRecyclerMachineCircuitboard
+
+# Tier 2
+
+- type: technology
+ id: RapidConstruction
+ name: research-technology-rapid-construction
+ icon:
+ sprite: Objects/Tools/rcd.rsi
+ state: icon
+ discipline: Industrial
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - RCD
+ - RCDAmmo
+
+- type: technology
+ id: Shuttlecraft
+ name: research-technology-shuttlecraft
+ icon:
+ sprite: Structures/Shuttles/gyroscope.rsi
+ state: base
+ discipline: Industrial
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - ShuttleConsoleCircuitboard
+ - RadarConsoleCircuitboard
+ - ThrusterMachineCircuitboard
+ - GyroscopeMachineCircuitboard
+
+- type: technology
+ id: RipleyAPLU
+ name: research-technology-ripley-aplu
+ icon:
+ sprite: Objects/Specific/Mech/mecha.rsi
+ state: ripley
+ discipline: Industrial
+ tier: 2
+ cost: 10000
+ recipeUnlocks:
+ - RipleyHarness
+ - RipleyLArm
+ - RipleyRArm
+ - RipleyLLeg
+ - RipleyRLeg
+ - RipleyCentralElectronics
+ - RipleyPeripheralsElectronics
+ - MechEquipmentGrabber
+
+- type: technology
+ id: AdvancedAtmospherics
+ name: research-technology-advanced-atmospherics
+ icon:
+ sprite: Objects/Devices/Holoprojectors/atmos.rsi
+ state: icon
+ discipline: Industrial
+ tier: 2
+ cost: 5000
+ recipeUnlocks:
+ - HolofanProjector
+ - PortableScrubberMachineCircuitBoard
+
+- type: technology
+ id: SuperPowercells
+ name: research-technology-super-powercells
+ icon:
+ sprite: Objects/Power/power_cells.rsi
+ state: high
+ discipline: Industrial
+ tier: 2
+ cost: 7500
+ recipeUnlocks:
+ - PowerCellHigh
+ technologyPrerequisites:
+ - AdvancedPowercells
+
+# Tier 3
+
+- type: technology
+ id: BluespaceStorage
+ name: research-technology-bluespace-storage
+ icon:
+ sprite: Clothing/Back/Backpacks/holding.rsi
+ state: holding
+ discipline: Industrial
+ tier: 3
+ cost: 15000
+ recipeUnlocks:
+ - ClothingBackpackHolding
+ - ClothingBackpackSatchelHolding
+ - ClothingBackpackDuffelHolding
diff --git a/Resources/Textures/Interface/Misc/research_disciplines.rsi/biochemical.png b/Resources/Textures/Interface/Misc/research_disciplines.rsi/biochemical.png
new file mode 100644
index 0000000000..80b9b30472
Binary files /dev/null and b/Resources/Textures/Interface/Misc/research_disciplines.rsi/biochemical.png differ
diff --git a/Resources/Textures/Interface/Misc/research_disciplines.rsi/civilianservices.png b/Resources/Textures/Interface/Misc/research_disciplines.rsi/civilianservices.png
new file mode 100644
index 0000000000..59ed52222e
Binary files /dev/null and b/Resources/Textures/Interface/Misc/research_disciplines.rsi/civilianservices.png differ
diff --git a/Resources/Textures/Interface/Misc/research_disciplines.rsi/experimental.png b/Resources/Textures/Interface/Misc/research_disciplines.rsi/experimental.png
new file mode 100644
index 0000000000..20ab8092ce
Binary files /dev/null and b/Resources/Textures/Interface/Misc/research_disciplines.rsi/experimental.png differ
diff --git a/Resources/Textures/Interface/Misc/research_disciplines.rsi/industrial.png b/Resources/Textures/Interface/Misc/research_disciplines.rsi/industrial.png
new file mode 100644
index 0000000000..80427526a6
Binary files /dev/null and b/Resources/Textures/Interface/Misc/research_disciplines.rsi/industrial.png differ
diff --git a/Resources/Textures/Interface/Misc/research_disciplines.rsi/meta.json b/Resources/Textures/Interface/Misc/research_disciplines.rsi/meta.json
new file mode 100644
index 0000000000..fc01e8aa90
--- /dev/null
+++ b/Resources/Textures/Interface/Misc/research_disciplines.rsi/meta.json
@@ -0,0 +1,23 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created by EmoGarbage404 (github) for Space Station 14.",
+ "size": {
+ "x": 8,
+ "y": 8
+ },
+ "states": [
+ {
+ "name": "biochemical"
+ },
+ {
+ "name": "civilianservices"
+ },
+ {
+ "name": "experimental"
+ },
+ {
+ "name": "industrial"
+ }
+ ]
+}
diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/display.png b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/display.png
new file mode 100644
index 0000000000..34c3cb5302
Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/display.png differ
diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
index 413b7c38ed..48fe81cafc 100644
--- a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
@@ -7,6 +7,9 @@
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},
diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/display.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/display.png
new file mode 100644
index 0000000000..41417d5226
Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/display.png differ
diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json b/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json
index 5100252ae9..cfb3d15960 100644
--- a/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json
@@ -7,6 +7,9 @@
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "fat"
},
diff --git a/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/display.png b/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/display.png
new file mode 100644
index 0000000000..e86c457962
Binary files /dev/null and b/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/display.png differ
diff --git a/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/meta.json b/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/meta.json
index 5959f53100..c3bd5aeaa4 100644
--- a/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/meta.json
@@ -7,6 +7,9 @@
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},
diff --git a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/display.png b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/display.png
new file mode 100644
index 0000000000..d016e6bd60
Binary files /dev/null and b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/display.png differ
diff --git a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json
index 413b7c38ed..48fe81cafc 100644
--- a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json
+++ b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json
@@ -7,6 +7,9 @@
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},