--- /dev/null
+<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>
--- /dev/null
+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();
+ }
+}
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();
}
}
-<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>
-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);
}
}
}
+
--- /dev/null
+<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>
--- /dev/null
+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();
+ }
+}
.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()),
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Content.Shared.Lathe;
using Content.Shared.Research.Prototypes;
[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()
{
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.");
}
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)
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);
}
return;
component.Points = _prototype.EnumeratePrototypes<TechnologyPrototype>()
- .Sum(tech => tech.RequiredPoints);
+ .Sum(tech => tech.Cost);
}
}
}
/// <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;
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;
}
}
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)
if (!CanRun(uid))
return;
- AddPointsToServer(uid, PointsPerSecond(uid, component) * time, component);
+ ModifyServerPoints(uid, GetPointsPerSecond(uid, component) * time, component);
}
/// <summary>
/// <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;
var ev = new ResearchRegistrationChangedEvent(server);
RaiseLocalEvent(client, ref ev);
- return true;
}
/// <summary>
/// <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;
/// <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;
using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
+using JetBrains.Annotations;
namespace Content.Server.Research.Systems;
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>
/// 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>
/// </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);
}
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);
/// 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);
/// 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);
+ }
}
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
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()
InitializeConsole();
InitializeSource();
InitializeServer();
+
+ SubscribeLocalEvent<TechnologyDatabaseComponent, ResearchRegistrationChangedEvent>(OnDatabaseRegistrationChanged);
}
/// <summary>
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);
{
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;
}
var allTechs = new List<string>();
foreach (var tech in _prototype.EnumeratePrototypes<TechnologyPrototype>())
{
- allTechs.AddRange(tech.UnlockedRecipes);
+ allTechs.AddRange(tech.RecipeUnlocks);
}
allTechs = allTechs.Distinct().ToList();
var allUnlocked = new List<string>();
foreach (var database in EntityQuery<TechnologyDatabaseComponent>())
{
- allUnlocked.AddRange(database.RecipeIds);
+ allUnlocked.AddRange(database.UnlockedRecipes);
}
allUnlocked = allUnlocked.Distinct().ToList();
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));
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);
+
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;
}
}
}
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;
--- /dev/null
+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;
+}
-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;
}
-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>
/// <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);
}
}
+++ /dev/null
-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.
-
## 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]
--- /dev/null
+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
+
+++ /dev/null
-# 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
- 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
state: server
- type: ResearchServer
- type: TechnologyDatabase
+ supportedDisciplines:
+ - Industrial
+ - Biochemical
+ - Experimental
+ - CivilianServices
- type: ApcPowerReceiver
powerLoad: 200
priority: Low
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
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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+- 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+{
+ "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"
+ }
+ ]
+}
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "fat"
},
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},
"y": 32
},
"states": [
+ {
+ "name": "display"
+ },
{
"name": "icon"
},