From f8eff3c54c06d547b5fec0055acf9cde6dd3a13d Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 15 Jun 2026 19:07:51 +0200 Subject: [PATCH 1/5] Hunter: Use GetPointsInRadius() instead of square --- libs/s25main/ai/aijh/AIPlayerJH.cpp | 61 +++++++++-------------------- libs/s25main/figures/nofHunter.cpp | 47 ++++++++-------------- libs/s25main/figures/nofSkinner.cpp | 2 +- libs/s25main/gameData/GameConsts.h | 3 ++ 4 files changed, 38 insertions(+), 75 deletions(-) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 72fbee111f..089d55a3a2 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -1965,51 +1965,26 @@ bool AIPlayerJH::HuntablesinRange(const MapPoint pt, unsigned min) // check first if no other hunter(or hunter buildingsite) is nearby if(aii.isBuildingNearby(BuildingType::Hunter, pt, 14)) return false; - unsigned maxrange = 25; - unsigned short fx, fy, lx, ly; - const unsigned short SQUARE_SIZE = 19; - unsigned huntablecount = 0; - if(pt.x > SQUARE_SIZE) - fx = pt.x - SQUARE_SIZE; - else - fx = 0; - if(pt.y > SQUARE_SIZE) - fy = pt.y - SQUARE_SIZE; - else - fy = 0; - if(pt.x + SQUARE_SIZE < gwb.GetWidth()) - lx = pt.x + SQUARE_SIZE; - else - lx = gwb.GetWidth() - 1; - if(pt.y + SQUARE_SIZE < gwb.GetHeight()) - ly = pt.y + SQUARE_SIZE; - else - ly = gwb.GetHeight() - 1; - // Durchgehen und nach Tieren suchen - for(MapPoint p2(0, fy); p2.y <= ly; ++p2.y) - { - for(p2.x = fx; p2.x <= lx; ++p2.x) + const unsigned maxrange = 25; + + const auto pointToAnimal = [this](const MapPoint pt, unsigned) -> const noAnimal* { + for(const noBase& fig : gwb.GetFigures(pt)) { - // Search for animals - for(const noBase& fig : gwb.GetFigures(p2)) - { - if(fig.GetType() == NodalObjectType::Animal) - { - // Ist das Tier überhaupt zum Jagen geeignet? - if(!static_cast(fig).CanHunted()) - continue; - // Und komme ich hin? - if(gwb.FindHumanPath(pt, static_cast(fig).GetPos(), maxrange)) - // Dann nehmen wir es - { - if(++huntablecount >= min) - return true; - } - } - } + if(fig.GetType() == NodalObjectType::Animal) + return static_cast(&fig); } - } - return false; + return nullptr; + }; + + const auto canAnimalBeHunted = [this, pt, maxrange](const noAnimal* const animal) { + return animal && animal->CanHunted() + && gwb.FindHumanPath(pt, animal->GetPos(), maxrange); + }; + + const auto available_animals = + gwb.GetPointsInRadius(pt, ANIMAL_RADIUS, pointToAnimal, canAnimalBeHunted, true); + + return available_animals.size() >= min; } void AIPlayerJH::InitStoreAndMilitarylists() diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index 80a57c8a79..f09ec1a926 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -131,39 +131,24 @@ void nofHunter::HandleDerivedEvent(unsigned /*id*/) void nofHunter::TryStartHunting() { - // Find animals in a square around building (actually should be circle, but animals are moving anyway) - const int SQUARE_SIZE = 19; - - // Liste mit den gefundenen Tieren - std::vector available_animals; - - // Durchgehen und nach Tieren suchen - Position curPos; - for(curPos.y = pos.y - SQUARE_SIZE; curPos.y <= pos.y + SQUARE_SIZE; ++curPos.y) - { - for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x) + // Find animals in a circle around building + const auto pointToAnimal = [world = this->world](const MapPoint pt, unsigned) -> noAnimal* { + for(auto& figure : world->GetFigures(pt)) { - MapPoint curMapPos = world->MakeMapPoint(curPos); - - // nach Tieren suchen - for(auto& figure : world->GetFigures(curMapPos)) - { - if(figure.GetType() != NodalObjectType::Animal) - continue; - // Ist das Tier überhaupt zum Jagen geeignet? - auto& animal = static_cast(figure); - if(!animal.CanHunted()) - continue; - - // Und komme ich hin? - if(pos == animal.GetPos() || world->FindHumanPath(pos, animal.GetPos(), MAX_HUNTING_DISTANCE)) - { - // Dann nehmen wir es - available_animals.push_back(&animal); - } - } + if(figure.GetType() != NodalObjectType::Animal) + continue; + return static_cast(&figure); } - } + return nullptr; + }; + + const auto canAnimalBeHunted = [pos = this->pos](const noAnimal* const animal) { + return animal && animal->CanHunted() + && (pos == animal->GetPos() || world->FindHumanPath(pos, animal->GetPos(), MAX_HUNTING_DISTANCE)); + }; + + const auto available_animals = + world->GetPointsInRadius(pos, ANIMAL_RADIUS, pointToAnimal, canAnimalBeHunted, true); // Gibt es überhaupt ein Tier, das ich jagen kann? if(!available_animals.empty()) diff --git a/libs/s25main/figures/nofSkinner.cpp b/libs/s25main/figures/nofSkinner.cpp index dbb02f61ff..bcb2705410 100644 --- a/libs/s25main/figures/nofSkinner.cpp +++ b/libs/s25main/figures/nofSkinner.cpp @@ -16,6 +16,7 @@ #include "random/Random.h" #include "world/GameWorld.h" #include "nodeObjs/noAnimal.h" +#include "gameData/GameConsts.h" #include "gameData/JobConsts.h" using namespace leatheraddon; @@ -187,7 +188,6 @@ void nofSkinner::TryStartSkinning() HandleStateWaiting1(); else { - const int ANIMAL_RADIUS = 19; const auto pointToAnimal = [world = this->world](const MapPoint pt, unsigned) -> noAnimal* { for(auto& figure : world->GetFigures(pt)) { diff --git a/libs/s25main/gameData/GameConsts.h b/libs/s25main/gameData/GameConsts.h index 47954cff7f..1cb1015b55 100644 --- a/libs/s25main/gameData/GameConsts.h +++ b/libs/s25main/gameData/GameConsts.h @@ -41,6 +41,9 @@ constexpr auto gfs_to_duration(const unsigned gfs) /// Reichweite der Bergarbeiter constexpr unsigned MINER_RADIUS = 2; +/// Suchradius für Tiere (Jäger, Gerber, KI) +constexpr unsigned ANIMAL_RADIUS = 19; + /// Konstante für die Pfadrichtung bei einer Schiffsverbindung constexpr unsigned char SHIP_DIR = 100; constexpr unsigned char INVALID_DIR = 0xFF; From e641dee51fffe768782bb21cdc570b63c89541d8 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 16 Jun 2026 10:53:16 +0200 Subject: [PATCH 2/5] DRY ANIMAL_RADIUS code --- libs/s25main/ai/aijh/AIPlayerJH.cpp | 21 ++++----------------- libs/s25main/figures/nofHunter.cpp | 29 +++++++++++++++++------------ libs/s25main/figures/nofHunter.h | 7 +++++++ libs/s25main/figures/nofSkinner.cpp | 19 +++---------------- libs/s25main/gameData/GameConsts.h | 2 +- 5 files changed, 32 insertions(+), 46 deletions(-) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 089d55a3a2..c2834fdbf8 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -32,6 +32,7 @@ #include "nodeObjs/noFlag.h" #include "nodeObjs/noShip.h" #include "nodeObjs/noTree.h" +#include "figures/nofHunter.h" #include "gameData/BuildingConsts.h" #include "gameData/BuildingProperties.h" #include "gameData/GameConsts.h" @@ -1965,24 +1966,10 @@ bool AIPlayerJH::HuntablesinRange(const MapPoint pt, unsigned min) // check first if no other hunter(or hunter buildingsite) is nearby if(aii.isBuildingNearby(BuildingType::Hunter, pt, 14)) return false; - const unsigned maxrange = 25; + constexpr unsigned maxrange = 25; - const auto pointToAnimal = [this](const MapPoint pt, unsigned) -> const noAnimal* { - for(const noBase& fig : gwb.GetFigures(pt)) - { - if(fig.GetType() == NodalObjectType::Animal) - return static_cast(&fig); - } - return nullptr; - }; - - const auto canAnimalBeHunted = [this, pt, maxrange](const noAnimal* const animal) { - return animal && animal->CanHunted() - && gwb.FindHumanPath(pt, animal->GetPos(), maxrange); - }; - - const auto available_animals = - gwb.GetPointsInRadius(pt, ANIMAL_RADIUS, pointToAnimal, canAnimalBeHunted, true); + const auto available_animals = nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, + [](const noAnimal* a) { return a->CanHunted(); }); return available_animals.size() >= min; } diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index f09ec1a926..b037805301 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -129,26 +129,31 @@ void nofHunter::HandleDerivedEvent(unsigned /*id*/) } } -void nofHunter::TryStartHunting() +std::vector nofHunter::GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, + unsigned radius, unsigned maxDistance, + bool (*isValidAnimal)(const noAnimal*)) { - // Find animals in a circle around building - const auto pointToAnimal = [world = this->world](const MapPoint pt, unsigned) -> noAnimal* { - for(auto& figure : world->GetFigures(pt)) + const auto pointToAnimal = [&world](const MapPoint pt, unsigned) -> noAnimal* { + for(auto& figure : world.GetFigures(pt)) { - if(figure.GetType() != NodalObjectType::Animal) - continue; - return static_cast(&figure); + if(figure.GetType() == NodalObjectType::Animal) + return static_cast(&figure); } return nullptr; }; - const auto canAnimalBeHunted = [pos = this->pos](const noAnimal* const animal) { - return animal && animal->CanHunted() - && (pos == animal->GetPos() || world->FindHumanPath(pos, animal->GetPos(), MAX_HUNTING_DISTANCE)); + const auto canAnimalBeUsed = [pos, maxDistance, &world, isValidAnimal](const noAnimal* const animal) { + return animal && isValidAnimal(animal) + && (pos == animal->GetPos() || world.FindHumanPath(pos, animal->GetPos(), maxDistance)); }; - const auto available_animals = - world->GetPointsInRadius(pos, ANIMAL_RADIUS, pointToAnimal, canAnimalBeHunted, true); + return world.GetPointsInRadius(pos, radius, pointToAnimal, canAnimalBeUsed, true); +} + +void nofHunter::TryStartHunting() +{ + const auto available_animals = GetAnimalsInRange(*world, pos, ANIMAL_RADIUS, MAX_HUNTING_DISTANCE, + [](const noAnimal* a) { return a->CanHunted(); }); // Gibt es überhaupt ein Tier, das ich jagen kann? if(!available_animals.empty()) diff --git a/libs/s25main/figures/nofHunter.h b/libs/s25main/figures/nofHunter.h index 1f780ebd5d..f9fef6e2d8 100644 --- a/libs/s25main/figures/nofHunter.h +++ b/libs/s25main/figures/nofHunter.h @@ -6,7 +6,9 @@ #include "nofBuildingWorker.h" #include "gameTypes/Direction.h" +#include +class GameWorldBase; class noAnimal; class SerializedGameData; class nobUsual; @@ -64,6 +66,11 @@ class nofHunter : public nofBuildingWorker void TryStartHunting(); + /// Find animals in a radius around a position that satisfy a predicate and are reachable + static std::vector GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, + unsigned radius, unsigned maxDistance, + bool (*isValidAnimal)(const noAnimal*)); + /// das Tier ist nicht mehr verfügbar (von selbst gestorben o.Ä.) void AnimalLost(); /// wird aufgerufen, wenn die Arbeit abgebrochen wird (von nofBuildingWorker aufgerufen) diff --git a/libs/s25main/figures/nofSkinner.cpp b/libs/s25main/figures/nofSkinner.cpp index bcb2705410..a609df7812 100644 --- a/libs/s25main/figures/nofSkinner.cpp +++ b/libs/s25main/figures/nofSkinner.cpp @@ -16,6 +16,7 @@ #include "random/Random.h" #include "world/GameWorld.h" #include "nodeObjs/noAnimal.h" +#include "figures/nofHunter.h" #include "gameData/GameConsts.h" #include "gameData/JobConsts.h" @@ -188,23 +189,9 @@ void nofSkinner::TryStartSkinning() HandleStateWaiting1(); else { - const auto pointToAnimal = [world = this->world](const MapPoint pt, unsigned) -> noAnimal* { - for(auto& figure : world->GetFigures(pt)) - { - if(figure.GetType() != NodalObjectType::Animal) - continue; - return checkedCast(&figure); - } - return nullptr; - }; - - const auto canAnimalBeSkinned = [pos = this->pos](const noAnimal* const animal) { - return animal && animal->CanBeSkinned() - && (pos == animal->GetPos() || world->FindHumanPath(pos, animal->GetPos(), MAX_SKINNING_DISTANCE)); - }; - const auto available_animals = - world->GetPointsInRadius(pos, ANIMAL_RADIUS, pointToAnimal, canAnimalBeSkinned, true); + nofHunter::GetAnimalsInRange(*world, pos, ANIMAL_RADIUS, MAX_SKINNING_DISTANCE, + [](const noAnimal* a) { return a->CanBeSkinned(); }); if(!available_animals.empty()) { diff --git a/libs/s25main/gameData/GameConsts.h b/libs/s25main/gameData/GameConsts.h index 1cb1015b55..0bd5cbda3d 100644 --- a/libs/s25main/gameData/GameConsts.h +++ b/libs/s25main/gameData/GameConsts.h @@ -41,7 +41,7 @@ constexpr auto gfs_to_duration(const unsigned gfs) /// Reichweite der Bergarbeiter constexpr unsigned MINER_RADIUS = 2; -/// Suchradius für Tiere (Jäger, Gerber, KI) +/// Search radius for animals (hunter, skinner, AI) constexpr unsigned ANIMAL_RADIUS = 19; /// Konstante für die Pfadrichtung bei einer Schiffsverbindung From a18d4f3cc376235649ab340ecca71db4cde41bef Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Tue, 16 Jun 2026 13:25:57 +0200 Subject: [PATCH 3/5] Fix formatting --- libs/s25main/ai/aijh/AIPlayerJH.cpp | 6 +++--- libs/s25main/figures/nofHunter.cpp | 5 ++--- libs/s25main/figures/nofHunter.h | 5 ++--- libs/s25main/figures/nofSkinner.cpp | 7 +++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index c2834fdbf8..82d9fab73d 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -17,6 +17,7 @@ #include "buildings/nobHarborBuilding.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" +#include "figures/nofHunter.h" #include "helpers/IdRange.h" #include "helpers/MaxEnumValue.h" #include "helpers/containerUtils.h" @@ -32,7 +33,6 @@ #include "nodeObjs/noFlag.h" #include "nodeObjs/noShip.h" #include "nodeObjs/noTree.h" -#include "figures/nofHunter.h" #include "gameData/BuildingConsts.h" #include "gameData/BuildingProperties.h" #include "gameData/GameConsts.h" @@ -1968,8 +1968,8 @@ bool AIPlayerJH::HuntablesinRange(const MapPoint pt, unsigned min) return false; constexpr unsigned maxrange = 25; - const auto available_animals = nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, - [](const noAnimal* a) { return a->CanHunted(); }); + const auto available_animals = + nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, [](const noAnimal* a) { return a->CanHunted(); }); return available_animals.size() >= min; } diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index b037805301..5ab4b0cec4 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -129,9 +129,8 @@ void nofHunter::HandleDerivedEvent(unsigned /*id*/) } } -std::vector nofHunter::GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, - unsigned radius, unsigned maxDistance, - bool (*isValidAnimal)(const noAnimal*)) +std::vector nofHunter::GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, unsigned radius, + unsigned maxDistance, bool (*isValidAnimal)(const noAnimal*)) { const auto pointToAnimal = [&world](const MapPoint pt, unsigned) -> noAnimal* { for(auto& figure : world.GetFigures(pt)) diff --git a/libs/s25main/figures/nofHunter.h b/libs/s25main/figures/nofHunter.h index f9fef6e2d8..217f39f595 100644 --- a/libs/s25main/figures/nofHunter.h +++ b/libs/s25main/figures/nofHunter.h @@ -67,9 +67,8 @@ class nofHunter : public nofBuildingWorker void TryStartHunting(); /// Find animals in a radius around a position that satisfy a predicate and are reachable - static std::vector GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, - unsigned radius, unsigned maxDistance, - bool (*isValidAnimal)(const noAnimal*)); + static std::vector GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, unsigned radius, + unsigned maxDistance, bool (*isValidAnimal)(const noAnimal*)); /// das Tier ist nicht mehr verfügbar (von selbst gestorben o.Ä.) void AnimalLost(); diff --git a/libs/s25main/figures/nofSkinner.cpp b/libs/s25main/figures/nofSkinner.cpp index a609df7812..13769c3bac 100644 --- a/libs/s25main/figures/nofSkinner.cpp +++ b/libs/s25main/figures/nofSkinner.cpp @@ -10,13 +10,13 @@ #include "SerializedGameData.h" #include "SoundManager.h" #include "buildings/nobUsual.h" +#include "figures/nofHunter.h" #include "network/GameClient.h" #include "notifications/BuildingNote.h" #include "ogl/glArchivItem_Bitmap_Player.h" #include "random/Random.h" #include "world/GameWorld.h" #include "nodeObjs/noAnimal.h" -#include "figures/nofHunter.h" #include "gameData/GameConsts.h" #include "gameData/JobConsts.h" @@ -189,9 +189,8 @@ void nofSkinner::TryStartSkinning() HandleStateWaiting1(); else { - const auto available_animals = - nofHunter::GetAnimalsInRange(*world, pos, ANIMAL_RADIUS, MAX_SKINNING_DISTANCE, - [](const noAnimal* a) { return a->CanBeSkinned(); }); + const auto available_animals = nofHunter::GetAnimalsInRange( + *world, pos, ANIMAL_RADIUS, MAX_SKINNING_DISTANCE, [](const noAnimal* a) { return a->CanBeSkinned(); }); if(!available_animals.empty()) { From e68942c984cf2a243f3fe5e55aa4cccd73210f76 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Wed, 17 Jun 2026 12:23:49 +0200 Subject: [PATCH 4/5] Fix formatting --- libs/s25main/ai/aijh/AIPlayerJH.cpp | 2 +- libs/s25main/figures/nofHunter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 82d9fab73d..c1706fd8c1 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -1705,7 +1705,7 @@ void AIPlayerJH::TrySeaAttack() if(!testseaidswithattackers.empty()) { undefendedTargets.push_back(milBld); - } // else - no attackers - do nothing + } // else - no attackers - do nothing } else // normal target - check is done after random shuffle so we dont have to check every possible // target and instead only enough to get 1 good one { diff --git a/libs/s25main/figures/nofHunter.h b/libs/s25main/figures/nofHunter.h index 217f39f595..3070db5a6e 100644 --- a/libs/s25main/figures/nofHunter.h +++ b/libs/s25main/figures/nofHunter.h @@ -67,7 +67,7 @@ class nofHunter : public nofBuildingWorker void TryStartHunting(); /// Find animals in a radius around a position that satisfy a predicate and are reachable - static std::vector GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, unsigned radius, + static std::vector GetAnimalsInRange(const GameWorldBase& world, MapPoint pos, unsigned radius, unsigned maxDistance, bool (*isValidAnimal)(const noAnimal*)); /// das Tier ist nicht mehr verfügbar (von selbst gestorben o.Ä.) From beb5f8dd5d2f613b9a90ea9d6a4080f1f679af41 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Thu, 18 Jun 2026 20:46:57 +0200 Subject: [PATCH 5/5] Replay compatiblity --- libs/s25main/Replay.cpp | 3 +- libs/s25main/ai/aijh/AIPlayerJH.cpp | 51 +++++++++++++++++++++++++++-- libs/s25main/figures/nofHunter.cpp | 33 +++++++++++++++++-- libs/s25main/network/GameClient.cpp | 2 ++ libs/s25main/world/GameWorldBase.h | 7 ++++ tests/s25Main/autoplay/main.cpp | 1 + 6 files changed, 91 insertions(+), 6 deletions(-) diff --git a/libs/s25main/Replay.cpp b/libs/s25main/Replay.cpp index d37c9d5cfe..f5ea3054a2 100644 --- a/libs/s25main/Replay.cpp +++ b/libs/s25main/Replay.cpp @@ -41,7 +41,8 @@ uint8_t Replay::GetLatestMinorVersion() const // 8.2: Set correct initial distributions if replay starts without savegame for leather addon (see GameClient.cpp // StartReplay function for detailed description) // 8.3 Remove invalid fish for replays started from start (i.e. map instead of savegame) - return 3; + // 8.4 Hunter/skinner/AI search for animals in a circle instead of a square (radius-based) + return 4; } uint8_t Replay::GetLatestMajorVersion() const diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index c1706fd8c1..3fe404e2f7 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -1968,10 +1968,55 @@ bool AIPlayerJH::HuntablesinRange(const MapPoint pt, unsigned min) return false; constexpr unsigned maxrange = 25; - const auto available_animals = - nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, [](const noAnimal* a) { return a->CanHunted(); }); + if(gwb.GetReplayMinorVersion() >= 4) + { + const auto available_animals = + nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, [](const noAnimal* a) { return a->CanHunted(); }); - return available_animals.size() >= min; + return available_animals.size() >= min; + } else + { + // Legacy square search for replays recorded with old code + unsigned short fx, fy, lx, ly; + const unsigned short SQUARE_SIZE = 19; + unsigned huntablecount = 0; + if(pt.x > SQUARE_SIZE) + fx = pt.x - SQUARE_SIZE; + else + fx = 0; + if(pt.y > SQUARE_SIZE) + fy = pt.y - SQUARE_SIZE; + else + fy = 0; + if(pt.x + SQUARE_SIZE < gwb.GetWidth()) + lx = pt.x + SQUARE_SIZE; + else + lx = gwb.GetWidth() - 1; + if(pt.y + SQUARE_SIZE < gwb.GetHeight()) + ly = pt.y + SQUARE_SIZE; + else + ly = gwb.GetHeight() - 1; + for(MapPoint p2(0, fy); p2.y <= ly; ++p2.y) + { + for(p2.x = fx; p2.x <= lx; ++p2.x) + { + for(const noBase& fig : gwb.GetFigures(p2)) + { + if(fig.GetType() == NodalObjectType::Animal) + { + if(!static_cast(fig).CanHunted()) + continue; + if(gwb.FindHumanPath(pt, static_cast(fig).GetPos(), maxrange)) + { + if(++huntablecount >= min) + return true; + } + } + } + } + } + return false; + } } void AIPlayerJH::InitStoreAndMilitarylists() diff --git a/libs/s25main/figures/nofHunter.cpp b/libs/s25main/figures/nofHunter.cpp index 5ab4b0cec4..0d9e096c4b 100644 --- a/libs/s25main/figures/nofHunter.cpp +++ b/libs/s25main/figures/nofHunter.cpp @@ -151,8 +151,37 @@ std::vector nofHunter::GetAnimalsInRange(const GameWorldBase& world, void nofHunter::TryStartHunting() { - const auto available_animals = GetAnimalsInRange(*world, pos, ANIMAL_RADIUS, MAX_HUNTING_DISTANCE, - [](const noAnimal* a) { return a->CanHunted(); }); + std::vector available_animals; + + if(world->GetReplayMinorVersion() >= 4) + { + available_animals = GetAnimalsInRange(*world, pos, ANIMAL_RADIUS, MAX_HUNTING_DISTANCE, + [](const noAnimal* a) { return a->CanHunted(); }); + } else + { + // Legacy square search for replays recorded with old code + const int SQUARE_SIZE = 19; + Position curPos; + for(curPos.y = pos.y - SQUARE_SIZE; curPos.y <= pos.y + SQUARE_SIZE; ++curPos.y) + { + for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x) + { + MapPoint curMapPos = world->MakeMapPoint(curPos); + for(auto& figure : world->GetFigures(curMapPos)) + { + if(figure.GetType() != NodalObjectType::Animal) + continue; + auto& animal = static_cast(figure); + if(!animal.CanHunted()) + continue; + if(pos == animal.GetPos() || world->FindHumanPath(pos, animal.GetPos(), MAX_HUNTING_DISTANCE)) + { + available_animals.push_back(&animal); + } + } + } + } + } // Gibt es überhaupt ein Tier, das ich jagen kann? if(!available_animals.empty()) diff --git a/libs/s25main/network/GameClient.cpp b/libs/s25main/network/GameClient.cpp index 0060d5964f..4d2a50036b 100644 --- a/libs/s25main/network/GameClient.cpp +++ b/libs/s25main/network/GameClient.cpp @@ -341,6 +341,8 @@ void GameClient::StartGame(const unsigned random_init) const bool fixFish = !GetReplay() || GetReplay()->GetMinorVersion() >= 3; MapLoader::SetupResources(gameWorld, fixFish); } + if(replayMode && replayinfo) + gameWorld.SetReplayMinorVersion(replayinfo->replay.GetMinorVersion()); gameWorld.InitAfterLoad(); // Update visual settings diff --git a/libs/s25main/world/GameWorldBase.h b/libs/s25main/world/GameWorldBase.h index 0a9ed60e36..3888f55b87 100644 --- a/libs/s25main/world/GameWorldBase.h +++ b/libs/s25main/world/GameWorldBase.h @@ -67,6 +67,8 @@ class GameWorldBase : public World GameInterface* gi; std::unique_ptr econHandler; std::unique_ptr tradePathCache; + /// Replay minor version when replaying. Set to current minor version for live games. + uint8_t replayMinorVersion_ = 255; public: GameWorldBase(std::vector players, const GlobalGameSettings& gameSettings, EventManager& em); @@ -162,6 +164,11 @@ class GameWorldBase : public World bool IsSinglePlayer() const; /// Return the game settings const GlobalGameSettings& GetGGS() const { return gameSettings; } + /// Get the replay minor version (used for backward compatibility) + uint8_t GetReplayMinorVersion() const { return replayMinorVersion_; } + /// Set the replay minor version (called when loading a replay) + void SetReplayMinorVersion(uint8_t version) { replayMinorVersion_ = version; } + EventManager& GetEvMgr() { return em; } const EventManager& GetEvMgr() const { return em; } SoundManager& GetSoundMgr() { return *soundManager; } diff --git a/tests/s25Main/autoplay/main.cpp b/tests/s25Main/autoplay/main.cpp index bc83245bd6..e6a98f1e87 100644 --- a/tests/s25Main/autoplay/main.cpp +++ b/tests/s25Main/autoplay/main.cpp @@ -92,6 +92,7 @@ static void playReplay(const boost::filesystem::path& replayPath, const bool isS gameWorld.GetPlayer(i).MakeStartPacts(); } + gameWorld.SetReplayMinorVersion(replay.GetMinorVersion()); gameWorld.InitAfterLoad(); bool endOfReplay = false;