diff --git a/Content.Server/Commands/ContentCompletionHelper.cs b/Content.Server/Commands/ContentCompletionHelper.cs
index bc80b28762..dbe200b9de 100644
--- a/Content.Server/Commands/ContentCompletionHelper.cs
+++ b/Content.Server/Commands/ContentCompletionHelper.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Station.Components;
+using Content.Shared.Station.Components;
using Robust.Shared.Console;
namespace Content.Server.Commands;
diff --git a/Content.Server/Toolshed/Commands/StatusEffects/StatusEffectCommand.cs b/Content.Server/Toolshed/Commands/StatusEffects/StatusEffectCommand.cs
index 4182bac081..c840978847 100644
--- a/Content.Server/Toolshed/Commands/StatusEffects/StatusEffectCommand.cs
+++ b/Content.Server/Toolshed/Commands/StatusEffects/StatusEffectCommand.cs
@@ -1,7 +1,7 @@
using Content.Server.Administration;
-using Content.Server.Toolshed.TypeParsers.StatusEffects;
using Content.Shared.Administration;
using Content.Shared.StatusEffectNew;
+using Content.Shared.Toolshed.TypeParsers;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
diff --git a/Content.Server/Toolshed/Commands/XenoArtifacts/XenoArtifactCommands.cs b/Content.Server/Toolshed/Commands/XenoArtifacts/XenoArtifactCommands.cs
new file mode 100644
index 0000000000..169147de0c
--- /dev/null
+++ b/Content.Server/Toolshed/Commands/XenoArtifacts/XenoArtifactCommands.cs
@@ -0,0 +1,242 @@
+using System.Text;
+using Content.Server.Administration;
+using Content.Server.Xenoarchaeology.Artifact;
+using Content.Shared.Administration;
+using Content.Shared.Toolshed.TypeParsers.XenoArtifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Map;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+
+namespace Content.Server.Toolshed.Commands.XenoArtifacts;
+
+///
+/// Toolshed commands for manipulating xeno artifact.
+///
+[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
+public sealed partial class XenoArtifactCommand : ToolshedCommand
+{
+ [Dependency] private IPrototypeManager _prototypeManager = default!;
+
+ private XenoArtifactSystem? _artifact;
+
+ public static readonly EntProtoId ArtifactPrototype = "ComplexXenoArtifactItem";
+
+ /// List existing artifacts.
+ [CommandImplementation("list")]
+ public IEnumerable List()
+ {
+ var query = EntityManager.EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _))
+ {
+ yield return uid;
+ }
+ }
+
+ ///
+ /// Output matrix of artifact nodes and how they are connected.
+ ///
+ [CommandImplementation("printmatrix")]
+ public string PrintMatrix([CommandArgument] Entity artifactEnt)
+ {
+ var nodeCount = artifactEnt.Comp.NodeVertices.Length;
+
+ var sb = new StringBuilder("\n |");
+ for (var i = 0; i < nodeCount; i++)
+ {
+ sb.Append($" {i:D2}|");
+ }
+
+ AddHorizontalFiller(sb);
+
+ for (var i = 0; i < nodeCount; i++)
+ {
+ sb.Append($"\n{i:D2}|");
+ for (var j = 0; j < nodeCount; j++)
+ {
+ var value = artifactEnt.Comp.NodeAdjacencyMatrix[i][j]
+ ? "X"
+ : " ";
+ sb.Append($" {value} |");
+ }
+ AddHorizontalFiller(sb);
+ }
+
+ return sb.ToString();
+
+ void AddHorizontalFiller(StringBuilder builder)
+ {
+ builder.AppendLine();
+ builder.Append("--+");
+ for (var i = 0; i < nodeCount; i++)
+ {
+ builder.Append("---+");
+ }
+ }
+ }
+
+ /// Output total research points artifact contains.
+ [CommandImplementation("totalresearch")]
+ public int TotalResearch([PipedArgument] EntityUid artifactEntityUid)
+ {
+ _artifact ??= Sys();
+ var comp = Comp(artifactEntityUid);
+
+ var sum = 0;
+
+ var nodes = _artifact.GetAllNodes((artifactEntityUid, comp));
+ foreach (var node in nodes)
+ {
+ sum += node.Comp.ResearchValue;
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Spawns a bunch of artifacts and gets average total research points they can yield.
+ ///
+ [CommandImplementation("averageresearch")]
+ public float AverageResearch()
+ {
+ const int n = 100;
+ var sum = 0;
+
+ for (var i = 0; i < n; i++)
+ {
+ var ent = Spawn(ArtifactPrototype, MapCoordinates.Nullspace);
+ sum += TotalResearch(ent);
+ Del(ent);
+ }
+
+ return (float)sum / n;
+ }
+
+ /// Unlocks all nodes of artifact.
+ [CommandImplementation("unlockallnodes")]
+ public void UnlockAllNodes([PipedArgument] EntityUid artifactEntityUid)
+ {
+ _artifact ??= Sys();
+ var comp = Comp(artifactEntityUid);
+
+ var nodes = _artifact.GetAllNodes((artifactEntityUid, comp));
+ foreach (var node in nodes)
+ {
+ _artifact.SetNodeUnlocked((node, node.Comp));
+ }
+ }
+
+ ///
+ /// Create node in artifact (new on depth 0 or attach next to existing one).
+ ///
+ [CommandImplementation("createnode")]
+ public void CreateNodeNew(
+ [CommandArgument] Entity artifact,
+ [CommandArgument(typeof(XenoEffectParser))] ProtoId effect,
+ [CommandArgument] ProtoId trigger
+ )
+ {
+ CreateNode(artifact, effect, trigger);
+ }
+
+ /// Add a new node to the given artifact.
+ [CommandImplementation("createnodeatdepth")]
+ public void CreateNodeAtDepth(
+ [CommandArgument(typeof(XenoArtifactNodeParser))] (Entity Artifact, Entity Node) tuple,
+ [CommandArgument(typeof(XenoEffectParser))] ProtoId effect,
+ [CommandArgument] ProtoId trigger
+ )
+ {
+ CreateNode(tuple.Artifact, effect, trigger, tuple.Node);
+ }
+
+ /// Spawns a new xeno artifact with single node with the given trigger and effect.
+ [CommandImplementation("spawnartwithnode")]
+ public void SpawnArtifactWithNode(
+ [CommandArgument] ICommonSession target,
+ [CommandArgument(typeof(XenoArtifactTypeParser))] ProtoId artifactType,
+ [CommandArgument(typeof(XenoEffectParser))] ProtoId effect,
+ [CommandArgument] ProtoId trigger
+ )
+ {
+ if (target.AttachedEntity == null)
+ return;
+
+ var entity = EntityManager.SpawnNextToOrDrop(artifactType, target.AttachedEntity.Value);
+ if (!TryComp(entity, out XenoArtifactComponent? artifactComp))
+ {
+ return;
+ }
+
+ CreateNode((entity, artifactComp), effect, trigger);
+ }
+
+ /// Marks a node as unlocked.
+ [CommandImplementation("unlocknode")]
+ public void UnlockNode(
+ [CommandArgument(typeof(XenoArtifactNodeParser))]
+ (Entity Artifact, Entity Node) tuple
+ )
+ {
+ _artifact ??= Sys();
+ _artifact.SetNodeUnlocked(tuple.Node.AsNullable());
+ }
+
+ /// Removes a node from a xeno artifact.
+ [CommandImplementation("removenode")]
+ public void RemoveNode(
+ [CommandArgument(typeof(XenoArtifactNodeParser))]
+ (Entity Artifact, Entity Node) tuple
+ )
+ {
+ _artifact ??= Sys();
+ _artifact.RemoveNode(tuple.Artifact.AsNullable(), tuple.Node.AsNullable());
+ }
+
+ /// Adds an edge between two nodes of a xeno artifact.
+ [CommandImplementation("addedge")]
+ public void AddEdge(
+ [CommandArgument(typeof(XenoArtifactNodeParser))]
+ (Entity Artifact, Entity Node) from,
+ [CommandArgument(typeof(XenoArtifactNodeParser))]
+ (Entity Artifact, Entity Node) to
+ )
+ {
+ // no inter-artifact edges or self-connects allowed
+ if (from.Artifact.Owner != to.Artifact.Owner || from.Node.Owner == to.Node.Owner)
+ return;
+
+ _artifact = Sys();
+ _artifact.AddEdge(from.Artifact.AsNullable(), from.Node, to.Node);
+ }
+
+ private void CreateNode(
+ Entity artifact,
+ ProtoId effect,
+ ProtoId trigger,
+ Entity? node = null
+ )
+ {
+ var depth = 0;
+ if (node.HasValue)
+ {
+ depth = node.Value.Comp.Depth + 1;
+ }
+
+ _artifact ??= Sys();
+ if (!_prototypeManager.Resolve(trigger, out var triggerPrototype))
+ return;
+
+ var createdNode = _artifact.CreateNode(artifact, effect.Id, triggerPrototype, depth);
+ if (node.HasValue)
+ {
+ _artifact.AddEdge(artifact.AsNullable(), node.Value, createdNode);
+ }
+ else
+ {
+ _artifact.RebuildXenoArtifactMetaData(artifact.AsNullable());
+ }
+ }
+}
diff --git a/Content.Server/Toolshed/TypeParsers/StatusEffects/StatusEffectCompletionParser.cs b/Content.Server/Toolshed/TypeParsers/StatusEffects/StatusEffectCompletionParser.cs
deleted file mode 100644
index 839f7fa9ca..0000000000
--- a/Content.Server/Toolshed/TypeParsers/StatusEffects/StatusEffectCompletionParser.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.StatusEffectNew;
-using Robust.Shared.Console;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Toolshed;
-using Robust.Shared.Toolshed.Syntax;
-using Robust.Shared.Toolshed.TypeParsers;
-
-namespace Content.Server.Toolshed.TypeParsers.StatusEffects;
-
-public sealed class StatusEffectCompletionParser : CustomCompletionParser
-{
- public override CompletionResult? TryAutocomplete(ParserContext ctx, CommandArgument? arg)
- {
- return CompletionResult.FromHintOptions(IoCManager.Resolve().System().StatusEffectPrototypes, GetArgHint(arg));
- }
-}
diff --git a/Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs b/Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs
deleted file mode 100644
index 2671b814e3..0000000000
--- a/Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Content.Shared.Xenoarchaeology.Artifact.Components;
-using Robust.Shared.Console;
-
-namespace Content.Server.Xenoarchaeology.Artifact;
-
-/// Command for unlocking a specific node of a xeno artifact.
-[AdminCommand(AdminFlags.Debug)]
-public sealed partial class UnlockNodeCommand : LocalizedEntityCommands
-{
- [Dependency] private XenoArtifactSystem _artiSystem = default!;
-
- public override string Command => "unlocknode";
-
- public override void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- if (args.Length != 2)
- {
- shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
- return;
- }
-
- if (!NetEntity.TryParse(args[1], out var netNode) || !EntityManager.TryGetEntity(netNode, out var entityUid))
- {
- shell.WriteError(Loc.GetString("shell-could-not-find-entity-with-uid", ("uid", args[1])));
- return;
- }
-
- _artiSystem.SetNodeUnlocked(entityUid.Value);
- }
-
- public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
- {
- switch (args.Length)
- {
- case 1:
- {
- var query = EntityManager.EntityQueryEnumerator();
- var completionOptions = new List();
- while (query.MoveNext(out var uid, out _))
- {
- completionOptions.Add(new CompletionOption(uid.ToString()));
- }
-
- return CompletionResult.FromHintOptions(completionOptions, Loc.GetString("cmd-unlocknode-artifact-hint"));
- }
- case 2 when
- NetEntity.TryParse(args[0], out var netEnt) &&
- EntityManager.TryGetEntity(netEnt, out var artifactUid) &&
- EntityManager.TryGetComponent(artifactUid, out var comp):
- {
- var result = new List();
- foreach (var node in _artiSystem.GetAllNodes((artifactUid.Value, comp)))
- {
- var metaData = EntityManager.MetaQuery.Comp(artifactUid.Value);
- var entityUidStr = EntityManager.GetNetEntity(node).ToString();
- var completionOption = new CompletionOption(entityUidStr, metaData.EntityName);
- result.Add(completionOption);
- }
-
- return CompletionResult.FromHintOptions(result, Loc.GetString("cmd-unlocknode-node-hint"));
- }
- default:
- return CompletionResult.Empty;
- }
- }
-}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs b/Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs
deleted file mode 100644
index 7fd7acca4b..0000000000
--- a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using System.Text;
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Content.Shared.Xenoarchaeology.Artifact.Components;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Toolshed;
-
-namespace Content.Server.Xenoarchaeology.Artifact;
-
-///
-/// Toolshed commands for manipulating xeno artifact.
-///
-[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
-public sealed class XenoArtifactCommand : ToolshedCommand
-{
- public static readonly EntProtoId ArtifactPrototype = "BaseXenoArtifact";
-
- /// List existing artifacts.
- [CommandImplementation("list")]
- public IEnumerable List()
- {
- var query = EntityManager.EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _))
- {
- yield return uid;
- }
- }
-
- ///
- /// Output matrix of artifact nodes and how they are connected.
- ///
- [CommandImplementation("printMatrix")]
- public string PrintMatrix([PipedArgument] EntityUid artifactEntitUid)
- {
- var comp = EntityManager.GetComponent(artifactEntitUid);
-
- var nodeCount = comp.NodeVertices.Length;
-
- var sb = new StringBuilder("\n |");
- for (var i = 0; i < nodeCount; i++)
- {
- sb.Append($" {i:D2}|");
- }
-
- AddHorizontalFiller(sb);
-
- for (var i = 0; i < nodeCount; i++)
- {
- sb.Append($"\n{i:D2}|");
- for (var j = 0; j < nodeCount; j++)
- {
- var value = comp.NodeAdjacencyMatrix[i][j]
- ? "X"
- : " ";
- sb.Append($" {value} |");
- }
- AddHorizontalFiller(sb);
- }
-
- return sb.ToString();
-
- void AddHorizontalFiller(StringBuilder builder)
- {
- builder.AppendLine();
- builder.Append("--+");
- for (var i = 0; i < nodeCount; i++)
- {
- builder.Append($"---+");
- }
- }
- }
-
- /// Output total research points artifact contains.
- [CommandImplementation("totalResearch")]
- public int TotalResearch([PipedArgument] EntityUid artifactEntityUid)
- {
- var artiSys = EntityManager.System();
- var comp = EntityManager.GetComponent(artifactEntityUid);
-
- var sum = 0;
-
- var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
- foreach (var node in nodes)
- {
- sum += node.Comp.ResearchValue;
- }
-
- return sum;
- }
-
- ///
- /// Spawns a bunch of artifacts and gets average total research points they can yield.
- ///
- [CommandImplementation("averageResearch")]
- public float AverageResearch()
- {
- const int n = 100;
- var sum = 0;
-
- for (var i = 0; i < n; i++)
- {
- var ent = Spawn(ArtifactPrototype, MapCoordinates.Nullspace);
- sum += TotalResearch(ent);
- Del(ent);
- }
-
- return (float) sum / n;
- }
-
- /// Unlocks all nodes of artifact.
- [CommandImplementation("unlockAllNodes")]
- public void UnlockAllNodes([PipedArgument] EntityUid artifactEntityUid)
- {
- var artiSys = EntityManager.System();
- var comp = EntityManager.GetComponent(artifactEntityUid);
-
- var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
- foreach (var node in nodes)
- {
- artiSys.SetNodeUnlocked((node, node.Comp));
- }
- }
-}
diff --git a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs
index f54a13d162..73c57776b0 100644
--- a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs
+++ b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs
@@ -1,10 +1,11 @@
-using System.Diagnostics.CodeAnalysis;
using Content.Shared.Rejuvenate;
using Content.Shared.StatusEffectNew.Components;
+using Content.Shared.Toolshed.TypeParsers;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.StatusEffectNew;
@@ -21,6 +22,8 @@ public sealed partial class StatusEffectsSystem : EntitySystem
[Dependency] private EntityQuery _containerQuery = default!;
[Dependency] private EntityQuery _effectQuery = default!;
+ // TODO: https://github.com/space-wizards/space-station-14/issues/45060
+ [Access(typeof(StatusEffectCompletionParser), Other = AccessPermissions.None)]
public readonly HashSet StatusEffectPrototypes = [];
public override void Initialize()
diff --git a/Content.Shared/Toolshed/TypeParsers/StatusEffectCompletionParser.cs b/Content.Shared/Toolshed/TypeParsers/StatusEffectCompletionParser.cs
new file mode 100644
index 0000000000..cff13d70e0
--- /dev/null
+++ b/Content.Shared/Toolshed/TypeParsers/StatusEffectCompletionParser.cs
@@ -0,0 +1,19 @@
+using Content.Shared.StatusEffectNew;
+using Robust.Shared.Console;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+using Robust.Shared.Toolshed.Syntax;
+using Robust.Shared.Toolshed.TypeParsers;
+
+namespace Content.Shared.Toolshed.TypeParsers;
+
+public sealed partial class StatusEffectCompletionParser : CustomCompletionParser
+{
+ [Dependency] private IEntityManager _entityManager = default!;
+
+ public override CompletionResult? TryAutocomplete(ParserContext ctx, CommandArgument? arg)
+ {
+ var statusEffects = _entityManager.System();
+ return CompletionResult.FromHintOptions(statusEffects.StatusEffectPrototypes, GetArgHint(arg));
+ }
+}
diff --git a/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactNodeParser.cs b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactNodeParser.cs
new file mode 100644
index 0000000000..23266f7a5d
--- /dev/null
+++ b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactNodeParser.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Console;
+using Robust.Shared.Toolshed;
+using Robust.Shared.Toolshed.Syntax;
+using Robust.Shared.Toolshed.TypeParsers;
+
+namespace Content.Shared.Toolshed.TypeParsers.XenoArtifact;
+
+///
+/// Argument parser for toolshed commands, which should autocomplete artifact nodes that exists on artifact.
+///
+public sealed partial class XenoArtifactNodeParser : CustomCompletionParser<(Entity, Entity)>
+{
+ [Dependency] private IEntityManager _entityManager = default!;
+
+ ///
+ public override CompletionResult? TryAutocomplete(ParserContext ctx, CommandArgument? arg)
+ {
+ if (!Toolshed.TryParse>(ctx, out var artifactEnt))
+ {
+ return GetHintedEntities(arg);
+ }
+
+ var hint = ToolshedCommand.GetArgHint(arg, typeof(Entity));
+
+ var artifact = _entityManager.System();
+ var list = artifact.GetAllNodes(artifactEnt)
+ .Select(
+ node =>
+ {
+ var metadata = _entityManager.GetComponent(node);
+ var entDescription = Loc.GetString(metadata.EntityDescription);
+ return new CompletionOption(
+ node.Owner.ToString(),
+ Loc.GetString(
+ "command-xenoartifact-common-node-hint",
+ ("depth", node.Comp.Depth),
+ ("nodeId", artifact.GetNodeId(node.Owner)),
+ ("nodeDetail", entDescription)
+ )
+ );
+ });
+
+ return CompletionResult.FromHintOptions(list, hint);
+ }
+
+ private CompletionResult? GetHintedEntities(CommandArgument? arg) where T : IComponent
+ {
+ var hint = ToolshedCommand.GetArgHint(arg, typeof(NetEntity));
+
+ // Avoid dumping too many entities
+ if (_entityManager.Count() > 128)
+ return CompletionResult.FromHint(hint);
+
+ var query = _entityManager.AllEntityQueryEnumerator();
+ var list = new List();
+ while (query.MoveNext(out _, out var metadata))
+ {
+ list.Add(new CompletionOption(metadata.NetEntity.ToString(), metadata.EntityName));
+ }
+
+ return CompletionResult.FromHintOptions(list, hint);
+ }
+}
diff --git a/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactTypeParser.cs b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactTypeParser.cs
new file mode 100644
index 0000000000..73cacb2811
--- /dev/null
+++ b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoArtifactTypeParser.cs
@@ -0,0 +1,28 @@
+using Robust.Shared.Console;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+using Robust.Shared.Toolshed.Syntax;
+using Robust.Shared.Toolshed.TypeParsers;
+
+namespace Content.Shared.Toolshed.TypeParsers.XenoArtifact;
+
+///
+/// Custom type parser for toolshed commands that will enable choosing between hand-held and
+/// stationary artifact types.
+///
+public sealed partial class XenoArtifactTypeParser : CustomCompletionParser>
+{
+ private static readonly EntProtoId ArtifactDummyItem = "DummyArtifactItem";
+ private static readonly EntProtoId ArtifactDummyStructure = "DummyArtifactStructure";
+
+ public override CompletionResult TryAutocomplete(ParserContext ctx, CommandArgument? arg)
+ {
+ return CompletionResult.FromHintOptions(
+ [
+ new CompletionOption(ArtifactDummyItem, Loc.GetString("command-spawnartifactwithnode-spawn-artifact-item-hint")),
+ new CompletionOption(ArtifactDummyStructure, Loc.GetString("command-spawnartifactwithnode-spawn-artifact-structure-hint")),
+ ],
+ Loc.GetString("command-spawnartifactwithnode-spawn-artifact-type-hint")
+ );
+ }
+}
diff --git a/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoEffectParser.cs b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoEffectParser.cs
new file mode 100644
index 0000000000..b1cc776d94
--- /dev/null
+++ b/Content.Shared/Toolshed/TypeParsers/XenoArtifact/XenoEffectParser.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Xenoarchaeology.Artifact;
+using Robust.Shared.Console;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+using Robust.Shared.Toolshed.Syntax;
+using Robust.Shared.Toolshed.TypeParsers;
+
+namespace Content.Shared.Toolshed.TypeParsers.XenoArtifact;
+
+///
+/// Custom type parser for toolshed commands
+/// that lets choose entity prototype of XenoArtifact effect.
+///
+public sealed partial class XenoEffectParser : CustomCompletionParser>
+{
+ [Dependency] private IEntityManager _entityManager = default!;
+
+ public override CompletionResult TryAutocomplete(ParserContext ctx, CommandArgument? arg)
+ {
+ var hint = ToolshedCommand.GetArgHint(arg, typeof(ProtoId));
+
+ var artifact = _entityManager.System();
+
+ return CompletionResult.FromHintOptions(artifact.EffectPrototypeIds, hint);
+ }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs
index b4da7d5def..cf63b0ec84 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs
@@ -94,8 +94,15 @@ public abstract partial class SharedXenoArtifactSystem
{
var entProtoId = _entityTable.GetSpawns(ent.Comp.EffectsTable)
.First();
+ return CreateNode(ent, entProtoId, trigger, depth);
+ }
- AddNode((ent, ent), entProtoId, out var nodeEnt, dirty: false);
+ ///
+ /// Creates artifact node entity, attaching trigger and marking depth level for future use.
+ ///
+ public Entity CreateNode(Entity ent, EntProtoId effect, XenoArchTriggerPrototype trigger, int depth = 0)
+ {
+ AddNode((ent, ent), effect, out var nodeEnt, dirty: false);
DebugTools.Assert(nodeEnt.HasValue, "Failed to create node on artifact.");
var nodeComponent = nodeEnt.Value.Comp;
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs
index 2cd02a1d13..f8be146ae9 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs
@@ -1,8 +1,10 @@
using Content.Shared.Actions;
using Content.Shared.Popups;
+using Content.Shared.Toolshed.TypeParsers.XenoArtifact;
using Content.Shared.Xenoarchaeology.Artifact.Components;
using Robust.Shared.Containers;
using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -20,9 +22,16 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem
[Dependency] private SharedContainerSystem _container = default!;
[Dependency] private SharedPopupSystem _popup = default!;
+ // TODO: https://github.com/space-wizards/space-station-14/issues/45060
+ /// Cached EntProtoIds of all XenoArtifactEffect prototypes. Used for text hints.
+ [Access(typeof(XenoEffectParser), Other = AccessPermissions.None)]
+ public readonly HashSet EffectPrototypeIds = [];
+
///
public override void Initialize()
{
+ SubscribeLocalEvent(OnPrototypesReloaded);
+
SubscribeLocalEvent(OnStartup);
SubscribeLocalEvent(OnSelfActivate);
@@ -30,6 +39,28 @@ public abstract partial class SharedXenoArtifactSystem : EntitySystem
InitializeUnlock();
InitializeXAT();
InitializeXAE();
+
+ ReloadEffectCache();
+ }
+
+ private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
+ {
+ if (!args.WasModified())
+ return;
+
+ ReloadEffectCache();
+ }
+
+ private void ReloadEffectCache()
+ {
+ EffectPrototypeIds.Clear();
+
+ foreach (var entityPrototype in ProtoMan.EnumeratePrototypes())
+ {
+ // all effect prototypes are marked as nodes, as nodes are born from those prototypes
+ if (entityPrototype.HasComp(Factory) && !entityPrototype.Abstract)
+ EffectPrototypeIds.Add(entityPrototype.ID);
+ }
}
///
diff --git a/Resources/Locale/en-US/commands/toolshed/xenoartifact-command.ftl b/Resources/Locale/en-US/commands/toolshed/xenoartifact-command.ftl
index b6fc94adf9..e65443defd 100644
--- a/Resources/Locale/en-US/commands/toolshed/xenoartifact-command.ftl
+++ b/Resources/Locale/en-US/commands/toolshed/xenoartifact-command.ftl
@@ -1,10 +1,29 @@
command-description-xenoartifact-list =
List all EntityUids of spawned artifacts.
-command-description-xenoartifact-printMatrix =
+command-description-xenoartifact-printmatrix =
Prints out matrix that displays all edges between nodes.
-command-description-xenoartifact-totalResearch =
+command-description-xenoartifact-totalresearch =
Gets all research points that can be extracted from artifact currently.
-command-description-xenoartifact-averageResearch =
+command-description-xenoartifact-averageresearch =
Calculates amount of research points average generated xeno artifact will output when fully activated.
-command-description-xenoartifact-unlockAllNodes =
+command-description-xenoartifact-unlockallnodes =
Unlocks all nodes of artifact.
+
+command-description-xenoartifact-createnode =
+ Create node in artifact (new on depth 0 or attach next to existing one).
+command-description-xenoartifact-createnodeatdepth =
+ Add a new node to the given artifact.
+command-description-xenoartifact-spawnartwithnode =
+ Spawns a new xeno artifact with single node with the given trigger and effect.
+command-description-xenoartifact-unlocknode =
+ Marks a node as unlocked.
+command-description-xenoartifact-removenode =
+ Removes a node from a xeno artifact.
+command-description-xenoartifact-addedge =
+ Adds an edge between two nodes of a xeno artifact.
+
+command-spawnartifactwithnode-spawn-artifact-item-hint = use hand-held artifact
+command-spawnartifactwithnode-spawn-artifact-structure-hint = use structure-like stationary artifact
+command-spawnartifactwithnode-spawn-artifact-type-hint =
+
+command-xenoartifact-common-node-hint = depth {$depth} node {$nodeId} trigger {$nodeDetail}
diff --git a/Resources/Locale/en-US/commands/unlocknode-command.ftl b/Resources/Locale/en-US/commands/unlocknode-command.ftl
deleted file mode 100644
index 5a3b8259dd..0000000000
--- a/Resources/Locale/en-US/commands/unlocknode-command.ftl
+++ /dev/null
@@ -1,4 +0,0 @@
-cmd-unlocknode-desc = Unlocks a node on a given artifact
-cmd-unlocknode-help = unlocknode
-cmd-unlocknode-artifact-hint =
-cmd-unlocknode-node-hint =
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml
index fb2a0aceb2..ea6a16482c 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml
@@ -1,4 +1,4 @@
-- type: entity
+- type: entity
parent: [BaseItem, BaseXenoArtifact]
id: BaseXenoArtifactItem
name: artifact
@@ -61,6 +61,15 @@
tableId: XenoArtifactEffectsHandheldOnlyTable
weight: 2
+- type: entity
+ id: DummyArtifactItem
+ parent: BaseXenoArtifactItem
+ name: artifact
+ suffix: Debug
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: false
+
- type: entity
parent: BaseXenoArtifactItem
id: ComplexXenoArtifactItem
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml
index 88a5bf087c..49f33b2cd6 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml
@@ -1,4 +1,4 @@
-- type: entity
+- type: entity
parent: [BaseStructureDynamic, BaseXenoArtifact]
id: BaseXenoArtifactStructure
name: artifact
@@ -40,6 +40,15 @@
mask:
- MachineMask
+- type: entity
+ id: DummyArtifactStructure
+ parent: BaseXenoArtifactStructure
+ name: artifact
+ suffix: Debug
+ components:
+ - type: XenoArtifact
+ isGenerationRequired: false
+
- type: entity
parent: BaseXenoArtifactStructure
id: ComplexXenoArtifact