mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
346 lines
9.0 KiB
C++
346 lines
9.0 KiB
C++
/*
|
|
* 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];
|
|
}
|