diff --git a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.Verbs.cs b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.Verbs.cs new file mode 100644 index 0000000000..61e517f101 --- /dev/null +++ b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.Verbs.cs @@ -0,0 +1,52 @@ +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Verbs; +using Content.Shared.Interfaces; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Log; + +namespace Content.Server.GameObjects.Components.Construction +{ + public partial class ConstructionComponent + { + [Verb] + public sealed class DeconstructibleVerb : Verb + { + protected override void GetData(IEntity user, ConstructionComponent component, VerbData data) + { + if (!ActionBlockerSystem.CanInteract(user)) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + if (((component.Target != null) && (component.Target.Name == component.DeconstructionNodeIdentifier)) || + ((component.Node != null) && (component.Node.Name == component.DeconstructionNodeIdentifier))) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.CategoryData = VerbCategories.Construction; + data.Text = Loc.GetString("Begin deconstructing"); + data.IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.96dpi.png"; + } + + protected override void Activate(IEntity user, ConstructionComponent component) + { + component.SetNewTarget(component.DeconstructionNodeIdentifier); + if (component.Target == null) + { + // Maybe check, but on the flip-side a better solution might be to not make it undeconstructible in the first place, no? + component.Owner.PopupMessage(user, Loc.GetString("There is no way to deconstruct this.")); + } + else + { + component.Owner.PopupMessage(user, Loc.GetString("Examine to see instructions.")); + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs index 95fa2ebd8a..8e85838e66 100644 --- a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs +++ b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs @@ -28,7 +28,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Construction { [RegisterComponent] - public class ConstructionComponent : Component, IExamine, IInteractUsing + public partial class ConstructionComponent : Component, IExamine, IInteractUsing { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -39,6 +39,7 @@ namespace Content.Server.GameObjects.Components.Construction private TaskCompletionSource? _handlingTask = null; private string _graphIdentifier = string.Empty; private string _startingNodeIdentifier = string.Empty; + private string _startingTargetNodeIdentifier = string.Empty; [ViewVariables] private HashSet _containers = new(); @@ -79,6 +80,9 @@ namespace Content.Server.GameObjects.Components.Construction [ViewVariables] public int EdgeStep { get; private set; } = 0; + [ViewVariables] + public string DeconstructionNodeIdentifier { get; private set; } = "start"; + /// public override void ExposeData(ObjectSerializer serializer) { @@ -86,6 +90,8 @@ namespace Content.Server.GameObjects.Components.Construction serializer.DataField(ref _graphIdentifier, "graph", string.Empty); serializer.DataField(ref _startingNodeIdentifier, "node", string.Empty); + serializer.DataField(ref _startingTargetNodeIdentifier, "defaultTarget", string.Empty); + serializer.DataField(this, x => x.DeconstructionNodeIdentifier, "deconstructionTarget", "start"); } /// @@ -495,6 +501,9 @@ namespace Content.Server.GameObjects.Components.Construction { Logger.Error($"Couldn't find prototype {_graphIdentifier} in construction component!"); } + + if (!string.IsNullOrEmpty(_startingTargetNodeIdentifier)) + SetNewTarget(_startingTargetNodeIdentifier); } protected override void Startup() diff --git a/Content.Shared/Construction/ConstructionGraphPrototype.cs b/Content.Shared/Construction/ConstructionGraphPrototype.cs index 0be6723f4b..857dff7bdb 100644 --- a/Content.Shared/Construction/ConstructionGraphPrototype.cs +++ b/Content.Shared/Construction/ConstructionGraphPrototype.cs @@ -14,7 +14,7 @@ namespace Content.Shared.Construction { private readonly Dictionary _nodes = new(); private readonly Dictionary, ConstructionGraphNode[]> _paths = new(); - private Dictionary _pathfinding = new(); + private readonly Dictionary> _pathfinding = new(); [ViewVariables] public string ID { get; private set; } @@ -44,8 +44,6 @@ namespace Content.Shared.Construction if(string.IsNullOrEmpty(Start) || !_nodes.ContainsKey(Start)) throw new InvalidDataException($"Starting node for construction graph {ID} is null, empty or invalid!"); - - _pathfinding = Pathfind(Start); } public ConstructionGraphEdge Edge(string startNode, string nextNode) @@ -61,6 +59,20 @@ namespace Content.Shared.Construction if (_paths.ContainsKey(tuple)) return _paths[tuple]; + // Get graph given the current start. + + Dictionary pathfindingForStart; + if (_pathfinding.ContainsKey(startNode)) + { + pathfindingForStart = _pathfinding[startNode]; + } + else + { + pathfindingForStart = _pathfinding[startNode] = PathsForStart(startNode); + } + + // Follow the chain backwards. + var start = _nodes[startNode]; var finish = _nodes[finishNode]; @@ -71,14 +83,14 @@ namespace Content.Shared.Construction path.Add(current); // No path. - if (current == null || !_pathfinding.ContainsKey(current)) + if (current == null || !pathfindingForStart.ContainsKey(current)) { // We remember this for next time. _paths[tuple] = null; return null; } - current = _pathfinding[current]; + current = pathfindingForStart[current]; } path.Reverse(); @@ -89,7 +101,7 @@ namespace Content.Shared.Construction /// Uses breadth first search for pathfinding. /// /// - private Dictionary Pathfind(string start) + private Dictionary PathsForStart(string start) { // TODO: Make this use A* or something, although it's not that important. var startNode = _nodes[start]; diff --git a/Content.Shared/GameObjects/Verbs/VerbCategories.cs b/Content.Shared/GameObjects/Verbs/VerbCategories.cs index 4311a7366b..824b22e295 100644 --- a/Content.Shared/GameObjects/Verbs/VerbCategories.cs +++ b/Content.Shared/GameObjects/Verbs/VerbCategories.cs @@ -9,5 +9,6 @@ namespace Content.Shared.GameObjects.Verbs ("Debug", "/Textures/Interface/VerbIcons/debug.svg.96dpi.png"); public static readonly VerbCategoryData Rotate = ("Rotate", null); + public static readonly VerbCategoryData Construction = ("Construction", null); } } diff --git a/Resources/Prototypes/Entities/Constructible/Power/particleAccelerator.yml b/Resources/Prototypes/Entities/Constructible/Power/particleAccelerator.yml index 19be0d019e..f2d66ba3e6 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/particleAccelerator.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/particleAccelerator.yml @@ -250,6 +250,7 @@ - type: Construction graph: particleAcceleratorControlBox node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -266,6 +267,7 @@ - type: Construction graph: particleAcceleratorEmitterLeft node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -282,6 +284,7 @@ - type: Construction graph: particleAcceleratorEmitterCenter node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -298,6 +301,7 @@ - type: Construction graph: particleAcceleratorEmitterRight node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -314,6 +318,7 @@ - type: Construction graph: particleAcceleratorEndCap node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -330,6 +335,7 @@ - type: Construction graph: particleAcceleratorFuelChamber node: start + defaultTarget: completed - type: entity parent: ParticleAcceleratorBase @@ -346,3 +352,5 @@ - type: Construction graph: particleAcceleratorPowerBox node: start + defaultTarget: completed +