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