Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion libs/s25main/Replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 44 additions & 37 deletions libs/s25main/ai/aijh/AIPlayerJH.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1704,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
{
Expand Down Expand Up @@ -1965,51 +1966,57 @@ 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)
constexpr unsigned maxrange = 25;

if(gwb.GetReplayMinorVersion() >= 4)
{
for(p2.x = fx; p2.x <= lx; ++p2.x)
{
// Search for animals
for(const noBase& fig : gwb.GetFigures(p2))
const auto available_animals =
nofHunter::GetAnimalsInRange(gwb, pt, ANIMAL_RADIUS, maxrange, [](const noAnimal* a) { return a->CanHunted(); });

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)
{
if(fig.GetType() == NodalObjectType::Animal)
for(const noBase& fig : gwb.GetFigures(p2))
{
// Ist das Tier überhaupt zum Jagen geeignet?
if(!static_cast<const noAnimal&>(fig).CanHunted())
continue;
// Und komme ich hin?
if(gwb.FindHumanPath(pt, static_cast<const noAnimal&>(fig).GetPos(), maxrange))
// Dann nehmen wir es
if(fig.GetType() == NodalObjectType::Animal)
{
if(++huntablecount >= min)
return true;
if(!static_cast<const noAnimal&>(fig).CanHunted())
continue;
if(gwb.FindHumanPath(pt, static_cast<const noAnimal&>(fig).GetPos(), maxrange))
{
if(++huntablecount >= min)
return true;
}
}
}
}
}
return false;
}
return false;
}

void AIPlayerJH::InitStoreAndMilitarylists()
Expand Down
64 changes: 41 additions & 23 deletions libs/s25main/figures/nofHunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,37 +129,55 @@ void nofHunter::HandleDerivedEvent(unsigned /*id*/)
}
}

void nofHunter::TryStartHunting()
std::vector<noAnimal*> nofHunter::GetAnimalsInRange(const GameWorldBase& world, const MapPoint pos, unsigned radius,
unsigned maxDistance, bool (*isValidAnimal)(const noAnimal*))
{
// Find animals in a square around building (actually should be circle, but animals are moving anyway)
Comment thread
Flamefire marked this conversation as resolved.
const int SQUARE_SIZE = 19;
const auto pointToAnimal = [&world](const MapPoint pt, unsigned) -> noAnimal* {
for(auto& figure : world.GetFigures(pt))
{
if(figure.GetType() == NodalObjectType::Animal)
return static_cast<noAnimal*>(&figure);
}
return nullptr;
};

const auto canAnimalBeUsed = [pos, maxDistance, &world, isValidAnimal](const noAnimal* const animal) {
return animal && isValidAnimal(animal)
&& (pos == animal->GetPos() || world.FindHumanPath(pos, animal->GetPos(), maxDistance));
};

return world.GetPointsInRadius(pos, radius, pointToAnimal, canAnimalBeUsed, true);
}

// Liste mit den gefundenen Tieren
void nofHunter::TryStartHunting()
{
std::vector<noAnimal*> available_animals;

// Durchgehen und nach Tieren suchen
Position curPos;
for(curPos.y = pos.y - SQUARE_SIZE; curPos.y <= pos.y + SQUARE_SIZE; ++curPos.y)
if(world->GetReplayMinorVersion() >= 4)
{
for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x)
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)
{
MapPoint curMapPos = world->MakeMapPoint(curPos);

// nach Tieren suchen
for(auto& figure : world->GetFigures(curMapPos))
for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x)
{
if(figure.GetType() != NodalObjectType::Animal)
continue;
// Ist das Tier überhaupt zum Jagen geeignet?
auto& animal = static_cast<noAnimal&>(figure);
if(!animal.CanHunted())
continue;

// Und komme ich hin?
if(pos == animal.GetPos() || world->FindHumanPath(pos, animal.GetPos(), MAX_HUNTING_DISTANCE))
MapPoint curMapPos = world->MakeMapPoint(curPos);
for(auto& figure : world->GetFigures(curMapPos))
{
// Dann nehmen wir es
available_animals.push_back(&animal);
if(figure.GetType() != NodalObjectType::Animal)
continue;
auto& animal = static_cast<noAnimal&>(figure);
if(!animal.CanHunted())
continue;
if(pos == animal.GetPos() || world->FindHumanPath(pos, animal.GetPos(), MAX_HUNTING_DISTANCE))
{
available_animals.push_back(&animal);
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions libs/s25main/figures/nofHunter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

#include "nofBuildingWorker.h"
#include "gameTypes/Direction.h"
#include <vector>

class GameWorldBase;
class noAnimal;
class SerializedGameData;
class nobUsual;
Expand Down Expand Up @@ -64,6 +66,10 @@ class nofHunter : public nofBuildingWorker

void TryStartHunting();

/// Find animals in a radius around a position that satisfy a predicate and are reachable
static std::vector<noAnimal*> 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.Ä.)
void AnimalLost();
/// wird aufgerufen, wenn die Arbeit abgebrochen wird (von nofBuildingWorker aufgerufen)
Expand Down
22 changes: 4 additions & 18 deletions libs/s25main/figures/nofSkinner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
#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 "gameData/GameConsts.h"
#include "gameData/JobConsts.h"

using namespace leatheraddon;
Expand Down Expand Up @@ -187,24 +189,8 @@ 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))
{
if(figure.GetType() != NodalObjectType::Animal)
continue;
return checkedCast<noAnimal*>(&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);
const auto available_animals = nofHunter::GetAnimalsInRange(
*world, pos, ANIMAL_RADIUS, MAX_SKINNING_DISTANCE, [](const noAnimal* a) { return a->CanBeSkinned(); });

if(!available_animals.empty())
{
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/gameData/GameConsts.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ constexpr auto gfs_to_duration(const unsigned gfs)
/// Reichweite der Bergarbeiter
constexpr unsigned MINER_RADIUS = 2;

/// Search radius for animals (hunter, skinner, AI)
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;
Expand Down
2 changes: 2 additions & 0 deletions libs/s25main/network/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions libs/s25main/world/GameWorldBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class GameWorldBase : public World
GameInterface* gi;
std::unique_ptr<EconomyModeHandler> econHandler;
std::unique_ptr<TradePathCache> tradePathCache;
/// Replay minor version when replaying. Set to current minor version for live games.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set to current minor version for live games.

Where?

uint8_t replayMinorVersion_ = 255;

public:
GameWorldBase(std::vector<GamePlayer> players, const GlobalGameSettings& gameSettings, EventManager& em);
Expand Down Expand Up @@ -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; }
Expand Down
1 change: 1 addition & 0 deletions tests/s25Main/autoplay/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ static void playReplay(const boost::filesystem::path& replayPath, const bool isS
gameWorld.GetPlayer(i).MakeStartPacts();
}

gameWorld.SetReplayMinorVersion(replay.GetMinorVersion());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here.

Replay is not accessible from hunter AI so that's why it's mirrored to GameWorldBase. Not ideal is it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the "live game" part. It is only ever set for replays.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah for live games it's default initialized to 255...

gameWorld.InitAfterLoad();

bool endOfReplay = false;
Expand Down