diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index be324065ca..33597d77ac 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -100,6 +100,9 @@ namespace Content.Client "PlayerInputMover", "Computer", "AsteroidRock", + "ResearchServer", + "ResearchPointSource", + "ResearchClient", "IdCard", "Access", "AccessReader", @@ -110,8 +113,10 @@ namespace Content.Client factory.RegisterIgnore(ignoreName); } + factory.Register(); factory.Register(); factory.Register(); + factory.Register(); factory.Register(); diff --git a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs index 8dd27a498c..571c5f9e6b 100644 --- a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs @@ -23,7 +23,7 @@ namespace Content.Client.GameObjects.Components.Research public MaterialStorageComponent Storage { get; private set; } public SharedLatheComponent Lathe { get; private set; } - public LatheDatabaseComponent Database { get; private set; } + public SharedLatheDatabaseComponent Database { get; private set; } [ViewVariables] public Queue QueuedRecipes => _queuedRecipes; @@ -41,13 +41,15 @@ namespace Content.Client.GameObjects.Components.Research if (!Owner.Owner.TryGetComponent(out MaterialStorageComponent storage) || !Owner.Owner.TryGetComponent(out SharedLatheComponent lathe) - || !Owner.Owner.TryGetComponent(out LatheDatabaseComponent database)) return; + || !Owner.Owner.TryGetComponent(out SharedLatheDatabaseComponent database)) return; + + Storage = storage; Lathe = lathe; Database = database; - menu = new LatheMenu {Owner = this}; + menu = new LatheMenu(this); queueMenu = new LatheQueueMenu { Owner = this }; menu.OnClose += Close; @@ -57,6 +59,16 @@ namespace Content.Client.GameObjects.Components.Research menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); }; + menu.ServerConnectButton.OnPressed += (args) => + { + SendMessage(new SharedLatheComponent.LatheServerSelectionMessage()); + }; + + menu.ServerSyncButton.OnPressed += (args) => + { + SendMessage(new SharedLatheComponent.LatheServerSyncMessage()); + }; + storage.OnMaterialStorageChanged += menu.PopulateDisabled; storage.OnMaterialStorageChanged += menu.PopulateMaterials; @@ -74,10 +86,10 @@ namespace Content.Client.GameObjects.Components.Research { case SharedLatheComponent.LatheProducingRecipeMessage msg: if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break; - queueMenu.SetInfo(recipe); + queueMenu?.SetInfo(recipe); break; case SharedLatheComponent.LatheStoppedProducingRecipeMessage msg: - queueMenu.ClearInfo(); + queueMenu?.ClearInfo(); break; case SharedLatheComponent.LatheFullQueueMessage msg: _queuedRecipes.Clear(); @@ -86,7 +98,7 @@ namespace Content.Client.GameObjects.Components.Research if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break; _queuedRecipes.Enqueue(recipePrototype); } - queueMenu.PopulateList(); + queueMenu?.PopulateList(); break; } } diff --git a/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs b/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs new file mode 100644 index 0000000000..71aeb4411f --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs @@ -0,0 +1,38 @@ +using System; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; + +namespace Content.Client.GameObjects.Components.Research +{ + [RegisterComponent] + [ComponentReference(typeof(SharedLatheDatabaseComponent))] + public class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent + { +#pragma warning disable CS0649 + [Dependency] + private IPrototypeManager _prototypeManager; +#pragma warning restore + + /// + /// Invoked when the database gets updated. + /// + public event Action OnDatabaseUpdated; + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + if (!(curState is ProtolatheDatabaseState state)) return; + Clear(); + foreach (var ID in state.Recipes) + { + if(!_prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe)) continue; + AddRecipe(recipe); + } + + OnDatabaseUpdated?.Invoke(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/ResearchClientBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/ResearchClientBoundUserInterface.cs new file mode 100644 index 0000000000..544418a123 --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/ResearchClientBoundUserInterface.cs @@ -0,0 +1,46 @@ +using Content.Shared.GameObjects.Components.Research; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects.Components.UserInterface; + +namespace Content.Client.GameObjects.Components.Research +{ + public class ResearchClientBoundUserInterface : BoundUserInterface + { + private ResearchClientServerSelectionMenu _menu; + + public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + SendMessage(new SharedResearchClientComponent.ResearchClientSyncMessage()); + } + + protected override void Open() + { + base.Open(); + + _menu = new ResearchClientServerSelectionMenu() { Owner = this }; + + _menu.OnClose += Close; + + _menu.OpenCentered(); + } + + public void SelectServer(int serverId) + { + SendMessage(new SharedResearchClientComponent.ResearchClientServerSelectedMessage(serverId)); + } + + public void DeselectServer() + { + SendMessage(new SharedResearchClientComponent.ResearchClientServerDeselectedMessage()); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (!(state is SharedResearchClientComponent.ResearchClientBoundInterfaceState rstate)) return; + _menu.Populate(rstate.ServerCount, rstate.ServerNames, rstate.ServerIds, rstate.SelectedServerId); + + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs b/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs new file mode 100644 index 0000000000..e32ccfa625 --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/ResearchClientServerSelectionMenu.cs @@ -0,0 +1,86 @@ +using System; +using Content.Shared.GameObjects.Components.Research; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; + +namespace Content.Client.GameObjects.Components.Research +{ + public class ResearchClientServerSelectionMenu : SS14Window + { + private ItemList _servers; + private int _serverCount = 0; + private string[] _serverNames = new string[]{}; + private int[] _serverIds = new int[]{}; + private int _selectedServerId = -1; + +#pragma warning disable 649 + [Dependency] private readonly ILocalizationManager _localizationManager; +#pragma warning restore 649 + + protected override Vector2? CustomSize => (300, 300); + public ResearchClientBoundUserInterface Owner { get; set; } + + public ResearchClientServerSelectionMenu() + { + IoCManager.InjectDependencies(this); + + Title = _localizationManager.GetString("Research Server Selection"); + + _servers = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single}; + + _servers.OnItemSelected += OnItemSelected; + _servers.OnItemDeselected += OnItemDeselected; + + var margin = new MarginContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + MarginTop = 5f, + MarginLeft = 5f, + MarginRight = -5f, + MarginBottom = -5f, + }; + + margin.AddChild(_servers); + + Contents.AddChild(margin); + } + + public void OnItemSelected(ItemList.ItemListSelectedEventArgs itemListSelectedEventArgs) + { + Owner.SelectServer(_serverIds[itemListSelectedEventArgs.ItemIndex]); + } + + public void OnItemDeselected(ItemList.ItemListDeselectedEventArgs itemListDeselectedEventArgs) + { + Owner.DeselectServer(); + } + + public void Populate(int serverCount, string[] serverNames, int[] serverIds, int selectedServerId) + { + _serverCount = serverCount; + _serverNames = serverNames; + _serverIds = serverIds; + _selectedServerId = selectedServerId; + + // Disable so we can select the new selected server without triggering a new sync request. + _servers.OnItemSelected -= OnItemSelected; + _servers.OnItemDeselected -= OnItemDeselected; + + _servers.Clear(); + for (var i = 0; i < _serverCount; i++) + { + var id = _serverIds[i]; + _servers.AddItem($"ID: {id} || {_serverNames[i]}"); + if(id == _selectedServerId) + _servers.Select(i); + } + + _servers.OnItemSelected += OnItemSelected; + _servers.OnItemDeselected += OnItemDeselected; + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/ResearchConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/ResearchConsoleBoundUserInterface.cs new file mode 100644 index 0000000000..9f6723e6db --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/ResearchConsoleBoundUserInterface.cs @@ -0,0 +1,80 @@ +using Content.Client.Research; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Log; + +namespace Content.Client.GameObjects.Components.Research +{ + public class ResearchConsoleBoundUserInterface : BoundUserInterface + { + public int Points { get; private set; } = 0; + public int PointsPerSecond { get; private set; } = 0; + private ResearchConsoleMenu _consoleMenu; + private TechnologyDatabaseComponent TechnologyDatabase; + + + public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + SendMessage(new SharedResearchConsoleComponent.ConsoleServerSyncMessage()); + } + + protected override void Open() + { + base.Open(); + + if (!Owner.Owner.TryGetComponent(out TechnologyDatabase)) return; + + _consoleMenu = new ResearchConsoleMenu(this); + + _consoleMenu.ServerSyncButton.OnPressed += (args) => + { + SendMessage(new SharedResearchConsoleComponent.ConsoleServerSyncMessage()); + }; + + _consoleMenu.ServerSelectionButton.OnPressed += (args) => + { + SendMessage(new SharedResearchConsoleComponent.ConsoleServerSelectionMessage()); + }; + + _consoleMenu.UnlockButton.OnPressed += (args) => + { + SendMessage(new SharedResearchConsoleComponent.ConsoleUnlockTechnologyMessage(_consoleMenu.TechnologySelected.ID)); + }; + + _consoleMenu.OpenCentered(); + + TechnologyDatabase.OnDatabaseUpdated += _consoleMenu.Populate; + } + + public bool IsTechnologyUnlocked(TechnologyPrototype technology) + { + return TechnologyDatabase.IsTechnologyUnlocked(technology); + } + + public bool CanUnlockTechnology(TechnologyPrototype technology) + { + return TechnologyDatabase.CanUnlockTechnology(technology); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (SharedResearchConsoleComponent.ResearchConsoleBoundInterfaceState)state; + Points = castState.Points; + PointsPerSecond = castState.PointsPerSecond; + // We update the user interface here. + _consoleMenu?.PopulatePoints(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + _consoleMenu?.Dispose(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/TechnologyDatabaseComponent.cs b/Content.Client/GameObjects/Components/Research/TechnologyDatabaseComponent.cs new file mode 100644 index 0000000000..98efd87227 --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/TechnologyDatabaseComponent.cs @@ -0,0 +1,35 @@ +using System; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using SixLabors.ImageSharp.Processing; + +namespace Content.Client.GameObjects.Components.Research +{ + [RegisterComponent] + public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent + { + /// + /// Event called when the database is updated. + /// + public event Action OnDatabaseUpdated; + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + if (!(curState is TechnologyDatabaseState state)) return; + _technologies.Clear(); + var protoManager = IoCManager.Resolve(); + foreach (var techID in state.Technologies) + { + if (!protoManager.TryIndex(techID, out TechnologyPrototype technology)) continue; + _technologies.Add(technology); + } + + OnDatabaseUpdated?.Invoke(); + + } + } +} diff --git a/Content.Client/Research/LatheMenu.cs b/Content.Client/Research/LatheMenu.cs index 4a0ffbae6e..fec807a8f2 100644 --- a/Content.Client/Research/LatheMenu.cs +++ b/Content.Client/Research/LatheMenu.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Client.GameObjects.Components.Research; +using Content.Shared.GameObjects.Components.Research; using Content.Shared.Materials; using Content.Shared.Research; using Robust.Client.UserInterface; @@ -25,6 +26,8 @@ namespace Content.Client.Research private LineEdit AmountLineEdit; private LineEdit SearchBar; public Button QueueButton; + public Button ServerConnectButton; + public Button ServerSyncButton; protected override Vector2? CustomSize => (300, 450); public LatheBoundUserInterface Owner { get; set; } @@ -32,10 +35,12 @@ namespace Content.Client.Research private List _recipes = new List(); private List _shownRecipes = new List(); - public LatheMenu() + public LatheMenu(LatheBoundUserInterface owner = null) { IoCManager.InjectDependencies(this); + Owner = owner; + Title = "Lathe Menu"; var margin = new MarginContainer() @@ -69,7 +74,23 @@ namespace Content.Client.Research { Text = "Queue", TextAlign = Button.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsStretchRatio = 1, + }; + + ServerConnectButton = new Button() + { + Text = "Server list", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsStretchRatio = 1, + }; + + ServerSyncButton = new Button() + { + Text = "Sync", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.Fill, SizeFlagsStretchRatio = 1, }; @@ -101,7 +122,7 @@ namespace Content.Client.Research { Text = "Filter", TextAlign = Button.AlignMode.Center, - SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.Fill, SizeFlagsStretchRatio = 1, Disabled = true, }; @@ -130,6 +151,12 @@ namespace Content.Client.Research }; hBoxButtons.AddChild(spacer); + if (Owner?.Database is ProtolatheDatabaseComponent database) + { + hBoxButtons.AddChild(ServerConnectButton); + hBoxButtons.AddChild(ServerSyncButton); + database.OnDatabaseUpdated += Populate; + } hBoxButtons.AddChild(QueueButton); hBoxFilter.AddChild(SearchBar); diff --git a/Content.Client/Research/ResearchConsoleMenu.cs b/Content.Client/Research/ResearchConsoleMenu.cs new file mode 100644 index 0000000000..aed3b4e3af --- /dev/null +++ b/Content.Client/Research/ResearchConsoleMenu.cs @@ -0,0 +1,316 @@ +using System.Collections.Generic; +using Content.Client.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.Utility; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; + +namespace Content.Client.Research +{ + public class ResearchConsoleMenu : SS14Window + { + public ResearchConsoleBoundUserInterface Owner { get; set; } + + protected override Vector2? CustomSize => (800, 400); + + private List _unlockedTechnologyPrototypes = new List(); + private List _unlockableTechnologyPrototypes = new List(); + private List _futureTechnologyPrototypes = new List(); + + private Label _pointLabel; + private Label _pointsPerSecondLabel; + private Label _technologyName; + private Label _technologyDescription; + private Label _technologyRequirements; + private TextureRect _technologyIcon; + private ItemList _unlockedTechnologies; + private ItemList _unlockableTechnologies; + private ItemList _futureTechnologies; + +#pragma warning disable 649 + [Dependency] private readonly ILocalizationManager _localizationManager; +#pragma warning restore 649 + + public Button UnlockButton { get; private set; } + public Button ServerSelectionButton { get; private set; } + public Button ServerSyncButton { get; private set; } + + public TechnologyPrototype TechnologySelected; + + public ResearchConsoleMenu(ResearchConsoleBoundUserInterface owner = null) + { + IoCManager.InjectDependencies(this); + + Title = _localizationManager.GetString("R&D Console"); + + Owner = owner; + + _unlockedTechnologies = new ItemList() + { + SelectMode = ItemList.ItemListSelectMode.Single, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + _unlockedTechnologies.OnItemSelected += UnlockedTechnologySelected; + + _unlockableTechnologies = new ItemList() + { + SelectMode = ItemList.ItemListSelectMode.Single, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + _unlockableTechnologies.OnItemSelected += UnlockableTechnologySelected; + + _futureTechnologies = new ItemList() + { + SelectMode = ItemList.ItemListSelectMode.Single, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + _futureTechnologies.OnItemSelected += FutureTechnologySelected; + + var vbox = new VBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + var hboxTechnologies = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 2, + SeparationOverride = 10, + }; + + var hboxSelected = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1 + }; + + var vboxPoints = new VBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + }; + + var vboxTechInfo = new VBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3, + }; + + _pointLabel = new Label() { Text = _localizationManager.GetString("Research Points") + ": 0" }; + _pointsPerSecondLabel = new Label() { Text = _localizationManager.GetString("Points per Second") + ": 0" }; + + var vboxPointsButtons = new VBoxContainer() + { + Align = BoxContainer.AlignMode.End, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + ServerSelectionButton = new Button() { Text = _localizationManager.GetString("Server list") }; + ServerSyncButton = new Button() { Text = _localizationManager.GetString("Sync")}; + UnlockButton = new Button() { Text = _localizationManager.GetString("Unlock"), Disabled = true }; + + + vboxPointsButtons.AddChild(ServerSelectionButton); + vboxPointsButtons.AddChild(ServerSyncButton); + vboxPointsButtons.AddChild(UnlockButton); + + vboxPoints.AddChild(_pointLabel); + vboxPoints.AddChild(_pointsPerSecondLabel); + vboxPoints.AddChild(vboxPointsButtons); + + _technologyIcon = new TextureRect() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + Stretch = TextureRect.StretchMode.KeepAspectCentered, + }; + _technologyName = new Label(); + _technologyDescription = new Label(); + _technologyRequirements = new Label(); + + vboxTechInfo.AddChild(_technologyName); + vboxTechInfo.AddChild(_technologyDescription); + vboxTechInfo.AddChild(_technologyRequirements); + + hboxSelected.AddChild(_technologyIcon); + hboxSelected.AddChild(vboxTechInfo); + hboxSelected.AddChild(vboxPoints); + + hboxTechnologies.AddChild(_unlockedTechnologies); + hboxTechnologies.AddChild(_unlockableTechnologies); + hboxTechnologies.AddChild(_futureTechnologies); + + vbox.AddChild(hboxTechnologies); + vbox.AddChild(hboxSelected); + + Contents.AddChild(vbox); + + UnlockButton.OnPressed += (args) => + { + CleanSelectedTechnology(); + }; + + Populate(); + } + + /// + /// Cleans the selected technology controls to blank. + /// + private void CleanSelectedTechnology() + { + UnlockButton.Disabled = true; + _technologyIcon.Texture = Texture.Transparent; + _technologyName.Text = ""; + _technologyDescription.Text = ""; + _technologyRequirements.Text = ""; + } + + /// + /// Called when an unlocked technology is selected. + /// + private void UnlockedTechnologySelected(ItemList.ItemListSelectedEventArgs obj) + { + TechnologySelected = _unlockedTechnologyPrototypes[obj.ItemIndex]; + + _unlockedTechnologies.Unselect(obj.ItemIndex); + + UnlockButton.Disabled = true; + + PopulateSelectedTechnology(); + } + + /// + /// Called when an unlockable technology is selected. + /// + private void UnlockableTechnologySelected(ItemList.ItemListSelectedEventArgs obj) + { + TechnologySelected = _unlockableTechnologyPrototypes[obj.ItemIndex]; + + _unlockableTechnologies.Unselect(obj.ItemIndex); + + UnlockButton.Disabled = Owner.Points < TechnologySelected.RequiredPoints; + + PopulateSelectedTechnology(); + } + + /// + /// Called when a future technology is selected + /// + private void FutureTechnologySelected(ItemList.ItemListSelectedEventArgs obj) + { + TechnologySelected = _futureTechnologyPrototypes[obj.ItemIndex]; + + _futureTechnologies.Unselect(obj.ItemIndex); + + UnlockButton.Disabled = true; + + PopulateSelectedTechnology(); + } + + /// + /// Populate all technologies in the ItemLists. + /// + public void PopulateItemLists() + { + _unlockedTechnologies.Clear(); + _unlockableTechnologies.Clear(); + _futureTechnologies.Clear(); + + _unlockedTechnologyPrototypes.Clear(); + _unlockableTechnologyPrototypes.Clear(); + _futureTechnologyPrototypes.Clear(); + + var prototypeMan = IoCManager.Resolve(); + + // For now, we retrieve all technologies. In the future, this should be changed. + foreach (var tech in prototypeMan.EnumeratePrototypes()) + { + if (Owner.IsTechnologyUnlocked(tech)) + { + _unlockedTechnologies.AddItem(tech.Name, tech.Icon.Frame0()); + _unlockedTechnologyPrototypes.Add(tech); + } + else if (Owner.CanUnlockTechnology(tech)) + { + _unlockableTechnologies.AddItem(tech.Name, tech.Icon.Frame0()); + _unlockableTechnologyPrototypes.Add(tech); + } + else + { + _futureTechnologies.AddItem(tech.Name, tech.Icon.Frame0());; + _futureTechnologyPrototypes.Add(tech); + } + } + } + + /// + /// Fills the selected technology controls with details. + /// + public void PopulateSelectedTechnology() + { + if (TechnologySelected == null) + { + _technologyName.Text = ""; + _technologyDescription.Text = ""; + _technologyRequirements.Text = ""; + return; + } + + _technologyIcon.Texture = TechnologySelected.Icon.Frame0(); + _technologyName.Text = TechnologySelected.Name; + _technologyDescription.Text = TechnologySelected.Description+$"\n{TechnologySelected.RequiredPoints} " + _localizationManager.GetString("research points"); + _technologyRequirements.Text = _localizationManager.GetString("No technology requirements."); + + var prototypeMan = IoCManager.Resolve(); + + for (var i = 0; i < TechnologySelected.RequiredTechnologies.Count; i++) + { + var requiredId = TechnologySelected.RequiredTechnologies[i]; + if (!prototypeMan.TryIndex(requiredId, out TechnologyPrototype prototype)) continue; + if (i == 0) + _technologyRequirements.Text = _localizationManager.GetString("Requires") + $": {prototype.Name}"; + else + _technologyRequirements.Text += $", {prototype.Name}"; + } + } + + /// + /// Updates the research point labels. + /// + public void PopulatePoints() + { + _pointLabel.Text = _localizationManager.GetString("Research Points") + $": {Owner.Points}"; + _pointsPerSecondLabel.Text = _localizationManager.GetString("Points per second") + $": {Owner.PointsPerSecond}"; + } + + /// + /// Updates the whole user interface. + /// + public void Populate() + { + PopulatePoints(); + PopulateSelectedTechnology(); + PopulateItemLists(); + } + } +} diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index fef9a48bca..201659a0fe 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -35,7 +35,7 @@ namespace Content.Server "SubFloorHide", "LowWall", "Window", - "CharacterInfo" + "CharacterInfo", }; foreach (var ignoreName in registerIgnore) diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index 705c2015e8..548ef3b368 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -6,6 +6,7 @@ using Content.Shared.GameObjects.Components.Research; using Content.Shared.Research; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.Timers; @@ -37,10 +38,9 @@ namespace Content.Server.GameObjects.Components.Research _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; } - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) + private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) { - var message = serverMsg.Message; - switch (message) + switch (message.Message) { case LatheQueueRecipeMessage msg: _prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); @@ -57,6 +57,20 @@ namespace Content.Server.GameObjects.Components.Research if (_producingRecipe != null) _userInterface.SendMessage(new LatheProducingRecipeMessage(_producingRecipe.ID)); break; + + case LatheServerSelectionMessage msg: + if (!Owner.TryGetComponent(out ResearchClientComponent researchClient)) return; + researchClient.OpenUserInterface(message.Session); + break; + + case LatheServerSyncMessage msg: + if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database) + || !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return; + + if(database.SyncWithServer()) + protoDatabase.Sync(); + + break; } } @@ -88,12 +102,17 @@ namespace Content.Server.GameObjects.Components.Research return true; } + public void OpenUserInterface(IPlayerSession session) + { + _userInterface.Open(session); + } + void IActivate.Activate(ActivateEventArgs eventArgs) { if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return; - _userInterface.Open(actor.playerSession); + OpenUserInterface(actor.playerSession); return; } bool IAttackBy.AttackBy(AttackByEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs b/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs index d43d5e052f..955bd935d1 100644 --- a/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs @@ -30,12 +30,14 @@ namespace Content.Server.GameObjects.Components.Research public override void Clear() { if (Static) return; + base.Clear(); Dirty(); } public override void AddRecipe(LatheRecipePrototype recipe) { if (Static) return; + base.AddRecipe(recipe); Dirty(); } diff --git a/Content.Server/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs b/Content.Server/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs new file mode 100644 index 0000000000..d14e953120 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/ProtolatheDatabaseComponent.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + [ComponentReference(typeof(SharedLatheDatabaseComponent))] + public class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent + { + public override string Name => "ProtolatheDatabase"; + + public override ComponentState GetComponentState() + { + return new ProtolatheDatabaseState(GetRecipeIdList()); + } + + /// + /// Adds unlocked recipes from technologies to the database. + /// + public void Sync() + { + if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)) return; + + var prototypeManager = IoCManager.Resolve(); + + foreach (var technology in database.Technologies) + { + foreach (var id in technology.UnlockedRecipes) + { + var recipe = (LatheRecipePrototype)prototypeManager.Index(typeof(LatheRecipePrototype), id); + UnlockRecipe(recipe); + } + } + + Dirty(); + } + + /// + /// Unlocks a recipe but only if it's one of the allowed recipes on this protolathe. + /// + /// The recipe + /// Whether it could add it or not. + public bool UnlockRecipe(LatheRecipePrototype recipe) + { + if (!ProtolatheRecipes.Contains(recipe)) return false; + + AddRecipe(recipe); + + return true; + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs new file mode 100644 index 0000000000..ee74335ad7 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/ResearchClientComponent.cs @@ -0,0 +1,109 @@ +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Research; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.GameObjects.Systems; +using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + public class ResearchClientComponent : SharedResearchClientComponent, IActivate + { + // TODO: Create GUI for changing RD server. + + private BoundUserInterface _userInterface; + +#pragma warning disable 649 + [Dependency] private readonly IEntitySystemManager _entitySystemManager; +#pragma warning restore 649 + + public bool ConnectedToServer => Server != null; + + [ViewVariables(VVAccess.ReadOnly)] + public ResearchServerComponent Server { get; set; } + + public bool RegisterServer(ResearchServerComponent server) + { + var result = server != null && server.RegisterClient(this); + return result; + } + + public void UnregisterFromServer() + { + Server?.UnregisterClient(this); + } + + public override void Initialize() + { + base.Initialize(); + // For now it just registers on the first server it can find. + var servers = _entitySystemManager.GetEntitySystem().Servers; + if(servers.Count > 0) + RegisterServer(servers[0]); + _userInterface = Owner.GetComponent().GetBoundUserInterface(ResearchClientUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + public void OpenUserInterface(IPlayerSession session) + { + UpdateUserInterface(); + _userInterface.Open(session); + } + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return; + + OpenUserInterface(actor.playerSession); + return; + } + + public void UpdateUserInterface() + { + _userInterface?.SetState(GetNewUiState()); + } + + private ResearchClientBoundInterfaceState GetNewUiState() + { + var rd = _entitySystemManager.GetEntitySystem(); + + return new ResearchClientBoundInterfaceState(rd.Servers.Count, rd.GetServerNames(), + rd.GetServerIds(), ConnectedToServer ? Server.Id : -1); + } + + private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage msg) + { + switch (msg.Message) + { + case ResearchClientSyncMessage _: + UpdateUserInterface(); + break; + + case ResearchClientServerSelectedMessage selectedMessage: + UnregisterFromServer(); + RegisterServer(_entitySystemManager.GetEntitySystem().GetServerById(selectedMessage.ServerId)); + UpdateUserInterface(); + break; + + case ResearchClientServerDeselectedMessage _: + UnregisterFromServer(); + UpdateUserInterface(); + break; + } + } + + public override void Shutdown() + { + base.Shutdown(); + UnregisterFromServer(); + + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs new file mode 100644 index 0000000000..63e083c653 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs @@ -0,0 +1,94 @@ +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class ResearchConsoleComponent : SharedResearchConsoleComponent, IActivate + { + private BoundUserInterface _userInterface; + private ResearchClientComponent _client; + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent().GetBoundUserInterface(ResearchConsoleUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + _client = Owner.GetComponent(); + } + + private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) + { + if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)) return; + + switch (message.Message) + { + case ConsoleUnlockTechnologyMessage msg: + var protoMan = IoCManager.Resolve(); + if (!protoMan.TryIndex(msg.Id, out TechnologyPrototype tech)) break; + if(!_client.Server.CanUnlockTechnology(tech)) break; + if (_client.Server.UnlockTechnology(tech)) + { + database.SyncWithServer(); + database.Dirty(); + UpdateUserInterface(); + } + + break; + + case ConsoleServerSyncMessage msg: + database.SyncWithServer(); + UpdateUserInterface(); + break; + + case ConsoleServerSelectionMessage msg: + if (!Owner.TryGetComponent(out ResearchClientComponent client)) break; + client.OpenUserInterface(message.Session); + break; + } + } + + /// + /// Method to update the user interface on the clients. + /// + public void UpdateUserInterface() + { + _userInterface.SetState(GetNewUiState()); + } + + private ResearchConsoleBoundInterfaceState GetNewUiState() + { + var points = _client.ConnectedToServer ? _client.Server.Point : 0; + var pointsPerSecond = _client.ConnectedToServer ? _client.Server.PointsPerSecond : 0; + + return new ResearchConsoleBoundInterfaceState(points, pointsPerSecond); + } + + /// + /// Open the user interface on a certain player session. + /// + /// Session where the UI will be shown + public void OpenUserInterface(IPlayerSession session) + { + _userInterface.Open(session); + } + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return; + + OpenUserInterface(actor.playerSession); + return; + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/ResearchPointSourceComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchPointSourceComponent.cs new file mode 100644 index 0000000000..905f2ef8f4 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/ResearchPointSourceComponent.cs @@ -0,0 +1,40 @@ +using Content.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class ResearchPointSourceComponent : ResearchClientComponent + { + public override string Name => "ResearchPointSource"; + + private int _pointsPerSecond; + private bool _active; + + [ViewVariables] + public int PointsPerSecond + { + get => _pointsPerSecond; + set => value = _pointsPerSecond; + } + + [ViewVariables] + public bool Active + { + get => _active; + set => value = _active; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _pointsPerSecond, "pointspersecond", 0); + serializer.DataField(ref _active, "active", false); + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs new file mode 100644 index 0000000000..7c0faf5e22 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/ResearchServerComponent.cs @@ -0,0 +1,162 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices.WindowsRuntime; +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + public class ResearchServerComponent : Component + { + public static int ServerCount = 0; + + public override string Name => "ResearchServer"; + + [ViewVariables(VVAccess.ReadWrite)] + public string ServerName => _serverName; + + private string _serverName = "RDSERVER"; + private float _timer = 0f; + public TechnologyDatabaseComponent Database { get; private set; } + + [ViewVariables(VVAccess.ReadWrite)] + private int _points = 0; + + [ViewVariables(VVAccess.ReadOnly)] + public int Id { get; private set; } + + // You could optimize research by keeping a list of unlocked recipes too. + [ViewVariables(VVAccess.ReadOnly)] + public IReadOnlyList UnlockedTechnologies => Database.Technologies; + [ViewVariables(VVAccess.ReadOnly)] + public List PointSources { get; } = new List(); + [ViewVariables(VVAccess.ReadOnly)] + public List Clients { get; } = new List(); + + public int Point => _points; + + /// + /// How many points per second this R&D server gets. + /// The value is calculated from all point sources connected to it. + /// + [ViewVariables(VVAccess.ReadOnly)] + public int PointsPerSecond + { + // This could be changed to PointsPerMinute quite easily for optimization. + get + { + var points = 0; + + foreach (var source in PointSources) + { + if (source.Active) points += source.PointsPerSecond; + } + + return points; + } + } + + public override void Initialize() + { + base.Initialize(); + Id = ServerCount++; + IoCManager.Resolve()?.GetEntitySystem()?.RegisterServer(this); + Database = Owner.GetComponent(); + } + + public override void Shutdown() + { + base.Shutdown(); + IoCManager.Resolve()?.GetEntitySystem()?.UnregisterServer(this); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _serverName, "servername", "RDSERVER"); + serializer.DataField(ref _points, "points", 0); + } + + public bool CanUnlockTechnology(TechnologyPrototype technology) + { + if (!Database.CanUnlockTechnology(technology) || _points < technology.RequiredPoints || Database.IsTechnologyUnlocked(technology)) return false; + return true; + } + + /// + /// Unlocks a technology, but only if there are enough research points for it. + /// If there are, it subtracts the amount of points from the total. + /// + /// + /// + public bool UnlockTechnology(TechnologyPrototype technology) + { + if (!CanUnlockTechnology(technology)) return false; + var result = Database.UnlockTechnology(technology); + if(result) + _points -= technology.RequiredPoints; + return result; + } + + /// + /// Check whether a technology is unlocked or not. + /// + /// + /// + public bool IsTechnologyUnlocked(TechnologyPrototype technology) + { + return Database.IsTechnologyUnlocked(technology); + } + + /// + /// Registers a remote client on this research server. + /// + /// + /// + public bool RegisterClient(ResearchClientComponent client) + { + if (client is ResearchPointSourceComponent source) + { + if (PointSources.Contains(source)) return false; + PointSources.Add(source); + source.Server = this; + return true; + } + + if (Clients.Contains(client)) return false; + Clients.Add(client); + client.Server = this; + return true; + } + + /// + /// Unregisters a remote client from this server. + /// + /// + public void UnregisterClient(ResearchClientComponent client) + { + if (client is ResearchPointSourceComponent source) + { + PointSources.Remove(source); + return; + } + + Clients.Remove(client); + } + + public void Update(float frameTime) + { + _timer += frameTime; + if (_timer < 1f) return; + _timer = 0f; + _points += PointsPerSecond; + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs b/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs new file mode 100644 index 0000000000..ccdc0eaf46 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs @@ -0,0 +1,77 @@ +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameObjects.Components.Research +{ + [RegisterComponent] + public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent + { + public override ComponentState GetComponentState() + { + return new TechnologyDatabaseState(_technologies); + } + + /// + /// Synchronizes this database against other, + /// adding all technologies from the other that + /// this one doesn't have. + /// + /// The other database + /// Whether the other database should be synced against this one too or not. + public void Sync(TechnologyDatabaseComponent otherDatabase, bool twoway = true) + { + foreach (var tech in otherDatabase.Technologies) + { + if (!IsTechnologyUnlocked(tech)) AddTechnology(tech); + } + + if(twoway) + otherDatabase.Sync(this, false); + + Dirty(); + } + + /// + /// If there's a research client component attached to the owner entity, + /// and the research client is connected to a research server, this method + /// syncs against the research server, and the server against the local database. + /// + /// Whether it could sync or not + public bool SyncWithServer() + { + if (!Owner.TryGetComponent(out ResearchClientComponent client)) return false; + if (!client.ConnectedToServer) return false; + + Sync(client.Server.Database); + + return true; + } + + /// + /// If possible, unlocks a technology on this database. + /// + /// + /// + public bool UnlockTechnology(TechnologyPrototype technology) + { + if (!CanUnlockTechnology(technology)) return false; + + AddTechnology(technology); + Dirty(); + return true; + } + + /// + /// Adds a technology to the database without checking if it could be unlocked. + /// + /// + public void AddTechnology(TechnologyPrototype technology) + { + _technologies.Add(technology); + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/ResearchSystem.cs b/Content.Server/GameObjects/EntitySystems/ResearchSystem.cs new file mode 100644 index 0000000000..6e4734139b --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/ResearchSystem.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class ResearchSystem : EntitySystem + { + public const float ResearchConsoleUIUpdateTime = 30f; + + private float _timer = ResearchConsoleUIUpdateTime; + private readonly List _servers = new List(); + private readonly IEntityQuery ConsoleQuery; + public IReadOnlyList Servers => _servers; + + public ResearchSystem() + { + ConsoleQuery = new TypeEntityQuery(typeof(ResearchConsoleComponent)); + } + + public bool RegisterServer(ResearchServerComponent server) + { + if (_servers.Contains(server)) return false; + _servers.Add(server); + return true; + } + + public void UnregisterServer(ResearchServerComponent server) + { + _servers.Remove(server); + } + + public ResearchServerComponent GetServerById(int id) + { + foreach (var server in Servers) + { + if (server.Id == id) return server; + } + + return null; + } + + public string[] GetServerNames() + { + var list = new string[Servers.Count]; + + for (var i = 0; i < Servers.Count; i++) + { + list[i] = Servers[i].ServerName; + } + + return list; + } + + public int[] GetServerIds() + { + var list = new int[Servers.Count]; + + for (var i = 0; i < Servers.Count; i++) + { + list[i] = Servers[i].Id; + } + + return list; + } + + public override void Update(float frameTime) + { + _timer += frameTime; + + foreach (var server in _servers) + { + server.Update(frameTime); + } + + if (_timer >= ResearchConsoleUIUpdateTime) + { + foreach (var console in EntityManager.GetEntities(ConsoleQuery)) + { + console.GetComponent().UpdateUserInterface(); + } + + _timer = 0f; + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs index c5ab7f76e2..fd35b3cffc 100644 --- a/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs +++ b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs @@ -54,6 +54,30 @@ namespace Content.Shared.GameObjects.Components.Research } } + + + /// + /// Sent to the server to sync the lathe's technology database with the research server. + /// + [Serializable, NetSerializable] + public class LatheServerSyncMessage : BoundUserInterfaceMessage + { + public LatheServerSyncMessage() + { + } + } + + /// + /// Sent to the server to open the ResearchClient UI. + /// + [Serializable, NetSerializable] + public class LatheServerSelectionMessage : BoundUserInterfaceMessage + { + public LatheServerSelectionMessage() + { + } + } + /// /// Sent to the client when the lathe is producing a recipe. /// diff --git a/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs index 91ae1c1737..67e5f56701 100644 --- a/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs +++ b/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs @@ -13,8 +13,8 @@ namespace Content.Shared.GameObjects.Components.Research public class SharedLatheDatabaseComponent : Component, IEnumerable { public override string Name => "LatheDatabase"; - public sealed override uint? NetID => ContentNetIDs.LATHE_DATABASE; - public sealed override Type StateType => typeof(LatheDatabaseState); + public override uint? NetID => ContentNetIDs.LATHE_DATABASE; + public override Type StateType => typeof(LatheDatabaseState); private List _recipes = new List(); @@ -34,7 +34,8 @@ namespace Content.Shared.GameObjects.Components.Research /// Whether it could be added or not public virtual void AddRecipe(LatheRecipePrototype recipe) { - _recipes.Add(recipe); + if(!Contains(recipe)) + _recipes.Add(recipe); } /// diff --git a/Content.Shared/GameObjects/Components/Research/SharedProtolatheDatabaseComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedProtolatheDatabaseComponent.cs new file mode 100644 index 0000000000..a0694f87ef --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedProtolatheDatabaseComponent.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Research +{ + + [ComponentReference(typeof(SharedLatheDatabaseComponent))] + public class SharedProtolatheDatabaseComponent : SharedLatheDatabaseComponent + { + public override string Name => "ProtolatheDatabase"; + public sealed override uint? NetID => ContentNetIDs.PROTOLATHE_DATABASE; + public sealed override Type StateType => typeof(ProtolatheDatabaseState); + + private List _protolatheRecipes = new List(); + + /// + /// A full list of recipes this protolathe can print. + /// + public List ProtolatheRecipes => _protolatheRecipes; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + if (serializer.Reading) + { + var recipes = serializer.ReadDataField("protolatherecipes", new List()); + var prototypeManager = IoCManager.Resolve(); + foreach (var id in recipes) + { + if (!prototypeManager.TryIndex(id, out LatheRecipePrototype recipe)) continue; + _protolatheRecipes.Add(recipe); + } + } else if (serializer.Writing) + { + var recipes = GetProtolatheRecipeIdList(); + serializer.DataField(ref recipes, "protolatherecipes", new List()); + } + } + + /// + /// Returns a list of the allowed protolathe recipe IDs. + /// + /// A list of recipe IDs allowed + public List GetProtolatheRecipeIdList() + { + var list = new List(); + + foreach (var recipe in ProtolatheRecipes) + { + list.Add(recipe.ID); + } + + return list; + } + } + + [NetSerializable, Serializable] + public class ProtolatheDatabaseState : ComponentState + { + public readonly List Recipes; + public ProtolatheDatabaseState(List recipes) : base(ContentNetIDs.PROTOLATHE_DATABASE) + { + Recipes = recipes; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedResearchClientComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedResearchClientComponent.cs new file mode 100644 index 0000000000..7551b7ddd4 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedResearchClientComponent.cs @@ -0,0 +1,73 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedResearchClientComponent : Component + { + public override string Name => "ResearchClient"; + + /// + /// Request that the server updates the client. + /// + [Serializable, NetSerializable] + public class ResearchClientSyncMessage : BoundUserInterfaceMessage + { + + public ResearchClientSyncMessage() + { + } + } + + /// + /// Sent to the server when the client chooses a research server. + /// + [Serializable, NetSerializable] + public class ResearchClientServerSelectedMessage : BoundUserInterfaceMessage + { + public int ServerId; + + public ResearchClientServerSelectedMessage(int serverId) + { + ServerId = serverId; + } + } + + /// + /// Sent to the server when the client deselects a research server. + /// + [Serializable, NetSerializable] + public class ResearchClientServerDeselectedMessage : BoundUserInterfaceMessage + { + public ResearchClientServerDeselectedMessage() + { + } + } + + [NetSerializable, Serializable] + public enum ResearchClientUiKey + { + Key, + } + + [Serializable, NetSerializable] + public sealed class ResearchClientBoundInterfaceState : BoundUserInterfaceState + { + public int ServerCount; + public string[] ServerNames; + public int[] ServerIds; + public int SelectedServerId; + + public ResearchClientBoundInterfaceState(int serverCount, string[] serverNames, int[] serverIds, int selectedServerId = -1) + { + ServerCount = serverCount; + ServerNames = serverNames; + ServerIds = serverIds; + SelectedServerId = selectedServerId; + } + } + } + +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedResearchConsoleComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedResearchConsoleComponent.cs new file mode 100644 index 0000000000..433f145198 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedResearchConsoleComponent.cs @@ -0,0 +1,55 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedResearchConsoleComponent : Component + { + public override string Name => "ResearchConsole"; + public override uint? NetID => ContentNetIDs.RESEARCH_CONSOLE; + + [NetSerializable, Serializable] + public enum ResearchConsoleUiKey + { + Key, + } + + [Serializable, NetSerializable] + public class ConsoleUnlockTechnologyMessage : BoundUserInterfaceMessage + { + public string Id; + public ConsoleUnlockTechnologyMessage(string id) + { + Id = id; + } + } + + [Serializable, NetSerializable] + public class ConsoleServerSyncMessage : BoundUserInterfaceMessage + { + public ConsoleServerSyncMessage() + {} + } + + [Serializable, NetSerializable] + public class ConsoleServerSelectionMessage : BoundUserInterfaceMessage + { + public ConsoleServerSelectionMessage() + {} + } + + [Serializable, NetSerializable] + public sealed class ResearchConsoleBoundInterfaceState : BoundUserInterfaceState + { + public int Points; + public int PointsPerSecond; + public ResearchConsoleBoundInterfaceState(int points, int pointsPerSecond) + { + Points = points; + PointsPerSecond = pointsPerSecond; + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedTechnologyDatabaseComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedTechnologyDatabaseComponent.cs new file mode 100644 index 0000000000..12f0af4052 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedTechnologyDatabaseComponent.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedTechnologyDatabaseComponent : Component, IEnumerable + { + public override string Name => "TechnologyDatabase"; + public override uint? NetID => ContentNetIDs.TECHNOLOGY_DATABASE; + public override Type StateType => typeof(TechnologyDatabaseState); + + protected List _technologies = new List(); + + /// + /// A read-only list of unlocked technologies. + /// + public IReadOnlyList Technologies => _technologies; + + public IEnumerator GetEnumerator() + { + return Technologies.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Returns a list with the IDs of all unlocked technologies. + /// + /// A list of technology IDs + public List GetTechnologyIdList() + { + List techIds = new List(); + + foreach (var tech in _technologies) + { + techIds.Add(tech.ID); + } + + return techIds; + } + + /// + /// Returns whether a technology is unlocked on this database or not. + /// + /// The technology to be checked + /// Whether it is unlocked or not + public bool IsTechnologyUnlocked(TechnologyPrototype technology) + { + return _technologies.Contains(technology); + } + + /// + /// Returns whether a technology can be unlocked on this database, + /// taking parent technologies into account. + /// + /// The technology to be checked + /// Whether it could be unlocked or not + public bool CanUnlockTechnology(TechnologyPrototype technology) + { + if (technology == null || IsTechnologyUnlocked(technology)) return false; + var protoMan = IoCManager.Resolve(); + foreach (var technologyId in technology.RequiredTechnologies) + { + protoMan.TryIndex(technologyId, out TechnologyPrototype requiredTechnology); + if (requiredTechnology == null) + return false; + + if (!IsTechnologyUnlocked(requiredTechnology)) + return false; + } + return true; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + if (serializer.Reading) + { + var techs = serializer.ReadDataField("technologies", new List()); + var prototypeManager = IoCManager.Resolve(); + foreach (var id in techs) + { + if (!prototypeManager.TryIndex(id, out TechnologyPrototype tech)) continue; + _technologies.Add(tech); + } + } else if (serializer.Writing) + { + var techs = GetTechnologyIdList(); + serializer.DataField(ref techs, "technologies", new List()); + } + } + } + + [Serializable, NetSerializable] + public class TechnologyDatabaseState : ComponentState + { + public List Technologies; + public TechnologyDatabaseState(List technologies) : base(ContentNetIDs.TECHNOLOGY_DATABASE) + { + technologies = technologies; + } + + public TechnologyDatabaseState(List technologies) : base(ContentNetIDs.TECHNOLOGY_DATABASE) + { + Technologies = new List(); + foreach (var technology in technologies) + { + Technologies.Add(technology.ID); + } + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index dcb8609547..28d43f282a 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -24,6 +24,9 @@ public const uint MATERIAL_STORAGE = 1018; public const uint HAND_TELEPORTER = 1019; public const uint VENDING_MACHINE = 1020; - public const uint WIRES = 1021; + public const uint PROTOLATHE_DATABASE = 1021; + public const uint TECHNOLOGY_DATABASE = 1022; + public const uint RESEARCH_CONSOLE = 1023; + public const uint WIRES = 1024; } } diff --git a/Content.Shared/Research/LatheRecipePrototype.cs b/Content.Shared/Research/LatheRecipePrototype.cs index 2f82b046f0..8ae61746e0 100644 --- a/Content.Shared/Research/LatheRecipePrototype.cs +++ b/Content.Shared/Research/LatheRecipePrototype.cs @@ -6,6 +6,7 @@ using Robust.Shared.IoC; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; using YamlDotNet.RepresentationModel; namespace Content.Shared.Research @@ -21,11 +22,13 @@ namespace Content.Shared.Research private int _completeTime; private Dictionary _requiredMaterials; + [ViewVariables] public string ID => _id; /// /// Name displayed in the lathe GUI. /// + [ViewVariables] public string Name { get @@ -43,6 +46,7 @@ namespace Content.Shared.Research /// /// Short description displayed in the lathe GUI. /// + [ViewVariables] public string Description { get @@ -60,17 +64,20 @@ namespace Content.Shared.Research /// /// Texture path used in the lathe GUI. /// + [ViewVariables] public SpriteSpecifier Icon => _icon; /// /// The prototype name of the resulting entity when the recipe is printed. /// + [ViewVariables] public string Result => _result; /// /// The materials required to produce this recipe. /// Takes a material ID as string. /// + [ViewVariables] public Dictionary RequiredMaterials { get => _requiredMaterials; @@ -78,10 +85,11 @@ namespace Content.Shared.Research } - /// + /// /// How many milliseconds it'll take for the lathe to finish this recipe. /// Might lower depending on the lathe's upgrade level. /// + [ViewVariables] public int CompleteTime => _completeTime; public void LoadFrom(YamlMappingNode mapping) diff --git a/Content.Shared/Research/TechnologyPrototype.cs b/Content.Shared/Research/TechnologyPrototype.cs new file mode 100644 index 0000000000..bd41e2016e --- /dev/null +++ b/Content.Shared/Research/TechnologyPrototype.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Research +{ + [NetSerializable, Serializable, Prototype("technology")] + public class TechnologyPrototype : IPrototype, IIndexedPrototype + { + private string _name; + private string _id; + private SpriteSpecifier _icon; + private string _description; + private int _requiredPoints; + private List _requiredTechnologies; + private List _unlockedRecipes; + + /// + /// The ID of this technology prototype. + /// + [ViewVariables] + public string ID => _id; + + /// + /// The name this technology will have on user interfaces. + /// + [ViewVariables] + public string Name => _name; + + /// + /// An icon that represent this technology. + /// + public SpriteSpecifier Icon => _icon; + + /// + /// A short description of the technology. + /// + [ViewVariables] + public string Description => _description; + + /// + /// The required research points to unlock this technology. + /// + [ViewVariables] + public int RequiredPoints => _requiredPoints; + + /// + /// A list of technology IDs required to unlock this technology. + /// + [ViewVariables] + public List RequiredTechnologies => _requiredTechnologies; + + /// + /// A list of recipe IDs this technology unlocks. + /// + [ViewVariables] + public List UnlockedRecipes => _unlockedRecipes; + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _description, "description", string.Empty); + serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid); + serializer.DataField(ref _requiredPoints, "requiredpoints", 0); + serializer.DataField(ref _requiredTechnologies, "requiredtechnologies", new List()); + serializer.DataField(ref _unlockedRecipes, "unlockedrecipes", new List()); + } + } +} diff --git a/Resources/Prototypes/Entities/Research.yml b/Resources/Prototypes/Entities/Research.yml new file mode 100644 index 0000000000..3b0aa90fc3 --- /dev/null +++ b/Resources/Prototypes/Entities/Research.yml @@ -0,0 +1,40 @@ +- type: entity + id: researchAndDevelopmentServer + name: "R&D Server" + components: + - type: Sprite + sprite: Buildings/research.rsi + state: server + - type: Icon + sprite: Buildings/research.rsi + state: server + - type: Clickable + - type: BoundingBox + - type: Collidable + - type: SnapGrid + offset: Center + - type: ResearchServer + - type: TechnologyDatabase + +- type: entity + id: baseResearchAndDevelopmentPointSource + name: "Base R&D Point Source" + components: + - type: Sprite + sprite: Buildings/research.rsi + state: tdoppler + - type: Icon + sprite: Buildings/research.rsi + state: tdoppler + - type: Clickable + - type: BoundingBox + - type: Collidable + - type: SnapGrid + offset: Center + - type: ResearchPointSource + pointspersecond: 1000 + active: true + - type: UserInterface + interfaces: + - key: enum.ResearchClientUiKey.Key + type: ResearchClientBoundUserInterface \ No newline at end of file diff --git a/Resources/Prototypes/Entities/buildings/computers.yml b/Resources/Prototypes/Entities/buildings/computers.yml index 9de6036e27..9b7e604a1f 100644 --- a/Resources/Prototypes/Entities/buildings/computers.yml +++ b/Resources/Prototypes/Entities/buildings/computers.yml @@ -10,7 +10,6 @@ - type: Icon sprite: Buildings/computer.rsi state: computer - - type: Computer - type: PowerDevice priority: High @@ -94,6 +93,15 @@ - type: ComputerVisualizer2D key: rd_key screen: rdcomp + - type: ResearchClient + - type: ResearchConsole + - type: TechnologyDatabase + - type: UserInterface + interfaces: + - key: enum.ResearchConsoleUiKey.Key + type: ResearchConsoleBoundUserInterface + - key: enum.ResearchClientUiKey.Key + type: ResearchClientBoundUserInterface - type: entity diff --git a/Resources/Prototypes/Entities/buildings/lathe.yml b/Resources/Prototypes/Entities/buildings/lathe.yml index d17cd313be..a96a6d2953 100644 --- a/Resources/Prototypes/Entities/buildings/lathe.yml +++ b/Resources/Prototypes/Entities/buildings/lathe.yml @@ -1,8 +1,25 @@ - type: entity + id: BaseLathe + name: "Lathe" + components: + - type: Clickable + - type: BoundingBox + - type: Collidable + - type: SnapGrid + offset: Center + - type: Lathe + - type: MaterialStorage + - type: UserInterface + interfaces: + - key: enum.LatheUiKey.Key + type: LatheBoundUserInterface + + +- type: entity + parent: BaseLathe id: autolathe name: "Autolathe" components: - - type: Clickable - type: Sprite sprite: Buildings/autolathe.rsi state: idle @@ -33,8 +50,45 @@ - CableStack - Crowbar - Multitool - - type: MaterialStorage + +- type: entity + parent: BaseLathe + id: protolathe + name: "Protolathe" + components: + - type: Sprite + sprite: Buildings/research.rsi + state: protolathe + - type: Icon + sprite: Buildings/research.rsi + state: protolathe + - type: Collidable + shapes: + - !type:PhysShapeAabb + mask: 19 + layer: 16 + - type: SnapGrid + offset: Center + - type: ResearchClient + - type: TechnologyDatabase + - type: ProtolatheDatabase + protolatherecipes: + - Brutepack + - Ointment + - LightTube + - LightBulb + - MetalStack + - GlassStack + - Wirecutter + - Screwdriver + - Welder + - Wrench + - CableStack + - Crowbar + - Multitool - type: UserInterface interfaces: - key: enum.LatheUiKey.Key - type: LatheBoundUserInterface \ No newline at end of file + type: LatheBoundUserInterface + - key: enum.ResearchClientUiKey.Key + type: ResearchClientBoundUserInterface \ No newline at end of file diff --git a/Resources/Prototypes/Technologies/sheet.yml b/Resources/Prototypes/Technologies/sheet.yml new file mode 100644 index 0000000000..8106312e76 --- /dev/null +++ b/Resources/Prototypes/Technologies/sheet.yml @@ -0,0 +1,26 @@ +- type: technology + name: "Test Technology 1" + id: Parent1 + description: Parent technology needed for sheets + icon: Objects/sheet_glass.png + requiredpoints: 5000 + +- type: technology + name: "Test Technology 2" + id: Parent2 + description: Parent technology 2 needed for sheets + icon: Objects/sheet_glass.png + requiredpoints: 5000 + +- type: technology + name: Material sheet printing + id: Sheets + description: Print those sheets! + icon: Objects/sheet_metal.png + requiredpoints: 500 + requiredtechnologies: + - Parent1 + - Parent2 + unlockedrecipes: + - MetalStack + - GlassStack \ No newline at end of file diff --git a/Resources/Textures/Buildings/research.rsi/circuit_imprinter.png b/Resources/Textures/Buildings/research.rsi/circuit_imprinter.png new file mode 100644 index 0000000000..c147a0743a Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/circuit_imprinter.png differ diff --git a/Resources/Textures/Buildings/research.rsi/circuit_imprinter_ani.png b/Resources/Textures/Buildings/research.rsi/circuit_imprinter_ani.png new file mode 100644 index 0000000000..c598e271b2 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/circuit_imprinter_ani.png differ diff --git a/Resources/Textures/Buildings/research.rsi/circuit_imprinter_t.png b/Resources/Textures/Buildings/research.rsi/circuit_imprinter_t.png new file mode 100644 index 0000000000..9177717715 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/circuit_imprinter_t.png differ diff --git a/Resources/Textures/Buildings/research.rsi/d_analyzer.png b/Resources/Textures/Buildings/research.rsi/d_analyzer.png new file mode 100644 index 0000000000..54fbf13c18 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/d_analyzer.png differ diff --git a/Resources/Textures/Buildings/research.rsi/d_analyzer_l.png b/Resources/Textures/Buildings/research.rsi/d_analyzer_l.png new file mode 100644 index 0000000000..86c82e87df Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/d_analyzer_l.png differ diff --git a/Resources/Textures/Buildings/research.rsi/d_analyzer_la.png b/Resources/Textures/Buildings/research.rsi/d_analyzer_la.png new file mode 100644 index 0000000000..54342922f8 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/d_analyzer_la.png differ diff --git a/Resources/Textures/Buildings/research.rsi/d_analyzer_process.png b/Resources/Textures/Buildings/research.rsi/d_analyzer_process.png new file mode 100644 index 0000000000..c18ad48f9a Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/d_analyzer_process.png differ diff --git a/Resources/Textures/Buildings/research.rsi/d_analyzer_t.png b/Resources/Textures/Buildings/research.rsi/d_analyzer_t.png new file mode 100644 index 0000000000..27179ba0f7 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/d_analyzer_t.png differ diff --git a/Resources/Textures/Buildings/research.rsi/meta.json b/Resources/Textures/Buildings/research.rsi/meta.json new file mode 100644 index 0000000000..d30c7ab0e8 --- /dev/null +++ b/Resources/Textures/Buildings/research.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/machines/excelsior/autolathe.dmi at 40b254106b46981b8ad95ccd5589deb8fa56e765", "states": [{"name": "circuit_imprinter", "directions": 1, "delays": [[1.0]]}, {"name": "circuit_imprinter_ani", "directions": 1, "delays": [[0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08]]}, {"name": "circuit_imprinter_t", "directions": 1, "delays": [[1.0]]}, {"name": "d_analyzer", "directions": 1, "delays": [[1.0]]}, {"name": "d_analyzer_l", "directions": 1, "delays": [[1.0]]}, {"name": "d_analyzer_la", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "d_analyzer_process", "directions": 1, "delays": [[0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09]]}, {"name": "d_analyzer_t", "directions": 1, "delays": [[1.0]]}, {"name": "protolathe", "directions": 1, "delays": [[1.0]]}, {"name": "protolathe_adamantine", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_bananium", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_diamond", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_glass", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_gold", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_metal", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_n", "directions": 1, "delays": [[0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05]]}, {"name": "protolathe_silver", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_solid plasma", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "protolathe_t", "directions": 1, "delays": [[1.0]]}, {"name": "protolathe_uranium", "directions": 1, "delays": [[0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088, 0.088]]}, {"name": "server", "directions": 1, "delays": [[1.0]]}, {"name": "server-nopower", "directions": 1, "delays": [[1.0]]}, {"name": "server-off", "directions": 1, "delays": [[1.0]]}, {"name": "server-on", "directions": 1, "delays": [[1.0]]}, {"name": "server_o", "directions": 1, "delays": [[1.0]]}, {"name": "tdoppler", "directions": 4, "delays": [[0.8, 0.2], [0.8, 0.2], [0.8, 0.2], [0.8, 0.2]]}, {"name": "tdoppler-broken", "directions": 1, "delays": [[0.1, 0.1, 0.1]]}, {"name": "tdoppler-off", "directions": 1, "delays": [[1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Buildings/research.rsi/protolathe.png b/Resources/Textures/Buildings/research.rsi/protolathe.png new file mode 100644 index 0000000000..0eb2d2ce37 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_adamantine.png b/Resources/Textures/Buildings/research.rsi/protolathe_adamantine.png new file mode 100644 index 0000000000..16e046556e Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_adamantine.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_bananium.png b/Resources/Textures/Buildings/research.rsi/protolathe_bananium.png new file mode 100644 index 0000000000..2ba3e3ecf5 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_bananium.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_diamond.png b/Resources/Textures/Buildings/research.rsi/protolathe_diamond.png new file mode 100644 index 0000000000..48b16a1879 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_diamond.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_glass.png b/Resources/Textures/Buildings/research.rsi/protolathe_glass.png new file mode 100644 index 0000000000..6076271b76 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_glass.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_gold.png b/Resources/Textures/Buildings/research.rsi/protolathe_gold.png new file mode 100644 index 0000000000..391e8be6cf Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_gold.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_metal.png b/Resources/Textures/Buildings/research.rsi/protolathe_metal.png new file mode 100644 index 0000000000..4f762ab383 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_metal.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_n.png b/Resources/Textures/Buildings/research.rsi/protolathe_n.png new file mode 100644 index 0000000000..143e17d0be Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_n.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_silver.png b/Resources/Textures/Buildings/research.rsi/protolathe_silver.png new file mode 100644 index 0000000000..e48844d6be Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_silver.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_solid plasma.png b/Resources/Textures/Buildings/research.rsi/protolathe_solid plasma.png new file mode 100644 index 0000000000..668c5453a6 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_solid plasma.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_t.png b/Resources/Textures/Buildings/research.rsi/protolathe_t.png new file mode 100644 index 0000000000..06128a0926 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_t.png differ diff --git a/Resources/Textures/Buildings/research.rsi/protolathe_uranium.png b/Resources/Textures/Buildings/research.rsi/protolathe_uranium.png new file mode 100644 index 0000000000..eb7a83f390 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/protolathe_uranium.png differ diff --git a/Resources/Textures/Buildings/research.rsi/server-nopower.png b/Resources/Textures/Buildings/research.rsi/server-nopower.png new file mode 100644 index 0000000000..a471835d97 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/server-nopower.png differ diff --git a/Resources/Textures/Buildings/research.rsi/server-off.png b/Resources/Textures/Buildings/research.rsi/server-off.png new file mode 100644 index 0000000000..a8c0d3dfb9 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/server-off.png differ diff --git a/Resources/Textures/Buildings/research.rsi/server-on.png b/Resources/Textures/Buildings/research.rsi/server-on.png new file mode 100644 index 0000000000..1de92ad7d0 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/server-on.png differ diff --git a/Resources/Textures/Buildings/research.rsi/server.png b/Resources/Textures/Buildings/research.rsi/server.png new file mode 100644 index 0000000000..dc18461b21 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/server.png differ diff --git a/Resources/Textures/Buildings/research.rsi/server_o.png b/Resources/Textures/Buildings/research.rsi/server_o.png new file mode 100644 index 0000000000..3df09c8f43 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/server_o.png differ diff --git a/Resources/Textures/Buildings/research.rsi/tdoppler-broken.png b/Resources/Textures/Buildings/research.rsi/tdoppler-broken.png new file mode 100644 index 0000000000..35194c227a Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/tdoppler-broken.png differ diff --git a/Resources/Textures/Buildings/research.rsi/tdoppler-off.png b/Resources/Textures/Buildings/research.rsi/tdoppler-off.png new file mode 100644 index 0000000000..e8aa031b11 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/tdoppler-off.png differ diff --git a/Resources/Textures/Buildings/research.rsi/tdoppler.png b/Resources/Textures/Buildings/research.rsi/tdoppler.png new file mode 100644 index 0000000000..67f4452a53 Binary files /dev/null and b/Resources/Textures/Buildings/research.rsi/tdoppler.png differ