protected override void Act(float deltaTime) { if (!Leak.IsRoomToRoom) { if (!HumanAIController.HasDivingSuit(character)) { TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager)); return; } } var weldingTool = character.Inventory.FindItemByTag("weldingtool"); if (weldingTool == null) { TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); return; } else { var containedItems = weldingTool.ContainedItems; if (containedItems == null) { #if DEBUG DebugConsole.ThrowError("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.HasTag("weldingfueltank") && i.Condition > 0.0f)) { TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent <ItemContainer>(), objectiveManager)); return; } } if (subObjectives.Any()) { return; } var repairTool = weldingTool.GetComponent <RepairTool>(); if (repairTool == null) { #if DEBUG DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); #endif abandon = true; return; } Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition; // TODO: use the collider size/reach? if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) { HumanAIController.AnimController.Crouching = true; } // Use a greater reach, because the distance is calculated from the character to the leak, not from the item to the leak. float reach = repairTool.Range + ((HumanoidAnimController)character.AnimController).ArmLength; bool canOperate = gapDiff.LengthSquared() < reach * reach; if (canOperate) { TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak)); } else { TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager) { CloseEnough = reach }); } }
protected override void Act(float deltaTime) { var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true); if (weldingTool == null) { TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, true), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getWeldingTool)); return; } else { var containedItems = weldingTool.ContainedItems; 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.HasTag("weldingfuel") && i.Condition > 0.0f)) { TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent <ItemContainer>(), objectiveManager), 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 = repairTool.Range + ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength); 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) { AllowGoingOutside = !Leak.IsRoomToRoom && objectiveManager.IsCurrentOrder <AIObjectiveFixLeaks>() && HumanAIController.HasDivingSuit(character, conditionPercentage: 50), CloseEnough = reach, DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null, TargetName = Leak.FlowTargetHull?.DisplayName }, onAbandon: () => { if (Check()) { IsCompleted = true; } else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > reach * reach * 2) { // Too far Abandon = true; } else { // We are close, try again. RemoveSubObjective(ref gotoObjective); } }, onCompleted: () => RemoveSubObjective(ref gotoObjective)); } }
protected override void Act(float deltaTime) { var currentHull = character.AnimController.CurrentHull; bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull); bool needsDivingSuit = needsDivingGear && (currentHull == null || currentHull.WaterPercentage > 90); bool needsEquipment = false; if (needsDivingSuit) { needsEquipment = !HumanAIController.HasDivingSuit(character); } else if (needsDivingGear) { needsEquipment = !HumanAIController.HasDivingMask(character); } if (needsEquipment) { TryAddSubObjective(ref divingGearObjective, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer)); } else { if (divingGearObjective != null && divingGearObjective.IsCompleted()) { // Reset the devotion. Priority = 0; divingGearObjective = null; } if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) { searchHullTimer = Math.Min(1, searchHullTimer); } if (searchHullTimer > 0.0f) { searchHullTimer -= deltaTime; } else { searchHullTimer = SearchHullInterval; previousSafeHull = currentSafeHull; currentSafeHull = FindBestHull(); if (currentSafeHull == null) { currentSafeHull = previousSafeHull; } if (currentSafeHull != null && currentSafeHull != currentHull) { if (goToObjective?.Target != currentSafeHull) { goToObjective = null; } TryAddSubObjective(ref goToObjective, constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true) { AllowGoingOutside = HumanAIController.HasDivingSuit(character) }, onAbandon: () => unreachable.Add(goToObjective.Target as Hull)); } else { goToObjective = null; } } if (goToObjective != null) { if (goToObjective.IsCompleted()) { objectiveManager.GetObjective <AIObjectiveIdle>()?.Wander(deltaTime); } Priority = 0; return; } if (currentHull == null) { return; } //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) // -> attempt to manually steer away from hazards Vector2 escapeVel = Vector2.Zero; // TODO: optimize foreach (FireSource fireSource in HumanAIController.VisibleHulls.SelectMany(h => h.FireSources)) { Vector2 dir = character.Position - fireSource.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } foreach (Character enemy in Character.CharacterList) { if (enemy.IsDead || enemy.IsUnconscious || enemy.Removed || HumanAIController.IsFriendly(enemy)) { continue; } if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull)) { Vector2 dir = character.Position - enemy.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } } if (escapeVel != Vector2.Zero) { float left = currentHull.Rect.X + 50; float right = currentHull.Rect.Right - 50; //only move if we haven't reached the edge of the room if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right) { character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); } else { character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; character.AIController.SteeringManager.Reset(); } } else { Priority = 0; objectiveManager.GetObjective <AIObjectiveIdle>()?.Wander(deltaTime); } } }
protected override void Act(float deltaTime) { if (followControlledCharacter) { if (Character.Controlled == null) { abandon = true; return; } Target = Character.Controlled; } if (Target == character) { character.AIController.SteeringManager.Reset(); abandon = true; return; } waitUntilPathUnreachable -= deltaTime; if (!character.IsClimbing) { character.SelectedConstruction = null; } if (Target is Entity e) { if (e.Removed) { abandon = true; } else { character.AIController.SelectTarget(e.AiTarget); } } bool isInside = character.CurrentHull != null; bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull; bool targetIsOutside = (Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); if (isInside && targetIsOutside && !AllowGoingOutside) { abandon = true; } else if (waitUntilPathUnreachable < 0) { if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable) { if (repeat) { SteeringManager.Reset(); } else { abandon = true; } } } if (abandon) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow); #endif if (objectiveManager.CurrentOrder != null) { character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f); } character.AIController.SteeringManager.Reset(); } else { Vector2 currTargetSimPos = Vector2.Zero; currTargetSimPos = Target.SimPosition; // Take the sub position into account in the sim pos if (SteeringManager != PathSteering && character.Submarine == null && Target.Submarine != null) { currTargetSimPos += Target.Submarine.SimPosition; } else if (character.Submarine != null && Target.Submarine == null) { currTargetSimPos -= character.Submarine.SimPosition; } else if (character.Submarine != Target.Submarine) { if (character.Submarine != null && Target.Submarine != null) { Vector2 diff = character.Submarine.SimPosition - Target.Submarine.SimPosition; currTargetSimPos -= diff; } } character.AIController.SteeringManager.SteeringSeek(currTargetSimPos); if (SteeringManager != PathSteering) { SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1, heading: VectorExtensions.Forward(character.AnimController.Collider.Rotation)); } if (getDivingGearIfNeeded) { Character followTarget = Target as Character; bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull) || mimic && HumanAIController.HasDivingMask(followTarget); bool needsDivingSuit = needsDivingGear && (targetHull == null || targetIsOutside || targetHull.WaterPercentage > 90) || mimic && HumanAIController.HasDivingSuit(followTarget); bool needsEquipment = false; if (needsDivingSuit) { needsEquipment = !HumanAIController.HasDivingSuit(character); } else if (needsDivingGear) { needsEquipment = !HumanAIController.HasDivingMask(character); } if (needsEquipment) { TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager)); } } } }
protected override void Act(float deltaTime) { var currentHull = character.AnimController.CurrentHull; if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null) { bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90; bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character); if (!hasEquipment) { divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit); } } if (divingGearObjective != null) { divingGearObjective.TryComplete(deltaTime); if (divingGearObjective.IsCompleted()) { divingGearObjective = null; Priority = 0; } else if (divingGearObjective.CanBeCompleted) { // If diving gear objective is active and can be completed, wait for it to complete. return; } else { divingGearObjective = null; // Reset the timer so that we get a safe hull target. searchHullTimer = 0; } } if (unreachableClearTimer > 0) { unreachableClearTimer -= deltaTime; } else { unreachableClearTimer = clearUnreachableInterval; unreachable.Clear(); } if (searchHullTimer > 0.0f) { searchHullTimer -= deltaTime; } else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) { var bestHull = FindBestHull(); if (bestHull != null && bestHull != currentHull) { if (goToObjective != null) { if (goToObjective.Target != bestHull) { // If we need diving gear, we should already have it, if possible. goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false) { AllowGoingOutside = HumanAIController.HasDivingSuit(character) }; } } else { goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false) { AllowGoingOutside = HumanAIController.HasDivingSuit(character) }; } } searchHullTimer = SearchHullInterval; } if (goToObjective != null) { goToObjective.TryComplete(deltaTime); if (!goToObjective.CanBeCompleted) { if (!unreachable.Contains(goToObjective.Target)) { unreachable.Add(goToObjective.Target as Hull); } goToObjective = null; HumanAIController.ObjectiveManager.GetObjective <AIObjectiveIdle>().Wander(deltaTime); //SteeringManager.SteeringWander(); } } else if (currentHull != null) { //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) // -> attempt to manually steer away from hazards Vector2 escapeVel = Vector2.Zero; foreach (FireSource fireSource in currentHull.FireSources) { Vector2 dir = character.Position - fireSource.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } foreach (Character enemy in Character.CharacterList) { //don't run from friendly NPCs if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; } //friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters) if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; } if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) { Vector2 dir = character.Position - enemy.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } } if (escapeVel != Vector2.Zero) { //only move if we haven't reached the edge of the room if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) { character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); } else { character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; character.AIController.SteeringManager.Reset(); } } else { character.AIController.SteeringManager.Reset(); } } }
private float?GetNodePenalty(PathNode node, PathNode nextNode) { if (character == null) { return(0.0f); } if (nextNode.Waypoint.isObstructed) { return(null); } float penalty = 0.0f; if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f) { var door = nextNode.Waypoint.ConnectedDoor; if (door == null) { penalty = 100.0f; } else { if (!CanAccessDoor(door, button => { // Ignore buttons that are on the wrong side of the door if (door.IsHorizontal) { if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y)) { return(false); } } else { if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X)) { return(false); } } return(true); })) { return(null); } } } bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y; //non-humanoids can't climb up ladders if (!(character.AnimController is HumanoidAnimController)) { if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && (!nextNode.Waypoint.Ladders.Item.IsInteractable(character) || character.LockHands) || (nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up nextNodeAboveWaterLevel)) //upper node not underwater { return(null); } } if (node.Waypoint.CurrentHull != null) { var hull = node.Waypoint.CurrentHull; if (hull.FireSources.Count > 0) { foreach (FireSource fs in hull.FireSources) { penalty += fs.Size.X * 10.0f; } } if (character.NeedsAir) { if (hull.WaterVolume / hull.Rect.Width > 100.0f) { if (!HumanAIController.HasDivingSuit(character)) { penalty += 500.0f; } } if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume) { penalty += 1000.0f; } } float yDist = Math.Abs(node.Position.Y - nextNode.Position.Y); if (nextNodeAboveWaterLevel && node.Waypoint.Ladders == null && nextNode.Waypoint.Ladders == null && node.Waypoint.Stairs == null && nextNode.Waypoint.Stairs == null) { penalty += yDist * 10.0f; } } return(penalty); }
protected override void Act(float deltaTime) { if (followControlledCharacter) { if (Character.Controlled == null) { Abandon = true; return; } Target = Character.Controlled; } if (Target == character) { // Wait character.AIController.SteeringManager.Reset(); return; } waitUntilPathUnreachable -= deltaTime; if (!character.IsClimbing) { character.SelectedConstruction = null; } if (Target is Entity e) { if (e.Removed) { Abandon = true; } else { character.AIController.SelectTarget(e.AiTarget); } } var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull; if (!followControlledCharacter) { // Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls. bool containsUnsafeNodes = HumanAIController.CurrentOrder == null && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls && PathSteering != null && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); if (containsUnsafeNodes || HumanAIController.UnreachableHulls.Contains(targetHull)) { Abandon = true; SteeringManager.Reset(); return; } } bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; bool isInside = character.CurrentHull != null; bool targetIsOutside = (Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); if (isInside && targetIsOutside && !AllowGoingOutside) { Abandon = true; } else if (waitUntilPathUnreachable < 0) { if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable && !PathSteering.IsPathDirty) { if (repeat) { SteeringManager.Reset(); } else { Abandon = true; } } } if (Abandon) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow); #endif if (objectiveManager.CurrentOrder != null && objectiveManager.CurrentOrder.ReportFailures) { character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f); } SteeringManager.Reset(); } else { if (getDivingGearIfNeeded && !character.LockHands) { Character followTarget = Target as Character; bool needsDivingSuit = targetIsOutside; bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(character, targetHull, out needsDivingSuit); if (!needsDivingGear && mimic) { if (HumanAIController.HasDivingSuit(followTarget)) { needsDivingGear = true; needsDivingSuit = true; } else if (HumanAIController.HasDivingMask(followTarget)) { needsDivingGear = true; } } bool needsEquipment = false; if (needsDivingSuit) { needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold); } else if (needsDivingGear) { needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold); } if (needsEquipment) { TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref findDivingGear)); return; } } if (repeat && IsCloseEnough) { OnCompleted(); return; } if (SteeringManager == PathSteering) { Func <PathNode, bool> nodeFilter = null; if (isInside && !AllowGoingOutside) { nodeFilter = node => node.Waypoint.CurrentHull != null; } PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1, startNodeFilter, endNodeFilter, nodeFilter); } else { SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10); } if (!insideSteering) { SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1); } } }