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/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ void GlobalGameSettings::registerAllAddons()
AddonWine,
AddonLeather,
AddonNoArmorDefault,
AddonArmorCapturedBld
AddonArmorCapturedBld,
AddonForesterFarmFieldAvoidance
>;
// clang-format on
using namespace boost::mp11;
Expand Down
18 changes: 18 additions & 0 deletions libs/s25main/addons/AddonForesterFarmFieldAvoidance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "AddonBool.h"
#include "mygettext/mygettext.h"

class AddonForesterFarmFieldAvoidance : public AddonBool
{
public:
AddonForesterFarmFieldAvoidance()
: AddonBool(AddonId::FORESTER_FARM_FIELD_AVOIDANCE, AddonGroup::GamePlay | AddonGroup::Economy,
_("Foresters avoid farm field spots"),
_("Prevents foresters from planting trees on spots that own farms could use for new fields."))
{}
};
1 change: 1 addition & 0 deletions libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
#include "addons/AddonAutoFlags.h"

#include "addons/AddonArmorCapturedBld.h"
#include "addons/AddonForesterFarmFieldAvoidance.h"
#include "addons/AddonLeather.h"
#include "addons/AddonNoArmorDefault.h"
#include "addons/AddonWine.h"
5 changes: 4 additions & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
// 00E Jonathan
// 00F Jarno
// 010 aztimh
// 011 DevOpsOfChaos

// Do not forget to add your Addon to GlobalGameSettings::registerAllAddons @ GlobalGameSettings.cpp!
// Never use a number twice!
Expand Down Expand Up @@ -75,7 +76,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x
AUTOFLAGS = 0x00F00000,

WINE = 0x01000000, LEATHER = 0x01000001, NO_ARMOR_DEFAULT = 0x01000002,
ARMOR_CAPTURED_BLD = 0x01000003)
ARMOR_CAPTURED_BLD = 0x01000003,

FORESTER_FARM_FIELD_AVOIDANCE = 0x01100000)
//-V:AddonId:801

enum class AddonGroup : unsigned
Expand Down
59 changes: 31 additions & 28 deletions libs/s25main/figures/nofFarmer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,36 @@ unsigned short nofFarmer::GetCarryID() const
return 71;
}

nofFarmhand::PointQuality nofFarmer::GetNewFieldPointQuality(const GameWorld& world, const MapPoint pt)
{
// Nicht auf Straßen bauen!
for(const auto dir : helpers::EnumRange<Direction>{})
{
if(world.GetPointRoad(pt, dir) != PointRoad::None)
return PointQuality::NotPossible;
}

// Terrain untersuchen
if(!world.IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
return PointQuality::NotPossible;

// Ist Platz frei?
NodalObjectType noType = world.GetNO(pt)->GetType();
if(noType != NodalObjectType::Environment && noType != NodalObjectType::Nothing)
return PointQuality::NotPossible;

for(const MapPoint nb : world.GetNeighbours(pt))
{
// Nicht direkt neben andere Getreidefelder und Gebäude setzen!
noType = world.GetNO(nb)->GetType();
if(noType == NodalObjectType::Grainfield || noType == NodalObjectType::Grapefield
|| noType == NodalObjectType::Building || noType == NodalObjectType::Buildingsite)
return PointQuality::NotPossible;
}

return PointQuality::Class2;
}

/// Abgeleitete Klasse informieren, wenn sie anfängt zu arbeiten (Vorbereitungen)
void nofFarmer::WorkStarted()
{
Expand Down Expand Up @@ -123,34 +153,7 @@ nofFarmhand::PointQuality nofFarmer::GetPointQuality(const MapPoint pt, bool /*
}
// oder einen freien Platz, wo wir ein neues sähen können
else
{
// Nicht auf Straßen bauen!
for(const auto dir : helpers::EnumRange<Direction>{})
{
if(world->GetPointRoad(pt, dir) != PointRoad::None)
return PointQuality::NotPossible;
}

// Terrain untersuchen
if(!world->IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
return PointQuality::NotPossible;

// Ist Platz frei?
NodalObjectType noType = world->GetNO(pt)->GetType();
if(noType != NodalObjectType::Environment && noType != NodalObjectType::Nothing)
return PointQuality::NotPossible;

for(const MapPoint nb : world->GetNeighbours(pt))
{
// Nicht direkt neben andere Getreidefelder und Gebäude setzen!
noType = world->GetNO(nb)->GetType();
if(noType == NodalObjectType::Grainfield || noType == NodalObjectType::Grapefield
|| noType == NodalObjectType::Building || noType == NodalObjectType::Buildingsite)
return PointQuality::NotPossible;
}

return PointQuality::Class2;
}
return GetNewFieldPointQuality(*world, pt);
}

void nofFarmer::WorkAborted()
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/figures/nofFarmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "nofFarmhand.h"
class GameWorld;
class SerializedGameData;
class nobUsual;

Expand Down Expand Up @@ -34,6 +35,8 @@ class nofFarmer : public nofFarmhand
nofFarmer(MapPoint pos, unsigned char player, nobUsual* workplace);
nofFarmer(SerializedGameData& sgd, unsigned obj_id);

static nofFarmhand::PointQuality GetNewFieldPointQuality(const GameWorld& world, MapPoint pt);

void Serialize(SerializedGameData& sgd) const override;

GO_Type GetGOT() const final { return GO_Type::NofFarmer; }
Expand Down
33 changes: 18 additions & 15 deletions libs/s25main/figures/nofFarmhand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ nofFarmhand::nofFarmhand(SerializedGameData& sgd, const unsigned obj_id)
: nofBuildingWorker(sgd, obj_id), dest(sgd.PopMapPoint())
{}

unsigned nofFarmhand::GetWorkRadius(const Job job)
{
switch(job)
{
case Job::Carpenter: return 0;
case Job::Hunter:
case Job::Farmer:
case Job::Winegrower: return 2;
case Job::CharBurner: return 3;
case Job::Woodcutter:
case Job::Forester: return 6;
case Job::Fisher: return 7;
case Job::Stonemason: return 8;
default: throw std::logic_error("Invalid job");
}
}

void nofFarmhand::WalkedDerived()
{
switch(state)
Expand Down Expand Up @@ -59,21 +76,7 @@ void nofFarmhand::HandleDerivedEvent(const unsigned /*id*/)
{
// Start working after the initial wait period
// Work radius
const unsigned max_radius = [](Job job) {
switch(job)
{
case Job::Carpenter: return 0;
case Job::Hunter:
case Job::Farmer:
case Job::Winegrower: return 2;
case Job::CharBurner: return 3;
case Job::Woodcutter:
case Job::Forester: return 6;
case Job::Fisher: return 7;
case Job::Stonemason: return 8;
default: throw std::logic_error("Invalid job");
}
}(job_);
const unsigned max_radius = GetWorkRadius(job_);
// Number of additional radii in which points should be found
// I.e. 0 => Don't search for points further away than ones already found
const unsigned additionalRadiiToFind = [](Job job) {
Expand Down
2 changes: 2 additions & 0 deletions libs/s25main/figures/nofFarmhand.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class nofFarmhand : public nofBuildingWorker
nofFarmhand(Job job, MapPoint pos, unsigned char player, nobUsual* workplace);
nofFarmhand(SerializedGameData& sgd, unsigned obj_id);

static unsigned GetWorkRadius(Job job);

void Serialize(SerializedGameData& sgd) const override;

void HandleDerivedEvent(unsigned id) override;
Expand Down
27 changes: 27 additions & 0 deletions libs/s25main/figures/nofForester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,38 @@

#include "GameInterface.h"
#include "GamePlayer.h"
#include "GlobalGameSettings.h"
#include "Loader.h"
#include "SoundManager.h"
#include "addons/const_addons.h"
#include "buildings/noBaseBuilding.h"
#include "network/GameClient.h"
#include "nofFarmer.h"
#include "ogl/glArchivItem_Bitmap_Player.h"
#include "random/Random.h"
#include "world/GameWorld.h"
#include "nodeObjs/noTree.h"
#include <boost/container/static_vector.hpp>

namespace {
bool IsPotentialNewFieldForOwnFarm(GameWorld& world, const MapPoint pt, const unsigned char player)
Comment thread
DevOpsOfChaos marked this conversation as resolved.
{
if(nofFarmer::GetNewFieldPointQuality(world, pt) == nofFarmhand::PointQuality::NotPossible)
return false;

return world.CheckPointsInRadius(
pt, nofFarmhand::GetWorkRadius(Job::Farmer),
[&world, player](const MapPoint farmPt, unsigned) {
if(world.GetNO(farmPt)->GetType() != NodalObjectType::Building)
return false;

const auto* building = world.GetSpecObj<noBaseBuilding>(farmPt);
return building && building->GetPlayer() == player && building->GetBuildingType() == BuildingType::Farm;
},
false);
}
} // namespace

nofForester::nofForester(const MapPoint pos, const unsigned char player, nobUsual* workplace)
: nofFarmhand(Job::Forester, pos, player, workplace)
{}
Expand Down Expand Up @@ -110,6 +133,10 @@ nofFarmhand::PointQuality nofForester::GetPointQuality(const MapPoint pt, bool /
return PointQuality::NotPossible;
}

if(world->GetGGS().isEnabled(AddonId::FORESTER_FARM_FIELD_AVOIDANCE)
&& IsPotentialNewFieldForOwnFarm(*world, pt, player))
return PointQuality::NotPossible;

// Terrain untersuchen
if(world->IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
return PointQuality::Class1;
Expand Down
25 changes: 25 additions & 0 deletions tests/s25Main/integration/testFarmer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "buildings/nobUsual.h"
#include "factories/BuildingFactory.h"
#include "figures/nofFarmhand.h"
#include "figures/nofForester.h"
#include "worldFixtures/CreateEmptyWorld.h"
#include "worldFixtures/WorldFixture.h"
#include "worldFixtures/initGameRNG.hpp"
Expand Down Expand Up @@ -33,6 +34,30 @@ struct FarmerFixture : public WorldFixture<CreateEmptyWorld, 1>
}
};

BOOST_FIXTURE_TEST_CASE(ForesterAvoidsPotentialFarmFieldSpots, FarmerFixture)
{
initGameRNG();

const auto isPointAvailable = [](const nofFarmhand& worker, const MapPoint pt) {
return worker.GetPointQuality(pt) != nofFarmhand::PointQuality::NotPossible;
};

const MapPoint fieldPt = world.GetNeighbour2(farmPt, 0);
BOOST_TEST_REQUIRE(isPointAvailable(*farmer, fieldPt));

const MapPoint foresterPt = world.MakeMapPoint(world.GetPlayer(0).GetHQPos() - Position(5, 0));
auto* foresterBuilding = dynamic_cast<nobUsual*>(
BuildingFactory::CreateBuilding(world, BuildingType::Forester, foresterPt, 0, Nation::Romans));
BOOST_TEST_REQUIRE(foresterBuilding);

nofForester foresterWorker(world.GetNeighbour(foresterPt, Direction::SouthEast), 0, foresterBuilding);

BOOST_TEST_REQUIRE(isPointAvailable(foresterWorker, fieldPt));

ggs.setSelection(AddonId::FORESTER_FARM_FIELD_AVOIDANCE, 1);
BOOST_TEST_REQUIRE(!isPointAvailable(foresterWorker, fieldPt));
}

BOOST_FIXTURE_TEST_CASE(FarmFieldPlanting, FarmerFixture)
{
initGameRNG();
Expand Down
Loading