public void Wander(float deltaTime) { if (character.IsClimbing) { return; } //steer away from edges of the hull var currentHull = character.CurrentHull; if (currentHull != null) { float roomWidth = currentHull.Rect.Width; if (roomWidth < WallAvoidDistance * 4) { PathSteering.Reset(); } else { float leftDist = character.Position.X - currentHull.Rect.X; float rightDist = currentHull.Rect.Right - character.Position.X; if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance) { if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2) { PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist)); } else { PathSteering.Reset(); } } else if (leftDist < WallAvoidDistance) { float speed = (WallAvoidDistance - leftDist) / WallAvoidDistance; PathSteering.SteeringManual(deltaTime, Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1)); PathSteering.WanderAngle = 0.0f; } else if (rightDist < WallAvoidDistance) { float speed = (WallAvoidDistance - rightDist) / WallAvoidDistance; PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1)); PathSteering.WanderAngle = MathHelper.Pi; } else { SteeringManager.SteeringWander(); } } } else { SteeringManager.SteeringWander(); } if (!character.AnimController.InWater) { //reset vertical steering to prevent dropping down from platforms etc character.AIController.SteeringManager.ResetY(); } }
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); } } }
protected override void Act(float deltaTime) { if (PathSteering == null) { return; } //don't keep dragging others when idling if (character.SelectedCharacter != null) { character.DeselectCharacter(); } if (!character.IsClimbing) { character.SelectedConstruction = null; } bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); if (currentTargetIsInvalid || (currentTarget == null && IsForbidden(character.CurrentHull))) { newTargetTimer = 0; standStillTimer = 0; } if (character.AnimController.InWater || character.IsClimbing) { standStillTimer = 0; } if (newTargetTimer <= 0.0f) { currentTarget = FindRandomHull(); if (currentTarget != null) { string errorMsg = null; #if DEBUG bool isRoomNameFound = currentTarget.RoomName != null; errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")"; #endif var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg); PathSteering.SetPath(path); } newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax); } newTargetTimer -= deltaTime; //wander randomly // - if reached the end of the path // - if the target is unreachable // - if the path requires going outside if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null && (PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes))) { standStillTimer -= deltaTime; if (standStillTimer > 0.0f) { walkDuration = Rand.Range(walkDurationMin, walkDurationMax); PathSteering.Reset(); return; } if (standStillTimer < -walkDuration) { standStillTimer = Rand.Range(standStillMin, standStillMax); } Wander(deltaTime); return; } if (currentTarget != null) { character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition); } }
protected override void Act(float deltaTime) { if (PathSteering == null) { return; } //don't keep dragging others when idling if (character.SelectedCharacter != null) { character.DeselectCharacter(); } if (!character.IsClimbing) { character.SelectedConstruction = null; } bool currentHullForbidden = IsForbidden(character.CurrentHull); if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0) { SteeringManager.Reset(); return; } bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden)) { newTargetTimer = 0; standStillTimer = 0; } else if (character.IsClimbing) { if (currentTarget == null) { newTargetTimer = 0; } else { // Don't allow new targets when climbing. newTargetTimer = Math.Max(newTargetIntervalMin, newTargetTimer); } } else if (character.AnimController.InWater) { if (currentTarget == null) { newTargetTimer = 0; } } if (newTargetTimer <= 0.0f) { if (!searchingNewHull) { //find all available hulls first FindTargetHulls(); searchingNewHull = true; return; } else if (targetHulls.Count > 0) { //choose a random available hull var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); if (isCurrentHullOK) { // Check that there is no unsafe or forbidden hulls on the way to the target // Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition); if (path.Unreachable || path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { //can't go to this room, remove it from the list and try another room next frame int index = targetHulls.IndexOf(randomHull); targetHulls.RemoveAt(index); hullWeights.RemoveAt(index); PathSteering.Reset(); return; } } currentTarget = randomHull; searchingNewHull = false; } if (currentTarget != null) { character.AIController.SelectTarget(currentTarget.AiTarget); string errorMsg = null; #if DEBUG bool isRoomNameFound = currentTarget.RoomName != null; errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")"; #endif var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg); PathSteering.SetPath(path); } newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax); } newTargetTimer -= deltaTime; //wander randomly // - if reached the end of the path // - if the target is unreachable // - if the path requires going outside if (!character.IsClimbing) { if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null && (PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes))) { if (!character.AnimController.InWater) { standStillTimer -= deltaTime; if (standStillTimer > 0.0f) { walkDuration = Rand.Range(walkDurationMin, walkDurationMax); PathSteering.Reset(); return; } if (standStillTimer < -walkDuration) { standStillTimer = Rand.Range(standStillMin, standStillMax); } } Wander(deltaTime); return; } } if (currentTarget != null) { character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition); } }