feat: add measureBehaviorTree and behaviorRoleName to Behavior

This commit is contained in:
dualfroz committed 2026-09-06 17:36:41 +02:00
1 parent 0f96fb55ff
commit f1278f9dcc
1 file changed
+158
+158
View File
@@ -309,3 +309,161 @@ BehaviorStatus InverterNode::onTick(BehaviorContext& ctx) {
return BehaviorStatus::Running;
}
RepeaterNode::RepeaterNode(std::string name, std::unique_ptr<BehaviorNode> child, int limit,
bool stopWhenChildFails)
: DecoratorNode(std::move(name), std::move(child)),
repeatLimit(limit),
stopOnFailure(stopWhenChildFails) {}
BehaviorStatus RepeaterNode::onTick(BehaviorContext& ctx) {
BehaviorStatus status = child().tick(ctx);
if (status == BehaviorStatus::Running) return BehaviorStatus::Running;
if (status == BehaviorStatus::Failure && stopOnFailure) {
completed = 0;
return BehaviorStatus::Failure;
}
++completed;
if (completed >= repeatLimit) {
completed = 0;
return BehaviorStatus::Success;
}
return BehaviorStatus::Running;
}
void RepeaterNode::onReset() { completed = 0; }
RetryUntilSuccessNode::RetryUntilSuccessNode(std::string name, std::unique_ptr<BehaviorNode> child,
int attemptLimit)
: DecoratorNode(std::move(name), std::move(child)), maxAttempts(attemptLimit) {}
BehaviorStatus RetryUntilSuccessNode::onTick(BehaviorContext& ctx) {
BehaviorStatus status = child().tick(ctx);
if (status == BehaviorStatus::Running) return BehaviorStatus::Running;
if (status == BehaviorStatus::Success) {
attempts = 0;
return BehaviorStatus::Success;
}
++attempts;
if (attempts >= maxAttempts) {
attempts = 0;
return BehaviorStatus::Failure;
}
child().reset();
return BehaviorStatus::Running;
}
void RetryUntilSuccessNode::onReset() { attempts = 0; }
CooldownNode::CooldownNode(std::string name, std::unique_ptr<BehaviorNode> child, int ticks)
: DecoratorNode(std::move(name), std::move(child)), cooldownTicks(ticks) {}
BehaviorStatus CooldownNode::onTick(BehaviorContext& ctx) {
if (remaining > 0) {
--remaining;
return BehaviorStatus::Failure;
}
BehaviorStatus status = child().tick(ctx);
if (status != BehaviorStatus::Running) remaining = cooldownTicks;
return status;
}
void CooldownNode::onReset() { remaining = 0; }
TimeLimitNode::TimeLimitNode(std::string name, std::unique_ptr<BehaviorNode> child, int ticks)
: DecoratorNode(std::move(name), std::move(child)), limitTicks(ticks) {}
BehaviorStatus TimeLimitNode::onTick(BehaviorContext& ctx) {
++elapsed;
BehaviorStatus status = child().tick(ctx);
if (status != BehaviorStatus::Running) {
elapsed = 0;
return status;
}
if (elapsed >= limitTicks) {
elapsed = 0;
child().reset();
return BehaviorStatus::Failure;
}
return BehaviorStatus::Running;
}
void TimeLimitNode::onReset() { elapsed = 0; }
BehaviorTreeStats measureBehaviorTree(const BehaviorNode* root) {
BehaviorTreeStats stats;
if (root == nullptr) return stats;
std::vector<std::pair<const BehaviorNode*, int>> pending;
pending.push_back({root, 1});
while (!pending.empty()) {
std::pair<const BehaviorNode*, int> entry = pending.back();
pending.pop_back();
++stats.nodeCount;
if (entry.second > stats.depth) stats.depth = entry.second;
for (int i = 0; i < entry.first->childCount(); ++i) {
pending.push_back({entry.first->childAt(i), entry.second + 1});
}
}
stats.withinDepthLimit = stats.depth <= maxBehaviorDepth;
return stats;
}
BehaviorTree::BehaviorTree(std::unique_ptr<BehaviorNode> newRoot) { setRoot(std::move(newRoot)); }
void BehaviorTree::setRoot(std::unique_ptr<BehaviorNode> newRoot) {
rootNode = std::move(newRoot);
treeStats = measureBehaviorTree(rootNode.get());
ticks = 0;
}
BehaviorStatus BehaviorTree::tick(BehaviorContext& ctx) {
if (!structureValid()) return BehaviorStatus::Failure;
++ticks;
++ctx.tick;
return rootNode->tick(ctx);
}
void BehaviorTree::reset() {
if (rootNode) rootNode->reset();
ticks = 0;
}
const char* behaviorRoleName(BehaviorRole role) {
switch (role) {
case BehaviorRole::HostileHunter: return "hostileHunter";
case BehaviorRole::GrazingAnimal: return "grazingAnimal";
case BehaviorRole::SunAvoider: return "sunAvoider";
case BehaviorRole::Count: break;
}
return "unknown";
}
BehaviorRole roleForSpecies(Species species) {
switch (species) {
case Species::Zombie:
case Species::Creeper:
return BehaviorRole::HostileHunter;
case Species::Skeleton:
return BehaviorRole::SunAvoider;
default:
return BehaviorRole::GrazingAnimal;
}
}
ItemId breedingFoodFor(Species species) {
switch (species) {
case Species::Sheep:
case Species::Cow:
return ItemId::Bread;
case Species::Pig:
return ItemId::Apple;
default:
return ItemId::None;
}
}
bool isBreedingFood(Species species, ItemId held) {
ItemId wanted = breedingFoodFor(species);
return wanted != ItemId::None && held == wanted;
}