File: BuildingManager.cpp

package info (click to toggle)
vcmi 1.6.5%2Bdfsg-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid, trixie
  • size: 32,060 kB
  • sloc: cpp: 238,971; python: 265; sh: 224; xml: 157; ansic: 78; objc: 61; makefile: 49
file content (257 lines) | stat: -rw-r--r-- 9,178 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*
* BuildingManager.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/

#include "StdInc.h"
#include "BuildingManager.h"

#include "../../CCallback.h"
#include "../../lib/mapObjects/MapObjects.h"
#include "../../lib/entities/building/CBuilding.h"

bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
{
	if (maxDays == 0)
	{
		logAi->warn("Request to build building %d in 0 days!", building.toEnum());
		return false;
	}

	if (!vstd::contains(t->getTown()->buildings, building))
		return false; // no such building in town

	if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
		return true;

	const CBuilding * buildPtr = t->getTown()->buildings.at(building);

	auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
	{
		return t->hasBuilt(buildID);
	});
	toBuild.push_back(building);

	for (BuildingID buildID : toBuild)
	{
		EBuildingState canBuild = cb->canBuildStructure(t, buildID);
		if (canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
			return false; //we won't be able to build this
	}

	if (maxDays && toBuild.size() > maxDays)
		return false;

	//TODO: calculate if we have enough resources to build it in maxDays?

	for (const auto & buildID : toBuild)
	{
		const CBuilding * b = t->getTown()->buildings.at(buildID);

		EBuildingState canBuild = cb->canBuildStructure(t, buildID);
		if (canBuild == EBuildingState::ALLOWED)
		{
			PotentialBuilding pb;
			pb.bid = buildID;
			pb.price = t->getBuildingCost(buildID);
			immediateBuildings.push_back(pb); //these are checked again in try
			return true;
		}
		else if (canBuild == EBuildingState::PREREQUIRES)
		{
			// can happen when dependencies have their own missing dependencies
			if (tryBuildThisStructure(t, buildID, maxDays - 1))
				return true;
		}
		else if (canBuild == EBuildingState::MISSING_BASE)
		{
			if (tryBuildThisStructure(t, b->upgrade, maxDays - 1))
				return true;
		}
		else if (canBuild == EBuildingState::NO_RESOURCES)
		{
			//we may need to gather resources for those
			PotentialBuilding pb;
			pb.bid = buildID;
			pb.price = t->getBuildingCost(buildID);
			expensiveBuildings.push_back(pb); //these are checked again in try
			return false;
		}
		else
			return false;
	}
	return false;
}

bool BuildingManager::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
	for (const auto & building : buildList)
	{
		if (t->hasBuilt(building))
			continue;
		return tryBuildThisStructure(t, building, maxDays);

	}
	return false; //Can't build anything
}

std::optional<BuildingID> BuildingManager::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
{
	for (const auto & building : buildList)
	{
		if (t->hasBuilt(building))
			continue;
		switch (cb->canBuildStructure(t, building))
		{
			case EBuildingState::ALLOWED:
			case EBuildingState::NO_RESOURCES: //TODO: allow this via optional parameter?
				return std::optional<BuildingID>(building);
				break;
		}
	}
	return std::optional<BuildingID>(); //Can't build anything
}

bool BuildingManager::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
	for (const auto & building : buildList)
	{
		if (t->hasBuilt(building))
			continue;
		return tryBuildThisStructure(t, building, maxDays);
	}
	return false; //Nothing to build
}

void BuildingManager::init(CPlayerSpecificInfoCallback * CB)
{
	cb = CB;
}

void BuildingManager::setAI(VCAI * AI)
{
	ai = AI;
}
//Set of buildings for different goals. Does not include any prerequisites.
static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
static const std::vector<BuildingID> defence = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE };
static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 };
static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP };
static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
static const std::vector<BuildingID> extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2,
BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings

bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
{
	//TODO make *real* town development system
	//TODO: faction-specific development: use special buildings, build dwellings in better order, etc
	//TODO: build resource silo, defences when needed
	//Possible - allow "locking" on specific building (build prerequisites and then building itself)
	
	//TODO: There is some disabled building code in GatherTroops and GatherArmy - take it into account when enhancing building. For now AI works best with building only via Build goal.

	immediateBuildings.clear();
	expensiveBuildings.clear();

	//below algorithm focuses on economy growth at start of the game, saving money instead of build rushing is handled by Build goal
	//changing code blocks order will alter behavior by changing order of adding elements to immediateBuildings / expensiveBuildings

	// TResources currentRes = cb->getResourceAmount();
	// TResources currentIncome = t->dailyIncome();

	if(tryBuildAnyStructure(t, essential))
		return true;

	if (cb->getDate(Date::DAY_OF_WEEK) < 5) // first 4 days of week - try to focus on dwellings
	{
		if (tryBuildNextStructure(t, unitsSource, 4))
			return true;
	}

	if (cb->getDate(Date::DAY_OF_WEEK) > 4) // last 3 days of week - try to focus on growth by building Fort/Citadel/Castle
	{
		if (tryBuildNextStructure(t, defence, 3))
			return true;
	}

	if (t->hasBuilt(BuildingID::CASTLE))
	{
		if (tryBuildAnyStructure(t, unitGrowth))
			return true;
	}

	//try to make City Hall
	if (tryBuildNextStructure(t, basicGoldSource))
		return true;

	//workaround for mantis #2696 - build capitol with separate algorithm if it is available
	if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
	{
		if(tryBuildNextStructure(t, capitolAndRequirements))
			return true;
	}

	//try to upgrade dwelling
	for (int i = 0; i < unitsUpgrade.size(); i++)
	{
		if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]) && t->hasBuilt(BuildingID::FORT))
		{
			if (tryBuildThisStructure(t, unitsUpgrade[i]))
				return true;
		}
	}

	//remaining tasks
	if (tryBuildNextStructure(t, _spells))
		return true;
	if (tryBuildAnyStructure(t, extra))
		return true;

	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
	std::vector<BuildingID> extraBuildings;
	for (auto buildingInfo : t->getTown()->buildings)
	{
		if (buildingInfo.first.IsDwelling() && BuildingID::getUpgradedFromDwelling(buildingInfo.first) > 1)
			extraBuildings.push_back(buildingInfo.first);
	}
	return tryBuildAnyStructure(t, extraBuildings);
}

BuildingID BuildingManager::getMaxPossibleGoldBuilding(const CGTownInstance * t)
{
	if(cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
		return BuildingID::CAPITOL;
	else if(cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
		return BuildingID::CITY_HALL;
	else if(cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN)
		return BuildingID::TOWN_HALL;
	else
		return BuildingID::VILLAGE_HALL;
}

std::optional<PotentialBuilding> BuildingManager::immediateBuilding() const
{
	if (immediateBuildings.size())
		return std::optional<PotentialBuilding>(immediateBuildings.front()); //back? whatever
	else
		return std::optional<PotentialBuilding>();
}

std::optional<PotentialBuilding> BuildingManager::expensiveBuilding() const
{
	if (expensiveBuildings.size())
		return std::optional<PotentialBuilding>(expensiveBuildings.front());
	else
		return std::optional<PotentialBuilding>();
}