protected override void Act(float deltaTime) { if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) { UpdateGapList(); lastGapUpdate = Timing.TotalTime; } if (objectiveList.Any()) { if (!objectiveList[objectiveList.Count - 1].Leak.IsRoomToRoom) { if (findDivingGear == null) { findDivingGear = new AIObjectiveFindDivingGear(character, true); } if (!findDivingGear.IsCompleted() && findDivingGear.CanBeCompleted) { findDivingGear.TryComplete(deltaTime); return; } } objectiveList[objectiveList.Count - 1].TryComplete(deltaTime); if (!objectiveList[objectiveList.Count - 1].CanBeCompleted || objectiveList[objectiveList.Count - 1].IsCompleted()) { objectiveList.RemoveAt(objectiveList.Count - 1); } } else { if (idleObjective == null) { idleObjective = new AIObjectiveIdle(character); } idleObjective.TryComplete(deltaTime); } }
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) { 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(); } } }
protected override void Act(float deltaTime) { if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) { Abandon = true; return; } var otherRescuer = targetCharacter.SelectedBy; if (otherRescuer != null && otherRescuer != character) { // Someone else is rescuing/holding the target. Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical"); return; } if (targetCharacter != character) { if (targetCharacter.IsIncapacitated) { // Check if the character needs more oxygen if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter)) { // Replace empty oxygen tank // First remove empty tanks if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable <Item> suits, requireEquipped: true)) { Item suit = suits.FirstOrDefault(); if (suit != null) { AIObjectiveFindDivingGear.EjectEmptyTanks(character, suit, out _); } } else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable <Item> masks, requireEquipped: true)) { Item mask = masks.FirstOrDefault(); if (mask != null) { AIObjectiveFindDivingGear.EjectEmptyTanks(character, mask, out _); } } bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0; if (ShouldRemoveDivingSuit()) { suits.ForEach(suit => suit.Drop(character)); } else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0))) { // The target has a suit equipped with an empty oxygen tank. // Can't remove the suit, because the target needs it. // If we happen to have an extra oxygen tank in the inventory, let's swap it. Item spareOxygenTank = FindOxygenTank(targetCharacter) ?? FindOxygenTank(character); if (spareOxygenTank != null) { Item suit = suits.FirstOrDefault(); if (suit != null) { // Insert the new oxygen tank TryAddSubObjective(ref replaceOxygenObjective, () => new AIObjectiveContainItem(character, spareOxygenTank, suit.GetComponent <ItemContainer>(), objectiveManager), onCompleted: () => RemoveSubObjective(ref replaceOxygenObjective), onAbandon: () => { RemoveSubObjective(ref replaceOxygenObjective); ignoreOxygen = true; if (ShouldRemoveDivingSuit()) { suits.ForEach(suit => suit.Drop(character)); } }); return; } } Item FindOxygenTank(Character c) => c.Inventory.FindItem(i => i.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && i.ConditionPercentage > 1 && i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag("diving")) == null, recursive: true); } } if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD) { // Incapacitated target is not in a safe place -> Move to a safe place first if (character.SelectedCharacter != targetCharacter) { if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null) { character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" }, new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); } // Go to the target and select it if (!character.CanInteractWith(targetCharacter)) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat, DialogueIdentifier = "dialogcannotreachpatient", TargetName = targetCharacter.DisplayName }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); Abandon = true; }); } else { character.SelectCharacter(targetCharacter); } } else { // Drag the character into safety if (safeHull == null) { if (findHullTimer > 0) { findHullTimer -= deltaTime; } else { safeHull = objectiveManager.GetObjective <AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls); findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); } } if (safeHull != null && character.CurrentHull != safeHull) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); safeHull = character.CurrentHull; }); } } } } } if (subObjectives.Any()) { return; } if (targetCharacter != character && !character.CanInteractWith(targetCharacter)) { RemoveSubObjective(ref replaceOxygenObjective); RemoveSubObjective(ref goToObjective); // Go to the target and select it TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager) { CloseEnough = CloseEnoughToTreat, DialogueIdentifier = "dialogcannotreachpatient", TargetName = targetCharacter.DisplayName }, onCompleted: () => RemoveSubObjective(ref goToObjective), onAbandon: () => { RemoveSubObjective(ref goToObjective); Abandon = true; }); } else { // We can start applying treatment if (character != targetCharacter && character.SelectedCharacter != targetCharacter) { if (targetCharacter.CurrentHull.DisplayName != null) { character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" }, new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }), null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f); } character.SelectCharacter(targetCharacter); } GiveTreatment(deltaTime); } }
protected override void Act(float deltaTime) { if (FollowControlledCharacter) { if (Character.Controlled == null) { return; } Target = Character.Controlled; } if (Target == character) { character.AIController.SteeringManager.Reset(); return; } waitUntilPathUnreachable -= deltaTime; if (!character.IsClimbing) { character.SelectedConstruction = null; } if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); } Vector2 currTargetPos = Vector2.Zero; if (Target == null) { currTargetPos = targetPos; } else { currTargetPos = Target.SimPosition; //if character is inside the sub and target isn't, transform the position if (character.Submarine != null && Target.Submarine == null) { currTargetPos -= character.Submarine.SimPosition; } } if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough) { character.AIController.SteeringManager.Reset(); character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; } else { bool targetIsOutside = (Target != null && Target.Submarine == null) || (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.HasOutdoorsNodes); if (targetIsOutside && character.CurrentHull != null && !AllowGoingOutside) { cannotReach = true; } else { character.AIController.SteeringManager.SteeringSeek(currTargetPos); if (getDivingGearIfNeeded) { if (targetIsOutside || Target is Hull h && HumanAIController.NeedsDivingGear(h) || Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) { if (findDivingGear == null) { findDivingGear = new AIObjectiveFindDivingGear(character, true); AddSubObjective(findDivingGear); } else if (!findDivingGear.CanBeCompleted) { abandon = true; } } } } } }
protected override void Act(float deltaTime) { if (!leak.IsRoomToRoom) { if (findDivingGear == null) { findDivingGear = new AIObjectiveFindDivingGear(character, true); AddSubObjective(findDivingGear); } else if (!findDivingGear.CanBeCompleted) { abandon = true; return; } } var weldingTool = character.Inventory.FindItemByTag("weldingtool"); if (weldingTool == null) { AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true)); return; } else { var containedItems = weldingTool.ContainedItems; if (containedItems == null) { return; } var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f); if (fuelTank == null) { AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent <ItemContainer>())); return; } } var repairTool = weldingTool.GetComponent <RepairTool>(); if (repairTool == null) { 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; } //float reach = HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range); float reach = ConvertUnits.ToSimUnits(repairTool.Range); bool cannotReach = ConvertUnits.ToSimUnits(gapDiff.Length()) > reach; if (cannotReach) { if (gotoObjective != null) { // Check if the objective is already removed -> completed/impossible if (!subObjectives.Contains(gotoObjective)) { if (!gotoObjective.CanBeCompleted) { abandon = true; } gotoObjective = null; return; } } else { gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character) { CloseEnough = reach }; if (!subObjectives.Contains(gotoObjective)) { AddSubObjective(gotoObjective); } } } if (gotoObjective == null || gotoObjective.IsCompleted()) { if (operateObjective == null) { operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak); AddSubObjective(operateObjective); } else if (!subObjectives.Contains(operateObjective)) { operateObjective = null; } } }