public static BTResult Swim(Animal agent, Vector2 direction, bool wandering, AnimalState state, bool surface, int tries = 10) { var targetPos = AIUtil.FindTargetSwimPosition(agent.Position, 5.0f, 20.0f, direction, 90, 360, tries, surface).XYZi; if (targetPos == agent.Position) { return(BTResult.Failure("target position is current position.")); } // Avoid fish swimming too close to coast line // TODO: Avoid building routes near coast, cache available points far away from coast if (!agent.Species.CanSwimNearCoast) { var waterHeight = World.World.GetWaterHeight(targetPos.XZ); var isNearCoast = ((WorldPosition3i)targetPos).SpiralOutXZIter(3).Any(groundPos => World.World.GetBlock((WrappedWorldPosition3i)groundPos.X_Z(waterHeight)).Is <Solid>()); if (isNearCoast) { return(BTResult.Failure("target position is too close to coast line")); } } var targetBlock = World.World.GetBlock(targetPos); // If an animal can't float on water surface - move it a block below highest water block TODO: make them move on underwater ground // TODO: Remove after pathfinder improvements if (!agent.Species.FloatOnSurface && targetBlock is WaterBlock && World.World.GetBlock(targetPos + Vector3i.Up).Is <Empty>()) { targetPos += Vector3i.Down; } //if (targetBlock.Is<Solid>()) targetPos += Vector3i.Up; if (targetBlock.Is <Empty>()) { targetPos += Vector3i.Down; // Fail if target position is too shallow if (World.World.GetBlock(targetPos).Is <Solid>()) { return(BTResult.Failure("target position is too thin")); } } // Clamp current position to ground or water, if can't float on water surface - stay below water height TODO: make them move on underwater ground var pos = World.World.ClampToWaterHeight(agent.Position.XYZi); // TODO: Remove after pathfinder improvements if (!agent.Species.FloatOnSurface && pos.y == World.World.GetWaterHeight(agent.Position.XZi)) { pos += Vector3i.Down; } var route = Route.Basic(agent.Species.GetTraversalData(wandering), agent.FacingDir, pos + Vector3i.Down, targetPos); //For fish, we need to compensate, since route is built from positions of the ground below var routeTime = route.IsValid ? agent.SetRoute(route, state, null) : 0f; return(routeTime < float.Epsilon ? BTResult.Failure("route not set") : BTResult.Success($"swimming path")); }
// If the animal is stuck somewhere it shouldn't be help it escape or die if its very far from walkable terrain public static BTResult LandAnimalUnStuckOrDie(Animal agent) { const int maxUnStuckDistance = 20; var nearestValid = RouteManager.NearestWalkableXYZ(agent.Position.XYZi, maxUnStuckDistance); if (nearestValid == agent.Position.WorldPosition3i) { agent.NextTick = WorldTime.Seconds + 10f; return(BTResult.Failure()); } if (!nearestValid.IsValid) { // cheating failed? time to die! agent.Kill(); return(BTResult.Success()); } // ignore terrain, path directly to a valid area, but only if noone is around *shy* or if he's really trying if (agent.TryGetMemory(Animal.TriesToUnStuckMemory, out int tries)) { tries += 1; } agent.SetMemory(Animal.TriesToUnStuckMemory, tries); if (!NetObjectManager.GetObjectsWithin(agent.Position.XZ, 20).OfType <Player>().Any() || tries >= 3) { var route = AIUtil.GetRoute(agent.FacingDir, agent.Position, (Vector3)nearestValid, agent.Species.GetTraversalData(true), null, null); if (!route.IsValid) { agent.NextTick = WorldTime.Seconds + 10; return(BTResult.Failure()); } // Proceed with route with time length more than 0 var timeToFinishRoute = agent.SetRoute(route, AnimalState.Wander); if (timeToFinishRoute > float.Epsilon) { agent.RemoveMemory(Animal.TriesToUnStuckMemory); return(BTResult.Success()); } } agent.NextTick = WorldTime.Seconds + 10; return(BTResult.Failure()); }
static BTResult DoSleepNearLeader(Behavior <Animal> beh, Animal agent) { var leader = GetLeader(agent); if (leader == null) { return(BTResult.Failure("No leader")); } if (leader == agent) { return(BTResult.Failure("We are leader")); } //If the leader is sleeping... if (leader.State == AnimalState.Sleeping) { // and we're already close enough, sleep now. if (Vector3.WrappedDistance(agent.Position, leader.Position) < agent.Species.HeadDistance * 3) { return(agent.ChangeState(AnimalState.LyingDown, 60f, true)); } //Otherwise, move to them to sleep. // TODO: avoid overlapping with other herd members, make the leader pick a spot to sleep that can accommodate the herd var routeProps = new RouteProperties { MaxTargetLocationHeightDelta = agent.Species.ClimbHeight }; var route = AIUtil.GetRouteFacingTarget(agent.FacingDir, agent.Position, leader.Position, agent.Species.GetTraversalData(true), agent.Species.HeadDistance * 2, routeProps: routeProps); if (!route.IsValid) { return(BTResult.Failure("can't get route to leader")); } agent.SetRoute(route, AnimalState.Wander); return(BTResult.Success("walking to sleep near leader")); } return(BTResult.Failure("leader not sleeping")); }
public static BTResult AmphibiousMovement(Animal agent, Vector2 generalDirection, bool wandering, AnimalState state, float minDistance = 2f, float maxDistance = 20f) { var startPathPos = agent.Position.WorldPosition3i; //Plop us into water if we're above it. if (World.World.GetBlock((Vector3i)startPathPos.Down()).GetType() == typeof(WaterBlock)) { startPathPos = startPathPos.Down(); } if (!World.World.IsUnderwater(startPathPos)) { startPathPos = RouteManager.NearestWalkablePathPos(agent.Position.WorldPosition3i); //Boot us down/up to the surface if we're on land. } if (!startPathPos.IsValid) { return(LandMovement(agent, generalDirection, wandering, state, minDistance, maxDistance)); } generalDirection = generalDirection == Vector2.zero ? Vector2.right.Rotate(RandomUtil.Range(0f, 360)) : generalDirection.Normalized; // Take random ground position in the given direction var targetGround = (agent.Position + (generalDirection * RandomUtil.Range(minDistance, maxDistance)).X_Z()).WorldPosition3i; // Floating animals will stick to water surface or land, non floating - land or return to swimming state if (World.World.IsUnderwater(targetGround) && agent.Species.FloatOnSurface) { targetGround.y = World.World.MaxWaterHeight[targetGround]; } else { targetGround = RouteManager.NearestWalkableXYZ(targetGround, 5); if (!agent.Species.FloatOnSurface && !targetGround.IsValid) { agent.Floating = false; return(BTResult.Failure("Can't float, continue swimming")); } } // This is a low-effort search that includes water surface and should occasionally fail, just pick a semi-random node that was visited when it fails var routeProps = new RouteProperties() { MaxTargetLocationHeightDelta = agent.Species.ClimbHeight }; var allowWaterSearch = new AStarSearch(RouteCacheData.NeighborsIncludeWater, agent.FacingDir, startPathPos, targetGround, 30, routeProps, false); if (allowWaterSearch.Status != SearchStatus.PathFound) { targetGround = allowWaterSearch.GroundNodes.Last().Key; allowWaterSearch.GetPathToWaterPos(targetGround); } // Apply land movement only for land positions if (!World.World.IsUnderwater(agent.Position.WorldPosition3i)) { if (allowWaterSearch.GroundPath.Count < 2) { return(LandMovement(agent, generalDirection, wandering, state, minDistance, maxDistance, skipRouteProperties: true)); } if (allowWaterSearch.Status == SearchStatus.Unpathable && allowWaterSearch.GroundNodes.Count < RouteRegions.MinimumRegionSize) { // Search region was unexpectedly small and agent is on land, might be trapped by player construction. // Try regular land movement so region checks can apply & the agent can get unstuck (or die) return(LandMovement(agent, generalDirection, wandering, state, minDistance, maxDistance)); } } var smoothed = allowWaterSearch.LineOfSightSmoothWaterPosition(agent.GroundPosition); var route = new Route(agent.Species.GetTraversalData(wandering), agent.FacingDir, smoothed); if (!route.IsValid) { route = Route.Basic(agent.Species.GetTraversalData(wandering), agent.FacingDir, agent.GroundPosition, route.EndPosition); } var routeTime = agent.SetRoute(route, state, null); return(routeTime < float.Epsilon ? BTResult.Failure("route not set") : BTResult.Success($"swimming path")); }