feat: add stepTowardPoint and planRouteTo to Behavior

This commit is contained in:
dualfroz committed 2026-09-06 17:44:08 +02:00
1 parent f1278f9dcc
commit 49a68fbdaf
1 file changed
+150
+150
View File
@@ -467,3 +467,153 @@ bool isBreedingFood(Species species, ItemId held) {
return wanted != ItemId::None && held == wanted;
}
bool stepTowardPoint(BehaviorAgent& agent, const Vec3& goal, float arriveEpsilon) {
Vec3 delta = goal - agent.position;
float distance = length(delta);
if (distance <= arriveEpsilon) {
agent.position = goal;
return true;
}
Vec3 direction = delta * (1.0f / distance);
Vec3 flat{direction.x, 0.0f, direction.z};
if (length(flat) > 1e-4f) agent.heading = normalize(flat);
if (agent.moveStep >= distance) {
agent.position = goal;
return true;
}
agent.position += direction * agent.moveStep;
return false;
}
bool planRouteTo(BehaviorContext& ctx, const Vec3i& goal, bool force) {
BehaviorAgent& agent = *ctx.agent;
bool sameGoal = agent.routeGoal == goal;
bool fresh = (ctx.tick - agent.routePlannedTick) < ctx.tuning.repathIntervalTicks;
if (!force && sameGoal && fresh && agent.route.size() >= 2) return true;
agent.route = findPath(*ctx.world, agent.cell(), goal, ctx.path);
agent.routeCursor = agent.route.empty() ? 0 : 1;
agent.routeGoal = goal;
agent.routePlannedTick = ctx.tick;
return agent.route.size() >= 2;
}
BehaviorStatus followRouteStep(BehaviorContext& ctx) {
BehaviorAgent& agent = *ctx.agent;
if (agent.routeFinished()) return BehaviorStatus::Success;
Vec3i next = agent.route[static_cast<std::size_t>(agent.routeCursor)];
if (stepTowardPoint(agent, behaviorCellCenter(next), ctx.tuning.arriveEpsilon)) {
++agent.routeCursor;
}
return agent.routeFinished() ? BehaviorStatus::Success : BehaviorStatus::Running;
}
bool findShadedCell(const World& world, const Vec3i& from, int radius, const PathConfig& config,
Vec3i& outCell) {
int best = -1;
Vec3i bestCell;
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
for (int dy = -2; dy <= 2; ++dy) {
Vec3i probe{from.x + dx, from.y + dy, from.z + dz};
if (!cellIsShaded(world, probe)) continue;
if (!isStandable(world, probe, config)) continue;
int cost = dx * dx + dz * dz + dy * dy * 4;
if (best >= 0 && cost >= best) continue;
best = cost;
bestCell = probe;
}
}
}
if (best < 0) return false;
outCell = bestCell;
return true;
}
BehaviorTree makeHostileHunterTree(const BehaviorTuning& tuning) {
auto strike = std::unique_ptr<SequenceNode>(new SequenceNode("strike"));
strike->add(condition("withinAttackReach", [](BehaviorContext& ctx) {
float reach = statsOf(ctx).attackRange;
return behaviorHorizontalDistance(ctx.agent->position, ctx.playerPosition) <= reach;
}));
strike->add(std::unique_ptr<BehaviorNode>(new CooldownNode(
"attackCooldown",
action("attack",
[](BehaviorContext& ctx) {
++ctx.agent->attacksLanded;
ctx.blackboard.setNumber("lastAttackDamage", statsOf(ctx).attackDamage);
return BehaviorStatus::Success;
}),
tuning.attackCooldownTicks)));
auto approach = std::unique_ptr<SequenceNode>(new SequenceNode("approach"));
approach->add(action("planRouteToTarget", [](BehaviorContext& ctx) {
Vec3i goal = ctx.blackboard.cell("targetCell", behaviorCellOf(ctx.playerPosition));
return planRouteTo(ctx, goal, false) ? BehaviorStatus::Success : BehaviorStatus::Failure;
}));
approach->add(action("followRoute", followRouteAction));
auto closeIn = std::unique_ptr<SelectorNode>(new SelectorNode("strikeOrApproach"));
closeIn->add(std::move(strike));
closeIn->add(std::move(approach));
auto engage = std::unique_ptr<SequenceNode>(new SequenceNode("engageTarget"));
engage->add(condition("targetInRange", [](BehaviorContext& ctx) {
if (!ctx.playerPresent) {
ctx.blackboard.setFlag("engaged", false);
return false;
}
const SpeciesStats& stats = statsOf(ctx);
bool engaged = ctx.blackboard.flag("engaged");
float radius = engaged ? stats.disengageRadius : stats.alertRadius;
float distance = behaviorHorizontalDistance(ctx.agent->position, ctx.playerPosition);
bool inRange = distance <= radius;
ctx.blackboard.setFlag("engaged", inRange);
if (inRange) {
ctx.blackboard.setPoint("lastSeen", ctx.playerPosition);
ctx.blackboard.setCell("targetCell", behaviorCellOf(ctx.playerPosition));
}
return inRange;
}));
engage->add(std::move(closeIn));
auto goHome = std::unique_ptr<SequenceNode>(new SequenceNode("returnHome"));
goHome->add(condition("awayFromHome", [](BehaviorContext& ctx) {
if (ctx.blackboard.flag("engaged")) return false;
return behaviorHorizontalDistance(ctx.agent->position, ctx.agent->homePosition) >
ctx.tuning.homeReturnRadius;
}));
goHome->add(action("planRouteHome", [](BehaviorContext& ctx) {
Vec3i goal = behaviorCellOf(ctx.agent->homePosition);
return planRouteTo(ctx, goal, false) ? BehaviorStatus::Success : BehaviorStatus::Failure;
}));
goHome->add(action("followRoute", followRouteAction));
auto root = std::unique_ptr<SelectorNode>(new SelectorNode("hostileRoot"));
root->add(std::move(engage));
root->add(std::move(goHome));
root->add(action("idle", [](BehaviorContext&) { return BehaviorStatus::Success; }));
return BehaviorTree(std::move(root));
}
BehaviorTree makeGrazingAnimalTree(const BehaviorTuning& tuning) {
auto flee = std::unique_ptr<SequenceNode>(new SequenceNode("flee"));
flee->add(condition("threatened", [](BehaviorContext& ctx) {
if (ctx.agent->health < ctx.agent->maxHealth) return true;
if (!ctx.playerPresent) return false;
if (isBreedingFood(ctx.agent->species, ctx.playerHeldItem)) return false;
return behaviorHorizontalDistance(ctx.agent->position, ctx.playerPosition) <
ctx.tuning.fleeRadius;
}));
flee->add(action("fleeStep", [](BehaviorContext& ctx) {
BehaviorAgent& agent = *ctx.agent;
Vec3 away{agent.position.x - ctx.playerPosition.x, 0.0f,
agent.position.z - ctx.playerPosition.z};
if (length(away) < 1e-3f) away = horizontalHeading(agent.rng);
away = normalize(away);
stepTowardPoint(agent, agent.position + away * 4.0f, ctx.tuning.arriveEpsilon);
return BehaviorStatus::Running;
}));