File: ResourceManager.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 (345 lines) | stat: -rw-r--r-- 9,222 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*
* ResourceManager.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 "ResourceManager.h"
#include "Goals/Goals.h"

#include "../../CCallback.h"
#include "../../lib/mapObjects/MapObjects.h"

ResourceObjective::ResourceObjective(const TResources & Res, Goals::TSubgoal Goal)
	: resources(Res), goal(Goal)
{
}

bool ResourceObjective::operator<(const ResourceObjective & ro) const
{
	return goal->priority < ro.goal->priority;
}

ResourceManager::ResourceManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
	: ai(AI), cb(CB)
{
}

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

void ResourceManager::setAI(VCAI * AI)
{
	ai = AI;
}

bool ResourceManager::canAfford(const TResources & cost) const
{
	return freeResources().canAfford(cost);
}

TResources ResourceManager::estimateIncome() const
{
	TResources ret;
	for (const CGTownInstance * t : cb->getTownsInfo())
	{
		ret += t->dailyIncome();
	}

	for (const CGObjectInstance * obj : ai->getFlaggedObjects())
	{
		if (obj->ID == Obj::MINE)
		{
			auto mine = dynamic_cast<const CGMine*>(obj);
			ret += mine->dailyIncome();
		}
	}

	return ret;
}

void ResourceManager::reserveResources(const TResources & res, Goals::TSubgoal goal)
{
	if (!goal->invalid())
		tryPush(ResourceObjective(res, goal));
	else
		logAi->warn("Attempt to reserve resources for Invalid goal");
}

Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o) const
{
	auto allResources = cb->getResourceAmount();
	auto income = estimateIncome();
	GameResID resourceType = EGameResID::NONE;
	TResource amountToCollect = 0;

	using resPair = std::pair<GameResID, TResource>;
	std::map<GameResID, TResource> missingResources;

	//TODO: unit test for complex resource sets

	//sum missing resources of given type for ALL reserved objectives
	for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
	{
		//choose specific resources we need for this goal (not 0)
		for (auto r = ResourceSet::nziterator(o.resources); r.valid(); r++)
			missingResources[r->resType] += it->resources[r->resType]; //goal it costs r units of resType
	}
	for (auto it = ResourceSet::nziterator(o.resources); it.valid(); it++)
	{
		missingResources[it->resType] -= allResources[it->resType]; //missing = (what we need) - (what we have)
		vstd::amax(missingResources[it->resType], 0); // if we have more resources than reserved, we don't need them
	}
	vstd::erase_if(missingResources, [=](const resPair & p) -> bool
	{
		return !(p.second); //in case evaluated to 0 or less
	});
	if (missingResources.empty()) //FIXME: should be unit-tested out
	{
		logAi->error("We don't need to collect resources %s for goal %s", o.resources.toString(), o.goal->name());
		return o.goal;
	}

	for (const resPair p : missingResources)
	{
		if (!income[p.first]) //prioritize resources with 0 income
		{
			resourceType = p.first;
			amountToCollect = p.second;
			break;
		}
	}
	if (resourceType == EGameResID::NONE) //no needed resources has 0 income,
	{
		//find the one which takes longest to collect
		using timePair = std::pair<GameResID, float>;
		std::map<GameResID, float> daysToEarn;
		for (auto it : missingResources)
			daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first];
		auto incomeComparer = [](const timePair & lhs, const timePair & rhs) -> bool
		{
			//theoretically income can be negative, but that falls into this comparison
			return lhs.second < rhs.second;
		};

		resourceType = boost::max_element(daysToEarn, incomeComparer)->first;
		amountToCollect = missingResources[resourceType];
	}

	//this is abstract goal and might take some time to complete
	return Goals::sptr(Goals::CollectRes(resourceType, amountToCollect).setisAbstract(true));
}

Goals::TSubgoal ResourceManager::whatToDo() const //suggest any goal
{
	if (queue.size())
	{
		auto o = queue.top();

		auto allResources = cb->getResourceAmount(); //we don't consider savings, it's out top-priority goal
		if (allResources.canAfford(o.resources))
			return o.goal;
		else //we can't afford even top-priority goal, need to collect resources
			return collectResourcesForOurGoal(o);
	}
	else
		return Goals::sptr(Goals::Invalid()); //nothing else to do
}

Goals::TSubgoal ResourceManager::whatToDo(TResources &res, Goals::TSubgoal goal)
{
	logAi->trace("ResourceManager: checking goal %s which requires resources %s", goal->name(), res.toString());

	TResources accumulatedResources;
	auto allResources = cb->getResourceAmount();

	ResourceObjective ro(res, goal);
	tryPush(ro);
	//check if we can afford all the objectives with higher priority first
	for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
	{
		accumulatedResources += it->resources;

		logAi->trace(
			"ResourceManager: checking goal %s, accumulatedResources=%s, available=%s",
			it->goal->name(),
			accumulatedResources.toString(),
			allResources.toString());

		if(!accumulatedResources.canBeAfforded(allResources))
		{
			//can't afford
			break;
		}
		else //can afford all goals up to this point
		{
			if(it->goal == goal)
			{
				logAi->debug("ResourceManager: can afford goal %s", goal->name());
				return goal; //can afford immediately
			}
		}
	}

	logAi->debug("ResourceManager: can not afford goal %s", goal->name());

	return collectResourcesForOurGoal(ro);
}

bool ResourceManager::containsObjective(Goals::TSubgoal goal) const
{
	logAi->trace("Entering ResourceManager.containsObjective goal=%s", goal->name());
	dumpToLog();

	//TODO: unit tests for once
	for (auto objective : queue)
	{
		if (objective.goal == goal)
			return true;
	}
	return false;
}

bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
{
	logAi->trace("Entering ResourceManager.notifyGoalCompleted goal=%s", goal->name());

	if (goal->invalid())
		logAi->warn("Attempt to complete Invalid goal");

	std::function<bool(const Goals::TSubgoal &)> equivalentGoalsCheck = [goal](const Goals::TSubgoal & x) -> bool
	{
		return x == goal || x->fulfillsMe(goal);
	};

	bool removedGoal = removeOutdatedObjectives(equivalentGoalsCheck);

	dumpToLog();

	return removedGoal;
}

bool ResourceManager::updateGoal(Goals::TSubgoal goal)
{
	//we update priority of goal if it is easier or more difficult to complete
	if (goal->invalid())
		logAi->warn("Attempt to update Invalid goal");

	auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
	{
		return ro.goal == goal;
	});
	if (it != queue.end())
	{
		it->goal->setpriority(goal->priority);
		auto handle = queue.s_handle_from_iterator(it);
		queue.update(handle); //restore order
		return true;
	}
	else
		return false;
}

void ResourceManager::dumpToLog() const
{
	for(auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
	{
		logAi->trace("ResourceManager contains goal %s which requires resources %s", it->goal->name(), it->resources.toString());
	}
}

bool ResourceManager::tryPush(const ResourceObjective & o)
{
	auto goal = o.goal;

	logAi->trace("ResourceManager: Trying to add goal %s which requires resources %s", goal->name(), o.resources.toString());
	dumpToLog();

	auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
	{
		return ro.goal == goal;
	});
	if (it != queue.end())
	{
		auto handle = queue.s_handle_from_iterator(it);
		vstd::amax(goal->priority, it->goal->priority); //increase priority if case
		//update resources with new value
		queue.update(handle, ResourceObjective(o.resources, goal)); //restore order
		return false;
	}
	else
	{
		queue.push(o); //add new objective
		logAi->debug("Reserved resources (%s) for %s", o.resources.toString(), goal->name());
		return true;
	}
}

bool ResourceManager::hasTasksLeft() const
{
	return !queue.empty();
}

bool ResourceManager::removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal &)> predicate)
{
	bool removedAnything = false;
	while(true)
	{ //unfortunately we can't use remove_if on heap
		auto it = boost::find_if(queue, [&](const ResourceObjective & ro) -> bool
		{
			return predicate(ro.goal);
		});

		if(it != queue.end()) //removed at least one
		{
			logAi->debug("Removing goal %s from ResourceManager.", it->goal->name());
			queue.erase(queue.s_handle_from_iterator(it));
			removedAnything = true;
		}
		else
		{ //found nothing more to remove
			break;
		}
	}
	return removedAnything;
}

TResources ResourceManager::reservedResources() const
{
	TResources res;
	for (auto it : queue) //subtract the value of reserved goals
		res += it.resources;
	return res;
}

TResources ResourceManager::freeResources() const
{
	TResources myRes = cb->getResourceAmount();
	myRes -= reservedResources(); //subtract the value of reserved goals

	for (auto & val : myRes)
		vstd::amax(val, 0); //never negative

	return myRes;
}

TResource ResourceManager::freeGold() const
{
	return freeResources()[EGameResID::GOLD];
}

TResources ResourceManager::allResources() const
{
	return cb->getResourceAmount();
}

TResource ResourceManager::allGold() const
{
	return cb->getResourceAmount()[EGameResID::GOLD];
}