diff --git a/core/src/Behavior.cpp b/core/src/Behavior.cpp index 72e88c5..0969728 100644 --- a/core/src/Behavior.cpp +++ b/core/src/Behavior.cpp @@ -309,3 +309,161 @@ BehaviorStatus InverterNode::onTick(BehaviorContext& ctx) { return BehaviorStatus::Running; } +RepeaterNode::RepeaterNode(std::string name, std::unique_ptr 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 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 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 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> pending; + pending.push_back({root, 1}); + while (!pending.empty()) { + std::pair 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 newRoot) { setRoot(std::move(newRoot)); } + +void BehaviorTree::setRoot(std::unique_ptr 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; +} +