Skip to content

Commit 57b0829

Browse files
authored
Merge pull request #1937 from DevOpsOfChaos/sidequest/foresters-avoid-farm-fields
Keep foresters off potential farm fields
2 parents 4ec1f62 + d0e8b11 commit 57b0829

10 files changed

Lines changed: 131 additions & 45 deletions

File tree

libs/s25main/GlobalGameSettings.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ void GlobalGameSettings::registerAllAddons()
104104
AddonWine,
105105
AddonLeather,
106106
AddonNoArmorDefault,
107-
AddonArmorCapturedBld
107+
AddonArmorCapturedBld,
108+
AddonForesterFarmFieldAvoidance
108109
>;
109110
// clang-format on
110111
using namespace boost::mp11;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later
4+
5+
#pragma once
6+
7+
#include "AddonBool.h"
8+
#include "mygettext/mygettext.h"
9+
10+
class AddonForesterFarmFieldAvoidance : public AddonBool
11+
{
12+
public:
13+
AddonForesterFarmFieldAvoidance()
14+
: AddonBool(AddonId::FORESTER_FARM_FIELD_AVOIDANCE, AddonGroup::GamePlay | AddonGroup::Economy,
15+
_("Foresters avoid farm field spots"),
16+
_("Prevents foresters from planting trees on spots that own farms could use for new fields."))
17+
{}
18+
};

libs/s25main/addons/Addons.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
#include "addons/AddonAutoFlags.h"
6363

6464
#include "addons/AddonArmorCapturedBld.h"
65+
#include "addons/AddonForesterFarmFieldAvoidance.h"
6566
#include "addons/AddonLeather.h"
6667
#include "addons/AddonNoArmorDefault.h"
6768
#include "addons/AddonWine.h"

libs/s25main/addons/const_addons.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
// 00E Jonathan
2727
// 00F Jarno
2828
// 010 aztimh
29+
// 011 DevOpsOfChaos
2930

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

7778
WINE = 0x01000000, LEATHER = 0x01000001, NO_ARMOR_DEFAULT = 0x01000002,
78-
ARMOR_CAPTURED_BLD = 0x01000003)
79+
ARMOR_CAPTURED_BLD = 0x01000003,
80+
81+
FORESTER_FARM_FIELD_AVOIDANCE = 0x01100000)
7982
//-V:AddonId:801
8083

8184
enum class AddonGroup : unsigned

libs/s25main/figures/nofFarmer.cpp

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ unsigned short nofFarmer::GetCarryID() const
5757
return 71;
5858
}
5959

60+
nofFarmhand::PointQuality nofFarmer::GetNewFieldPointQuality(const GameWorld& world, const MapPoint pt)
61+
{
62+
// Nicht auf Straßen bauen!
63+
for(const auto dir : helpers::EnumRange<Direction>{})
64+
{
65+
if(world.GetPointRoad(pt, dir) != PointRoad::None)
66+
return PointQuality::NotPossible;
67+
}
68+
69+
// Terrain untersuchen
70+
if(!world.IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
71+
return PointQuality::NotPossible;
72+
73+
// Ist Platz frei?
74+
NodalObjectType noType = world.GetNO(pt)->GetType();
75+
if(noType != NodalObjectType::Environment && noType != NodalObjectType::Nothing)
76+
return PointQuality::NotPossible;
77+
78+
for(const MapPoint nb : world.GetNeighbours(pt))
79+
{
80+
// Nicht direkt neben andere Getreidefelder und Gebäude setzen!
81+
noType = world.GetNO(nb)->GetType();
82+
if(noType == NodalObjectType::Grainfield || noType == NodalObjectType::Grapefield
83+
|| noType == NodalObjectType::Building || noType == NodalObjectType::Buildingsite)
84+
return PointQuality::NotPossible;
85+
}
86+
87+
return PointQuality::Class2;
88+
}
89+
6090
/// Abgeleitete Klasse informieren, wenn sie anfängt zu arbeiten (Vorbereitungen)
6191
void nofFarmer::WorkStarted()
6292
{
@@ -123,34 +153,7 @@ nofFarmhand::PointQuality nofFarmer::GetPointQuality(const MapPoint pt, bool /*
123153
}
124154
// oder einen freien Platz, wo wir ein neues sähen können
125155
else
126-
{
127-
// Nicht auf Straßen bauen!
128-
for(const auto dir : helpers::EnumRange<Direction>{})
129-
{
130-
if(world->GetPointRoad(pt, dir) != PointRoad::None)
131-
return PointQuality::NotPossible;
132-
}
133-
134-
// Terrain untersuchen
135-
if(!world->IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
136-
return PointQuality::NotPossible;
137-
138-
// Ist Platz frei?
139-
NodalObjectType noType = world->GetNO(pt)->GetType();
140-
if(noType != NodalObjectType::Environment && noType != NodalObjectType::Nothing)
141-
return PointQuality::NotPossible;
142-
143-
for(const MapPoint nb : world->GetNeighbours(pt))
144-
{
145-
// Nicht direkt neben andere Getreidefelder und Gebäude setzen!
146-
noType = world->GetNO(nb)->GetType();
147-
if(noType == NodalObjectType::Grainfield || noType == NodalObjectType::Grapefield
148-
|| noType == NodalObjectType::Building || noType == NodalObjectType::Buildingsite)
149-
return PointQuality::NotPossible;
150-
}
151-
152-
return PointQuality::Class2;
153-
}
156+
return GetNewFieldPointQuality(*world, pt);
154157
}
155158

156159
void nofFarmer::WorkAborted()

libs/s25main/figures/nofFarmer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include "nofFarmhand.h"
8+
class GameWorld;
89
class SerializedGameData;
910
class nobUsual;
1011

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

38+
static nofFarmhand::PointQuality GetNewFieldPointQuality(const GameWorld& world, MapPoint pt);
39+
3740
void Serialize(SerializedGameData& sgd) const override;
3841

3942
GO_Type GetGOT() const final { return GO_Type::NofFarmer; }

libs/s25main/figures/nofFarmhand.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ nofFarmhand::nofFarmhand(SerializedGameData& sgd, const unsigned obj_id)
2727
: nofBuildingWorker(sgd, obj_id), dest(sgd.PopMapPoint())
2828
{}
2929

30+
unsigned nofFarmhand::GetWorkRadius(const Job job)
31+
{
32+
switch(job)
33+
{
34+
case Job::Carpenter: return 0;
35+
case Job::Hunter:
36+
case Job::Farmer:
37+
case Job::Winegrower: return 2;
38+
case Job::CharBurner: return 3;
39+
case Job::Woodcutter:
40+
case Job::Forester: return 6;
41+
case Job::Fisher: return 7;
42+
case Job::Stonemason: return 8;
43+
default: throw std::logic_error("Invalid job");
44+
}
45+
}
46+
3047
void nofFarmhand::WalkedDerived()
3148
{
3249
switch(state)
@@ -59,21 +76,7 @@ void nofFarmhand::HandleDerivedEvent(const unsigned /*id*/)
5976
{
6077
// Start working after the initial wait period
6178
// Work radius
62-
const unsigned max_radius = [](Job job) {
63-
switch(job)
64-
{
65-
case Job::Carpenter: return 0;
66-
case Job::Hunter:
67-
case Job::Farmer:
68-
case Job::Winegrower: return 2;
69-
case Job::CharBurner: return 3;
70-
case Job::Woodcutter:
71-
case Job::Forester: return 6;
72-
case Job::Fisher: return 7;
73-
case Job::Stonemason: return 8;
74-
default: throw std::logic_error("Invalid job");
75-
}
76-
}(job_);
79+
const unsigned max_radius = GetWorkRadius(job_);
7780
// Number of additional radii in which points should be found
7881
// I.e. 0 => Don't search for points further away than ones already found
7982
const unsigned additionalRadiiToFind = [](Job job) {

libs/s25main/figures/nofFarmhand.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class nofFarmhand : public nofBuildingWorker
5454
nofFarmhand(Job job, MapPoint pos, unsigned char player, nobUsual* workplace);
5555
nofFarmhand(SerializedGameData& sgd, unsigned obj_id);
5656

57+
static unsigned GetWorkRadius(Job job);
58+
5759
void Serialize(SerializedGameData& sgd) const override;
5860

5961
void HandleDerivedEvent(unsigned id) override;

libs/s25main/figures/nofForester.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,38 @@
66

77
#include "GameInterface.h"
88
#include "GamePlayer.h"
9+
#include "GlobalGameSettings.h"
910
#include "Loader.h"
1011
#include "SoundManager.h"
12+
#include "addons/const_addons.h"
13+
#include "buildings/noBaseBuilding.h"
1114
#include "network/GameClient.h"
15+
#include "nofFarmer.h"
1216
#include "ogl/glArchivItem_Bitmap_Player.h"
1317
#include "random/Random.h"
1418
#include "world/GameWorld.h"
1519
#include "nodeObjs/noTree.h"
1620
#include <boost/container/static_vector.hpp>
1721

22+
namespace {
23+
bool IsPotentialNewFieldForOwnFarm(GameWorld& world, const MapPoint pt, const unsigned char player)
24+
{
25+
if(nofFarmer::GetNewFieldPointQuality(world, pt) == nofFarmhand::PointQuality::NotPossible)
26+
return false;
27+
28+
return world.CheckPointsInRadius(
29+
pt, nofFarmhand::GetWorkRadius(Job::Farmer),
30+
[&world, player](const MapPoint farmPt, unsigned) {
31+
if(world.GetNO(farmPt)->GetType() != NodalObjectType::Building)
32+
return false;
33+
34+
const auto* building = world.GetSpecObj<noBaseBuilding>(farmPt);
35+
return building && building->GetPlayer() == player && building->GetBuildingType() == BuildingType::Farm;
36+
},
37+
false);
38+
}
39+
} // namespace
40+
1841
nofForester::nofForester(const MapPoint pos, const unsigned char player, nobUsual* workplace)
1942
: nofFarmhand(Job::Forester, pos, player, workplace)
2043
{}
@@ -110,6 +133,10 @@ nofFarmhand::PointQuality nofForester::GetPointQuality(const MapPoint pt, bool /
110133
return PointQuality::NotPossible;
111134
}
112135

136+
if(world->GetGGS().isEnabled(AddonId::FORESTER_FARM_FIELD_AVOIDANCE)
137+
&& IsPotentialNewFieldForOwnFarm(*world, pt, player))
138+
return PointQuality::NotPossible;
139+
113140
// Terrain untersuchen
114141
if(world->IsOfTerrain(pt, [](const auto& desc) { return desc.IsVital(); }))
115142
return PointQuality::Class1;

tests/s25Main/integration/testFarmer.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "buildings/nobUsual.h"
77
#include "factories/BuildingFactory.h"
88
#include "figures/nofFarmhand.h"
9+
#include "figures/nofForester.h"
910
#include "worldFixtures/CreateEmptyWorld.h"
1011
#include "worldFixtures/WorldFixture.h"
1112
#include "worldFixtures/initGameRNG.hpp"
@@ -33,6 +34,30 @@ struct FarmerFixture : public WorldFixture<CreateEmptyWorld, 1>
3334
}
3435
};
3536

37+
BOOST_FIXTURE_TEST_CASE(ForesterAvoidsPotentialFarmFieldSpots, FarmerFixture)
38+
{
39+
initGameRNG();
40+
41+
const auto isPointAvailable = [](const nofFarmhand& worker, const MapPoint pt) {
42+
return worker.GetPointQuality(pt) != nofFarmhand::PointQuality::NotPossible;
43+
};
44+
45+
const MapPoint fieldPt = world.GetNeighbour2(farmPt, 0);
46+
BOOST_TEST_REQUIRE(isPointAvailable(*farmer, fieldPt));
47+
48+
const MapPoint foresterPt = world.MakeMapPoint(world.GetPlayer(0).GetHQPos() - Position(5, 0));
49+
auto* foresterBuilding = dynamic_cast<nobUsual*>(
50+
BuildingFactory::CreateBuilding(world, BuildingType::Forester, foresterPt, 0, Nation::Romans));
51+
BOOST_TEST_REQUIRE(foresterBuilding);
52+
53+
nofForester foresterWorker(world.GetNeighbour(foresterPt, Direction::SouthEast), 0, foresterBuilding);
54+
55+
BOOST_TEST_REQUIRE(isPointAvailable(foresterWorker, fieldPt));
56+
57+
ggs.setSelection(AddonId::FORESTER_FARM_FIELD_AVOIDANCE, 1);
58+
BOOST_TEST_REQUIRE(!isPointAvailable(foresterWorker, fieldPt));
59+
}
60+
3661
BOOST_FIXTURE_TEST_CASE(FarmFieldPlanting, FarmerFixture)
3762
{
3863
initGameRNG();

0 commit comments

Comments
 (0)