1
0

ConstructionSystem.Graph.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. using Content.Server.Construction.Components;
  2. using Content.Server.Containers;
  3. using Content.Shared.Construction;
  4. using Content.Shared.Construction.Prototypes;
  5. using Content.Shared.Construction.Steps;
  6. using Content.Shared.Containers;
  7. using Content.Shared.Database;
  8. using Robust.Server.Containers;
  9. using Robust.Shared.Containers;
  10. using Robust.Shared.Prototypes;
  11. using System.Linq;
  12. namespace Content.Server.Construction
  13. {
  14. public sealed partial class ConstructionSystem
  15. {
  16. private void InitializeGraphs()
  17. {
  18. }
  19. /// <summary>
  20. /// Sets a container on an entity as being handled by Construction. This essentially means that it will
  21. /// be transferred if the entity prototype changes. <seealso cref="ChangeEntity"/>
  22. /// </summary>
  23. /// <param name="uid">The target entity.</param>
  24. /// <param name="container">The container identifier. This method does not check whether the container exists.</param>
  25. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  26. /// <returns>Whether we could set the container as being handled by construction or not. Also returns false if
  27. /// the entity does not have a <see cref="ConstructionComponent"/>.</returns>
  28. public bool AddContainer(EntityUid uid, string container, ConstructionComponent? construction = null)
  29. {
  30. if (!Resolve(uid, ref construction))
  31. return false;
  32. return construction.Containers.Add(container);
  33. }
  34. /// <summary>
  35. /// Gets the current construction graph of an entity, or null.
  36. /// </summary>
  37. /// <param name="uid">The target entity.</param>
  38. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  39. /// <returns>The current construction graph of an entity or null if invalid. Also returns null if the entity
  40. /// does not have a <see cref="ConstructionComponent"/>.</returns>
  41. /// <remarks>An entity with a valid construction state will always have a valid graph.</remarks>
  42. public ConstructionGraphPrototype? GetCurrentGraph(EntityUid uid, ConstructionComponent? construction = null)
  43. {
  44. if (!Resolve(uid, ref construction, false))
  45. return null;
  46. // If the set graph prototype does not exist, also return null. This could be due to admemes changing values
  47. // in ViewVariables, so even though the construction state is invalid, just return null.
  48. return PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
  49. }
  50. /// <summary>
  51. /// Gets the construction graph node the entity is currently at, or null.
  52. /// </summary>
  53. /// <param name="uid">The target entity.</param>
  54. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  55. /// <returns>The current construction graph node the entity is currently at, or null if invalid. Also returns
  56. /// null if the entity does not have a <see cref="ConstructionComponent"/>.</returns>
  57. /// <remarks>An entity with a valid construction state will always be at a valid node.</remarks>
  58. public ConstructionGraphNode? GetCurrentNode(EntityUid uid, ConstructionComponent? construction = null)
  59. {
  60. if (!Resolve(uid, ref construction, false))
  61. return null;
  62. if (construction.Node is not {} nodeIdentifier)
  63. return null;
  64. return GetCurrentGraph(uid, construction) is not {} graph ? null : GetNodeFromGraph(graph, nodeIdentifier);
  65. }
  66. /// <summary>
  67. /// Gets the construction graph edge the entity is currently at, or null.
  68. /// </summary>
  69. /// <param name="uid">The target entity.</param>
  70. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  71. /// <returns>The construction graph edge the entity is currently at, if any. Also returns null if the entity
  72. /// does not have a <see cref="ConstructionComponent"/>.</returns>
  73. /// <remarks>An entity with a valid construction state might not always be at an edge.</remarks>
  74. public ConstructionGraphEdge? GetCurrentEdge(EntityUid uid, ConstructionComponent? construction = null)
  75. {
  76. if (!Resolve(uid, ref construction, false))
  77. return null;
  78. if (construction.EdgeIndex is not {} edgeIndex)
  79. return null;
  80. return GetCurrentNode(uid, construction) is not {} node ? null : GetEdgeFromNode(node, edgeIndex);
  81. }
  82. /// <summary>
  83. /// Variant of <see cref="GetCurrentEdge"/> that returns both the node and edge.
  84. /// </summary>
  85. public (ConstructionGraphNode?, ConstructionGraphEdge?) GetCurrentNodeAndEdge(EntityUid uid, ConstructionComponent? construction = null)
  86. {
  87. if (!Resolve(uid, ref construction, false))
  88. return (null, null);
  89. if (GetCurrentNode(uid, construction) is not { } node)
  90. return (null, null);
  91. if (construction.EdgeIndex is not {} edgeIndex)
  92. return (node, null);
  93. return (node, GetEdgeFromNode(node, edgeIndex));
  94. }
  95. /// <summary>
  96. /// Gets the construction graph step the entity is currently at, or null.
  97. /// </summary>
  98. /// <param name="uid">The target entity.</param>
  99. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  100. /// <returns>The construction graph step the entity is currently at, if any. Also returns null if the entity
  101. /// does not have a <see cref="ConstructionComponent"/>.</returns>
  102. /// <remarks>An entity with a valid construction state might not always be at a step or an edge.</remarks>
  103. public ConstructionGraphStep? GetCurrentStep(EntityUid uid, ConstructionComponent? construction = null)
  104. {
  105. if (!Resolve(uid, ref construction, false))
  106. return null;
  107. if (GetCurrentEdge(uid, construction) is not {} edge)
  108. return null;
  109. return GetStepFromEdge(edge, construction.StepIndex);
  110. }
  111. /// <summary>
  112. /// Gets the construction graph node the entity's construction pathfinding is currently targeting, if any.
  113. /// </summary>
  114. /// <param name="uid">The target entity.</param>
  115. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  116. /// <returns>The construction graph node the entity's construction pathfinding is currently targeting, or null
  117. /// if it's not currently targeting any node. Also returns null if the entity does not have a
  118. /// <see cref="ConstructionComponent"/>.</returns>
  119. /// <remarks>Target nodes are entirely optional and only used for pathfinding purposes.</remarks>
  120. public ConstructionGraphNode? GetTargetNode(EntityUid uid, ConstructionComponent? construction)
  121. {
  122. if (!Resolve(uid, ref construction))
  123. return null;
  124. if (construction.TargetNode is not {} targetNodeId)
  125. return null;
  126. if (GetCurrentGraph(uid, construction) is not {} graph)
  127. return null;
  128. return GetNodeFromGraph(graph, targetNodeId);
  129. }
  130. /// <summary>
  131. /// Gets the construction graph edge the entity's construction pathfinding is currently targeting, if any.
  132. /// </summary>
  133. /// <param name="uid">The target entity.</param>
  134. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  135. /// <returns>The construction graph edge the entity's construction pathfinding is currently targeting, or null
  136. /// if it's not currently targeting any edge. Also returns null if the entity does not have a
  137. /// <see cref="ConstructionComponent"/>.</returns>
  138. /// <remarks>Target edges are entirely optional and only used for pathfinding purposes. The targeted edge will
  139. /// be an edge on the current construction node the entity is at.</remarks>
  140. public ConstructionGraphEdge? GetTargetEdge(EntityUid uid, ConstructionComponent? construction)
  141. {
  142. if (!Resolve(uid, ref construction))
  143. return null;
  144. if (construction.TargetEdgeIndex is not {} targetEdgeIndex)
  145. return null;
  146. if (GetCurrentNode(uid, construction) is not {} node)
  147. return null;
  148. return GetEdgeFromNode(node, targetEdgeIndex);
  149. }
  150. /// <summary>
  151. /// Gets both the construction edge and step the entity is currently at, if any.
  152. /// </summary>
  153. /// <param name="uid">The target entity.</param>
  154. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  155. /// <returns>A tuple containing the current edge and step the entity's construction state is at.</returns>
  156. /// <remarks>The edge, step or both could be null. A valid construction state does not necessarily need them.</remarks>
  157. public (ConstructionGraphEdge? edge, ConstructionGraphStep? step) GetCurrentEdgeAndStep(EntityUid uid,
  158. ConstructionComponent? construction = null)
  159. {
  160. if (!Resolve(uid, ref construction, false))
  161. return default;
  162. var edge = GetCurrentEdge(uid, construction);
  163. if (edge == null)
  164. return default;
  165. var step = GetStepFromEdge(edge, construction.StepIndex);
  166. return (edge, step);
  167. }
  168. /// <summary>
  169. /// Gets a node from a construction graph given its identifier.
  170. /// </summary>
  171. /// <param name="graph">The construction graph where to get the node.</param>
  172. /// <param name="id">The identifier that corresponds to the node.</param>
  173. /// <returns>The node that corresponds to the identifier, or null if it doesn't exist.</returns>
  174. public ConstructionGraphNode? GetNodeFromGraph(ConstructionGraphPrototype graph, string id)
  175. {
  176. return graph.Nodes.TryGetValue(id, out var node) ? node : null;
  177. }
  178. /// <summary>
  179. /// Gets an edge from a construction node given its index.
  180. /// </summary>
  181. /// <param name="node">The construction node where to get the edge.</param>
  182. /// <param name="index">The index or position of the edge on the node.</param>
  183. /// <returns>The edge on that index in the construction node, or null if none.</returns>
  184. public ConstructionGraphEdge? GetEdgeFromNode(ConstructionGraphNode node, int index)
  185. {
  186. return node.Edges.Count > index ? node.Edges[index] : null;
  187. }
  188. /// <summary>
  189. /// Gets a step from a construction edge given its index.
  190. /// </summary>
  191. /// <param name="edge">The construction edge where to get the step.</param>
  192. /// <param name="index">The index or position of the step on the edge.</param>
  193. /// <returns>The edge on that index in the construction edge, or null if none.</returns>
  194. public ConstructionGraphStep? GetStepFromEdge(ConstructionGraphEdge edge, int index)
  195. {
  196. return edge.Steps.Count > index ? edge.Steps[index] : null;
  197. }
  198. /// <summary>
  199. /// Performs a node change on a construction entity, optionally performing the actions for the new node.
  200. /// </summary>
  201. /// <param name="uid">The target entity.</param>
  202. /// <param name="userUid">An optional user entity, for actions.</param>
  203. /// <param name="id">The identifier of the node to change to.</param>
  204. /// <param name="performActions">Whether the actions for the new node will be performed or not.</param>
  205. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  206. /// <returns>Whether the node change succeeded or not. Also returns false if the entity does not have a <see cref="ConstructionComponent"/>.</returns>
  207. /// <remarks>This method also updates the construction pathfinding automatically, if the node change succeeds.</remarks>
  208. public bool ChangeNode(EntityUid uid, EntityUid? userUid, string id, bool performActions = true, ConstructionComponent? construction = null)
  209. {
  210. if (!Resolve(uid, ref construction))
  211. return false;
  212. if (GetCurrentGraph(uid, construction) is not {} graph
  213. || GetNodeFromGraph(graph, id) is not {} node)
  214. return false;
  215. var oldNode = construction.Node;
  216. construction.Node = id;
  217. if (userUid != null)
  218. _adminLogger.Add(LogType.Construction, LogImpact.Low,
  219. $"{ToPrettyString(userUid.Value):player} changed {ToPrettyString(uid):entity}'s node from \"{oldNode}\" to \"{id}\"");
  220. // ChangeEntity will handle the pathfinding update.
  221. if (node.Entity.GetId(uid, userUid, new(EntityManager)) is {} newEntity
  222. && ChangeEntity(uid, userUid, newEntity, construction) != null)
  223. return true;
  224. if(performActions)
  225. PerformActions(uid, userUid, node.Actions);
  226. // An action might have deleted the entity... Account for this.
  227. if (!Exists(uid))
  228. return false;
  229. UpdatePathfinding(uid, construction);
  230. return true;
  231. }
  232. /// <summary>
  233. /// Performs an entity prototype change on a construction entity.
  234. /// The old entity will be removed, and a new one will be spawned in its place. Some values will be kept,
  235. /// and any containers handled by construction will be transferred to the new entity as well.
  236. /// </summary>
  237. /// <param name="uid">The target entity.</param>
  238. /// <param name="userUid">An optional user entity, for actions.</param>
  239. /// <param name="newEntity">The entity prototype identifier for the new entity.</param>
  240. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  241. /// <param name="metaData">The metadata component of the target entity. Will be resolved if null.</param>
  242. /// <param name="transform">The transform component of the target entity. Will be resolved if null.</param>
  243. /// <param name="containerManager">The container manager component of the target entity. Will be resolved if null,
  244. /// but it is an optional component and not required for the method to work.</param>
  245. /// <returns>The new entity, or null if the method did not succeed.</returns>
  246. private EntityUid? ChangeEntity(EntityUid uid, EntityUid? userUid, string newEntity,
  247. ConstructionComponent? construction = null,
  248. MetaDataComponent? metaData = null,
  249. TransformComponent? transform = null,
  250. ContainerManagerComponent? containerManager = null)
  251. {
  252. if (!Resolve(uid, ref construction, ref metaData, ref transform))
  253. {
  254. // Failed resolve logs an error, but we want to actually log information about the failed construction
  255. // graph. So lets let the UpdateInteractions() try-catch log that info for us.
  256. throw new Exception("Missing construction components");
  257. }
  258. // Exit if the new entity's prototype is the same as the original, or the prototype is invalid
  259. if (newEntity == metaData.EntityPrototype?.ID || !PrototypeManager.HasIndex<EntityPrototype>(newEntity))
  260. return null;
  261. // [Optional] Exit if the new entity's prototype is a parent of the original
  262. // E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that
  263. // had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would
  264. // exit here because 'AirlockCommand' is derived from 'Airlock'
  265. if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
  266. metaData.EntityPrototype?.ID != null)
  267. {
  268. var parents = PrototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
  269. if (parents != null && parents.Any(x => x.ID == newEntity))
  270. return null;
  271. }
  272. // Optional resolves.
  273. Resolve(uid, ref containerManager, false);
  274. // We create the new entity.
  275. var newUid = EntityManager.CreateEntityUninitialized(newEntity, transform.Coordinates);
  276. // Construction transferring.
  277. var newConstruction = EntityManager.EnsureComponent<ConstructionComponent>(newUid);
  278. // Transfer all construction-owned containers.
  279. newConstruction.Containers.UnionWith(construction.Containers);
  280. // Prevent MapInitEvent spawned entities from spawning into the containers.
  281. // Containers created by ChangeNode() actions do not exist until after this function is complete,
  282. // but this should be fine, as long as the target entity properly declared its managed containers.
  283. if (TryComp(newUid, out ContainerFillComponent? containerFill) && containerFill.IgnoreConstructionSpawn)
  284. {
  285. foreach (var id in newConstruction.Containers)
  286. {
  287. containerFill.Containers.Remove(id);
  288. }
  289. }
  290. // If the new entity has the *same* construction graph, stay on the same node.
  291. // If not, we effectively restart the construction graph, so the new entity can be completed.
  292. if (construction.Graph == newConstruction.Graph)
  293. {
  294. ChangeNode(newUid, userUid, construction.Node, false, newConstruction);
  295. // Retain the target node if an entity change happens in response to deconstruction;
  296. // in that case, we must continue to move towards the start node.
  297. if (construction.TargetNode is {} targetNode)
  298. SetPathfindingTarget(newUid, targetNode, newConstruction);
  299. }
  300. // Transfer all pending interaction events too.
  301. while (construction.InteractionQueue.TryDequeue(out var ev))
  302. {
  303. newConstruction.InteractionQueue.Enqueue(ev);
  304. }
  305. if (newConstruction.InteractionQueue.Count > 0 && _queuedUpdates.Add(newUid))
  306. _constructionUpdateQueue.Enqueue(newUid);
  307. // Transform transferring.
  308. var newTransform = Transform(newUid);
  309. TransformSystem.AttachToGridOrMap(newUid, newTransform); // in case in hands or a container
  310. newTransform.LocalRotation = transform.LocalRotation;
  311. newTransform.Anchored = transform.Anchored;
  312. // Container transferring.
  313. if (containerManager != null)
  314. {
  315. // Ensure the new entity has a container manager. Also for resolve goodness.
  316. var newContainerManager = EntityManager.EnsureComponent<ContainerManagerComponent>(newUid);
  317. // Transfer all construction-owned containers from the old entity to the new one.
  318. foreach (var container in construction.Containers)
  319. {
  320. if (!_container.TryGetContainer(uid, container, out var ourContainer, containerManager))
  321. continue;
  322. if (!_container.TryGetContainer(newUid, container, out var otherContainer, newContainerManager))
  323. {
  324. // NOTE: Only Container is supported by Construction!
  325. // todo: one day, the ensured container should be the same type as ourContainer
  326. otherContainer = _container.EnsureContainer<Container>(newUid, container, newContainerManager);
  327. }
  328. for (var i = ourContainer.ContainedEntities.Count - 1; i >= 0; i--)
  329. {
  330. var entity = ourContainer.ContainedEntities[i];
  331. _container.Remove(entity, ourContainer, reparent: false, force: true);
  332. _container.Insert(entity, otherContainer);
  333. }
  334. }
  335. }
  336. var entChangeEv = new ConstructionChangeEntityEvent(newUid, uid);
  337. RaiseLocalEvent(uid, entChangeEv);
  338. RaiseLocalEvent(newUid, entChangeEv, broadcast: true);
  339. foreach (var logic in GetCurrentNode(newUid, newConstruction)!.TransformLogic)
  340. {
  341. logic.Transform(uid, newUid, userUid, new(EntityManager));
  342. }
  343. EntityManager.InitializeAndStartEntity(newUid);
  344. QueueDel(uid);
  345. return newUid;
  346. }
  347. /// <summary>
  348. /// Performs a construction graph change on a construction entity, also changing the node to a valid one on
  349. /// the new graph.
  350. /// </summary>
  351. /// <param name="uid">The target entity.</param>
  352. /// <param name="userUid">An optional user entity, for actions.</param>
  353. /// <param name="graphId">The identifier for the construction graph to switch to.</param>
  354. /// <param name="nodeId">The identifier for a node on the new construction graph to switch to.</param>
  355. /// <param name="performActions">Whether actions on the new node will be performed or not.</param>
  356. /// <param name="construction">The construction component of the target entity. Will be resolved if null.</param>
  357. /// <returns>Whether the construction graph change succeeded or not. Returns false if the entity does not have
  358. /// a <see cref="ConstructionComponent"/>.</returns>
  359. public bool ChangeGraph(EntityUid uid, EntityUid? userUid, string graphId, string nodeId, bool performActions = true, ConstructionComponent? construction = null)
  360. {
  361. if (!Resolve(uid, ref construction))
  362. return false;
  363. if (!PrototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
  364. return false;
  365. if(GetNodeFromGraph(graph, nodeId) is not {})
  366. return false;
  367. construction.Graph = graphId;
  368. return ChangeNode(uid, userUid, nodeId, performActions, construction);
  369. }
  370. }
  371. /// <summary>
  372. /// This event gets raised when an entity changes prototype / uid during construction. The event is raised
  373. /// directed both at the old and new entity.
  374. /// </summary>
  375. public sealed class ConstructionChangeEntityEvent : EntityEventArgs
  376. {
  377. public readonly EntityUid New;
  378. public readonly EntityUid Old;
  379. public ConstructionChangeEntityEvent(EntityUid newUid, EntityUid oldUid)
  380. {
  381. New = newUid;
  382. Old = oldUid;
  383. }
  384. }
  385. }