private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args) { if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype constructionPrototype)) { Logger.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!"); RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack)); return; } if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype constructionGraph)) { Logger.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!"); RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack)); return; } var startNode = constructionGraph.Nodes[constructionPrototype.StartNode]; var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode]; var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name); var user = args.SenderSession.AttachedEntity; if (_beingBuilt.TryGetValue(args.SenderSession, out var set)) { if (!set.Add(ev.Ack)) { user.PopupMessageCursor(Loc.GetString("You are already building that!")); return; } } else { var newSet = new HashSet <int> { ev.Ack }; _beingBuilt[args.SenderSession] = newSet; } foreach (var condition in constructionPrototype.Conditions) { if (!condition.Condition(user, ev.Location, ev.Angle.GetCardinalDir())) { Cleanup(); return; } } void Cleanup() { _beingBuilt[args.SenderSession].Remove(ev.Ack); } if (user == null || !ActionBlockerSystem.CanInteract(user) || !user.TryGetComponent(out HandsComponent? hands) || hands.GetActiveHand == null || !user.InRangeUnobstructed(ev.Location, ignoreInsideBlocker:constructionPrototype.CanBuildInImpassable)) { Cleanup(); return; } if (pathFind == null) { throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}"); } var edge = startNode.GetEdge(pathFind[0].Name); if (edge == null) { throw new InvalidDataException($"Can't find edge from starting node to the next node in pathfinding! Recipe: {ev.PrototypeName}"); } var valid = false; var holding = hands.GetActiveHand?.Owner; if (holding == null) { Cleanup(); return; } // No support for conditions here! foreach (var step in edge.Steps) { switch (step) { case EntityInsertConstructionGraphStep entityInsert: if (entityInsert.EntityValid(holding)) { valid = true; } break; case ToolConstructionGraphStep _: case NestedConstructionGraphStep _: throw new InvalidDataException("Invalid first step for item recipe!"); } if (valid) { break; } } if (!valid) { Cleanup(); return; } var structure = await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph, edge, targetNode); if (structure == null) { Cleanup(); return; } structure.Transform.Coordinates = ev.Location; structure.Transform.LocalRotation = constructionPrototype.CanRotate ? ev.Angle : Angle.South; RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack)); Cleanup(); }
private void UserInteraction(IEntity player, GridCoordinates coordinates, EntityUid clickedUid) { // Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null if (!EntityManager.TryGetEntity(clickedUid, out var attacked)) { attacked = null; } // Verify player has a transform component if (!player.TryGetComponent <ITransformComponent>(out var playerTransform)) { return; } // Verify player is on the same map as the entity he clicked on if (_mapManager.GetGrid(coordinates.GridID).ParentMapId != playerTransform.MapID) { Logger.WarningS("system.interaction", $"Player named {player.Name} clicked on a map he isn't located on"); return; } // Verify player has a hand, and find what object he is currently holding in his active hand if (!player.TryGetComponent <IHandsComponent>(out var hands)) { return; } var item = hands.GetActiveHand?.Owner; if (!ActionBlockerSystem.CanInteract(player)) { return; } playerTransform.LocalRotation = new Angle(coordinates.ToMapPos(_mapManager) - playerTransform.MapPosition.Position); // TODO: Check if client should be able to see that object to click on it in the first place // Clicked on empty space behavior, try using ranged attack if (attacked == null) { if (item != null) { // After attack: Check if we clicked on an empty location, if so the only interaction we can do is AfterAttack InteractAfterAttack(player, item, coordinates); } return; } // Verify attacked object is on the map if we managed to click on it somehow if (!attacked.Transform.IsMapTransform) { Logger.WarningS("system.interaction", $"Player named {player.Name} clicked on object {attacked.Name} that isn't currently on the map somehow"); return; } // Check if ClickLocation is in object bounds here, if not lets log as warning and see why if (attacked.TryGetComponent(out ICollidableComponent collideComp)) { if (!collideComp.WorldAABB.Contains(coordinates.ToMapPos(_mapManager))) { Logger.WarningS("system.interaction", $"Player {player.Name} clicked {attacked.Name} outside of its bounding box component somehow"); return; } } // RangedAttack/AfterAttack: Check distance between user and clicked item, if too large parse it in the ranged function // TODO: have range based upon the item being used? or base it upon some variables of the player himself? var distance = (playerTransform.WorldPosition - attacked.Transform.WorldPosition).LengthSquared; if (distance > InteractionRangeSquared) { if (item != null) { RangedInteraction(player, item, attacked, coordinates); return; } return; // Add some form of ranged AttackHand here if you need it someday, or perhaps just ways to modify the range of AttackHand } // We are close to the nearby object and the object isn't contained in our active hand // AttackBy/AfterAttack: We will either use the item on the nearby object if (item != null) { Interaction(player, item, attacked, coordinates); } // AttackHand/Activate: Since our hand is empty we will use AttackHand/Activate else { Interaction(player, attacked); } }
private async void HandleStartItemConstruction(TryStartItemConstructionMessage ev, EntitySessionEventArgs args) { if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype constructionPrototype)) { Logger.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!"); return; } if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype constructionGraph)) { Logger.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!"); return; } var startNode = constructionGraph.Nodes[constructionPrototype.StartNode]; var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode]; var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name); var user = args.SenderSession.AttachedEntity; if (user == null || !ActionBlockerSystem.CanInteract(user)) { return; } if (!user.TryGetComponent(out HandsComponent? hands)) { return; } foreach (var condition in constructionPrototype.Conditions) { if (!condition.Condition(user, user.ToCoordinates(), Direction.South)) { return; } } if (pathFind == null) { throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}"); } var edge = startNode.GetEdge(pathFind[0].Name); if (edge == null) { throw new InvalidDataException($"Can't find edge from starting node to the next node in pathfinding! Recipe: {ev.PrototypeName}"); } // No support for conditions here! foreach (var step in edge.Steps) { switch (step) { case ToolConstructionGraphStep _: case NestedConstructionGraphStep _: throw new InvalidDataException("Invalid first step for construction recipe!"); } } var item = await Construct(user, "item_construction", constructionGraph, edge, targetNode); if (item != null && item.TryGetComponent(out ItemComponent? itemComp)) { hands.PutInHandOrDrop(itemComp); } }
private void UserInteraction(IEntity player, GridCoordinates coordinates, EntityUid clickedUid) { //Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null if (!EntityManager.TryGetEntity(clickedUid, out var attacked)) { attacked = null; } //Verify player has a transform component if (!player.TryGetComponent <ITransformComponent>(out var playerTransform)) { return; } //Verify player is on the same map as the entity he clicked on else if (coordinates.MapID != playerTransform.MapID) { Logger.Warning(string.Format("Player named {0} clicked on a map he isn't located on", player.Name)); return; } //Verify player has a hand, and find what object he is currently holding in his active hand if (!player.TryGetComponent <IHandsComponent>(out var hands)) { return; } var item = hands.GetActiveHand?.Owner; if (!ActionBlockerSystem.CanInteract(player)) { return; } //TODO: Check if client should be able to see that object to click on it in the first place, prevent using locaters by firing a laser or something //Clicked on empty space behavior, try using ranged attack if (attacked == null && item != null) { //AFTERATTACK: Check if we clicked on an empty location, if so the only interaction we can do is afterattack InteractAfterattack(player, item, coordinates); return; } else if (attacked == null) { return; } //Verify attacked object is on the map if we managed to click on it somehow if (!attacked.GetComponent <ITransformComponent>().IsMapTransform) { Logger.Warning(string.Format("Player named {0} clicked on object {1} that isn't currently on the map somehow", player.Name, attacked.Name)); return; } //Check if ClickLocation is in object bounds here, if not lets log as warning and see why if (attacked.TryGetComponent(out BoundingBoxComponent boundingbox)) { if (!boundingbox.WorldAABB.Contains(coordinates.Position)) { Logger.Warning(string.Format("Player {0} clicked {1} outside of its bounding box component somehow", player.Name, attacked.Name)); return; } } //RANGEDATTACK/AFTERATTACK: Check distance between user and clicked item, if too large parse it in the ranged function //TODO: have range based upon the item being used? or base it upon some variables of the player himself? var distance = (playerTransform.WorldPosition - attacked.GetComponent <ITransformComponent>().WorldPosition).LengthSquared; if (distance > INTERACTION_RANGE_SQUARED) { if (item != null) { RangedInteraction(player, item, attacked, coordinates); return; } return; //Add some form of ranged attackhand here if you need it someday, or perhaps just ways to modify the range of attackhand } //We are close to the nearby object and the object isn't contained in our active hand //ATTACKBY/AFTERATTACK: We will either use the item on the nearby object if (item != null) { Interaction(player, item, attacked, coordinates); } //ATTACKHAND: Since our hand is empty we will use attackhand else { Interaction(player, attacked); } }