feat: add stepTowardPoint and planRouteTo to Behavior
This commit is contained in:
1 parent
f1278f9dcc
commit
49a68fbdaf
1 file changed
+150
@@ -467,3 +467,153 @@ bool isBreedingFood(Species species, ItemId held) {
|
|||||||
return wanted != ItemId::None && held == wanted;
|
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;
|
||||||
|
}));
|
||||||
|
|
||||||
Reference in new issue
Block a user