From 5dbb3220ddbf515f7265405555138b9d9e8474d8 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 28 Jun 2022 22:54:08 +1000 Subject: [PATCH] ECS and bandaid research (#9251) --- Content.Client/Entry/EntryPoint.cs | 3 +- .../Research/TechnologyDatabaseComponent.cs | 4 +- .../UI/ResearchClientBoundUserInterface.cs | 3 +- .../UI/ResearchConsoleBoundUserInterface.cs | 4 +- .../Systems/NetworkConfiguratorSystem.cs | 2 +- Content.Server/Lathe/LatheSystem.cs | 8 +- .../Components/ResearchClientComponent.cs | 88 +---------- .../Components/ResearchConsoleComponent.cs | 86 +---------- .../ResearchPointSourceComponent.cs | 17 -- .../Components/ResearchServerComponent.cs | 145 +----------------- .../Components/TechnologyDatabaseComponent.cs | 68 +------- .../Research/Systems/ResearchSystem.Client.cs | 87 +++++++++++ .../Systems/ResearchSystem.Console.cs | 74 +++++++++ .../Systems/ResearchSystem.PointSource.cs | 12 ++ .../Research/Systems/ResearchSystem.Server.cs | 109 +++++++++++++ .../Systems/ResearchSystem.Technology.cs | 78 ++++++++++ .../Research/{ => Systems}/ResearchSystem.cs | 33 ++-- .../SharedResearchClientComponent.cs | 113 +++++++------- .../SharedResearchConsoleComponent.cs | 75 ++++----- .../SharedTechnologyDatabaseComponent.cs | 15 +- .../Entities/Structures/Machines/research.yml | 3 +- 21 files changed, 496 insertions(+), 531 deletions(-) create mode 100644 Content.Server/Research/Systems/ResearchSystem.Client.cs create mode 100644 Content.Server/Research/Systems/ResearchSystem.Console.cs create mode 100644 Content.Server/Research/Systems/ResearchSystem.PointSource.cs create mode 100644 Content.Server/Research/Systems/ResearchSystem.Server.cs create mode 100644 Content.Server/Research/Systems/ResearchSystem.Technology.cs rename Content.Server/Research/{ => Systems}/ResearchSystem.cs (65%) diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 8bec3815e2..df52b7c6a2 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -70,7 +70,7 @@ namespace Content.Client.Entry factory.DoAutoRegistrations(); factory.IgnoreMissingComponents(); - factory.RegisterClass(); + // Do not add to these, they are legacy. factory.RegisterClass(); factory.RegisterClass(); factory.RegisterClass(); @@ -78,6 +78,7 @@ namespace Content.Client.Entry factory.RegisterClass(); factory.RegisterClass(); factory.RegisterClass(); + // Do not add to the above, they are legacy prototypes.RegisterIgnore("accent"); prototypes.RegisterIgnore("material"); diff --git a/Content.Client/Research/TechnologyDatabaseComponent.cs b/Content.Client/Research/TechnologyDatabaseComponent.cs index b3e63b9726..f69bdfdda3 100644 --- a/Content.Client/Research/TechnologyDatabaseComponent.cs +++ b/Content.Client/Research/TechnologyDatabaseComponent.cs @@ -21,14 +21,14 @@ namespace Content.Client.Research if (curState is not TechnologyDatabaseState state) return; - _technologies.Clear(); + Technologies.Clear(); var protoManager = IoCManager.Resolve(); foreach (var techID in state.Technologies) { if (!protoManager.TryIndex(techID, out TechnologyPrototype? technology)) continue; - _technologies.Add(technology); + Technologies.Add(technology); } OnDatabaseUpdated?.Invoke(); diff --git a/Content.Client/Research/UI/ResearchClientBoundUserInterface.cs b/Content.Client/Research/UI/ResearchClientBoundUserInterface.cs index 1a70f1e4fd..7720fd5e6b 100644 --- a/Content.Client/Research/UI/ResearchClientBoundUserInterface.cs +++ b/Content.Client/Research/UI/ResearchClientBoundUserInterface.cs @@ -1,6 +1,5 @@ +using Content.Shared.Research.Components; using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using static Content.Shared.Research.Components.SharedResearchClientComponent; namespace Content.Client.Research.UI { diff --git a/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs b/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs index 5957f9bd16..adb7a43714 100644 --- a/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs +++ b/Content.Client/Research/UI/ResearchConsoleBoundUserInterface.cs @@ -1,9 +1,7 @@ +using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; using JetBrains.Annotations; using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using static Content.Shared.Research.Components.SharedResearchConsoleComponent; namespace Content.Client.Research.UI { diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 042f806941..c6bc1b49cc 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -74,7 +74,7 @@ public sealed class NetworkConfiguratorSystem : EntitySystem private void TryAddNetworkDevice(EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator, DeviceNetworkComponent? device = null) { - if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device)) + if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device, false)) return; if (string.IsNullOrEmpty(device.Address)) diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 1a7a184b36..87e642c94f 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -8,8 +8,10 @@ using Content.Server.Materials; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Research; using Content.Server.Stack; using Content.Server.UserInterface; +using Content.Shared.Research.Components; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Player; @@ -274,14 +276,16 @@ namespace Content.Server.Lathe case LatheServerSelectionMessage _: if (!TryComp(uid, out ResearchClientComponent? researchClient)) return; - researchClient.OpenUserInterface(message.Session); + IoCManager.Resolve() + .GetEntitySystem() + .TryOpen(uid, ResearchClientUiKey.Key, message.Session); break; case LatheServerSyncMessage _: if (!TryComp(uid, out TechnologyDatabaseComponent? database) || !TryComp(uid, out ProtolatheDatabaseComponent? protoDatabase)) return; - if (database.SyncWithServer()) + if (IoCManager.Resolve().GetEntitySystem().SyncWithServer(database)) protoDatabase.Sync(); break; diff --git a/Content.Server/Research/Components/ResearchClientComponent.cs b/Content.Server/Research/Components/ResearchClientComponent.cs index bd2afc858f..89d5aa43f8 100644 --- a/Content.Server/Research/Components/ResearchClientComponent.cs +++ b/Content.Server/Research/Components/ResearchClientComponent.cs @@ -1,96 +1,12 @@ -using Content.Server.UserInterface; -using Content.Shared.Research.Components; -using Robust.Server.GameObjects; -using Robust.Server.Player; - -namespace Content.Server.Research.Components +namespace Content.Server.Research.Components { [RegisterComponent] [Virtual] - public class ResearchClientComponent : SharedResearchClientComponent + public class ResearchClientComponent : Component { - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - // TODO: Create GUI for changing RD server. - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ResearchClientUiKey.Key); - 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); - } - - protected 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]); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } - } - - public void OpenUserInterface(IPlayerSession session) - { - UpdateUserInterface(); - UserInterface?.Open(session); - } - 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; - } - } - - /// - protected override void Shutdown() - { - base.Shutdown(); - UnregisterFromServer(); - - } } } diff --git a/Content.Server/Research/Components/ResearchConsoleComponent.cs b/Content.Server/Research/Components/ResearchConsoleComponent.cs index f12d7bf59e..30663b0819 100644 --- a/Content.Server/Research/Components/ResearchConsoleComponent.cs +++ b/Content.Server/Research/Components/ResearchConsoleComponent.cs @@ -1,89 +1,5 @@ -using Content.Server.Power.Components; -using Content.Server.UserInterface; -using Content.Shared.Research.Components; -using Content.Shared.Research.Prototypes; -using Robust.Server.GameObjects; -using Robust.Shared.Prototypes; - namespace Content.Server.Research.Components { [RegisterComponent] - public sealed class ResearchConsoleComponent : SharedResearchConsoleComponent - { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - [ViewVariables] private bool Powered => !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; - - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ResearchConsoleUiKey.Key); - - protected override void Initialize() - { - base.Initialize(); - - Owner.EnsureComponentWarn(); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } - - Owner.EnsureComponent(); - } - - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) - { - if (!_entMan.TryGetComponent(Owner, out TechnologyDatabaseComponent? database)) - return; - if (!_entMan.TryGetComponent(Owner, out ResearchClientComponent? client)) - return; - if (!Powered) - return; - - switch (message.Message) - { - case ConsoleUnlockTechnologyMessage msg: - if (!_prototypeManager.TryIndex(msg.Id, out TechnologyPrototype? tech)) break; - if (client.Server == null) break; - if (!client.Server.CanUnlockTechnology(tech)) break; - if (client.Server.UnlockTechnology(tech)) - { - database.SyncWithServer(); - database.Dirty(); - UpdateUserInterface(); - } - - break; - - case ConsoleServerSyncMessage _: - database.SyncWithServer(); - UpdateUserInterface(); - break; - - case ConsoleServerSelectionMessage _: - client.OpenUserInterface(message.Session); - break; - } - } - - /// - /// Method to update the user interface on the clients. - /// - public void UpdateUserInterface() - { - UserInterface?.SetState(GetNewUiState()); - } - - private ResearchConsoleBoundInterfaceState GetNewUiState() - { - if (!_entMan.TryGetComponent(Owner, out ResearchClientComponent? client) || - client.Server == null) - return new ResearchConsoleBoundInterfaceState(default, default); - - var points = client.ConnectedToServer ? client.Server.Point : 0; - var pointsPerSecond = client.ConnectedToServer ? client.Server.PointsPerSecond : 0; - - return new ResearchConsoleBoundInterfaceState(points, pointsPerSecond); - } - } + public sealed class ResearchConsoleComponent : Component {} } diff --git a/Content.Server/Research/Components/ResearchPointSourceComponent.cs b/Content.Server/Research/Components/ResearchPointSourceComponent.cs index 02874297c6..5dcbb6e8e0 100644 --- a/Content.Server/Research/Components/ResearchPointSourceComponent.cs +++ b/Content.Server/Research/Components/ResearchPointSourceComponent.cs @@ -1,5 +1,3 @@ -using Content.Server.Power.Components; - namespace Content.Server.Research.Components { [RegisterComponent] @@ -9,8 +7,6 @@ namespace Content.Server.Research.Components private int _pointsPerSecond; [DataField("active")] private bool _active; - private ApcPowerReceiverComponent? _powerReceiver; - [ViewVariables(VVAccess.ReadWrite)] public int PointsPerSecond { @@ -24,18 +20,5 @@ namespace Content.Server.Research.Components get => _active; set => _active = value; } - - /// - /// Whether this can be used to produce research points. - /// - /// If no is found, it's assumed power is not required. - [ViewVariables] - public bool CanProduce => Active && (_powerReceiver is null || _powerReceiver.Powered); - - protected override void Initialize() - { - base.Initialize(); - IoCManager.Resolve().TryGetComponent(Owner, out _powerReceiver); - } } } diff --git a/Content.Server/Research/Components/ResearchServerComponent.cs b/Content.Server/Research/Components/ResearchServerComponent.cs index ab5c475b1f..268dc9b84b 100644 --- a/Content.Server/Research/Components/ResearchServerComponent.cs +++ b/Content.Server/Research/Components/ResearchServerComponent.cs @@ -1,163 +1,22 @@ -using Content.Server.Power.Components; -using Content.Shared.Research.Prototypes; - namespace Content.Server.Research.Components { [RegisterComponent] public sealed class ResearchServerComponent : Component { - public static int ServerCount = 0; - [ViewVariables(VVAccess.ReadWrite)] public string ServerName => _serverName; [DataField("servername")] private string _serverName = "RDSERVER"; - private float _timer = 0f; - public TechnologyDatabaseComponent? Database { get; private set; } - [ViewVariables(VVAccess.ReadWrite)] [DataField("points")] private int _points = 0; + [ViewVariables(VVAccess.ReadWrite)] [DataField("points")] + public 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(); [ViewVariables(VVAccess.ReadOnly)] public List Clients { get; } = new(); - - 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; - - if (CanRun) - { - foreach (var source in PointSources) - { - if (source.CanProduce) points += source.PointsPerSecond; - } - } - - return points; - } - } - - /// If no is found, it's assumed power is not required. - [ViewVariables] - public bool CanRun => _powerReceiver is null || _powerReceiver.Powered; - - private ApcPowerReceiverComponent? _powerReceiver; - - protected override void Initialize() - { - base.Initialize(); - Id = ServerCount++; - EntitySystem.Get()?.RegisterServer(this); - Database = Owner.EnsureComponent(); - IoCManager.Resolve().TryGetComponent(Owner, out _powerReceiver); - } - - /// - protected override void Shutdown() - { - base.Shutdown(); - EntitySystem.Get()?.UnregisterServer(this); - } - - public bool CanUnlockTechnology(TechnologyPrototype technology) - { - if (Database == null) - return false; - - 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) ?? false; - if (result) - _points -= technology.RequiredPoints; - return result; - } - - /// - /// Check whether a technology is unlocked or not. - /// - /// - /// - public bool IsTechnologyUnlocked(TechnologyPrototype technology) - { - return Database?.IsTechnologyUnlocked(technology) ?? false; - } - - /// - /// 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) - { - if (!CanRun) return; - _timer += frameTime; - if (_timer < 1f) return; - _timer = 0f; - _points += PointsPerSecond; - } } } diff --git a/Content.Server/Research/Components/TechnologyDatabaseComponent.cs b/Content.Server/Research/Components/TechnologyDatabaseComponent.cs index a673f9a82b..86d2bd7a45 100644 --- a/Content.Server/Research/Components/TechnologyDatabaseComponent.cs +++ b/Content.Server/Research/Components/TechnologyDatabaseComponent.cs @@ -1,73 +1,7 @@ using Content.Shared.Research.Components; -using Content.Shared.Research.Prototypes; namespace Content.Server.Research.Components { [RegisterComponent] - public sealed 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 (!IoCManager.Resolve().TryGetComponent(Owner, out ResearchClientComponent? client)) return false; - if (client.Server?.Database == null) 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); - } - } + public sealed class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent {} } diff --git a/Content.Server/Research/Systems/ResearchSystem.Client.cs b/Content.Server/Research/Systems/ResearchSystem.Client.cs new file mode 100644 index 0000000000..ca2205b6a6 --- /dev/null +++ b/Content.Server/Research/Systems/ResearchSystem.Client.cs @@ -0,0 +1,87 @@ +using Content.Server.Research.Components; +using Content.Shared.Research.Components; + +namespace Content.Server.Research; + +public sealed partial class ResearchSystem +{ + private void InitializeClient() + { + SubscribeLocalEvent(OnClientStartup); + SubscribeLocalEvent(OnClientShutdown); + SubscribeLocalEvent(OnClientUIOpen); + + SubscribeLocalEvent(OnClientSyncMessage); + SubscribeLocalEvent(OnClientSelected); + SubscribeLocalEvent(OnClientDeselected); + } + + #region UI + + private void OnClientDeselected(EntityUid uid, ResearchClientComponent component, ResearchClientServerDeselectedMessage args) + { + UnregisterClientServer(component); + UpdateClientInterface(component); + + if (TryComp(uid, out var console)) + { + UpdateConsoleInterface(console, component); + } + } + + private void OnClientSelected(EntityUid uid, ResearchClientComponent component, ResearchClientServerSelectedMessage args) + { + UnregisterClientServer(component); + RegisterClientServer(component, GetServerById(args.ServerId)); + UpdateClientInterface(component); + + if (TryComp(uid, out var console)) + { + UpdateConsoleInterface(console, component); + } + } + + private void OnClientSyncMessage(EntityUid uid, ResearchClientComponent component, ResearchClientSyncMessage args) + { + UpdateClientInterface(component); + } + + #endregion + + private void OnClientStartup(EntityUid uid, ResearchClientComponent component, ComponentStartup args) + { + if (Servers.Count > 0) + RegisterClientServer(component, Servers[0]); + } + + private void OnClientShutdown(EntityUid uid, ResearchClientComponent component, ComponentShutdown args) + { + UnregisterClientServer(component); + } + + private void OnClientUIOpen(EntityUid uid, ResearchClientComponent component, BoundUIOpenedEvent args) + { + UpdateClientInterface(component); + } + + private void UpdateClientInterface(ResearchClientComponent component) + { + var state = new ResearchClientBoundInterfaceState(Servers.Count, GetServerNames(), + GetServerIds(), component.ConnectedToServer ? component.Server!.Id : -1); + + _uiSystem.GetUiOrNull(component.Owner, ResearchClientUiKey.Key)?.SetState(state); + } + + private bool RegisterClientServer(ResearchClientComponent component, ResearchServerComponent? server = null) + { + if (server == null) return false; + return RegisterServerClient(server, component); + } + + private void UnregisterClientServer(ResearchClientComponent component) + { + if (component.Server == null) return; + + UnregisterServerClient(component.Server, component); + } +} diff --git a/Content.Server/Research/Systems/ResearchSystem.Console.cs b/Content.Server/Research/Systems/ResearchSystem.Console.cs new file mode 100644 index 0000000000..6772e55de9 --- /dev/null +++ b/Content.Server/Research/Systems/ResearchSystem.Console.cs @@ -0,0 +1,74 @@ +using Content.Server.Power.EntitySystems; +using Content.Server.Research.Components; +using Content.Shared.Research.Components; +using Content.Shared.Research.Prototypes; +using Robust.Server.Player; + +namespace Content.Server.Research; + +public sealed partial class ResearchSystem +{ + private void InitializeConsole() + { + SubscribeLocalEvent(OnConsoleUnlock); + SubscribeLocalEvent(OnConsoleSync); + SubscribeLocalEvent(OnConsoleSelect); + } + + private void OnConsoleSelect(EntityUid uid, ResearchConsoleComponent component, ConsoleServerSelectionMessage args) + { + if (!HasComp(uid) || + !HasComp(uid) || + !this.IsPowered(uid, EntityManager)) + return; + + _uiSystem.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session); + } + + private void OnConsoleSync(EntityUid uid, ResearchConsoleComponent component, ConsoleServerSyncMessage args) + { + if (!TryComp(uid, out var database) || + !HasComp(uid) || + !this.IsPowered(uid, EntityManager)) + return; + + SyncWithServer(database); + UpdateConsoleInterface(component); + } + + private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args) + { + if (!TryComp(uid, out var database) || + !TryComp(uid, out var client) || + !this.IsPowered(uid, EntityManager)) + return; + + if (!_prototypeManager.TryIndex(args.Id, out TechnologyPrototype? tech) || + client.Server == null || + !CanUnlockTechnology(client.Server, tech)) return; + + if (!UnlockTechnology(client.Server, tech)) return; + + SyncWithServer(database); + Dirty(database); + UpdateConsoleInterface(component); + } + + private void UpdateConsoleInterface(ResearchConsoleComponent component, ResearchClientComponent? clientComponent = null) + { + ResearchConsoleBoundInterfaceState state; + + if (!Resolve(component.Owner, ref clientComponent, false) || + clientComponent.Server == null) + { + state = new ResearchConsoleBoundInterfaceState(default, default); + } + else + { + var points = clientComponent.ConnectedToServer ? clientComponent.Server.Points : 0; + var pointsPerSecond = clientComponent.ConnectedToServer ? PointsPerSecond(clientComponent.Server) : 0; + state = new ResearchConsoleBoundInterfaceState(points, pointsPerSecond); + } + _uiSystem.GetUiOrNull(component.Owner, ResearchConsoleUiKey.Key)?.SetState(state); + } +} diff --git a/Content.Server/Research/Systems/ResearchSystem.PointSource.cs b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs new file mode 100644 index 0000000000..0803d7ac4c --- /dev/null +++ b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs @@ -0,0 +1,12 @@ +using Content.Server.Power.EntitySystems; +using Content.Server.Research.Components; + +namespace Content.Server.Research; + +public sealed partial class ResearchSystem +{ + public bool CanProduce(ResearchPointSourceComponent component) + { + return component.Active && this.IsPowered(component.Owner, EntityManager); + } +} diff --git a/Content.Server/Research/Systems/ResearchSystem.Server.cs b/Content.Server/Research/Systems/ResearchSystem.Server.cs new file mode 100644 index 0000000000..b563b0c4ef --- /dev/null +++ b/Content.Server/Research/Systems/ResearchSystem.Server.cs @@ -0,0 +1,109 @@ +using Content.Server.Power.EntitySystems; +using Content.Server.Research.Components; +using Content.Shared.Research.Prototypes; + +namespace Content.Server.Research; + +public sealed partial class ResearchSystem +{ + private void InitializeServer() + { + SubscribeLocalEvent(OnServerStartup); + SubscribeLocalEvent(OnServerShutdown); + } + + private void OnServerShutdown(EntityUid uid, ResearchServerComponent component, ComponentShutdown args) + { + UnregisterServer(component); + } + + private void OnServerStartup(EntityUid uid, ResearchServerComponent component, ComponentStartup args) + { + RegisterServer(component); + } + + private bool CanRun(ResearchServerComponent component) + { + return this.IsPowered(component.Owner, EntityManager); + } + + private void UpdateServer(ResearchServerComponent component, int time) + { + if (!CanRun(component)) return; + component.Points += PointsPerSecond(component) * time; + } + + public bool RegisterServerClient(ResearchServerComponent component, ResearchClientComponent clientComponent) + { + // TODO: This is shit but I'm just trying to fix RND for now until it gets bulldozed + if (TryComp(clientComponent.Owner, out var source)) + { + if (component.PointSources.Contains(source)) return false; + component.PointSources.Add(source); + source.Server = component; + } + + if (component.Clients.Contains(clientComponent)) return false; + component.Clients.Add(clientComponent); + clientComponent.Server = component; + return true; + } + + public void UnregisterServerClient(ResearchServerComponent component, ResearchClientComponent clientComponent) + { + if (TryComp(clientComponent.Owner, out var source)) + { + component.PointSources.Remove(source); + } + + component.Clients.Remove(clientComponent); + clientComponent.Server = null; + } + + public bool IsTechnologyUnlocked(ResearchServerComponent component, TechnologyPrototype prototype, + TechnologyDatabaseComponent? databaseComponent = null) + { + if (!Resolve(component.Owner, ref databaseComponent, false)) return false; + return databaseComponent.IsTechnologyUnlocked(prototype); + } + + public bool CanUnlockTechnology(ResearchServerComponent component, TechnologyPrototype technology, TechnologyDatabaseComponent? databaseComponent = null) + { + if (!Resolve(component.Owner, ref databaseComponent, false)) + return false; + + if (!databaseComponent.CanUnlockTechnology(technology) || + component.Points < technology.RequiredPoints || + IsTechnologyUnlocked(component, technology, databaseComponent)) + return false; + + return true; + } + + public bool UnlockTechnology(ResearchServerComponent component, TechnologyPrototype prototype, + TechnologyDatabaseComponent? databaseComponent = null) + { + if (!Resolve(component.Owner, ref databaseComponent, false)) return false; + + if (!CanUnlockTechnology(component, prototype, databaseComponent)) return false; + var result = UnlockTechnology(databaseComponent, prototype); + if (result) + component.Points -= prototype.RequiredPoints; + return result; + } + + public int PointsPerSecond(ResearchServerComponent component) + { + var points = 0; + + if (CanRun(component)) + { + foreach (var source in component.PointSources) + { + if (CanProduce(source)) points += source.PointsPerSecond; + } + } + + return points; + } +} diff --git a/Content.Server/Research/Systems/ResearchSystem.Technology.cs b/Content.Server/Research/Systems/ResearchSystem.Technology.cs new file mode 100644 index 0000000000..99219369fc --- /dev/null +++ b/Content.Server/Research/Systems/ResearchSystem.Technology.cs @@ -0,0 +1,78 @@ +using Content.Server.Research.Components; +using Content.Shared.Research.Components; +using Content.Shared.Research.Prototypes; +using Robust.Shared.GameStates; + +namespace Content.Server.Research; + +public sealed partial class ResearchSystem +{ + private void InitializeTechnology() + { + SubscribeLocalEvent(OnTechnologyGetState); + } + + private void OnTechnologyGetState(EntityUid uid, TechnologyDatabaseComponent component, ref ComponentGetState args) + { + args.State = new TechnologyDatabaseState(component.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 component, TechnologyDatabaseComponent otherDatabase, bool twoway = true) + { + foreach (var tech in otherDatabase.Technologies) + { + if (!component.IsTechnologyUnlocked(tech)) AddTechnology(component, tech); + } + + if (twoway) + Sync(otherDatabase, component, false); + + Dirty(component); + } + + /// + /// 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(TechnologyDatabaseComponent component, ResearchClientComponent? clientComponent = null) + { + if (!Resolve(component.Owner, ref clientComponent, false)) return false; + if (!TryComp(clientComponent.Server?.Owner, out var clientDatabase)) return false; + + Sync(component, clientDatabase); + + return true; + } + + /// + /// If possible, unlocks a technology on this database. + /// + /// + /// + public bool UnlockTechnology(TechnologyDatabaseComponent component, TechnologyPrototype technology) + { + if (!component.CanUnlockTechnology(technology)) return false; + + AddTechnology(component, technology); + Dirty(component); + return true; + } + + /// + /// Adds a technology to the database without checking if it could be unlocked. + /// + /// + public void AddTechnology(TechnologyDatabaseComponent component, TechnologyPrototype technology) + { + component.Technologies.Add(technology); + } +} diff --git a/Content.Server/Research/ResearchSystem.cs b/Content.Server/Research/Systems/ResearchSystem.cs similarity index 65% rename from Content.Server/Research/ResearchSystem.cs rename to Content.Server/Research/Systems/ResearchSystem.cs index bef59d439c..1d67fcaa71 100644 --- a/Content.Server/Research/ResearchSystem.cs +++ b/Content.Server/Research/Systems/ResearchSystem.cs @@ -1,12 +1,18 @@ using Content.Server.Research.Components; +using Content.Shared.Research.Components; using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Server.Research { [UsedImplicitly] - public sealed class ResearchSystem : EntitySystem + public sealed partial class ResearchSystem : EntitySystem { - private const float ResearchConsoleUIUpdateTime = 30f; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + + private const int ResearchConsoleUIUpdateTime = 5; private float _timer = ResearchConsoleUIUpdateTime; private readonly List _servers = new(); @@ -15,6 +21,10 @@ namespace Content.Server.Research public override void Initialize() { base.Initialize(); + InitializeClient(); + InitializeConsole(); + InitializeServer(); + InitializeTechnology(); } public bool RegisterServer(ResearchServerComponent server) @@ -67,19 +77,20 @@ namespace Content.Server.Research { _timer += frameTime; - foreach (var server in _servers) + while (_timer > ResearchConsoleUIUpdateTime) { - server.Update(frameTime); - } - - if (_timer >= ResearchConsoleUIUpdateTime) - { - foreach (var console in EntityManager.EntityQuery()) + foreach (var server in _servers) { - console.UpdateUserInterface(); + UpdateServer(server, ResearchConsoleUIUpdateTime); } - _timer = 0f; + foreach (var console in EntityManager.EntityQuery()) + { + if (!_uiSystem.IsUiOpen(console.Owner, ResearchConsoleUiKey.Key)) continue; + UpdateConsoleInterface(console); + } + + _timer -= ResearchConsoleUIUpdateTime; } } } diff --git a/Content.Shared/Research/Components/SharedResearchClientComponent.cs b/Content.Shared/Research/Components/SharedResearchClientComponent.cs index 82ee33ad2c..bd64d9f98c 100644 --- a/Content.Shared/Research/Components/SharedResearchClientComponent.cs +++ b/Content.Shared/Research/Components/SharedResearchClientComponent.cs @@ -2,68 +2,63 @@ namespace Content.Shared.Research.Components { - [Virtual] - public class SharedResearchClientComponent : Component + /// + /// Sent to the server when the client deselects a research server. + /// + [Serializable, NetSerializable] + public sealed class ResearchClientServerDeselectedMessage : BoundUserInterfaceMessage { - /// - /// Request that the server updates the client. - /// - [Serializable, NetSerializable] - public sealed class ResearchClientSyncMessage : BoundUserInterfaceMessage + public ResearchClientServerDeselectedMessage() { - - public ResearchClientSyncMessage() - { - } - } - - /// - /// Sent to the server when the client chooses a research server. - /// - [Serializable, NetSerializable] - public sealed 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 sealed 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; - } } } + /// + /// Sent to the server when the client chooses a research server. + /// + [Serializable, NetSerializable] + public sealed class ResearchClientServerSelectedMessage : BoundUserInterfaceMessage + { + public int ServerId; + + public ResearchClientServerSelectedMessage(int serverId) + { + ServerId = serverId; + } + } + + /// + /// Request that the server updates the client. + /// + [Serializable, NetSerializable] + public sealed class ResearchClientSyncMessage : BoundUserInterfaceMessage + { + + public ResearchClientSyncMessage() + { + } + } + + [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/Research/Components/SharedResearchConsoleComponent.cs b/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs index 19e0080967..b7b2a4e9be 100644 --- a/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs +++ b/Content.Shared/Research/Components/SharedResearchConsoleComponent.cs @@ -1,54 +1,47 @@ -using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Research.Components { - [NetworkedComponent()] - [Virtual] - public class SharedResearchConsoleComponent : Component + [NetSerializable, Serializable] + public enum ResearchConsoleUiKey : byte { + Key, + } - [NetSerializable, Serializable] - public enum ResearchConsoleUiKey + [Serializable, NetSerializable] + public sealed class ConsoleUnlockTechnologyMessage : BoundUserInterfaceMessage + { + public string Id; + + public ConsoleUnlockTechnologyMessage(string id) { - Key, + Id = id; } + } - [Serializable, NetSerializable] - public sealed class ConsoleUnlockTechnologyMessage : BoundUserInterfaceMessage + [Serializable, NetSerializable] + public sealed class ConsoleServerSyncMessage : BoundUserInterfaceMessage + { + public ConsoleServerSyncMessage() + {} + } + + [Serializable, NetSerializable] + public sealed class ConsoleServerSelectionMessage : BoundUserInterfaceMessage + { + public ConsoleServerSelectionMessage() + {} + } + + [Serializable, NetSerializable] + public sealed class ResearchConsoleBoundInterfaceState : BoundUserInterfaceState + { + public int Points; + public int PointsPerSecond; + public ResearchConsoleBoundInterfaceState(int points, int pointsPerSecond) { - public string Id; - - public ConsoleUnlockTechnologyMessage(string id) - { - Id = id; - } - } - - [Serializable, NetSerializable] - public sealed class ConsoleServerSyncMessage : BoundUserInterfaceMessage - { - public ConsoleServerSyncMessage() - {} - } - - [Serializable, NetSerializable] - public sealed 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; - } + Points = points; + PointsPerSecond = pointsPerSecond; } } } diff --git a/Content.Shared/Research/Components/SharedTechnologyDatabaseComponent.cs b/Content.Shared/Research/Components/SharedTechnologyDatabaseComponent.cs index 500c80fce2..1c95a2c04b 100644 --- a/Content.Shared/Research/Components/SharedTechnologyDatabaseComponent.cs +++ b/Content.Shared/Research/Components/SharedTechnologyDatabaseComponent.cs @@ -11,18 +11,13 @@ namespace Content.Shared.Research.Components { [DataField("technologies")] private List _technologyIds = new(); - protected List _technologies = new(); - - /// - /// A read-only list of unlocked technologies. - /// - public IReadOnlyList Technologies => _technologies; + public List Technologies = new(); void ISerializationHooks.BeforeSerialization() { var techIds = new List(); - foreach (var tech in _technologies) + foreach (var tech in Technologies) { techIds.Add(tech.ID); } @@ -38,7 +33,7 @@ namespace Content.Shared.Research.Components { if (prototypeManager.TryIndex(id, out TechnologyPrototype? tech)) { - _technologies.Add(tech); + Technologies.Add(tech); } } } @@ -61,7 +56,7 @@ namespace Content.Shared.Research.Components { List techIds = new List(); - foreach (var tech in _technologies) + foreach (var tech in Technologies) { techIds.Add(tech.ID); } @@ -76,7 +71,7 @@ namespace Content.Shared.Research.Components /// Whether it is unlocked or not public bool IsTechnologyUnlocked(TechnologyPrototype technology) { - return _technologies.Contains(technology); + return Technologies.Contains(technology); } /// diff --git a/Resources/Prototypes/Entities/Structures/Machines/research.yml b/Resources/Prototypes/Entities/Structures/Machines/research.yml index 3fd5759bc7..b8f094894c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/research.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/research.yml @@ -34,7 +34,7 @@ range: 5 sound: path: /Audio/Ambience/Objects/server_fans.ogg - + - type: entity id: BaseResearchAndDevelopmentPointSource parent: BaseMachinePowered @@ -48,6 +48,7 @@ - state: rndpointsource shader: unshaded map: ["enum.PowerDeviceVisualLayers.Powered"] + - type: ResearchClient - type: ResearchPointSource pointspersecond: 100 active: true