]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
RND Rework [Death to Techweb] (#16370)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Mon, 15 May 2023 20:17:30 +0000 (16:17 -0400)
committerGitHub <noreply@github.com>
Mon, 15 May 2023 20:17:30 +0000 (15:17 -0500)
* Techweb rework

* more ui work

* finishing ui

* Finish all the C# logic

* the techs + lathes

* remove old-tech

* mirror-review

51 files changed:
Content.Client/Research/UI/MiniTechnologyCardControl.xaml [new file with mode: 0644]
Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs [new file with mode: 0644]
Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs
Content.Client/Research/UI/ResearchConsoleMenu.xaml
Content.Client/Research/UI/ResearchConsoleMenu.xaml.cs
Content.Client/Research/UI/TechnologyCardControl.xaml [new file with mode: 0644]
Content.Client/Research/UI/TechnologyCardControl.xaml.cs [new file with mode: 0644]
Content.Client/Stylesheets/StyleNano.cs
Content.IntegrationTests/Tests/ResearchTest.cs
Content.Server/Lathe/LatheSystem.cs
Content.Server/Research/Disk/ResearchDiskSystem.cs
Content.Server/Research/Systems/ResearchSystem.Client.cs
Content.Server/Research/Systems/ResearchSystem.Console.cs
Content.Server/Research/Systems/ResearchSystem.Server.cs
Content.Server/Research/Systems/ResearchSystem.Technology.cs
Content.Server/Research/Systems/ResearchSystem.cs
Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs
Content.Server/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
Content.Shared/Research/Components/ResearchServerComponent.cs
Content.Shared/Research/Components/SharedResearchConsoleComponent.cs
Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
Content.Shared/Research/Prototypes/TechDisciplinePrototype.cs [new file with mode: 0644]
Content.Shared/Research/Prototypes/TechnologyPrototype.cs
Content.Shared/Research/Systems/SharedResearchSystem.cs
Resources/Locale/en-US/prototypes/catalog/research/technologies.ftl [deleted file]
Resources/Locale/en-US/research/components/research-console-component.ftl
Resources/Locale/en-US/research/technologies.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Research/technologies.yml [deleted file]
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Entities/Structures/Machines/research.yml
Resources/Prototypes/Recipes/Lathes/devices.yml
Resources/Prototypes/Recipes/Lathes/janitorial.yml
Resources/Prototypes/Research/biochemical.yml [new file with mode: 0644]
Resources/Prototypes/Research/civilianservices.yml [new file with mode: 0644]
Resources/Prototypes/Research/disciplines.yml [new file with mode: 0644]
Resources/Prototypes/Research/experimental.yml [new file with mode: 0644]
Resources/Prototypes/Research/industrial.yml [new file with mode: 0644]
Resources/Textures/Interface/Misc/research_disciplines.rsi/biochemical.png [new file with mode: 0644]
Resources/Textures/Interface/Misc/research_disciplines.rsi/civilianservices.png [new file with mode: 0644]
Resources/Textures/Interface/Misc/research_disciplines.rsi/experimental.png [new file with mode: 0644]
Resources/Textures/Interface/Misc/research_disciplines.rsi/industrial.png [new file with mode: 0644]
Resources/Textures/Interface/Misc/research_disciplines.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Machines/artifact_analyzer.rsi/display.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json
Resources/Textures/Structures/Machines/fat_sucker.rsi/display.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json
Resources/Textures/Structures/Machines/tech_disk_printer.rsi/display.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/tech_disk_printer.rsi/meta.json
Resources/Textures/Structures/Machines/traversal_distorter.rsi/display.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json

diff --git a/Content.Client/Research/UI/MiniTechnologyCardControl.xaml b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml
new file mode 100644 (file)
index 0000000..2808995
--- /dev/null
@@ -0,0 +1,29 @@
+<Control xmlns="https://spacestation14.io">
+    <BoxContainer Orientation="Horizontal">
+        <PanelContainer Name="Background"
+                        Access="Public"
+                        StyleClasses="PDABackground"
+                        VerticalExpand="False"
+                        HorizontalExpand="False"
+                        MaxWidth="10"
+                        Margin="0 0 -5 0"/>
+        <Button Name="Main"
+                Disabled="True"
+                HorizontalExpand="True"
+                VerticalExpand="False"
+                StyleClasses="ButtonSquare"
+                Margin="0"
+                ToolTip="foobar"
+                TooltipDelay="0.25">
+            <BoxContainer Orientation="Horizontal" Margin="0">
+                <TextureRect Name="Texture"
+                             HorizontalExpand="False"
+                             VerticalExpand="False"
+                             Margin="1"
+                             TextureScale="0.5 0.5"/>
+                <Control MinWidth="5"/>
+                <RichTextLabel Name="NameLabel" StyleClasses="LabelSubText" VerticalAlignment="Center"/>
+            </BoxContainer>
+        </Button>
+    </BoxContainer>
+</Control>
diff --git a/Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs b/Content.Client/Research/UI/MiniTechnologyCardControl.xaml.cs
new file mode 100644 (file)
index 0000000..8b1a583
--- /dev/null
@@ -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<TechDisciplinePrototype>(technology.Discipline);
+        Background.ModulateSelfOverride = discipline.Color;
+        Texture.Texture = spriteSys.Frame0(technology.Icon);
+        NameLabel.SetMessage(Loc.GetString(technology.Name));
+        Main.ToolTip = description.ToString();
+    }
+}
index c9a3fb4379ff99fb8c6109d19cb0e3fb369a1c57..97556c931da1f3462998bcf0896e5f912e5269b9 100644 (file)
@@ -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;
-
-        public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
-        {
-            SendMessage(new ConsoleServerSyncMessage());
-            _entityManager = IoCManager.Resolve<IEntityManager>();
-            _research = _entityManager.System<SharedResearchSystem>();
-        }
-
-        protected override void Open()
-        {
-            base.Open();
+namespace Content.Client.Research.UI;
 
-            if (!_entityManager.TryGetComponent(Owner.Owner, out _technologyDatabase))
-                return;
+[UsedImplicitly]
+public sealed class ResearchConsoleBoundUserInterface : BoundUserInterface
+{
 
-            _consoleMenu = new ResearchConsoleMenu(this);
+    private ResearchConsoleMenu? _consoleMenu;
 
-            _consoleMenu.OnClose += Close;
 
-            _consoleMenu.ServerSyncButton.OnPressed += (_) =>
-            {
-                SendMessage(new ConsoleServerSyncMessage());
-            };
+    public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+    {
+        SendMessage(new ConsoleServerSyncMessage());
+    }
 
-            _consoleMenu.ServerSelectionButton.OnPressed += (_) =>
-            {
-                SendMessage(new ConsoleServerSelectionMessage());
-            };
+    protected override void Open()
+    {
+        base.Open();
 
-            _consoleMenu.UnlockButton.OnPressed += (_) =>
-            {
-                if (_consoleMenu.TechnologySelected != null)
-                {
-                    SendMessage(new ConsoleUnlockTechnologyMessage(_consoleMenu.TechnologySelected.ID));
-                }
-            };
+        var owner = Owner.Owner;
 
-            _consoleMenu.OpenCentered();
-        }
+        _consoleMenu = new ResearchConsoleMenu(owner);
 
-        public bool IsTechnologyUnlocked(TechnologyPrototype technology)
+        _consoleMenu.OnTechnologyCardPressed += id =>
         {
-            if (_technologyDatabase == null)
-                return false;
+            SendMessage(new ConsoleUnlockTechnologyMessage(id));
+        };
 
-            return _research.IsTechnologyUnlocked(_technologyDatabase.Owner, technology, _technologyDatabase);
-        }
+        _consoleMenu.OnServerButtonPressed += () =>
+        {
+            SendMessage(new ConsoleServerSelectionMessage());
+        };
 
-        public bool CanUnlockTechnology(TechnologyPrototype technology)
+        _consoleMenu.OnSyncButtonPressed += () =>
         {
-            if (_technologyDatabase == null)
-                return false;
+            SendMessage(new ConsoleServerSyncMessage());
+        };
 
-            return _research.ArePrerequesitesUnlocked(_technologyDatabase.Owner, technology, _technologyDatabase);
-        }
+        _consoleMenu.OnClose += Close;
 
-        protected override void UpdateState(BoundUserInterfaceState state)
-        {
-            base.UpdateState(state);
+        _consoleMenu.OpenCentered();
+    }
 
-            var castState = (ResearchConsoleBoundInterfaceState)state;
-            Points = castState.Points;
-            PointsPerSecond = castState.PointsPerSecond;
-            // We update the user interface here.
-            _consoleMenu?.PopulatePoints();
-            _consoleMenu?.Populate();
-        }
+    protected override void UpdateState(BoundUserInterfaceState state)
+    {
+        base.UpdateState(state);
 
-        protected override void Dispose(bool disposing)
-        {
-            base.Dispose(disposing);
-            if (!disposing)
-                return;
-            _consoleMenu?.Dispose();
-        }
+        if (state is not ResearchConsoleBoundInterfaceState castState)
+            return;
+        _consoleMenu?.UpdatePanels(castState);
+        _consoleMenu?.UpdateInformationPanel(castState);
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+        if (!disposing)
+            return;
+        _consoleMenu?.Dispose();
     }
 }
index df892b882004ea24699cc1caadc47a713215fd4b..a332458de3107b2cdc9ca214cc86d9086d406996 100644 (file)
-<DefaultWindow xmlns="https://spacestation14.io"
-            Title="{Loc 'research-console-menu-title'}"
-            MinSize="800 400"
-            SetSize="800 400">
+<controls:FancyWindow xmlns="https://spacestation14.io"
+                      xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+                      xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+                      xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                      Title="{Loc 'research-console-menu-title'}"
+                      MinSize="625 400"
+                      SetSize="700 550">
     <BoxContainer Orientation="Vertical"
                   HorizontalExpand="True"
                   VerticalExpand="True">
         <BoxContainer Orientation="Horizontal"
                       HorizontalExpand="True"
-                      VerticalExpand="True"
-                      SizeFlagsStretchRatio="2"
-                      SeparationOverride="10">
-            <BoxContainer Orientation="Vertical"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                <Label Text="{Loc 'research-console-menu-unlocked-technologies-label'}" />
-                <ItemList Name="UnlockedTechnologies"
-                          SelectMode="Button"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                    <!-- Unlocked technologies are added here by code -->
-                </ItemList>
-            </BoxContainer>
-            <BoxContainer Orientation="Vertical"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                <Label Text="{Loc 'research-console-menu-unlockable-technologies-label'}" />
-                <ItemList Name="UnlockableTechnologies"
-                          SelectMode="Button"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                    <!-- Unlockable technologies are added here by code -->
-                </ItemList>
+                      VerticalExpand="False"
+                      MinHeight="85"
+                      Margin="10">
+            <BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
+                <RichTextLabel Name="ResearchAmountLabel"/>
+                <RichTextLabel Name="MainDisciplineLabel"/>
+                <Control VerticalExpand="True"/>
+                <BoxContainer Name="TierDisplayContainer" Orientation="Horizontal" HorizontalExpand="True" VerticalAlignment="Bottom"/>
+                <!-- This is where we put all of the little graphics that display discipline tiers!-->
             </BoxContainer>
-            <BoxContainer Orientation="Vertical"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                <Label Text="{Loc 'research-console-menu-future-technologies-label'}" />
-                <ItemList Name="FutureTechnologies"
-                          SelectMode="Button"
-                          HorizontalExpand="True"
-                          VerticalExpand="True">
-                    <!-- Future technologies are added here by code -->
-                </ItemList>
+            <BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalAlignment="Right">
+                <Button Name="ServerButton" Text="{Loc 'research-console-menu-server-selection-button'}" VerticalExpand="True"/>
+                <Control MinHeight="5"/>
+                <!--todo is this button even necessary?!-->
+                <Button Name="SyncButton" Text="{Loc 'research-console-menu-server-sync-button'}" VerticalExpand="True"/>
             </BoxContainer>
         </BoxContainer>
         <BoxContainer Orientation="Horizontal"
                       HorizontalExpand="True"
-                      VerticalExpand="True"
-                      SizeFlagsStretchRatio="1">
-            <TextureRect Name="TechnologyIcon"
-                         HorizontalExpand="True"
-                         VerticalExpand="True"
-                         SizeFlagsStretchRatio="1"
-                         Stretch="KeepAspectCentered" />
+                      VerticalExpand="True">
             <BoxContainer Orientation="Vertical"
-                          HorizontalExpand="True"
                           VerticalExpand="True"
-                          SizeFlagsStretchRatio="3">
-                <Label Name="TechnologyName" />
-                <Label Name="TechnologyDescription" />
-                <Label Name="TechnologyRequirements" />
+                          HorizontalExpand="True"
+                          SizeFlagsStretchRatio="2"
+                          Margin="10 0 10 10"
+                          MinWidth="175">
+                <Label Text="{Loc 'research-console-available-text'}" HorizontalAlignment="Center"/>
+                <customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
+                <PanelContainer VerticalExpand="True">
+                    <PanelContainer.PanelOverride>
+                        <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
+                    </PanelContainer.PanelOverride>
+                    <ScrollContainer
+                        HScrollEnabled="False"
+                        HorizontalExpand="True"
+                        VerticalExpand="True">
+                        <BoxContainer
+                            Name="AvailableCardsContainer"
+                            Orientation="Vertical"
+                            VerticalExpand="True">
+                        </BoxContainer>
+                    </ScrollContainer>
+                </PanelContainer>
+                <Control MinHeight="10"/>
+                <Label Text="{Loc 'research-console-unlocked-text'}" HorizontalAlignment="Center"/>
+                <customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
+                <PanelContainer VerticalExpand="True">
+                    <PanelContainer.PanelOverride>
+                        <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
+                    </PanelContainer.PanelOverride>
+                    <ScrollContainer
+                        HScrollEnabled="False"
+                        HorizontalExpand="True"
+                        VerticalExpand="True">
+                        <BoxContainer
+                            Name="UnlockedCardsContainer"
+                            Orientation="Vertical"
+                            VerticalExpand="True">
+                        </BoxContainer>
+                    </ScrollContainer>
+                </PanelContainer>
             </BoxContainer>
             <BoxContainer Orientation="Vertical"
-                          HorizontalExpand="True"
                           VerticalExpand="True"
-                          SizeFlagsStretchRatio="1">
-                <Label Name="PointLabel" />
-                <Label Name="PointsPerSecondLabel" />
-                <BoxContainer Orientation="Vertical"
-                              Align="End"
-                              HorizontalExpand="True"
-                              VerticalExpand="True">
-                    <Button Name="ServerSelectionButton"
-                            Access="Public"
-                            Text="{Loc 'research-console-menu-server-selection-button'}" />
-                    <Button Name="ServerSyncButton"
-                            Access="Public"
-                            Text="{Loc 'research-console-menu-server-sync-button'}" />
-                    <Button Name="UnlockButton"
-                            Access="Public"
-                            Disabled="True" />
-                </BoxContainer>
+                          HorizontalExpand="True"
+                          SizeFlagsStretchRatio="3"
+                          Margin="0 0 10 10">
+                <PanelContainer VerticalExpand="True" MinSize="0 200">
+                    <PanelContainer.PanelOverride>
+                        <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
+                    </PanelContainer.PanelOverride>
+                    <ScrollContainer
+                        HScrollEnabled="False"
+                        HorizontalExpand="True"
+                        SizeFlagsStretchRatio="2"
+                        VerticalExpand="True">
+                        <BoxContainer
+                            Name="TechnologyCardsContainer"
+                            MinSize="100 256"
+                            Orientation="Vertical"
+                            SizeFlagsStretchRatio="2"
+                            VerticalExpand="True">
+                        </BoxContainer>
+                    </ScrollContainer>
+                </PanelContainer>
             </BoxContainer>
         </BoxContainer>
     </BoxContainer>
-</DefaultWindow>
+</controls:FancyWindow>
index bc7470e19826b3f28cda41ddcc597a79d972d743..16ae456a93abfd1850d34bacf748e91aed69b60a 100644 (file)
-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
-{
-    [GenerateTypedNameReferences]
-    public sealed partial class ResearchConsoleMenu : DefaultWindow
-    {
-        public ResearchConsoleBoundUserInterface Owner { get; }
+namespace Content.Client.Research.UI;
 
-        private readonly List<TechnologyPrototype> _unlockedTechnologyPrototypes = new();
-        private readonly List<TechnologyPrototype> _unlockableTechnologyPrototypes = new();
-        private readonly List<TechnologyPrototype> _futureTechnologyPrototypes = new();
+[GenerateTypedNameReferences]
+public sealed partial class ResearchConsoleMenu : FancyWindow
+{
+    public Action<string>? OnTechnologyCardPressed;
+    public Action? OnServerButtonPressed;
+    public Action? OnSyncButtonPressed;
 
-        public TechnologyPrototype? TechnologySelected;
+    [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 ResearchConsoleMenu(ResearchConsoleBoundUserInterface owner)
-        {
-            RobustXamlLoader.Load(this);
-            IoCManager.InjectDependencies(this);
+    public readonly EntityUid Entity;
 
-            Owner = owner;
+    public ResearchConsoleMenu(EntityUid entity)
+    {
+        RobustXamlLoader.Load(this);
+        IoCManager.InjectDependencies(this);
 
-            UnlockedTechnologies.OnItemSelected += UnlockedTechnologySelected;
-            UnlockableTechnologies.OnItemSelected += UnlockableTechnologySelected;
-            FutureTechnologies.OnItemSelected += FutureTechnologySelected;
+        _research = _entity.System<ResearchSystem>();
+        _sprite = _entity.System<SpriteSystem>();
+        Entity = entity;
 
-            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));
+        ServerButton.OnPressed += _ => OnServerButtonPressed?.Invoke();
+        SyncButton.OnPressed += _ => OnSyncButtonPressed?.Invoke();
 
-            UnlockButton.Text = Loc.GetString("research-console-menu-server-unlock-button");
+        _entity.TryGetComponent(entity, out _technologyDatabase);
+    }
 
-            UnlockButton.OnPressed += _ =>
-            {
-                CleanSelectedTechnology();
-            };
+    public void  UpdatePanels(ResearchConsoleBoundInterfaceState state)
+    {
+        var allTech = _research.GetAvailableTechnologies(Entity);
+        AvailableCardsContainer.Children.Clear();
+        TechnologyCardsContainer.Children.Clear();
+        UnlockedCardsContainer.Children.Clear();
 
-            Populate();
+        foreach (var tech in allTech)
+        {
+            var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false));
+            AvailableCardsContainer.AddChild(mini);
         }
 
-        /// <summary>
-        ///     Cleans the selected technology controls to blank.
-        /// </summary>
-        private void CleanSelectedTechnology()
+        if (_technologyDatabase == null)
+            return;
+
+        // i can't figure out the spacing so here you go
+        TechnologyCardsContainer.AddChild(new Control
+        {
+            MinHeight = 10
+        });
+        foreach (var techId in _technologyDatabase.CurrentTechnologyCards)
         {
-            UnlockButton.Disabled = true;
-            TechnologyIcon.Texture = Texture.Transparent;
-            TechnologyName.Text = string.Empty;
-            TechnologyDescription.Text = string.Empty;
-            TechnologyRequirements.Text = string.Empty;
+            var tech = _prototype.Index<TechnologyPrototype>(techId);
+            var cardControl = new TechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech), state.Points);
+            cardControl.OnPressed += () => OnTechnologyCardPressed?.Invoke(techId);
+            TechnologyCardsContainer.AddChild(cardControl);
         }
 
-        /// <summary>
-        ///     Called when an unlocked technology is selected.
-        /// </summary>
-        private void UnlockedTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
+        foreach (var unlocked in _technologyDatabase.UnlockedTechnologies)
         {
-            TechnologySelected = _unlockedTechnologyPrototypes[obj.ItemIndex];
-
-            UnlockButton.Disabled = true;
-
-            PopulateSelectedTechnology();
+            var tech = _prototype.Index<TechnologyPrototype>(unlocked);
+            var cardControl = new MiniTechnologyCardControl(tech, _prototype, _sprite, GetTechnologyDescription(tech, false));
+            UnlockedCardsContainer.AddChild(cardControl);
         }
+    }
 
-        /// <summary>
-        ///     Called when an unlockable technology is selected.
-        /// </summary>
-        private void UnlockableTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
+    public FormattedMessage GetTechnologyDescription(TechnologyPrototype technology, bool includeCost = true)
+    {
+        var description = new FormattedMessage();
+        if (includeCost)
         {
-            TechnologySelected = _unlockableTechnologyPrototypes[obj.ItemIndex];
-
-            UnlockButton.Disabled = Owner.Points < TechnologySelected.RequiredPoints;
-
-            PopulateSelectedTechnology();
+            description.AddMarkup(Loc.GetString("research-console-cost", ("amount", technology.Cost)));
+            description.PushNewline();
         }
-
-        /// <summary>
-        ///     Called when a future technology is selected
-        /// </summary>
-        private void FutureTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
+        description.AddMarkup(Loc.GetString("research-console-unlocks-list-start"));
+        foreach (var recipe in technology.RecipeUnlocks)
         {
-            TechnologySelected = _futureTechnologyPrototypes[obj.ItemIndex];
-
-            UnlockButton.Disabled = true;
-
-            PopulateSelectedTechnology();
+            var recipeProto = _prototype.Index<LatheRecipePrototype>(recipe);
+            description.PushNewline();
+            description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry",
+                ("name",recipeProto.Name)));
         }
-
-        /// <summary>
-        ///     Populate all technologies in the ItemLists.
-        /// </summary>
-        public void PopulateItemLists()
+        foreach (var generic in technology.GenericUnlocks)
         {
-            UnlockedTechnologies.Clear();
-            UnlockableTechnologies.Clear();
-            FutureTechnologies.Clear();
+            description.PushNewline();
+            description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry-generic",
+                ("name", Loc.GetString(generic.UnlockDescription))));
+        }
 
-            _unlockedTechnologyPrototypes.Clear();
-            _unlockableTechnologyPrototypes.Clear();
-            _futureTechnologyPrototypes.Clear();
+        return description;
+    }
 
-            var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
+    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);
 
-            // For now, we retrieve all technologies. In the future, this should be changed.
-            foreach (var tech in prototypeMan.EnumeratePrototypes<TechnologyPrototype>())
-            {
-                var techName = GetTechName(tech);
-                if (Owner.IsTechnologyUnlocked(tech))
-                {
-                    UnlockedTechnologies.AddItem(techName, tech.Icon.Frame0());
-                    _unlockedTechnologyPrototypes.Add(tech);
-                }
-                else if (Owner.CanUnlockTechnology(tech))
-                {
-                    UnlockableTechnologies.AddItem(techName, tech.Icon.Frame0());
-                    _unlockableTechnologyPrototypes.Add(tech);
-                }
-                else
-                {
-                    FutureTechnologies.AddItem(techName, tech.Icon.Frame0());
-                    _futureTechnologyPrototypes.Add(tech);
-                }
-            }
-        }
+        if (_technologyDatabase == null)
+            return;
 
-        private string GetTechName(TechnologyPrototype prototype)
+        var disciplineText = Loc.GetString("research-discipline-none");
+        var disciplineColor = Color.Gray;
+        if (_technologyDatabase.MainDiscipline != null)
         {
-            if (prototype.Name is { } name)
-                return Loc.GetString(name);
-
-            return prototype.ID;
+            var discipline = _prototype.Index<TechDisciplinePrototype>(_technologyDatabase.MainDiscipline);
+            disciplineText = Loc.GetString(discipline.Name);
+            disciplineColor = discipline.Color;
         }
 
-        /// <summary>
-        ///     Fills the selected technology controls with details.
-        /// </summary>
-        public void PopulateSelectedTechnology()
-        {
-            if (TechnologySelected == null)
-            {
-                TechnologyName.Text = string.Empty;
-                TechnologyDescription.Text = string.Empty;
-                TechnologyRequirements.Text = string.Empty;
-                return;
-            }
+        var msg = new FormattedMessage();
+        msg.AddMarkup(Loc.GetString("research-console-menu-main-discipline",
+            ("name", disciplineText), ("color", disciplineColor)));
+        MainDisciplineLabel.SetMessage(msg);
 
-            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");
+        TierDisplayContainer.Children.Clear();
+        foreach (var disciplineId in _technologyDatabase.SupportedDisciplines)
+        {
+            var discipline = _prototype.Index<TechDisciplinePrototype>(disciplineId);
+            var tier = _research.GetHighestDisciplineTier(_technologyDatabase, discipline);
 
-            var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
+            // don't show tiers with no available tech
+            if (tier == 0)
+                continue;
 
-            for (var i = 0; i < TechnologySelected.RequiredTechnologies.Count; i++)
+            // i'm building the small-ass control here to spare me some mild annoyance in making a new file
+            var texture = new TextureRect
             {
-                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}";
-            }
-        }
-
-        /// <summary>
-        ///     Updates the research point labels.
-        /// </summary>
-        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));
-        }
+                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)));
 
-        /// <summary>
-        ///     Updates the whole user interface.
-        /// </summary>
-        public void Populate()
-        {
-            PopulatePoints();
-            PopulateSelectedTechnology();
-            PopulateItemLists();
+            var control = new BoxContainer
+            {
+                Children =
+                {
+                    texture,
+                    label,
+                    new Control
+                    {
+                        MinWidth = 10
+                    }
+                }
+            };
+            TierDisplayContainer.AddChild(control);
         }
     }
 }
+
diff --git a/Content.Client/Research/UI/TechnologyCardControl.xaml b/Content.Client/Research/UI/TechnologyCardControl.xaml
new file mode 100644 (file)
index 0000000..e8d0201
--- /dev/null
@@ -0,0 +1,42 @@
+<Control xmlns="https://spacestation14.io"
+         xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
+    <BoxContainer Name="MainContainer" Orientation="Vertical" Margin="10 0 10 10">
+        <PanelContainer Name="Background"
+                        Access="Public"
+                        StyleClasses="PDABackground"
+                        MinHeight="15"
+                        VerticalExpand="False"
+                        HorizontalExpand="True"
+                        Margin="0 0 0 -5"/>
+        <Button Name="MainButton"
+                Disabled="True"
+                HorizontalExpand="True"
+                VerticalExpand="True"
+                StyleClasses="ButtonSquare"
+                Margin="0">
+            <BoxContainer Orientation="Vertical"
+                          VerticalExpand="True"
+                          Margin="5">
+                <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+                    <TextureRect Name="TechnologyTexture"
+                                 TextureScale="2 2"
+                                 VerticalAlignment="Center"
+                                 HorizontalAlignment="Center"/>
+                    <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center" Margin="10 0 0 0">
+                        <BoxContainer Orientation="Horizontal">
+                            <Label Name="TechnologyNameLabel" StyleClasses="LabelKeyText" HorizontalExpand="True"/>
+                            <TextureRect Name="DisciplineTexture" TextureScale="2 2" VerticalAlignment="Top" HorizontalAlignment="Right"/>
+                        </BoxContainer>
+                        <customControls:HSeparator StyleClasses="LowDivider" Margin="0 5 0 5"/>
+                        <BoxContainer Orientation="Horizontal">
+                            <RichTextLabel Name="TierLabel" HorizontalAlignment="Left" StyleClasses="LabelSubText" HorizontalExpand="True"/>
+                            <Button Name="ResearchButton" Text="{Loc 'research-console-menu-server-research-button'}"/>
+                        </BoxContainer>
+                    </BoxContainer>
+                </BoxContainer>
+                <Control MinHeight="5"></Control>
+                <RichTextLabel Name="UnlocksLabel" HorizontalExpand="True" StyleClasses="LabelSubText"></RichTextLabel>
+            </BoxContainer>
+        </Button>
+    </BoxContainer>
+</Control>
diff --git a/Content.Client/Research/UI/TechnologyCardControl.xaml.cs b/Content.Client/Research/UI/TechnologyCardControl.xaml.cs
new file mode 100644 (file)
index 0000000..76e3035
--- /dev/null
@@ -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<TechDisciplinePrototype>(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();
+    }
+}
index a377b7434045b1086f357ffda1e4d16539357685..5097132a860c2c63ef81524944b0cf4cc5d175a2 100644 (file)
@@ -1351,6 +1351,10 @@ namespace Content.Client.Stylesheets
                     .Prop(Label.StylePropertyFont, notoSans12)
                     .Prop(Control.StylePropertyModulateSelf, Color.FromHex("#111111")),
 
+                Element<RichTextLabel>().Class("LabelSubText")
+                    .Prop(Label.StylePropertyFont, notoSans10)
+                    .Prop(Label.StylePropertyFontColor, Color.DarkGray),
+
                 Element<LineEdit>().Class("PaperLineEdit")
                     .Prop(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
 
index 7462af4daa78c0214a0408f4b58151f60e2945e7..e38698ebaa3403a63daa62f7692556ce961e4f22 100644 (file)
@@ -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<IPrototypeManager>();
+
+        await server.WaitAssertion(() =>
+        {
+            var allTechs = protoManager.EnumeratePrototypes<TechnologyPrototype>().ToList();
+
+            foreach (var discipline in protoManager.EnumeratePrototypes<TechDisciplinePrototype>())
+            {
+                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<TechnologyPrototype>())
             {
-                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.");
                 }
index f71c618c1ab8ec8f09d571c88e899ec768971a86..2cdbc8a87f27fbdd50788cd47d559dfb4f9f7dbc 100644 (file)
@@ -194,7 +194,7 @@ namespace Content.Server.Lathe
             if (uid != args.Lathe || !TryComp<LatheComponent>(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)
index 9d257c89da42dd2a1a2bc514d84f0251db6da47f..d32c49ce6fed493761084ae380bb7d14dd174bad 100644 (file)
@@ -28,7 +28,7 @@ namespace Content.Server.Research.Disk
             if (!TryComp<ResearchServerComponent>(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<TechnologyPrototype>()
-                .Sum(tech => tech.RequiredPoints);
+                .Sum(tech => tech.Cost);
         }
     }
 }
index d1041fb1c3d2cf94a1ce1df4bd19ece355101c29..0416c42d87a37921a83322ab745c8e9537a8810c 100644 (file)
@@ -105,8 +105,10 @@ public sealed partial class ResearchSystem
     /// <param name="serverComponent">The server's ResearchServerComponent. Null if false</param>
     /// <param name="component">The client's Researchclient component</param>
     /// <returns>If the server was successfully retrieved.</returns>
-    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<ResearchServerComponent>(component.Server, out var sc))
+        if (!TryComp(component.Server, out serverComponent))
             return false;
 
         server = component.Server;
-        serverComponent = sc;
         return true;
     }
 }
index 9e2f29dafcc507e60fdcb075bf791167e54ad221..fce7303062606da6dd019d38ac4272d730c9f547 100644 (file)
@@ -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)
index d7a9c5fafb4706ac3d6a5a502d5d2b5fa55b09ad..8d37e58f1889cfe982f658064543c7b69437e583 100644 (file)
@@ -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);
     }
 
     /// <summary>
@@ -60,15 +60,14 @@ public sealed partial class ResearchSystem
     /// <param name="clientComponent"></param>
     /// <param name="serverComponent"></param>
     /// <param name="dirtyServer">Whether or not to dirty the server component after registration</param>
-    /// <returns>Whether or not the client was successfully registered to the server</returns>
-    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;
     }
 
     /// <summary>
@@ -130,7 +128,7 @@ public sealed partial class ResearchSystem
     /// <param name="uid"></param>
     /// <param name="component"></param>
     /// <returns></returns>
-    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
     /// <param name="uid">The server</param>
     /// <param name="points">The amount of points being added</param>
     /// <param name="component"></param>
-    public void AddPointsToServer(EntityUid uid, int points, ResearchServerComponent? component = null)
+    public void ModifyServerPoints(EntityUid uid, int points, ResearchServerComponent? component = null)
     {
         if (points == 0)
             return;
index 85057dafc84962cbb0f1b5fb3c26d5fd58bdc2e1..f18a0ca33ff57e6e975e65e63893013dc05c065e 100644 (file)
@@ -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);
     }
 
     /// <summary>
@@ -28,16 +32,15 @@ public sealed partial class ResearchSystem
     ///     syncs against the research server, and the server against the local database.
     /// </summary>
     /// <returns>Whether it could sync or not</returns>
-    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<TechnologyDatabaseComponent>(clientComponent.Server, out var serverDatabase))
-            return false;
+            return;
 
         Sync(uid, clientComponent.Server.Value, databaseComponent, serverDatabase);
-        return true;
     }
 
     /// <summary>
@@ -45,45 +48,49 @@ public sealed partial class ResearchSystem
     /// </summary>
     /// <returns>If the technology was successfully added</returns>
     public bool UnlockTechnology(EntityUid client, string prototypeid, ResearchClientComponent? component = null,
-        TechnologyDatabaseComponent? databaseComponent = null)
+        TechnologyDatabaseComponent? clientDatabase = null)
     {
-        if (!_prototypeManager.TryIndex<TechnologyPrototype>(prototypeid, out var prototype))
-        {
-            Logger.Error("invalid technology prototype");
+        if (!PrototypeManager.TryIndex<TechnologyPrototype>(prototypeid, out var prototype))
             return false;
-        }
-        return UnlockTechnology(client, prototype, component, databaseComponent);
+
+        return UnlockTechnology(client, prototype, component, clientDatabase);
     }
 
     /// <summary>
     /// Tries to add a technology to a database, checking if it is able to
     /// </summary>
     /// <returns>If the technology was successfully added</returns>
-    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;
     }
 
     /// <summary>
     ///     Adds a technology to the database without checking if it could be unlocked.
     /// </summary>
+    [PublicAPI]
     public void AddTechnology(EntityUid uid, string technology, TechnologyDatabaseComponent? component = null)
     {
         if (!Resolve(uid, ref component))
             return;
 
-        if (!_prototypeManager.TryIndex<TechnologyPrototype>(technology, out var prototype))
+        if (!PrototypeManager.TryIndex<TechnologyPrototype>(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.
     /// </summary>
-    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.
     /// </summary>
     /// <returns>Whether it could be unlocked or not</returns>
-    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<TechnologyPrototype>(technology, out var prototype))
-            return false;
-        return CanUnlockTechnology(uid, prototype, database, client);
-    }
 
-    /// <summary>
-    ///     Returns whether a technology can be unlocked on this database,
-    ///     taking parent technologies into account.
-    /// </summary>
-    /// <returns>Whether it could be unlocked or not</returns>
-    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<string>();
+        component.SupportedDisciplines = new List<string>();
+        component.UnlockedTechnologies = new List<string>();
+        component.UnlockedRecipes = new List<string>();
+        Dirty(component);
+    }
 }
index 0e1630b788e649698fafd0495fffeac8465e7716..92e17df7f93b779dc7a42ba0ddc0a94d52d4f115 100644 (file)
@@ -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<TechnologyDatabaseComponent, ResearchRegistrationChangedEvent>(OnDatabaseRegistrationChanged);
         }
 
         /// <summary>
index 341a8d489bff4dc56e5b6fc02bde6a7429f9c024..b784c37d0b098288ab9a92805be5a031e1f7c4dc 100644 (file)
@@ -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<DiskConsolePrintingComponent>(uid);
index 2f4bce403ea7a6c783d4a39336fa2c9144d06076..e0b10257ac5cecd071cb8f68b970d09d49f68cb9 100644 (file)
@@ -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<string>();
         foreach (var tech in _prototype.EnumeratePrototypes<TechnologyPrototype>())
         {
-            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<string>();
         foreach (var database in EntityQuery<TechnologyDatabaseComponent>())
         {
-            allUnlocked.AddRange(database.RecipeIds);
+            allUnlocked.AddRange(database.UnlockedRecipes);
         }
         allUnlocked = allUnlocked.Distinct().ToList();
 
index 8989df0ec10115b8d84dcb62763678c15174a862..d4375f95e18d0fa8e88648288a576fabca0b3df5 100644 (file)
@@ -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));
index 220c6ff412fd97593d1340510e3f2edbdb5e9e15..763b5d9059ad1caf4e13558db2dd8b446de593a1 100644 (file)
@@ -2,73 +2,62 @@ using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Shared.Research.Components
-{
-    [RegisterComponent, NetworkedComponent]
-    public sealed class ResearchServerComponent : Component
-    {
-        /// <summary>
-        /// The name of the server
-        /// </summary>
-        [DataField("servername"), ViewVariables(VVAccess.ReadWrite)]
-        public string ServerName = "RDSERVER";
-
-        /// <summary>
-        /// The amount of points on the server.
-        /// </summary>
-        [DataField("points"), ViewVariables(VVAccess.ReadWrite)]
-        public int Points;
-
-        /// <summary>
-        /// A unique numeric id representing the server
-        /// </summary>
-        [ViewVariables(VVAccess.ReadOnly)]
-        public int Id;
-
-        /// <summary>
-        /// Entities connected to the server
-        /// </summary>
-        /// <remarks>
-        /// This is not safe to read clientside
-        /// </remarks>
-        [ViewVariables(VVAccess.ReadOnly)]
-        public List<EntityUid> Clients = new();
+namespace Content.Shared.Research.Components;
 
-        [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
-        public TimeSpan NextUpdateTime = TimeSpan.Zero;
-
-        [DataField("researchConsoleUpdateTime"), ViewVariables(VVAccess.ReadWrite)]
-        public readonly TimeSpan ResearchConsoleUpdateTime = TimeSpan.FromSeconds(1);
-    }
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ResearchServerComponent : Component
+{
+    /// <summary>
+    /// The name of the server
+    /// </summary>
+    [AutoNetworkedField]
+    [DataField("serverName"), ViewVariables(VVAccess.ReadWrite)]
+    public string ServerName = "RDSERVER";
 
-    [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;
-        }
-    }
+    /// <summary>
+    /// The amount of points on the server.
+    /// </summary>
+    [AutoNetworkedField]
+    [DataField("points"), ViewVariables(VVAccess.ReadWrite)]
+    public int Points;
 
     /// <summary>
-    /// Event raised on a server's clients when the point value of the server is changed.
+    /// A unique numeric id representing the server
     /// </summary>
-    /// <param name="Server"></param>
-    /// <param name="Total"></param>
-    /// <param name="Delta"></param>
-    [ByRefEvent]
-    public readonly record struct ResearchServerPointsChangedEvent(EntityUid Server, int Total, int Delta);
+    [AutoNetworkedField]
+    [ViewVariables(VVAccess.ReadOnly)]
+    public int Id;
 
     /// <summary>
-    /// Event raised every second to calculate the amount of points added to the server.
+    /// Entities connected to the server
     /// </summary>
-    /// <param name="Server"></param>
-    /// <param name="Points"></param>
-    [ByRefEvent]
-    public record struct ResearchServerGetPointsPerSecondEvent(EntityUid Server, int Points);
+    /// <remarks>
+    /// This is not safe to read clientside
+    /// </remarks>
+    [ViewVariables(VVAccess.ReadOnly)]
+    public List<EntityUid> Clients = new();
+
+    [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan NextUpdateTime = TimeSpan.Zero;
+
+    [DataField("researchConsoleUpdateTime"), ViewVariables(VVAccess.ReadWrite)]
+    public readonly TimeSpan ResearchConsoleUpdateTime = TimeSpan.FromSeconds(1);
 }
+
+/// <summary>
+/// Event raised on a server's clients when the point value of the server is changed.
+/// </summary>
+/// <param name="Server"></param>
+/// <param name="Total"></param>
+/// <param name="Delta"></param>
+[ByRefEvent]
+public readonly record struct ResearchServerPointsChangedEvent(EntityUid Server, int Total, int Delta);
+
+/// <summary>
+/// Event raised every second to calculate the amount of points added to the server.
+/// </summary>
+/// <param name="Server"></param>
+/// <param name="Points"></param>
+[ByRefEvent]
+public record struct ResearchServerGetPointsPerSecondEvent(EntityUid Server, int Points);
+
index a8304e97fb60b0bb725b0c63bfe8de44781921da..0e0fbbb4a9e04ab78499c954c671bb333ff0e314 100644 (file)
@@ -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;
         }
     }
 }
index 0970d3b077f751abe40e07d88f2ed5fc829e04fa..28999087c689e1f98b7fb1f31f0538917fb1283d 100644 (file)
@@ -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
+namespace Content.Shared.Research.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedResearchSystem)), AutoGenerateComponentState]
+public sealed partial class TechnologyDatabaseComponent : Component
 {
-    [RegisterComponent, NetworkedComponent]
-    public sealed class TechnologyDatabaseComponent : Component
-    {
-        /// <summary>
-        /// The ids of all the technologies which have been unlocked.
-        /// </summary>
-        [DataField("technologyIds", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))]
-        public List<string> TechnologyIds = new();
+    /// <summary>
+    /// A main discipline that locks out other discipline technology past a certain tier.
+    /// </summary>
+    [AutoNetworkedField]
+    [DataField("mainDiscipline", customTypeSerializer: typeof(PrototypeIdSerializer<TechDisciplinePrototype>))]
+    public string? MainDiscipline;
 
-        /// <summary>
-        /// The ids of all the lathe recipes which have been unlocked.
-        /// This is maintained alongside the TechnologyIds
-        /// </summary>
-        [DataField("recipeIds", customTypeSerializer: typeof(PrototypeIdListSerializer<LatheRecipePrototype>))]
-        public List<string> RecipeIds = new();
-    }
+    [AutoNetworkedField(true)]
+    [DataField("currentTechnologyCards")]
+    public List<string> CurrentTechnologyCards = new();
 
     /// <summary>
-    /// Event raised on the database whenever its
-    /// technologies or recipes are modified.
+    /// Which research disciplines are able to be unlocked
     /// </summary>
-    /// <remarks>
-    /// This event is forwarded from the
-    /// server to all of it's clients.
-    /// </remarks>
-    [ByRefEvent]
-    public readonly record struct TechnologyDatabaseModifiedEvent;
+    [AutoNetworkedField(true)]
+    [DataField("supportedDisciplines", customTypeSerializer: typeof(PrototypeIdListSerializer<TechDisciplinePrototype>))]
+    public List<string> SupportedDisciplines = new();
 
-    [Serializable, NetSerializable]
-    public sealed class TechnologyDatabaseState : ComponentState
-    {
-        public List<string> Technologies;
-        public List<string> Recipes;
+    /// <summary>
+    /// The ids of all the technologies which have been unlocked.
+    /// </summary>
+    [AutoNetworkedField(true)]
+    [DataField("unlockedTechnologies", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))]
+    public List<string> UnlockedTechnologies = new();
 
-        public TechnologyDatabaseState(List<string> technologies, List<string> recipes)
-        {
-            Technologies = technologies;
-            Recipes = recipes;
-        }
-    }
+    /// <summary>
+    /// The ids of all the lathe recipes which have been unlocked.
+    /// This is maintained alongside the TechnologyIds
+    /// </summary>
+    /// 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<LatheRecipePrototype>))]
+    public List<string> UnlockedRecipes = new();
 }
+
+/// <summary>
+/// Event raised on the database whenever its
+/// technologies or recipes are modified.
+/// </summary>
+/// <remarks>
+/// This event is forwarded from the
+/// server to all of it's clients.
+/// </remarks>
+[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 (file)
index 0000000..d5c7ad8
--- /dev/null
@@ -0,0 +1,48 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Research.Prototypes;
+
+/// <summary>
+/// This is a prototype for a research discipline, a category
+/// that governs how <see cref="TechnologyPrototype"/>s are unlocked.
+/// </summary>
+[Prototype("techDiscipline")]
+public sealed class TechDisciplinePrototype : IPrototype
+{
+    /// <inheritdoc/>
+    [IdDataField]
+    public string ID { get; } = default!;
+
+    /// <summary>
+    /// Player-facing name.
+    /// Supports locale strings.
+    /// </summary>
+    [DataField("name", required: true)]
+    public readonly string Name = string.Empty;
+
+    /// <summary>
+    /// A color used for UI
+    /// </summary>
+    [DataField("color", required: true)]
+    public readonly Color Color;
+
+    /// <summary>
+    /// An icon used to visually represent the discipline in UI.
+    /// </summary>
+    [DataField("icon")]
+    public readonly SpriteSpecifier Icon = default!;
+
+    /// <summary>
+    /// For each tier a discipline supports, what percentage
+    /// of the previous tier must be unlocked for it to become available
+    /// </summary>
+    [DataField("tierPrerequisites", required: true)]
+    public readonly Dictionary<int, float> TierPrerequisites = new();
+
+    /// <summary>
+    /// Purchasing this tier of technology causes a server to become "locked" to this discipline.
+    /// </summary>
+    [DataField("lockoutTier")]
+    public readonly int LockoutTier = 3;
+}
index 4c0ca81fdabf5176f6505cd1e44de55774836fbb..8b04be7209b714992becafdfc69467a9ddc8032d 100644 (file)
@@ -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;
+
+/// <summary>
+/// This is a prototype for a technology that can be unlocked.
+/// </summary>
+[Prototype("technology")]
+public sealed class TechnologyPrototype : IPrototype
+{
+    /// <inheritdoc/>
+    [IdDataField]
+    public string ID { get; } = default!;
+
+    /// <summary>
+    /// The name of the technology.
+    /// Supports locale strings
+    /// </summary>
+    [DataField("name", required: true)]
+    public readonly string Name = string.Empty;
+
+    /// <summary>
+    /// An icon used to visually represent the technology in UI.
+    /// </summary>
+    [DataField("icon", required: true)]
+    public readonly SpriteSpecifier Icon = default!;
+
+    /// <summary>
+    /// What research discipline this technology belongs to.
+    /// </summary>
+    [DataField("discipline", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<TechDisciplinePrototype>))]
+    public readonly string Discipline = default!;
+
+    /// <summary>
+    /// What tier research is this?
+    /// The tier governs how much lower-tier technology
+    /// needs to be unlocked before this one.
+    /// </summary>
+    [DataField("tier", required: true)]
+    public readonly int Tier;
+
+    /// <summary>
+    /// Hidden tech is not ever available at the research console.
+    /// </summary>
+    [DataField("hidden")]
+    public readonly bool Hidden;
+
+    /// <summary>
+    /// How much research is needed to unlock.
+    /// </summary>
+    [DataField("cost")]
+    public readonly int Cost = 10000;
+
+    /// <summary>
+    /// A list of <see cref="TechnologyPrototype"/>s that need to be unlocked in order to unlock this technology.
+    /// </summary>
+    [DataField("technologyPrerequisites", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))]
+    public readonly IReadOnlyList<string> TechnologyPrerequisites = new List<string>();
+
+    /// <summary>
+    /// A list of <see cref="LatheRecipePrototype"/>s that are unlocked by this technology
+    /// </summary>
+    [DataField("recipeUnlocks", customTypeSerializer: typeof(PrototypeIdListSerializer<LatheRecipePrototype>))]
+    public readonly IReadOnlyList<string> RecipeUnlocks = new List<string>();
+
+    /// <summary>
+    /// A list of non-standard effects that are done when this technology is unlocked.
+    /// </summary>
+    [DataField("genericUnlocks")]
+    public readonly IReadOnlyList<GenericUnlock> GenericUnlocks = new List<GenericUnlock>();
+}
+
+[DataDefinition]
+public record struct GenericUnlock()
 {
-    [NetSerializable, Serializable, Prototype("technology")]
-    public sealed class TechnologyPrototype : IPrototype
-    {
-        /// <summary>
-        ///     The ID of this technology prototype.
-        /// </summary>
-        [ViewVariables]
-        [IdDataField]
-        public string ID { get; } = default!;
-
-        /// <summary>
-        ///     The name this technology will have on user interfaces.
-        /// </summary>
-        [DataField("name")]
-        public string? Name { get; private set; }
-
-        /// <summary>
-        ///     An icon that represent this technology.
-        /// </summary>
-        [DataField("icon")]
-        public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid;
-
-        /// <summary>
-        ///     A short description of the technology.
-        /// </summary>
-        [DataField("description")]
-        public string Description { get; private set; } = "";
-
-        /// <summary>
-        ///    The required research points to unlock this technology.
-        /// </summary>
-        [DataField("requiredPoints")]
-        public int RequiredPoints { get; }
-
-        /// <summary>
-        ///     A list of technology IDs required to unlock this technology.
-        /// </summary>
-        [DataField("requiredTechnologies", customTypeSerializer: typeof(PrototypeIdListSerializer<TechnologyPrototype>))]
-        public List<string> RequiredTechnologies { get; } = new();
-
-        /// <summary>
-        ///     A list of recipe IDs this technology unlocks.
-        /// </summary>
-        [DataField("unlockedRecipes", customTypeSerializer:typeof(PrototypeIdListSerializer<LatheRecipePrototype>))]
-        public List<string> UnlockedRecipes { get; } = new();
-    }
+    /// <summary>
+    /// What event is raised when this is unlocked?
+    /// Used for doing non-standard logic.
+    /// </summary>
+    [DataField("purchaseEvent")]
+    public readonly object? PurchaseEvent = null;
+
+    /// <summary>
+    /// A player facing tooltip for what the unlock does.
+    /// Supports locale strings.
+    /// </summary>
+    [DataField("unlockDescription")]
+    public readonly string UnlockDescription = string.Empty;
 }
index 224535f7ad8cd1e2ee6c00f76834bd29dc057158..fffbad847e5d2562625c6caecc8f3ec48fb6ee84 100644 (file)
-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<ResearchServerComponent, ComponentGetState>(OnServerGetState);
-        SubscribeLocalEvent<ResearchServerComponent, ComponentHandleState>(OnServerHandleState);
-        SubscribeLocalEvent<TechnologyDatabaseComponent, ComponentGetState>(OnTechnologyGetState);
-        SubscribeLocalEvent<TechnologyDatabaseComponent, ComponentHandleState>(OnTechnologyHandleState);
+        SubscribeLocalEvent<TechnologyDatabaseComponent, MapInitEvent>(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<TechnologyPrototype> 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<TechnologyPrototype>();
+
+        var availableTechnologies = new List<TechnologyPrototype>();
+        var disciplineTiers = GetDisciplineTiers(component);
+        foreach (var tech in PrototypeManager.EnumeratePrototypes<TechnologyPrototype>())
+        {
+            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<string, int>? 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<string, int> GetDisciplineTiers(TechnologyDatabaseComponent component)
+    {
+        var tiers = new Dictionary<string, int>();
+        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<TechDisciplinePrototype>(disciplineId));
+    }
+
+    public int GetHighestDisciplineTier(TechnologyDatabaseComponent component, TechDisciplinePrototype techDiscipline)
+    {
+        var allTech = PrototypeManager.EnumeratePrototypes<TechnologyPrototype>()
+            .Where(p => p.Discipline == techDiscipline.ID && !p.Hidden).ToList();
+        var allUnlocked = new List<TechnologyPrototype>();
+        foreach (var recipe in component.UnlockedTechnologies)
+        {
+            var proto = PrototypeManager.Index<TechnologyPrototype>(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;
     }
 
     /// <summary>
@@ -58,26 +157,18 @@ public abstract class SharedResearchSystem : EntitySystem
     /// <returns>Whether it is unlocked or not</returns>
     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);
     }
 
-    /// <summary>
-    /// Returns whether or not all the prerequisite
-    /// technologies for a technology are unlocked.
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="prototype"></param>
-    /// <param name="component"></param>
-    /// <returns>Whether or not the prerequesites are present</returns>
-    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<TechDisciplinePrototype>(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 (file)
index 87b8371..0000000
+++ /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.
-
index 6ea90313b28096a3c13ff6b1d9f97b7a35c16d3d..bc53ad5d9d682b2d544b5bd62a191c46dc9284cc 100644 (file)
@@ -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 (file)
index 0000000..e6c9ae3
--- /dev/null
@@ -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 (file)
index 69ce2a6..0000000
+++ /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
index 23643b593fd4add5092f5752177ac2976d2fbcf9..50e847f54df6d34576fd6cba3fb91b9cf4e63134 100644 (file)
       - 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
 
     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
       - 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
       - ClothingShoesBootsMag
       - NodeScanner
       - HolofanProjector
-      - Vape
+      - ClothingBackpackHolding
+      - ClothingBackpackSatchelHolding
+      - ClothingBackpackDuffelHolding
 
 - type: entity
   parent: Protolathe
     idleState: icon
     runningState: building
     dynamicRecipes:
-      - FirelockElectronics
-      - DoorElectronics
-      - APCElectronics
-      - AirAlarmElectronics
-      - FireAlarmElectronics
-      - MailingUnitElectronics
-      - IntercomElectronics
       - SignalTimerElectronics
-      - SMESMachineCircuitboard
-      - SubstationMachineCircuitboard
       - ThermomachineFreezerMachineCircuitBoard
       - PortableScrubberMachineCircuitBoard
       - CloningPodMachineCircuitboard
       - ClothingHandsGlovesLatex
       - ClothingMaskSterile
       - DiseaseSwab
-      - Scalpel
-      - Retractor
-      - Cautery
-      - Drill
-      - Saw
-      - Hemostat
       - Beaker
-      - LargeBeaker
-      - CryostasisBeaker
-      - Dropper
       - Syringe
       - Implanter
       - PillCanister
     dynamicRecipes:
       - HandheldCrewMonitor
       - ClothingHandsGlovesNitrile
+      - CryostasisBeaker
+      - LargeBeaker
+      - Dropper
+      - Scalpel
+      - Retractor
+      - Cautery
+      - Drill
+      - Saw
+      - Hemostat
   - type: Machine
     board: MedicalTechFabCircuitboard
 
index 885e57c4ef99e2a8b02bd50bf920d9f4a6a91046..8f891b40012f64c8b3d06828e1217980ad54ded3 100644 (file)
@@ -9,6 +9,11 @@
     state: server
   - type: ResearchServer
   - type: TechnologyDatabase
+    supportedDisciplines:
+    - Industrial
+    - Biochemical
+    - Experimental
+    - CivilianServices
   - type: ApcPowerReceiver
     powerLoad: 200
     priority: Low
index 0fbfd7ccb435c9d578cb4a3dbc90e79952c51b38..24127c16bb9fdc6925171915f53e0a4ad3bcbff8 100644 (file)
   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
index b1cd8db385694f28ed790f4158ad93bc5a5ec80c..902569e4b076ee9bcd721ade79ab40de44b7e6f3 100644 (file)
     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 (file)
index 0000000..938ed8b
--- /dev/null
@@ -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 (file)
index 0000000..8dd40a4
--- /dev/null
@@ -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 (file)
index 0000000..bd39059
--- /dev/null
@@ -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 (file)
index 0000000..edac88d
--- /dev/null
@@ -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 (file)
index 0000000..803870f
--- /dev/null
@@ -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 (file)
index 0000000..80b9b30
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 (file)
index 0000000..59ed522
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 (file)
index 0000000..20ab809
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 (file)
index 0000000..8042752
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 (file)
index 0000000..fc01e8a
--- /dev/null
@@ -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 (file)
index 0000000..34c3cb5
Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/display.png differ
index 413b7c38edec70a99ba0bfaef8f1851df17ba0a4..48fe81cafc140a2d4211440445ebe7a3436596d5 100644 (file)
@@ -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 (file)
index 0000000..41417d5
Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/display.png differ
index 5100252ae97ae03b6b5695ca17af85fc4aebe77d..cfb3d15960ca7d4170d8104f7bb89b7572cd0125 100644 (file)
@@ -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 (file)
index 0000000..e86c457
Binary files /dev/null and b/Resources/Textures/Structures/Machines/tech_disk_printer.rsi/display.png differ
index 5959f531008b753bc842745c94706d83eb14f32a..c3bd5aeaa46a342922c8032b9e02ef8f62a9201e 100644 (file)
@@ -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 (file)
index 0000000..d016e6b
Binary files /dev/null and b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/display.png differ
index 413b7c38edec70a99ba0bfaef8f1851df17ba0a4..48fe81cafc140a2d4211440445ebe7a3436596d5 100644 (file)
@@ -7,6 +7,9 @@
     "y": 32
   },
   "states": [
+    {
+      "name": "display"
+    },
     {
       "name": "icon"
     },