public static IEnumerator <BTStatus> FoodFinder(Animal agent, Func <Animal, BTStatus> noNearbyFoodBehavior, float hungerRestored) { var lastPos = Vector3.Zero; Queue <Vector3> foodSources = new Queue <Vector3>(); while (agent.Hunger > Brain.HungerSatiated) { foodSources.Clear(); while (foodSources.Count == 0) { if (Vector3.WrappedDistanceSq(lastPos, agent.Position) > 100) { var agentRegion = RouteRegions.GetRegion(agent.Position.WorldPosition3i); foodSources.AddRange(EcoSim.PlantSim.PlantsWithinRange(agent.Position, 10, plant => agent.Species.Eats(plant.Species) && RouteRegions.GetRegion(plant.Position.WorldPosition3i.Down()) == agentRegion).Shuffle().Select(plant => plant.Position + Vector3.Down)); lastPos = agent.Position; } else { yield return(noNearbyFoodBehavior(agent)); } } while (foodSources.Count > 0 && agent.Hunger > Brain.HungerSatiated && Vector3.WrappedDistanceSq(lastPos, agent.Position) < 100) { // low-effort search for the first option or any other option visited while trying to hit the first Vector3 targetPlantPosition; PiecewiseLinearFunction route = AIUtilities.GetRouteToAny(agent.Position, foodSources, agent.Species.WanderingSpeed, out targetPlantPosition, 100, 20, agent.Species.HeadDistance); if (route == null) { break; } agent.Target.SetPath(route); agent.Target.LookPos = targetPlantPosition; agent.AnimationState = AnimalAnimationState.LookingForFood; var target = route.EndPosition; // just in case something interrupts the path while (agent.Target.TargetTime > WorldTime.Seconds && agent.Target.TargetPosition == target) { agent.NextTick = agent.Target.TargetTime; yield return(BTStatus.Running); } if (Vector3.WrappedDistanceSq(target, agent.Position) < 4) { agent.AnimationState = AnimalAnimationState.Eating; agent.NextTick = WorldTime.Seconds + 10; agent.Hunger -= hungerRestored; agent.Target.LookPos = targetPlantPosition; yield return(BTStatus.Running); } // something interrupted eating, probably need to find new food if (agent.Target.TargetPosition != target) { break; } } } }