123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- /*
- * 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"
- #define GOLD_RESERVE (10000); //at least we'll be able to reach capitol
- 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];
- }
|