private async Task <IEntity?> Construct(IEntity user, string materialContainer, ConstructionGraphPrototype graph, ConstructionGraphEdge edge, ConstructionGraphNode targetNode) { // We need a place to hold our construction items! var container = ContainerManagerComponent.Ensure <Container>(materialContainer, user, out var existed); if (existed) { user.PopupMessageCursor(Loc.GetString("You can't start another construction now!")); return(null); } var containers = new Dictionary <string, Container>(); var doAfterTime = 0f; // HOLY SHIT THIS IS SOME HACKY CODE. // But I'd rather do this shit than risk having collisions with other containers. Container GetContainer(string name) { if (containers !.ContainsKey(name)) { return(containers[name]); } while (true) { var random = _robustRandom.Next(); var c = ContainerManagerComponent.Ensure <Container>(random.ToString(), user !, out var existed); if (existed) { continue; } containers[name] = c; return(c); } } void FailCleanup() { foreach (var entity in container !.ContainedEntities.ToArray()) { container.Remove(entity); } foreach (var cont in containers !.Values) { foreach (var entity in cont.ContainedEntities.ToArray()) { cont.Remove(entity); } } // If we don't do this, items are invisible for some f*****g reason. Nice. Timer.Spawn(1, ShutdownContainers); } void ShutdownContainers() { container !.Shutdown(); foreach (var c in containers !.Values.ToArray()) { c.Shutdown(); } } var failed = false; var steps = new List <ConstructionGraphStep>(); foreach (var step in edge.Steps) { doAfterTime += step.DoAfter; var handled = false; switch (step) { case MaterialConstructionGraphStep materialStep: foreach (var entity in EnumerateNearby(user)) { if (!materialStep.EntityValid(entity, out var sharedStack)) { continue; } var stack = (StackComponent)sharedStack; if (!stack.Split(materialStep.Amount, user.ToCoordinates(), out var newStack)) { continue; } if (string.IsNullOrEmpty(materialStep.Store)) { if (!container.Insert(newStack)) { continue; } } else if (!GetContainer(materialStep.Store).Insert(newStack)) { continue; } handled = true; break; } break; case ComponentConstructionGraphStep componentStep: foreach (var entity in EnumerateNearby(user)) { if (!componentStep.EntityValid(entity)) { continue; } if (string.IsNullOrEmpty(componentStep.Store)) { if (!container.Insert(entity)) { continue; } } else if (!GetContainer(componentStep.Store).Insert(entity)) { continue; } handled = true; break; } break; case PrototypeConstructionGraphStep prototypeStep: foreach (var entity in EnumerateNearby(user)) { if (!prototypeStep.EntityValid(entity)) { continue; } if (string.IsNullOrEmpty(prototypeStep.Store)) { if (!container.Insert(entity)) { continue; } } else if (!GetContainer(prototypeStep.Store).Insert(entity)) { continue; } handled = true; break; } break; } if (handled == false) { failed = true; break; } steps.Add(step); } if (failed) { user.PopupMessageCursor(Loc.GetString("You don't have the materials to build that!")); FailCleanup(); return(null); } var doAfterSystem = Get <DoAfterSystem>(); var doAfterArgs = new DoAfterEventArgs(user, doAfterTime) { BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = false, BreakOnUserMove = true, NeedHand = true, }; if (await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Cancelled) { FailCleanup(); return(null); } var newEntity = EntityManager.SpawnEntity(graph.Nodes[edge.Target].Entity, user.Transform.Coordinates); // Yes, this should throw if it's missing the component. var construction = newEntity.GetComponent <ConstructionComponent>(); // We attempt to set the pathfinding target. construction.Target = targetNode; // We preserve the containers... foreach (var(name, cont) in containers) { var newCont = ContainerManagerComponent.Ensure <Container>(name, newEntity); foreach (var entity in cont.ContainedEntities.ToArray()) { cont.ForceRemove(entity); newCont.Insert(entity); } } // We now get rid of all them. ShutdownContainers(); // We have step completed steps! foreach (var step in steps) { foreach (var completed in step.Completed) { await completed.PerformAction(newEntity, user); } } // And we also have edge completed effects! foreach (var completed in edge.Completed) { await completed.PerformAction(newEntity, user); } return(newEntity); }
private bool UpdatePathfinding(EntityUid uid, ConstructionGraphPrototype graph, ConstructionGraphNode currentNode, ConstructionGraphNode targetNode, ConstructionGraphEdge?currentEdge, ConstructionComponent?construction = null) { if (!Resolve(uid, ref construction)) { return(false); } construction.TargetNode = targetNode.Name; // Check if we reached the target node. if (currentNode == targetNode) { ClearPathfinding(uid, construction); return(true); } // If we don't have a path, generate it. if (construction.NodePathfinding == null) { var path = graph.PathId(currentNode.Name, targetNode.Name); if (path == null || path.Length == 0) { // No path. ClearPathfinding(uid, construction); return(false); } construction.NodePathfinding = new Queue <string>(path); } // If the next pathfinding node is the one we're at, dequeue it. if (construction.NodePathfinding.Peek() == currentNode.Name) { construction.NodePathfinding.Dequeue(); } if (currentEdge != null && construction.TargetEdgeIndex is {} targetEdgeIndex) { if (currentNode.Edges.Count >= targetEdgeIndex) { // Target edge is incorrect. construction.TargetEdgeIndex = null; } else if (currentNode.Edges[targetEdgeIndex] != currentEdge) { // We went the wrong way, clean up! ClearPathfinding(uid, construction); return(false); } } if (construction.EdgeIndex == null && construction.TargetEdgeIndex == null && construction.NodePathfinding != null) { construction.TargetEdgeIndex = (currentNode.GetEdgeIndex(construction.NodePathfinding.Peek())); } return(true); }