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")); }