Skip to content
Merged
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
53 changes: 5 additions & 48 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 @@ -938,7 +939,7 @@ MapPoint AIPlayerJH::FindPositionForBuildingAround(BuildingType type, const MapP
case BuildingType::Hunter:
{
// check if there are any animals in range
if(HuntablesinRange(around, (2 << GetBldPlanner().GetNumBuildings(BuildingType::Hunter))))
if(HuntablesInRange(around, (2 << GetBldPlanner().GetNumBuildings(BuildingType::Hunter))))
foundPos = SimpleFindPosition(around, BUILDING_SIZE[type], searchRadius);
break;
}
Expand Down Expand Up @@ -1960,56 +1961,12 @@ unsigned AIPlayerJH::SoldierAvailable(int rank)
return freeSoldiers;
}

bool AIPlayerJH::HuntablesinRange(const MapPoint pt, unsigned min)
bool AIPlayerJH::HuntablesInRange(const MapPoint pt, unsigned min)
{
// check first if no other hunter(or hunter buildingsite) is nearby
// 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)
{
// 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<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(++huntablecount >= min)
return true;
}
}
}
}
}
return false;
return nofHunter::GetHuntableAnimalsInRange(gwb, pt).size() >= min;
}

void AIPlayerJH::InitStoreAndMilitarylists()
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/ai/aijh/AIPlayerJH.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class AIPlayerJH final : public AIPlayer
// check if there are free soldiers (in hq/storehouses)
unsigned SoldierAvailable(int rank = -1);

bool HuntablesinRange(MapPoint pt, unsigned min);
bool HuntablesInRange(MapPoint pt, unsigned min);

bool ValidTreeinRange(MapPoint pt);

Expand Down
68 changes: 45 additions & 23 deletions libs/s25main/figures/nofHunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,37 +129,59 @@ 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));
};

// Liste mit den gefundenen Tieren
return world.GetPointsInRadius(pos, radius, pointToAnimal, canAnimalBeUsed, true);
}

std::vector<noAnimal*> nofHunter::GetHuntableAnimalsInRange(const GameWorldBase& world, MapPoint pos)
{
return GetAnimalsInRange(world, pos, ANIMAL_RADIUS, MAX_HUNTING_DISTANCE,
[](const noAnimal* a) { return a->CanHunted(); });
}

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->GetReplayCompatVersion() >= 4)
available_animals = GetHuntableAnimalsInRange(*world, pos);
else
{
for(curPos.x = pos.x - SQUARE_SIZE; curPos.x <= pos.x + SQUARE_SIZE; ++curPos.x)
// 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
7 changes: 7 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,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<noAnimal*> GetAnimalsInRange(const GameWorldBase& world, MapPoint pos, unsigned radius,
unsigned maxDistance, bool (*isValidAnimal)(const noAnimal*));
static std::vector<noAnimal*> GetHuntableAnimalsInRange(const GameWorldBase& world, MapPoint pos);

/// 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 @@ -43,6 +43,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 @@ -343,6 +343,8 @@ void GameClient::StartGame(const unsigned random_init)
const bool fixFish = !GetReplay() || GetReplay()->GetMinorVersion() >= 3;
MapLoader::SetupResources(gameWorld, fixFish);
}
if(replayMode && replayinfo)
gameWorld.SetReplayCompatVersion(replayinfo->replay.GetMinorVersion());
gameWorld.InitAfterLoad();

// Update visual settings
Expand Down
8 changes: 8 additions & 0 deletions libs/s25main/world/GameWorldBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "postSystem/PostManager.h"
#include "world/World.h"
#include "s25util/span.hpp"
#include <limits>
#include <memory>
#include <set>
#include <vector>
Expand Down Expand Up @@ -68,6 +69,8 @@ class GameWorldBase : public World
/// Interface zum GUI
GameInterface* gi;
std::unique_ptr<EconomyModeHandler> econHandler;
/// Replay minor version for backwards compatibility. Default is highest possible, i.e. current behavior.
uint8_t replayCompatVersion_ = std::numeric_limits<uint8_t>::max();

public:
GameWorldBase(std::vector<GamePlayer> players, const GlobalGameSettings& gameSettings, EventManager& em);
Expand Down Expand Up @@ -164,6 +167,11 @@ class GameWorldBase : public World
bool IsSinglePlayer() const;
/// Return the game settings
const GlobalGameSettings& GetGGS() const { return gameSettings; }
/// Get the replay (minor) version with which (backward) compatibility is enabled.
uint8_t GetReplayCompatVersion() const { return replayCompatVersion_; }
/// Set the replay minor version to change the behavior of the game for backward compatibility.
void SetReplayCompatVersion(uint8_t version) { replayCompatVersion_ = 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
player.MakeStartPacts();
}

gameWorld.SetReplayCompatVersion(replay.GetMinorVersion());
gameWorld.InitAfterLoad();

bool endOfReplay = false;
Expand Down
Loading