diff --git a/Minecraft.World/Minecraft.World.vcxproj b/Minecraft.World/Minecraft.World.vcxproj index bae4d2733..464d40378 100644 --- a/Minecraft.World/Minecraft.World.vcxproj +++ b/Minecraft.World/Minecraft.World.vcxproj @@ -2445,6 +2445,7 @@ + @@ -2468,6 +2469,8 @@ + + @@ -2847,6 +2850,8 @@ + + @@ -3519,6 +3524,7 @@ + @@ -3540,6 +3546,8 @@ + + @@ -3649,6 +3657,8 @@ + + diff --git a/Minecraft.World/Minecraft.World.vcxproj.filters b/Minecraft.World/Minecraft.World.vcxproj.filters index ec5b3cb68..101c163f0 100644 --- a/Minecraft.World/Minecraft.World.vcxproj.filters +++ b/Minecraft.World/Minecraft.World.vcxproj.filters @@ -3203,6 +3203,21 @@ net\minecraft\world\level\tile + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\level\biome + @@ -5638,5 +5653,20 @@ net\minecraft\world\level\tile + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\entity\ai\goal\target + + + net\minecraft\world\level\biome + \ No newline at end of file diff --git a/Minecraft.World/OwnerHurtByTargetGoal.cpp b/Minecraft.World/OwnerHurtByTargetGoal.cpp index 787eb2aec..129680e09 100644 --- a/Minecraft.World/OwnerHurtByTargetGoal.cpp +++ b/Minecraft.World/OwnerHurtByTargetGoal.cpp @@ -32,5 +32,13 @@ void OwnerHurtByTargetGoal::start() timestamp = owner->getLastHurtByMobTimestamp(); } + tameAnimal->onTameAggroStarted(owner->getLastHurtByMob()); + TargetGoal::start(); -} \ No newline at end of file +} + +void OwnerHurtByTargetGoal::stop() +{ + tameAnimal->onTameAggroStopped(); + TargetGoal::stop(); +} diff --git a/Minecraft.World/OwnerHurtByTargetGoal.h b/Minecraft.World/OwnerHurtByTargetGoal.h index 2cdc93781..15d7b822c 100644 --- a/Minecraft.World/OwnerHurtByTargetGoal.h +++ b/Minecraft.World/OwnerHurtByTargetGoal.h @@ -16,4 +16,5 @@ public: bool canUse(); void start(); + void stop(); }; \ No newline at end of file diff --git a/Minecraft.World/PrioritizeOwnerThreatGoal.cpp b/Minecraft.World/PrioritizeOwnerThreatGoal.cpp new file mode 100644 index 000000000..4aa43295b --- /dev/null +++ b/Minecraft.World/PrioritizeOwnerThreatGoal.cpp @@ -0,0 +1,75 @@ +#include "stdafx.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.animal.h" +#include "PrioritizeOwnerThreatGoal.h" + +PrioritizeOwnerThreatGoal::PrioritizeOwnerThreatGoal(TamableAnimal* tameAnimal) + : TargetGoal(tameAnimal, false) +{ + this->tameAnimal = tameAnimal; + this->timestamp = 0; + setRequiredControlFlags(TargetGoal::TargetFlag); +} + +// --------------------------------------------------------------------------- +bool PrioritizeOwnerThreatGoal::canUse() +{ + // Только прирученный и не сидящий + if (!tameAnimal->isTame()) return false; + if (tameAnimal->isSitting()) return false; + + // Этот goal — именно ПЕРЕКЛЮЧАТЕЛЬ, а не первичный выбор цели. + // Если текущей цели нет — пусть этим занимаются + // HurtByTargetGoal / TameAnimalDefendGoal + shared_ptr currentTarget = mob->getTarget(); + if (currentTarget == nullptr || !currentTarget->isAlive()) return false; + + // Нужен живой хозяин + shared_ptr owner = + dynamic_pointer_cast(tameAnimal->getOwner()); + if (owner == nullptr || !owner->isAlive()) return false; + + // Кто последний ударил хозяина? + shared_ptr ownerAttacker = owner->getLastHurtByMob(); + int ts = owner->getLastHurtByMobTimestamp(); + + // Ничего нового с момента последней обработки + if (ts == timestamp) return false; + + // Уже атакуем именно эту угрозу — незачем переключаться + if (ownerAttacker == currentTarget) return false; + + // Стандартные проверки: цель жива, не союзник, видна и т.д. + if (!canAttack(ownerAttacker, false)) return false; + + // Питомец-специфичный фильтр (Wolf: не атаковать Creeper, + // питомцев того же хозяина, PvP-флаг и т.д.) + if (!tameAnimal->wantsToAttack(ownerAttacker, owner)) return false; + + // Переключаемся только если новая угроза ощутимо ближе к хозяину, + // чем текущая цель — иначе не стоит бросать начатое + double distToOwnerSq = owner->distanceToSqr(ownerAttacker); + double currentDistToOwnerSq = owner->distanceToSqr(currentTarget); + + if (distToOwnerSq >= currentDistToOwnerSq - SWITCH_DIST_ADVANTAGE_SQ) + return false; + + newThreat = weak_ptr(ownerAttacker); + return true; +} + +// --------------------------------------------------------------------------- +void PrioritizeOwnerThreatGoal::start() +{ + shared_ptr target = newThreat.lock(); + mob->setTarget(target); + tameAnimal->onTameAggroStarted(target); // Wolf: setAngry(true) + + // Запомнить timestamp чтобы не обрабатывать это событие повторно + shared_ptr owner = + dynamic_pointer_cast(tameAnimal->getOwner()); + if (owner != nullptr) + timestamp = owner->getLastHurtByMobTimestamp(); + + TargetGoal::start(); +} diff --git a/Minecraft.World/PrioritizeOwnerThreatGoal.h b/Minecraft.World/PrioritizeOwnerThreatGoal.h new file mode 100644 index 000000000..9569f3844 --- /dev/null +++ b/Minecraft.World/PrioritizeOwnerThreatGoal.h @@ -0,0 +1,31 @@ +#pragma once + +#include "TargetGoal.h" + +class TamableAnimal; + +// Если волк уже дерётся с мобом A, но хозяина начал бить моб B — +// переключиться на B. Работает только при наличии активной цели, +// иначе уступает TameAnimalDefendGoal и HurtByTargetGoal. +class PrioritizeOwnerThreatGoal : public TargetGoal +{ +private: + TamableAnimal* tameAnimal; + weak_ptr newThreat; + + // Запоминаем timestamp последней обработанной атаки на хозяина, + // чтобы не переключаться на одну и ту же угрозу дважды + int timestamp; + + // Минимальная разница дистанций (в блоках²), при которой + // новая угроза считается "ближе" и стоит переключиться. + // Защищает от дёрганья между двумя равноудалёнными целями. + static constexpr double SWITCH_DIST_ADVANTAGE_SQ = 4.0 * 4.0; + +public: + PrioritizeOwnerThreatGoal(TamableAnimal* tameAnimal); + + bool canUse() override; + void start() override; +}; + diff --git a/Minecraft.World/TamableAnimal.h b/Minecraft.World/TamableAnimal.h index 7d17a43c4..5c456cef9 100644 --- a/Minecraft.World/TamableAnimal.h +++ b/Minecraft.World/TamableAnimal.h @@ -36,6 +36,8 @@ public: virtual void setOwnerUUID(const wstring &name); virtual shared_ptr getOwner(); virtual SitGoal *getSitGoal(); + virtual void onTameAggroStarted(shared_ptr target) {} + virtual void onTameAggroStopped() {} bool wantsToAttack(shared_ptr target, shared_ptr owner); Team *getTeam(); bool isAlliedTo(shared_ptr other); diff --git a/Minecraft.World/TameAnimalDefendGoal.cpp b/Minecraft.World/TameAnimalDefendGoal.cpp new file mode 100644 index 000000000..547190cb6 --- /dev/null +++ b/Minecraft.World/TameAnimalDefendGoal.cpp @@ -0,0 +1,88 @@ +#include "stdafx.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.animal.h" +#include "net.minecraft.world.entity.monster.h" +#include "net.minecraft.world.phys.h" +#include "TameAnimalDefendGoal.h" +#include "Level.h" + +TameAnimalDefendGoal::TameAnimalDefendGoal(TamableAnimal* tameAnimal, bool mustSee) + : TargetGoal(tameAnimal, mustSee) +{ + this->tameAnimal = tameAnimal; + setRequiredControlFlags(TargetGoal::TargetFlag); +} + +bool TameAnimalDefendGoal::canUse() +{ + // + if (!tameAnimal->isTame()) return false; + if (tameAnimal->isSitting()) return false; + + // ( ) + if (mob->getTarget() != nullptr && mob->getTarget()->isAlive()) return false; + + shared_ptr owner = + dynamic_pointer_cast(tameAnimal->getOwner()); + // owner nullptr (-) , wantsToAttack + // + + double range = getFollowDistance(); + + // Monster- + vector>* candidates = mob->level->getEntitiesOfClass( + typeid(Monster), + mob->bb->grow(range, 4.0, range), + nullptr // canAttack + ); + + if (candidates == nullptr || candidates->empty()) + { + delete candidates; + return false; + } + + shared_ptr best = nullptr; + double bestSq = DBL_MAX; + + for (auto& e : *candidates) + { + shared_ptr le = dynamic_pointer_cast(e); + if (!le) continue; + + // , , + if (!canAttack(le, false)) continue; + + // + // ( , ..) + if (owner != nullptr && !tameAnimal->wantsToAttack(le, owner)) continue; + + double distSq = mob->distanceToSqr(le); + if (distSq < bestSq) + { + bestSq = distSq; + best = le; + } + } + + delete candidates; + + if (!best) return false; + + nearestHostile = weak_ptr(best); + return true; +} + +void TameAnimalDefendGoal::start() +{ + shared_ptr target = nearestHostile.lock(); + mob->setTarget(target); + tameAnimal->onTameAggroStarted(target); + TargetGoal::start(); +} + +void TameAnimalDefendGoal::stop() +{ + tameAnimal->onTameAggroStopped(); + TargetGoal::stop(); +} diff --git a/Minecraft.World/TameAnimalDefendGoal.h b/Minecraft.World/TameAnimalDefendGoal.h new file mode 100644 index 000000000..8f225071a --- /dev/null +++ b/Minecraft.World/TameAnimalDefendGoal.h @@ -0,0 +1,22 @@ +#pragma once + +#include "TargetGoal.h" + +class TamableAnimal; + +// +class TameAnimalDefendGoal : public TargetGoal +{ +private: + TamableAnimal* tameAnimal; + weak_ptr nearestHostile; + +public: + // mustSee=true: + // scanRadius: 0, FOLLOW_RANGE + TameAnimalDefendGoal(TamableAnimal* tameAnimal, bool mustSee); + + bool canUse() override; + void start() override; + void stop() override; +}; diff --git a/Minecraft.World/Wolf.cpp b/Minecraft.World/Wolf.cpp index 7588a67b0..cbfa46a2b 100644 --- a/Minecraft.World/Wolf.cpp +++ b/Minecraft.World/Wolf.cpp @@ -20,6 +20,7 @@ #include "Wolf.h" #include "..\Minecraft.Client\Textures.h" #include "SoundTypes.h" +#include "PrimedTnt.h" @@ -39,20 +40,28 @@ Wolf::Wolf(Level *level) : TamableAnimal( level ) getNavigation()->setAvoidWater(true); goalSelector.addGoal(1, new FloatGoal(this)); - goalSelector.addGoal(2, sitGoal, false); - goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4)); - goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, true)); - goalSelector.addGoal(5, new FollowOwnerGoal(this, 1.0, 10, 2)); - goalSelector.addGoal(6, new BreedGoal(this, 1.0)); - goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); - goalSelector.addGoal(8, new BegGoal(this, 8)); - goalSelector.addGoal(9, new LookAtPlayerGoal(this, typeid(Player), 8)); - goalSelector.addGoal(9, new RandomLookAroundGoal(this)); + goalSelector.addGoal(2, new WolfRetreatGoal(this, 0.25f, 1.5f)); + goalSelector.addGoal(3, new AvoidPlayerGoal(this, typeid(Creeper), 12, 1.25, 1.25)); + goalSelector.addGoal(4, new AvoidPlayerGoal(this, typeid(PrimedTnt), 12, 1.25, 1.25)); + goalSelector.addGoal(5, sitGoal, false); + goalSelector.addGoal(6, new LeapAtTargetGoal(this, 0.4)); + goalSelector.addGoal(7, new MeleeAttackGoal(this, 1.0, true)); + goalSelector.addGoal(8, new FollowOwnerGoal(this, 1.0, 10, 2)); + goalSelector.addGoal(9, new BreedGoal(this, 1.0)); + goalSelector.addGoal(10, new RandomStrollGoal(this, 1.0)); + goalSelector.addGoal(11, new BegGoal(this, 8)); + goalSelector.addGoal(12, new LookAtPlayerGoal(this, typeid(Player), 8)); + goalSelector.addGoal(13, new LookAtPlayerGoal(this, typeid(Wolf), 8)); + goalSelector.addGoal(14, new RandomLookAroundGoal(this)); - targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this)); - targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); - targetSelector.addGoal(3, new HurtByTargetGoal(this, true)); - targetSelector.addGoal(4, new NonTameRandomTargetGoal(this, typeid(Sheep), 200, false)); + targetSelector.addGoal(1, new HurtByTargetGoal(this, true)); + targetSelector.addGoal(2, new OwnerHurtByTargetGoal(this)); + targetSelector.addGoal(3, new PrioritizeOwnerThreatGoal(this)); + targetSelector.addGoal(4, new OwnerHurtTargetGoal(this)); + targetSelector.addGoal(5, new PackDistributedTargetGoal(this, true, 20.0f)); + targetSelector.addGoal(6, new TameAnimalDefendGoal(this, true)); + targetSelector.addGoal(7, new NonTameRandomTargetGoal(this, typeid(Sheep), 200, false)); + targetSelector.addGoal(8, new NonTameRandomTargetGoal(this, typeid(Skeleton), 200, false)); setTame(false); // Initialize health } @@ -81,14 +90,20 @@ bool Wolf::useNewAi() void Wolf::setTarget(shared_ptr target) { TamableAnimal::setTarget(target); - if ( target == nullptr ) - { + if (target == nullptr) setAngry(false); - } - else if(!isTame()) - { + else setAngry(true); - } +} + +void Wolf::onTameAggroStarted(shared_ptr target) +{ + setAngry(true); +} + +void Wolf::onTameAggroStopped() +{ + setAngry(false); } void Wolf::serverAiMobStep() @@ -474,8 +489,7 @@ bool Wolf::isFood(shared_ptr item) int Wolf::getMaxSpawnClusterSize() { - // 4J - changed - was 8 but we have a limit of only 8 wolves in the world so doesn't seem right potentially spawning them all in once cluster - return 4; + return 8; } bool Wolf::isAngry() diff --git a/Minecraft.World/Wolf.h b/Minecraft.World/Wolf.h index edfd38146..823385e05 100644 --- a/Minecraft.World/Wolf.h +++ b/Minecraft.World/Wolf.h @@ -76,6 +76,9 @@ public: // For tooltips int GetSynchedHealth(); + virtual void onTameAggroStarted(shared_ptr target) override; + virtual void onTameAggroStopped() override; + protected: virtual shared_ptr getBreedOffspring(shared_ptr target); diff --git a/Minecraft.World/WolfPackDistributedTargetGoal.cpp b/Minecraft.World/WolfPackDistributedTargetGoal.cpp new file mode 100644 index 000000000..062812542 --- /dev/null +++ b/Minecraft.World/WolfPackDistributedTargetGoal.cpp @@ -0,0 +1,182 @@ +#include "stdafx.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.animal.h" +#include "net.minecraft.world.entity.monster.h" +#include "net.minecraft.world.phys.h" +#include "Level.h" +#include "Wolf.h" +#include "WolfPackDistributedTargetGoal.h" + +// --------------------------------------------------------------------------- +PackDistributedTargetGoal::PackDistributedTargetGoal( + TamableAnimal* tameAnimal, + bool mustSee, + float maxChaseDistFromOwner) + : TargetGoal(tameAnimal, mustSee) +{ + this->tameAnimal = tameAnimal; + this->maxChaseDistFromOwner = maxChaseDistFromOwner; + setRequiredControlFlags(TargetGoal::TargetFlag); +} + +// --------------------------------------------------------------------------- +vector> PackDistributedTargetGoal::getPackmates( + double range) +{ + vector> result; + + wstring ownerUUID = tameAnimal->getOwnerUUID(); + if (ownerUUID.empty()) return result; + + // Ищем всех волков в радиусе + vector>* nearby = mob->level->getEntitiesOfClass( + typeid(Wolf), + mob->bb->grow(range, 4.0, range), + nullptr); + + if (nearby == nullptr) + return result; + + for (auto& e : *nearby) + { + // Пропустить самого себя + if (e.get() == mob) continue; + + shared_ptr wolf = dynamic_pointer_cast(e); + if (wolf == nullptr) continue; + if (!wolf->isTame()) continue; + + // Только волки того же хозяина + if (wolf->getOwnerUUID() != ownerUUID) continue; + + result.push_back(wolf); + } + + delete nearby; + return result; +} + +// --------------------------------------------------------------------------- +int PackDistributedTargetGoal::countAssignments( + const vector>& packmates, + shared_ptr target) +{ + if (target == nullptr) return 0; + + int count = 0; + for (auto& packmate : packmates) + { + shared_ptr packTarget = + dynamic_pointer_cast( + dynamic_pointer_cast(packmate) != nullptr + ? dynamic_pointer_cast(packmate)->getTarget() + : nullptr); + + if (packTarget != nullptr && packTarget.get() == target.get()) + count++; + } + return count; +} + +// --------------------------------------------------------------------------- +shared_ptr PackDistributedTargetGoal::pickBestTarget( + const vector>* candidates, + const vector>& packmates, + bool respectLimit) +{ + shared_ptr best = nullptr; + double bestScore = DBL_MAX; + + shared_ptr owner = + dynamic_pointer_cast(tameAnimal->getOwner()); + + for (auto& e : *candidates) + { + shared_ptr le = dynamic_pointer_cast(e); + if (le == nullptr) continue; + if (!canAttack(le, false)) continue; + if (owner != nullptr && !tameAnimal->wantsToAttack(le, owner)) continue; + + int assigned = countAssignments(packmates, le); + + // Фаза 1: уважаем жёсткий лимит + if (respectLimit && assigned >= MAX_WOLVES_PER_TARGET) + continue; + + // score = штраф за назначенных волков + дистанция² + // Это позволяет предпочесть менее занятую цель, + // но при равном числе назначений — выбрать ближайшую + double distSq = mob->distanceToSqr(le); + double score = assigned * ASSIGNMENT_PENALTY_SQ + distSq; + + if (score < bestScore) + { + bestScore = score; + best = le; + } + } + + return best; +} + +// --------------------------------------------------------------------------- +bool PackDistributedTargetGoal::canUse() +{ + if (!tameAnimal->isTame()) return false; + if (tameAnimal->isSitting()) return false; + if (mob->getTarget() != nullptr && mob->getTarget()->isAlive()) + return false; + + double range = getFollowDistance(); + + vector>* candidates = mob->level->getEntitiesOfClass( + typeid(Monster), + mob->bb->grow(range, 4.0, range), + nullptr); + + if (candidates == nullptr || candidates->empty()) + { + delete candidates; + return false; + } + + vector> packmates = getPackmates(range); + + // Фаза 1: ищем цель с учётом лимита MAX_WOLVES_PER_TARGET + shared_ptr target = pickBestTarget(candidates, packmates, true); + + // Фаза 2: если все цели заняты сверх лимита — игнорируем лимит, + // чтобы волк не простаивал + if (target == nullptr) + target = pickBestTarget(candidates, packmates, false); + + delete candidates; + + if (target == nullptr) return false; + + assignedTarget = weak_ptr(target); + return true; +} + +// --------------------------------------------------------------------------- +bool PackDistributedTargetGoal::canContinueToUse() +{ + // Базовые проверки TargetGoal (цель жива, в радиусе follow_range и т.д.) + if (!TargetGoal::canContinueToUse()) return false; + + // Не удаляться от хозяина дальше поводка + shared_ptr owner = tameAnimal->getOwner(); + if (owner == nullptr) return false; + + return mob->distanceToSqr(owner) <= + maxChaseDistFromOwner * maxChaseDistFromOwner; +} + +// --------------------------------------------------------------------------- +void PackDistributedTargetGoal::start() +{ + shared_ptr target = assignedTarget.lock(); + mob->setTarget(target); + tameAnimal->onTameAggroStarted(target); + TargetGoal::start(); +} diff --git a/Minecraft.World/WolfPackDistributedTargetGoal.h b/Minecraft.World/WolfPackDistributedTargetGoal.h new file mode 100644 index 000000000..0cd2bff0e --- /dev/null +++ b/Minecraft.World/WolfPackDistributedTargetGoal.h @@ -0,0 +1,56 @@ +#pragma once + +#include "Goal.h" +#include "TargetGoal.h" +#include "LivingEntity.h" + +class TamableAnimal; + +// TameAnimalDefendGoal . +// , +// "" : +// score = assignedWolves * ASSIGNMENT_PENALTY_SQ + distanceSq +// score = . +class PackDistributedTargetGoal : public TargetGoal +{ +private: + TamableAnimal* tameAnimal; + weak_ptr assignedTarget; + float maxChaseDistFromOwner; + + // . + // " " 12 . + // . + // . + static constexpr double ASSIGNMENT_PENALTY_SQ = 12.0 * 12.0; + + // Ƹ . + // + // ( ). + static constexpr int MAX_WOLVES_PER_TARGET = 2; + +public: + PackDistributedTargetGoal(TamableAnimal* tameAnimal, + bool mustSee, + float maxChaseDistFromOwner = 20.0f); + + bool canUse() override; + bool canContinueToUse() override; + void start() override; + +private: + // range + vector> getPackmates(double range); + + // packmates target + int countAssignments( + const vector>& packmates, + shared_ptr target); + + // candidates . + // respectLimit: true >= MAX_WOLVES_PER_TARGET + shared_ptr pickBestTarget( + const vector>* candidates, + const vector>& packmates, + bool respectLimit); +}; diff --git a/Minecraft.World/WolfRetreatGoal.cpp b/Minecraft.World/WolfRetreatGoal.cpp new file mode 100644 index 000000000..9bac32d76 --- /dev/null +++ b/Minecraft.World/WolfRetreatGoal.cpp @@ -0,0 +1,102 @@ +#include "stdafx.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.entity.animal.h" +#include "net.minecraft.world.entity.ai.navigation.h" +#include "WolfRetreatGoal.h" + +WolfRetreatGoal::WolfRetreatGoal(TamableAnimal* tameAnimal, + float retreatHealthFraction, + float runSpeedMultiplier) +{ + this->tameAnimal = tameAnimal; + this->retreatHealthFraction = retreatHealthFraction; + this->runSpeedMultiplier = runSpeedMultiplier; + this->recalcPathTicks = 0; + + // FollowOwnerGoal, + // RandomStrollGoal .. + setRequiredControlFlags(0); +} + +// --------------------------------------------------------------------------- +bool WolfRetreatGoal::canUse() +{ + // + if (!tameAnimal->isTame()) return false; + if (tameAnimal->isSitting()) return false; + + // + float hpFraction = tameAnimal->getHealth() / tameAnimal->getMaxHealth(); + if (hpFraction > retreatHealthFraction) return false; + + // + shared_ptr owner = tameAnimal->getOwner(); + if (owner == nullptr) return false; + + shared_ptr ownerLiving = + dynamic_pointer_cast(owner); + return ownerLiving != nullptr && ownerLiving->isAlive(); +} + +// --------------------------------------------------------------------------- +bool WolfRetreatGoal::canContinueToUse() +{ + // + if (tameAnimal->isSitting()) return false; + + // ( ) + float hpFraction = tameAnimal->getHealth() / tameAnimal->getMaxHealth(); + if (hpFraction >= retreatHealthFraction + HYSTERESIS) return false; + + // + shared_ptr owner = tameAnimal->getOwner(); + if (owner == nullptr) return false; + shared_ptr ownerLiving = + dynamic_pointer_cast(owner); + if (ownerLiving == nullptr || !ownerLiving->isAlive()) return false; + + // + if (tameAnimal->distanceToSqr(owner) <= SAFE_DIST_SQ) return false; + + return true; +} + +// --------------------------------------------------------------------------- +void WolfRetreatGoal::start() +{ + recalcPathTicks = 0; + + // + tameAnimal->setTarget(nullptr); + tameAnimal->onTameAggroStopped(); // Wolf: setAngry(false) + + // + shared_ptr owner = tameAnimal->getOwner(); + if (owner != nullptr) + { + tameAnimal->getNavigation()->moveTo(owner, runSpeedMultiplier); + } +} + +// --------------------------------------------------------------------------- +void WolfRetreatGoal::stop() +{ + tameAnimal->getNavigation()->stop(); +} + +// --------------------------------------------------------------------------- +void WolfRetreatGoal::tick() +{ + // , + // + if (--recalcPathTicks <= 0) + { + recalcPathTicks = PATH_RECALC_INTERVAL; + + shared_ptr owner = tameAnimal->getOwner(); + if (owner != nullptr) + { + tameAnimal->getNavigation()->moveTo(owner, runSpeedMultiplier); + } + } +} diff --git a/Minecraft.World/WolfRetreatGoal.h b/Minecraft.World/WolfRetreatGoal.h new file mode 100644 index 000000000..e070ad16b --- /dev/null +++ b/Minecraft.World/WolfRetreatGoal.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Goal.h" + +class TamableAnimal; + +class WolfRetreatGoal : public Goal +{ +private: + TamableAnimal* tameAnimal; + + // HP (0.01.0), + float retreatHealthFraction; + + // + float runSpeedMultiplier; + + // c ( ) + int recalcPathTicks; + + // : , + // - + static constexpr float HYSTERESIS = 0.15f; + + // , "" + static constexpr float SAFE_DIST_SQ = 4.0f * 4.0f; + + // N + static constexpr int PATH_RECALC_INTERVAL = 10; + +public: + WolfRetreatGoal(TamableAnimal* tameAnimal, + float retreatHealthFraction, + float runSpeedMultiplier = 1.5f); + + bool canUse() override; + bool canContinueToUse() override; + void start() override; + void stop() override; + void tick() override; +}; diff --git a/Minecraft.World/net.minecraft.world.entity.ai.goal.target.h b/Minecraft.World/net.minecraft.world.entity.ai.goal.target.h index 98b96a16a..961360521 100644 --- a/Minecraft.World/net.minecraft.world.entity.ai.goal.target.h +++ b/Minecraft.World/net.minecraft.world.entity.ai.goal.target.h @@ -6,4 +6,8 @@ #include "NearestAttackableTargetGoal.h" #include "NonTameRandomTargetGoal.h" #include "OwnerHurtByTargetGoal.h" -#include "OwnerHurtTargetGoal.h" \ No newline at end of file +#include "OwnerHurtTargetGoal.h" +#include "TameAnimalDefendGoal.h" +#include "WolfRetreatGoal.h" +#include "WolfPackDistributedTargetGoal.h" +#include "PrioritizeOwnerThreatGoal.h"