private void OperateRepairTool(float deltaTime) { character.CursorPosition = Item.WorldPosition; if (character.Submarine != null) { character.CursorPosition -= character.Submarine.Position; } if (repairTool.Item.RequireAimToUse) { character.SetInput(InputType.Aim, false, true); } Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position; if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2)) { // Too close -> steer away character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2); } else { character.AIController.SteeringManager.Reset(); } if (VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4) { repairTool.Use(deltaTime, character); } }
private Vector2 DiffToCurrentNode() { if (currentPath == null || currentPath.Unreachable) { return(Vector2.Zero); } if (currentPath.Finished) { Vector2 pos2 = host.SimPosition; if (character != null && character.Submarine == null && CurrentPath.Nodes.Count > 0 && CurrentPath.Nodes.Last().Submarine != null) { pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition; } return(currentTarget - pos2); } if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f) { CheckDoorsInPath(); } Vector2 pos = host.SimPosition; if (character != null && currentPath.CurrentNode != null) { if (CurrentPath.CurrentNode.Submarine != null) { if (character.Submarine == null) { pos -= CurrentPath.CurrentNode.Submarine.SimPosition; } else if (character.Submarine != currentPath.CurrentNode.Submarine) { pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); } } } bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; //only humanoids can climb ladders if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent) { if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item && currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition)) { currentPath.CurrentNode.Ladders.Item.TryInteract(character, false, true); } } var collider = character.AnimController.Collider; if (character.IsClimbing && !isDiving) { Vector2 diff = currentPath.CurrentNode.SimPosition - pos; bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent; if (nextLadderSameAsCurrent) { //climbing ladders -> don't move horizontally diff.X = 0.0f; } //at the same height as the waypoint if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f) { float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY; if (heightFromFloor <= 0.0f) { diff.Y = Math.Max(diff.Y, 1.0f); } bool aboveFloor = heightFromFloor > 0 && heightFromFloor < collider.height * 1.5f; if (aboveFloor || IsNextNodeLadder) { if (!nextLadderSameAsCurrent) { character.AnimController.Anim = AnimController.Animation.None; } currentPath.SkipToNextNode(); } } else if (nextLadderSameAsCurrent) { //if the current node is below the character and the next one is above (or vice versa) //and both are on ladders, we can skip directly to the next one //e.g. no point in going down to reach the starting point of a path when we could go directly to the one above if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y)) { currentPath.SkipToNextNode(); } } return(diff); } else if (character.AnimController.InWater) { // If the character is underwater, we don't need the ladders anymore if (character.IsClimbing && isDiving) { character.AnimController.Anim = AnimController.Animation.None; character.SelectedConstruction = null; } float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1)); if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 2 * multiplier, 2)) { currentPath.SkipToNextNode(); } } else { Vector2 colliderBottom = character.AnimController.GetColliderBottom(); Vector2 colliderSize = collider.GetSize(); Vector2 velocity = collider.LinearVelocity; // Cannot use the head position, because not all characters have head or it can be below the total height of the character float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor; float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y; bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight; float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1)); float targetDistance = collider.radius * margin; if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh) { currentPath.SkipToNextNode(); } } if (currentPath.CurrentNode == null) { return(Vector2.Zero); } return(currentPath.CurrentNode.SimPosition - pos); }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; } else { if (i == 0) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { currentWaypoint = currentPath.PrevNode; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node shouldBeOpen = true; } else { door = currentWaypoint.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int currentDir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f; } else { int currentDir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f; } } } if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return(true); } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, door.Item.WorldPosition); if (closestButton == null || distance < closestDist) { closestButton = button; closestDist = distance; } return(true); }); if (canAccess) { if (door.HasIntegratedButtons) { door.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else if (closestButton != null) { if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else { // Can't reach the button closest to the door. // It's possible that we could reach another buttons. // If this becomes an issue, we could go through them here and check if any of them are reachable // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) //currentPath.Unreachable = true; return; } } } else if (shouldBeOpen) { currentPath.Unreachable = true; return; } } } }
protected override void Act(float deltaTime) { var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true); if (weldingTool == null) { TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC), onAbandon: () => { if (character.IsOnPlayerTeam && objectiveManager.IsCurrentOrder <AIObjectiveFixLeaks>()) { character.Speak(TextManager.Get("dialogcannotfindweldingequipment"), null, 0.0f, "dialogcannotfindweldingequipment", 10.0f); } Abandon = true; }, onCompleted: () => RemoveSubObjective(ref getWeldingTool)); return; } else { if (weldingTool.OwnInventory == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory"); #endif Abandon = true; return; } HumanAIController.UnequipContainedItems(weldingTool, it => !it.HasTag("weldingfuel")); HumanAIController.UnequipEmptyItems(weldingTool); if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f)) { TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC), onAbandon: () => { Abandon = true; ReportWeldingFuelTankCount(); }, onCompleted: () => { RemoveSubObjective(ref refuelObjective); ReportWeldingFuelTankCount(); }); void ReportWeldingFuelTankCount() { int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1); if (remainingOxygenTanks == 0) { character.Speak(TextManager.Get("DialogOutOfWeldingFuel"), null, 0.0f, "outofweldingfuel", 30.0f); } else if (remainingOxygenTanks < 4) { character.Speak(TextManager.Get("DialogLowOnWeldingFuel"), null, 0.0f, "lowonweldingfuel", 30.0f); } } return; } } if (subObjectives.Any()) { return; } var repairTool = weldingTool.GetComponent <RepairTool>(); if (repairTool == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); #endif Abandon = true; return; } Vector2 toLeak = Leak.WorldPosition - character.WorldPosition; // TODO: use the collider size/reach? if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150) { HumanAIController.AnimController.Crouching = true; } float reach = CalculateReach(repairTool, character); bool canOperate = toLeak.LengthSquared() < reach * reach; if (canOperate) { TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak), onAbandon: () => Abandon = true, onCompleted: () => { if (Check()) { IsCompleted = true; } else { // Failed to operate. Probably too far. Abandon = true; } }); } else { TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager) { CloseEnough = reach, DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null, TargetName = Leak.FlowTargetHull?.DisplayName, CheckVisibility = false }, onAbandon: () => { if (Check()) { IsCompleted = true; } else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2)) { // Too far Abandon = true; } else { // We are close, try again. RemoveSubObjective(ref gotoObjective); } }, onCompleted: () => RemoveSubObjective(ref gotoObjective)); } }
public Hull FindBestHull(IEnumerable <Hull> ignoredHulls = null, bool allowChangingTheSubmarine = true) { Hull bestHull = null; float bestValue = 0; foreach (Hull hull in Hull.hullList) { if (hull.Submarine == null) { continue; } if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } if (unreachable.Contains(hull)) { continue; } float hullSafety = 0; if (character.CurrentHull != null && character.Submarine != null) { // Inside if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, character); // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); hullSafety *= distanceFactor; //skip the hull if the safety is already less than the best hull //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) if (hullSafety < bestValue) { continue; } var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); if (path.Unreachable) { unreachable.Add(hull); continue; } // Each unsafe node reduces the hull safety value. // Ignore the current hull, because otherwise we couldn't find a path out. int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); hullSafety /= 1 + unsafeNodes; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { hullSafety /= 10; } } else { // Outside if (hull.RoomName != null && hull.RoomName.ToLowerInvariant().Contains("airlock")) { hullSafety = 100; } else { // TODO: could also target gaps that get us inside? foreach (Item item in Item.ItemList) { if (item.CurrentHull != hull && item.HasTag("airlock")) { hullSafety = 100; break; } } } // TODO: could we get a closest door to the outside and target the flowing hull if no airlock is found? // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); hullSafety *= distanceFactor; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC) { hullSafety /= 10; } } if (hullSafety > bestValue) { bestHull = hull; bestValue = hullSafety; } } return(bestHull); }
public Hull FindBestHull(IEnumerable <Hull> ignoredHulls = null) { Hull bestHull = null; float bestValue = 0; foreach (Hull hull in Hull.hullList) { if (hull.Submarine == null) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } float hullSafety = 0; if (character.Submarine != null && SteeringManager == PathSteering) { // Inside or outside near the sub if (unreachable.Contains(hull)) { continue; } if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, character); // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); hullSafety *= distanceFactor; // Each unsafe node reduces the hull safety value. // Ignore current hull, because otherwise the would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); if (path.Unreachable) { continue; } int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); hullSafety /= 1 + unsafeNodes; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { hullSafety /= 10; } } else { // Outside if (hull.RoomName?.ToLowerInvariant() == "airlock") { hullSafety = 100; } else { // TODO: could also target gaps that get us inside? foreach (Item item in Item.ItemList) { if (item.CurrentHull == hull && item.HasTag("airlock")) { hullSafety = 100; break; } } } // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); hullSafety *= distanceFactor; // If the target is not inside a friendly submarine, considerably reduce the hull safety. if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC) { hullSafety /= 10; } } if (hullSafety > bestValue) { bestHull = hull; bestValue = hullSafety; } } return(bestHull); }
private void CheckDoorsInPath() { if (!canOpenDoors) { return; } for (int i = 0; i < 5; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; if (i > 0) { break; } } else { bool closeDoors = character.IsBot && character.IsInFriendlySub || character.Params.AI != null && character.Params.AI.KeepDoorsClosed; if (i == 0 || !closeDoors) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { int previousIndex = currentPath.CurrentIndex - i; if (previousIndex < 0) { break; } currentWaypoint = currentPath.Nodes[previousIndex]; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node if (currentWaypoint.ConnectedDoor.LinkedGap != null) { // Keep the airlock doors closed, but not in ruins/wrecks if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom || currentWaypoint.Submarine?.Info.IsRuin != null || currentWaypoint.Submarine?.Info.IsWreck != null) { shouldBeOpen = true; door = currentWaypoint.ConnectedDoor; } } } else { float colliderLength = GetColliderLength(); door = currentWaypoint.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); float size = character.AnimController.InWater ? colliderLength : GetColliderSize().X; shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -size; } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -colliderLength; } } } if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if ((door.IsOpen || door.IsBroken) != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return(true); } // Check that the button is on the right side of the door. if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.X - door.Item.WorldPosition.X); if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return(false); } } else { int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y); if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return(false); } } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition); if (closestButton == null || distance < closestDist) { closestButton = button; closestDist = distance; } return(true); }); if (canAccess) { if (door.HasIntegratedButtons) { door.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else if (closestButton != null) { if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance + GetColliderLength(), 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else { // Can't reach the button closest to the character. // It's possible that we could reach another buttons. // If this becomes an issue, we could go through them here and check if any of them are reachable // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) var body = Submarine.PickBody(character.SimPosition, character.GetRelativeSimPosition(closestButton.Item), collisionCategory: Physics.CollisionWall | Physics.CollisionLevel); if (body != null) { if (body.UserData is Item item) { var d = item.GetComponent <Door>(); if (d == null || d.IsOpen) { return; } } // The button is on the wrong side of the door or a wall currentPath.Unreachable = true; } return; } } } else if (shouldBeOpen) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Pathfinding error: Cannot access the door", Color.Yellow); #endif currentPath.Unreachable = true; return; } } } }
protected override void Act(float deltaTime) { FindTargetItem(); if (targetItem == null || moveToTarget == null) { HumanAIController.ObjectiveManager.GetObjective <AIObjectiveIdle>().Wander(deltaTime); //SteeringManager.SteeringWander(); return; } if (moveToTarget.CurrentHull == character.CurrentHull && Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2)) { int targetSlot = -1; if (equip) { var pickable = targetItem.GetComponent <Pickable>(); if (pickable == null) { canBeCompleted = false; return; } //check if all the slots required by the item are free foreach (InvSlotType slots in pickable.AllowedSlots) { if (slots.HasFlag(InvSlotType.Any)) { continue; } for (int i = 0; i < character.Inventory.Items.Length; i++) { //slot not needed by the item, continue if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; } targetSlot = i; //slot free, continue if (character.Inventory.Items[i] == null) { continue; } //try to move the existing item to LimbSlot.Any and continue if successful if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List <InvSlotType>() { InvSlotType.Any })) { continue; } //if everything else fails, simply drop the existing item character.Inventory.Items[i].Drop(character); } } } targetItem.TryInteract(character, false, true); if (targetSlot > -1 && !character.HasEquippedItem(targetItem)) { character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character); } } else { if (goToObjective == null || moveToTarget != goToObjective.Target) { //check if we're already looking for a diving gear bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) || (itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit"))); //don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear); } goToObjective.TryComplete(deltaTime); if (!goToObjective.CanBeCompleted) { targetItem = null; moveToTarget = null; ignoredItems.Add(targetItem); } } }
protected override void Act(float deltaTime) { var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true); if (weldingTool == null) { TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getWeldingTool)); return; } else { var containedItems = weldingTool.OwnInventory?.Items; if (containedItems == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory"); #endif Abandon = true; return; } // Drop empty tanks foreach (Item containedItem in containedItems) { if (containedItem == null) { continue; } if (containedItem.Condition <= 0.0f) { containedItem.Drop(character); } } if (containedItems.None(i => i != null && i.HasTag("weldingfuel") && i.Condition > 0.0f)) { TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent <ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref refuelObjective)); return; } } if (subObjectives.Any()) { return; } var repairTool = weldingTool.GetComponent <RepairTool>(); if (repairTool == null) { #if DEBUG DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); #endif Abandon = true; return; } Vector2 toLeak = Leak.WorldPosition - character.WorldPosition; // TODO: use the collider size/reach? if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150) { HumanAIController.AnimController.Crouching = true; } float reach = CalculateReach(repairTool, character); bool canOperate = toLeak.LengthSquared() < reach * reach; if (canOperate) { TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak), onAbandon: () => Abandon = true, onCompleted: () => { if (Check()) { IsCompleted = true; } else { // Failed to operate. Probably too far. Abandon = true; } }); } else { TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager) { CloseEnough = reach, DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null, TargetName = Leak.FlowTargetHull?.DisplayName, CheckVisibility = false }, onAbandon: () => { if (Check()) { IsCompleted = true; } else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2)) { // Too far Abandon = true; } else { // We are close, try again. RemoveSubObjective(ref gotoObjective); } }, onCompleted: () => RemoveSubObjective(ref gotoObjective)); } }
private void CheckDoorsInPath() { for (int i = 0; i < 2; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; if (currentPath.Nodes.Count == 1) { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; } else { if (i == 0) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { currentWaypoint = currentPath.PrevNode; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node shouldBeOpen = true; } else { door = currentWaypoint.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -50.0f; } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -80.0f; } } } if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; bool canAccess = CanAccessDoor(door, button => { if (currentWaypoint == null) { return(true); } // Check that the button is on the right side of the door. if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return(false); } } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return(false); } } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition); if (closestButton == null || distance < closestDist) { closestButton = button; closestDist = distance; } return(true); }); if (canAccess) { if (door.HasIntegratedButtons) { door.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else if (closestButton != null) { if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; break; } else { // Can't reach the button closest to the character. // It's possible that we could reach another buttons. // If this becomes an issue, we could go through them here and check if any of them are reachable // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) var body = Submarine.PickBody(character.SimPosition, character.GetRelativeSimPosition(closestButton.Item), collisionCategory: Physics.CollisionWall | Physics.CollisionLevel); if (body != null) { if (body.UserData is Item item) { var d = item.GetComponent <Door>(); if (d == null || d.IsOpen) { return; } } // The button is on the wrong side of the door or a wall currentPath.Unreachable = true; } return; } } } else if (shouldBeOpen) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Pathfinding error: Cannot access the door", Color.Yellow); #endif currentPath.Unreachable = true; return; } } } }