mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Rebase
This commit is contained in:
parent
aa9c6d3716
commit
6615870045
@ -11,12 +11,15 @@
|
||||
|
||||
#include "AIhelper.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "BuildingManager.h"
|
||||
|
||||
boost::thread_specific_ptr<AIhelper> ah;
|
||||
|
||||
AIhelper::AIhelper()
|
||||
{
|
||||
resourceManager.reset(new ResourceManager());
|
||||
buildingManager.reset(new BuildingManager());
|
||||
//TODO: push to vector
|
||||
}
|
||||
|
||||
AIhelper::~AIhelper()
|
||||
@ -30,12 +33,36 @@ bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
|
||||
|
||||
void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
|
||||
{
|
||||
//TODO: for
|
||||
resourceManager->setCB(CB);
|
||||
buildingManager->setCB(CB);
|
||||
}
|
||||
|
||||
void AIhelper::setAI(VCAI * AI)
|
||||
{
|
||||
//TODO: for loop
|
||||
resourceManager->setAI(AI);
|
||||
buildingManager->setAI(AI);
|
||||
}
|
||||
|
||||
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
|
||||
{
|
||||
return buildingManager->getBuildingOptions(t);
|
||||
}
|
||||
|
||||
boost::optional<PotentialBuilding> AIhelper::immediateBuilding() const
|
||||
{
|
||||
return buildingManager->immediateBuilding();
|
||||
}
|
||||
|
||||
boost::optional<PotentialBuilding> AIhelper::expensiveBuilding() const
|
||||
{
|
||||
return buildingManager->expensiveBuilding();
|
||||
}
|
||||
|
||||
boost::optional<BuildingID> AIhelper::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
|
||||
{
|
||||
return buildingManager->canBuildAnyStructure(t, buildList, maxDays);
|
||||
}
|
||||
|
||||
Goals::TSubgoal AIhelper::whatToDo(TResources & res, Goals::TSubgoal goal)
|
||||
|
@ -15,23 +15,28 @@
|
||||
*/
|
||||
|
||||
#include "ResourceManager.h"
|
||||
#include "BuildingManager.h"
|
||||
|
||||
class ResourceManager;
|
||||
class BuildingManager;
|
||||
|
||||
|
||||
//indirection interface for various modules
|
||||
class DLL_EXPORT AIhelper : public IResourceManager
|
||||
class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager
|
||||
{
|
||||
friend class VCAI;
|
||||
friend struct SetGlobalState; //mess?
|
||||
|
||||
//members are thread_specific. AIhelper is global
|
||||
std::shared_ptr<ResourceManager> resourceManager;
|
||||
std::shared_ptr<BuildingManager> buildingManager;
|
||||
//TODO: vector<IAbstractManager>
|
||||
std::shared_ptr<VCAI> ai;
|
||||
public:
|
||||
AIhelper();
|
||||
~AIhelper();
|
||||
|
||||
//TODO: consider common interface with Resource Manager?
|
||||
//from ResourceManager
|
||||
bool canAfford(const TResources & cost) const;
|
||||
TResources reservedResources() const override;
|
||||
TResources freeResources() const override;
|
||||
@ -49,5 +54,12 @@ private:
|
||||
|
||||
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||
void setAI(VCAI * AI) override;
|
||||
|
||||
//from BuildingManager
|
||||
public:
|
||||
bool getBuildingOptions(const CGTownInstance * t) override;
|
||||
boost::optional<PotentialBuilding> immediateBuilding() const override;
|
||||
boost::optional<PotentialBuilding> expensiveBuilding() const override;
|
||||
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
|
||||
};
|
||||
|
||||
|
248
AI/VCAI/BuildingManager.cpp
Normal file
248
AI/VCAI/BuildingManager.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
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->town->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->town->buildings.at(building);
|
||||
|
||||
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
{
|
||||
return t->hasBuilt(buildID);
|
||||
});
|
||||
toBuild.push_back(building);
|
||||
|
||||
for (BuildingID buildID : toBuild)
|
||||
{
|
||||
EBuildingState::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->town->buildings.at(buildID);
|
||||
|
||||
EBuildingState::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
|
||||
}
|
||||
|
||||
|
||||
boost::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;
|
||||
if (cb->canBuildStructure(t, building))
|
||||
return boost::optional<BuildingID>(building);
|
||||
}
|
||||
return boost::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::setCB(CPlayerSpecificInfoCallback * CB)
|
||||
{
|
||||
cb = CB;
|
||||
}
|
||||
|
||||
void BuildingManager::setAI(VCAI * AI)
|
||||
{
|
||||
ai = AI;
|
||||
}
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
static const BuildingID essential[] = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
|
||||
static const BuildingID goldSource[] = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL };
|
||||
static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
|
||||
static const 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 };
|
||||
static const 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 };
|
||||
static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
|
||||
static const BuildingID _spells[] = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
|
||||
static const BuildingID extra[] = { 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)
|
||||
|
||||
immediateBuildings.clear();
|
||||
expensiveBuildings.clear();
|
||||
|
||||
//below algorithm focuses on economy growth at start of the game.
|
||||
|
||||
TResources currentRes = cb->getResourceAmount();
|
||||
TResources currentIncome = t->dailyIncome();
|
||||
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
|
||||
return true;
|
||||
|
||||
//the more gold the better and less problems later
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
|
||||
return true;
|
||||
|
||||
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
|
||||
if (vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) &&
|
||||
cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL)
|
||||
{
|
||||
if (cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements,
|
||||
capitolRequirements + ARRAY_COUNT(capitolRequirements))))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: save money for capitol or city hall if capitol unavailable
|
||||
//do not build other things (unless gold source buildings are disabled in map editor)
|
||||
|
||||
|
||||
if (cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
|
||||
{
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
|
||||
return true;
|
||||
}
|
||||
|
||||
// first in-game week or second half of any week: try build dwellings
|
||||
if (cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
|
||||
{
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource,
|
||||
unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to upgrade dwelling
|
||||
for (int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
|
||||
{
|
||||
if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
|
||||
{
|
||||
if (tryBuildThisStructure(t, unitsUpgrade[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//remaining tasks
|
||||
if (tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
|
||||
return true;
|
||||
if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(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->town->buildings)
|
||||
{
|
||||
if (buildingInfo.first > 43)
|
||||
extraBuildings.push_back(buildingInfo.first);
|
||||
}
|
||||
if (tryBuildAnyStructure(t, extraBuildings))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
boost::optional<PotentialBuilding> BuildingManager::immediateBuilding() const
|
||||
{
|
||||
if (immediateBuildings.size())
|
||||
return boost::optional<PotentialBuilding>(immediateBuildings.front()); //back? whatever
|
||||
else
|
||||
return boost::optional<PotentialBuilding>();
|
||||
}
|
||||
|
||||
boost::optional<PotentialBuilding> BuildingManager::expensiveBuilding() const
|
||||
{
|
||||
if (expensiveBuildings.size())
|
||||
return boost::optional<PotentialBuilding>(expensiveBuildings.front());
|
||||
else
|
||||
return boost::optional<PotentialBuilding>();
|
||||
}
|
||||
|
74
AI/VCAI/BuildingManager.h
Normal file
74
AI/VCAI/BuildingManager.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* BuildingManager.h, 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AIUtility.h"
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CBuildingHandler.h"
|
||||
#include "VCAI.h"
|
||||
|
||||
struct DLL_EXPORT PotentialBuilding
|
||||
{
|
||||
BuildingID bid;
|
||||
TResources price;
|
||||
//days to build?
|
||||
};
|
||||
|
||||
class DLL_EXPORT IBuildingManager //: public: IAbstractManager
|
||||
{ //info about town development
|
||||
public:
|
||||
virtual ~IBuildingManager() = default;
|
||||
virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
|
||||
virtual void setAI(VCAI * AI) = 0;
|
||||
|
||||
virtual bool getBuildingOptions(const CGTownInstance * t) = 0;
|
||||
virtual boost::optional<PotentialBuilding> immediateBuilding() const = 0;
|
||||
virtual boost::optional<PotentialBuilding> expensiveBuilding() const = 0;
|
||||
virtual boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const = 0;
|
||||
};
|
||||
|
||||
class DLL_EXPORT BuildingManager : public IBuildingManager
|
||||
{
|
||||
friend class VCAI;
|
||||
friend class AIhelper;
|
||||
friend struct SetGlobalState;
|
||||
|
||||
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
|
||||
VCAI * ai;
|
||||
|
||||
public:
|
||||
|
||||
//try build anything in given town, and execute resulting Goal if any
|
||||
bool getBuildingOptions(const CGTownInstance * t) override;
|
||||
boost::optional<PotentialBuilding> immediateBuilding() const override;
|
||||
boost::optional<PotentialBuilding> expensiveBuilding() const override;
|
||||
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
|
||||
|
||||
protected:
|
||||
|
||||
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
|
||||
//try build first unbuilt structure
|
||||
bool tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays = 7);
|
||||
//try build ANY unbuilt structure
|
||||
|
||||
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
|
||||
|
||||
private:
|
||||
//TODO: remember current town?
|
||||
std::vector<PotentialBuilding> immediateBuildings; //what we can build right now in current town
|
||||
std::vector<PotentialBuilding> expensiveBuildings; //what we coudl build but can't afford
|
||||
|
||||
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||
void setAI(VCAI * AI) override;
|
||||
};
|
@ -11,6 +11,8 @@ set(VCAI_SRCS
|
||||
AIUtility.cpp
|
||||
AIhelper.cpp
|
||||
ResourceManager.cpp
|
||||
BuildingManager.cpp
|
||||
MapObjectsEvaluator.cpp
|
||||
Fuzzy.cpp
|
||||
Goals.cpp
|
||||
main.cpp
|
||||
@ -23,6 +25,8 @@ set(VCAI_HEADERS
|
||||
AIUtility.h
|
||||
AIhelper.h
|
||||
ResourceManager.h
|
||||
BuildingManager.h
|
||||
MapObjectsEvaluator.h
|
||||
Fuzzy.h
|
||||
Goals.h
|
||||
VCAI.h
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "VCAI.h"
|
||||
#include "MapObjectsEvaluator.h"
|
||||
|
||||
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
@ -97,6 +98,8 @@ FuzzyHelper::FuzzyHelper()
|
||||
ta.configure();
|
||||
initVisitTile();
|
||||
vt.configure();
|
||||
initWanderTarget();
|
||||
wanderTarget.configure();
|
||||
}
|
||||
|
||||
|
||||
@ -199,6 +202,20 @@ void FuzzyHelper::initTacticalAdvantage()
|
||||
}
|
||||
}
|
||||
|
||||
float FuzzyHelper::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
|
||||
{
|
||||
float turns = 0.0f;
|
||||
float distance = CPathfinderHelper::getMovementCost(h, tile);
|
||||
if(distance)
|
||||
{
|
||||
if(distance < h->movement) //we can move there within one turn
|
||||
turns = (fl::scalar)distance / h->movement;
|
||||
else
|
||||
turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land?
|
||||
}
|
||||
return turns;
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
@ -272,6 +289,38 @@ float FuzzyHelper::getTacticalAdvantage(const CArmedInstance * we, const CArmedI
|
||||
return output;
|
||||
}
|
||||
|
||||
float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj)
|
||||
{
|
||||
float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos);
|
||||
boost::optional<int> objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID);
|
||||
int objValue = 0;
|
||||
|
||||
if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map
|
||||
{
|
||||
objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000);
|
||||
}
|
||||
else
|
||||
{
|
||||
MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
|
||||
logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
|
||||
}
|
||||
|
||||
float output = -1.0f;
|
||||
try
|
||||
{
|
||||
wanderTarget.distance->setValue(distFromObject);
|
||||
wanderTarget.objectValue->setValue(objValue);
|
||||
wanderTarget.engine.process();
|
||||
output = wanderTarget.visitGain->getValue();
|
||||
}
|
||||
catch (fl::Exception & fe)
|
||||
{
|
||||
logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat());
|
||||
}
|
||||
assert(output >= 0.0f);
|
||||
return output;
|
||||
}
|
||||
|
||||
FuzzyHelper::TacticalAdvantage::~TacticalAdvantage()
|
||||
{
|
||||
//TODO: smart pointers?
|
||||
@ -413,6 +462,55 @@ void FuzzyHelper::initVisitTile()
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzyHelper::initWanderTarget()
|
||||
{
|
||||
try
|
||||
{
|
||||
wanderTarget.distance = new fl::InputVariable("distance"); //distance on map from object
|
||||
wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI
|
||||
wanderTarget.visitGain = new fl::OutputVariable("visitGain");
|
||||
wanderTarget.visitGain->setMinimum(0);
|
||||
wanderTarget.visitGain->setMaximum(10);
|
||||
|
||||
wanderTarget.engine.addInputVariable(wanderTarget.distance);
|
||||
wanderTarget.engine.addInputVariable(wanderTarget.objectValue);
|
||||
wanderTarget.engine.addOutputVariable(wanderTarget.visitGain);
|
||||
|
||||
//for now distance variable same as in as VisitTile
|
||||
wanderTarget.distance->addTerm(new fl::Ramp("SHORT", 0.5, 0));
|
||||
wanderTarget.distance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
|
||||
wanderTarget.distance->addTerm(new fl::Ramp("LONG", 0.5, 3));
|
||||
wanderTarget.distance->setRange(0, 3.0);
|
||||
|
||||
//objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges
|
||||
wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms
|
||||
wanderTarget.objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000));
|
||||
wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000));
|
||||
wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down.
|
||||
|
||||
wanderTarget.visitGain->addTerm(new fl::Ramp("LOW", 5, 0));
|
||||
wanderTarget.visitGain->addTerm(new fl::Triangle("MEDIUM", 4, 6));
|
||||
wanderTarget.visitGain->addTerm(new fl::Ramp("HIGH", 5, 10));
|
||||
wanderTarget.visitGain->setRange(0, 10);
|
||||
|
||||
wanderTarget.addRule("if distance is LONG and objectValue is HIGH then visitGain is MEDIUM");
|
||||
wanderTarget.addRule("if distance is MEDIUM and objectValue is HIGH then visitGain is somewhat HIGH");
|
||||
wanderTarget.addRule("if distance is SHORT and objectValue is HIGH then visitGain is HIGH");
|
||||
|
||||
wanderTarget.addRule("if distance is LONG and objectValue is MEDIUM then visitGain is somewhat LOW");
|
||||
wanderTarget.addRule("if distance is MEDIUM and objectValue is MEDIUM then visitGain is MEDIUM");
|
||||
wanderTarget.addRule("if distance is SHORT and objectValue is MEDIUM then visitGain is somewhat HIGH");
|
||||
|
||||
wanderTarget.addRule("if distance is LONG and objectValue is LOW then visitGain is very LOW");
|
||||
wanderTarget.addRule("if distance is MEDIUM and objectValue is LOW then visitGain is LOW");
|
||||
wanderTarget.addRule("if distance is SHORT and objectValue is LOW then visitGain is MEDIUM");
|
||||
}
|
||||
catch(fl::Exception & fe)
|
||||
{
|
||||
logAi->error("FindWanderTarget: %s", fe.getWhat());
|
||||
}
|
||||
}
|
||||
|
||||
float FuzzyHelper::evaluate(Goals::VisitTile & g)
|
||||
{
|
||||
//we assume that hero is already set and we want to choose most suitable one for the mission
|
||||
@ -420,20 +518,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g)
|
||||
return 0;
|
||||
|
||||
//assert(cb->isInTheMap(g.tile));
|
||||
float turns = 0;
|
||||
float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile);
|
||||
if(!distance) //we stand on that tile
|
||||
{
|
||||
turns = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(distance < g.hero->movement) //we can move there within one turn
|
||||
turns = (fl::scalar)distance / g.hero->movement;
|
||||
else
|
||||
turns = 1 + (fl::scalar)(distance - g.hero->movement) / g.hero->maxMovePoints(true); //bool on land?
|
||||
}
|
||||
|
||||
float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile);
|
||||
float missionImportance = 0;
|
||||
if(vstd::contains(ai->lockedHeroes, g.hero))
|
||||
missionImportance = ai->lockedHeroes[g.hero]->priority;
|
||||
@ -549,3 +634,10 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa
|
||||
{
|
||||
g->setpriority(g->accept(this)); //this enforces returned value is set
|
||||
}
|
||||
|
||||
FuzzyHelper::EvalWanderTargetObject::~EvalWanderTargetObject()
|
||||
{
|
||||
delete distance;
|
||||
delete objectValue;
|
||||
delete visitGain;
|
||||
}
|
||||
|
@ -55,7 +55,17 @@ class FuzzyHelper
|
||||
~EvalVisitTile();
|
||||
} vt;
|
||||
|
||||
class EvalWanderTargetObject : public engineBase //designed for use with VCAI::wander()
|
||||
{
|
||||
public:
|
||||
fl::InputVariable * distance;
|
||||
fl::InputVariable * objectValue;
|
||||
fl::OutputVariable * visitGain;
|
||||
~EvalWanderTargetObject();
|
||||
} wanderTarget;
|
||||
|
||||
private:
|
||||
float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const;
|
||||
|
||||
public:
|
||||
enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE};
|
||||
@ -64,6 +74,7 @@ public:
|
||||
FuzzyHelper();
|
||||
void initTacticalAdvantage();
|
||||
void initVisitTile();
|
||||
void initWanderTarget();
|
||||
|
||||
float evaluate(Goals::Explore & g);
|
||||
float evaluate(Goals::RecruitHero & g);
|
||||
@ -82,6 +93,7 @@ public:
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank);
|
||||
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
|
||||
float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj);
|
||||
|
||||
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
|
||||
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "VCAI.h"
|
||||
#include "Fuzzy.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "BuildingManager.h"
|
||||
#include "../../lib/mapping/CMap.h" //for victory conditions
|
||||
#include "../../lib/CPathfinder.h"
|
||||
#include "../../lib/StringConstants.h"
|
||||
@ -1367,10 +1368,10 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
||||
}
|
||||
}
|
||||
//build dwelling
|
||||
auto bid = ai->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
|
||||
if (bid != BuildingID::NONE)
|
||||
auto bid = ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
|
||||
if (bid.is_initialized())
|
||||
{
|
||||
auto goal = sptr(BuildThis(bid, t).setpriority(priority));
|
||||
auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
|
||||
if (!ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
|
||||
ret.push_back(goal);
|
||||
}
|
||||
|
63
AI/VCAI/MapObjectsEvaluator.cpp
Normal file
63
AI/VCAI/MapObjectsEvaluator.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "StdInc.h"
|
||||
#include "MapObjectsEvaluator.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
|
||||
MapObjectsEvaluator & MapObjectsEvaluator::getInstance()
|
||||
{
|
||||
static std::unique_ptr<MapObjectsEvaluator> singletonInstance;
|
||||
if(singletonInstance == nullptr)
|
||||
singletonInstance.reset(new MapObjectsEvaluator());
|
||||
|
||||
return *(singletonInstance.get());
|
||||
}
|
||||
|
||||
MapObjectsEvaluator::MapObjectsEvaluator()
|
||||
{
|
||||
for(auto primaryID : VLC->objtypeh->knownObjects())
|
||||
{
|
||||
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
|
||||
{
|
||||
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
|
||||
if(!handler->isStaticObject())
|
||||
{
|
||||
if(handler->getAiValue() != boost::none)
|
||||
{
|
||||
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get();
|
||||
}
|
||||
else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists
|
||||
{
|
||||
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get();
|
||||
}
|
||||
else
|
||||
{
|
||||
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; //some default handling when aiValue not found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<int> MapObjectsEvaluator::getObjectValue(int primaryID, int secondaryID) const
|
||||
{
|
||||
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
|
||||
auto object = objectDatabase.find(internalIdentifier);
|
||||
if(object != objectDatabase.end())
|
||||
return object->second;
|
||||
|
||||
logGlobal->trace("Unknown object for AI, ID: " + std::to_string(primaryID) + ", SubID: " + std::to_string(secondaryID));
|
||||
return boost::optional<int>();
|
||||
}
|
||||
|
||||
void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database
|
||||
{
|
||||
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
|
||||
objectDatabase[internalIdentifier] = value;
|
||||
}
|
||||
|
||||
void MapObjectsEvaluator::removeObjectData(int primaryID, int secondaryID)
|
||||
{
|
||||
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
|
||||
vstd::erase_if_present(objectDatabase, internalIdentifier);
|
||||
}
|
||||
|
25
AI/VCAI/MapObjectsEvaluator.h
Normal file
25
AI/VCAI/MapObjectsEvaluator.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* MapObjectsEvaluator.h, 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../lib/mapObjects/CObjectClassesHandler.h"
|
||||
|
||||
class MapObjectsEvaluator
|
||||
{
|
||||
private:
|
||||
std::map<CompoundMapObjectID, int> objectDatabase; //value for each object type
|
||||
|
||||
public:
|
||||
MapObjectsEvaluator();
|
||||
static MapObjectsEvaluator & getInstance();
|
||||
boost::optional<int> getObjectValue(int primaryID, int secondaryID) const;
|
||||
void addObjectData(int primaryID, int secondaryID, int value);
|
||||
void removeObjectData(int primaryID, int secondaryID);
|
||||
};
|
||||
|
285
AI/VCAI/VCAI.cpp
285
AI/VCAI/VCAI.cpp
@ -11,6 +11,7 @@
|
||||
#include "VCAI.h"
|
||||
#include "Fuzzy.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "BuildingManager.h"
|
||||
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
@ -1181,241 +1182,35 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
|
||||
}
|
||||
}
|
||||
|
||||
bool VCAI::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->town->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->town->buildings.at(building);
|
||||
|
||||
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
|
||||
{
|
||||
return t->hasBuilt(buildID);
|
||||
});
|
||||
toBuild.push_back(building);
|
||||
|
||||
for(BuildingID buildID : toBuild)
|
||||
{
|
||||
EBuildingState::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->town->buildings.at(buildID);
|
||||
|
||||
EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
|
||||
if (canBuild == EBuildingState::ALLOWED)
|
||||
{
|
||||
buildStructure(t, buildID);
|
||||
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);
|
||||
potentialBuildings.push_back(pb); //these are checked again in try
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VCAI::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
|
||||
}
|
||||
|
||||
BuildingID VCAI::canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) const
|
||||
{
|
||||
for(const auto & building : buildList)
|
||||
{
|
||||
if(t->hasBuilt(building))
|
||||
continue;
|
||||
if(cb->canBuildStructure(t, building))
|
||||
return building;
|
||||
}
|
||||
return BuildingID::NONE; //Can't build anything
|
||||
}
|
||||
|
||||
bool VCAI::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 VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
|
||||
{
|
||||
auto name = t->town->buildings.at(building)->Name();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", playerID, name, t->name, t->pos.toString());
|
||||
cb->buildBuilding(t, building); //just do this;
|
||||
}
|
||||
|
||||
//Set of buildings for different goals. Does not include any prerequisites.
|
||||
static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
|
||||
static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
|
||||
static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
|
||||
static const 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};
|
||||
static const 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};
|
||||
static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
|
||||
static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
|
||||
static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
|
||||
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
|
||||
|
||||
bool VCAI::tryBuildStructure(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)
|
||||
|
||||
//below algorithm focuses on economy growth at start of the game.
|
||||
TResources currentRes = cb->getResourceAmount();
|
||||
TResources currentIncome = t->dailyIncome();
|
||||
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
|
||||
return true;
|
||||
|
||||
//the more gold the better and less problems later
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
|
||||
return true;
|
||||
|
||||
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
|
||||
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) &&
|
||||
cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL)
|
||||
{
|
||||
if(cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements,
|
||||
capitolRequirements + ARRAY_COUNT(capitolRequirements))))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: save money for capitol or city hall if capitol unavailable
|
||||
//do not build other things (unless gold source buildings are disabled in map editor)
|
||||
|
||||
|
||||
if(cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
|
||||
{
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
|
||||
return true;
|
||||
}
|
||||
|
||||
// first in-game week or second half of any week: try build dwellings
|
||||
if(cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
|
||||
{
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource,
|
||||
unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to upgrade dwelling
|
||||
for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
|
||||
{
|
||||
if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
|
||||
{
|
||||
if(tryBuildThisStructure(t, unitsUpgrade[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//remaining tasks
|
||||
if(tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
|
||||
return true;
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(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->town->buildings)
|
||||
{
|
||||
if(buildingInfo.first > 43)
|
||||
extraBuildings.push_back(buildingInfo.first);
|
||||
}
|
||||
if(tryBuildAnyStructure(t, extraBuildings))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm)
|
||||
{
|
||||
const int3 pos = obj->visitablePos();
|
||||
const int3 targetPos = sm.firstTileToGet(h, pos);
|
||||
if(!targetPos.valid())
|
||||
if (!targetPos.valid())
|
||||
return false;
|
||||
if(!isTileNotReserved(h.get(), targetPos))
|
||||
if (!isTileNotReserved(h.get(), targetPos))
|
||||
return false;
|
||||
if(obj->wasVisited(playerID))
|
||||
if (obj->wasVisited(playerID))
|
||||
return false;
|
||||
if(cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
|
||||
if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
|
||||
return false; // Otherwise we flag or get weekly resources / creatures
|
||||
if(!isSafeToVisit(h, pos))
|
||||
if (!isSafeToVisit(h, pos))
|
||||
return false;
|
||||
if(!shouldVisit(h, obj))
|
||||
if (!shouldVisit(h, obj))
|
||||
return false;
|
||||
if(vstd::contains(alreadyVisited, obj))
|
||||
if (vstd::contains(alreadyVisited, obj))
|
||||
return false;
|
||||
if(vstd::contains(reservedObjs, obj))
|
||||
if (vstd::contains(reservedObjs, obj))
|
||||
return false;
|
||||
if(!isAccessibleForHero(targetPos, h))
|
||||
if (!isAccessibleForHero(targetPos, h))
|
||||
return false;
|
||||
|
||||
const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj
|
||||
//we don't try visiting object on which allied or owned hero stands
|
||||
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
||||
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
//we don't try visiting object on which allied or owned hero stands
|
||||
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
||||
if (topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
return false;
|
||||
else
|
||||
return true; //all of the following is met
|
||||
|
||||
}
|
||||
|
||||
bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t)
|
||||
@ -1592,7 +1387,12 @@ void VCAI::wander(HeroPtr h)
|
||||
|
||||
if(dests.size()) //performance improvement
|
||||
{
|
||||
const ObjectIdRef & dest = *boost::min_element(dests, CDistanceSorter(h.get())); //find next closest one
|
||||
auto fuzzyLogicSorter = [h](const ObjectIdRef & l, const ObjectIdRef & r) -> bool //TODO: create elementar GetObj goal usable for goal decomposition and Wander based on VisitTile logic and object value on top of it
|
||||
{
|
||||
return fh->getWanderTargetObjectValue( *h.get(), l) < fh->getWanderTargetObjectValue(*h.get(), r);
|
||||
};
|
||||
|
||||
const ObjectIdRef & dest = *boost::max_element(dests, fuzzyLogicSorter); //find best object to visit based on fuzzy logic evaluation, TODO: use elementar version of GetObj here in future
|
||||
|
||||
//wander should not cause heroes to be reserved - they are always considered free
|
||||
logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum());
|
||||
@ -2079,6 +1879,14 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
|
||||
{
|
||||
auto name = t->town->buildings.at(building)->Name();
|
||||
logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->name, t->pos.toString());
|
||||
cb->buildBuilding(t, building); //just do this;
|
||||
}
|
||||
|
||||
void VCAI::tryRealize(Goals::Explore & g)
|
||||
{
|
||||
throw cannotFulfillGoalException("EXPLORE is not an elementar goal!");
|
||||
@ -2211,23 +2019,32 @@ void VCAI::tryRealize(Goals::Build & g)
|
||||
for(const CGTownInstance * t : cb->getTownsInfo())
|
||||
{
|
||||
logAi->debug("Looking into %s", t->name);
|
||||
potentialBuildings.clear(); //start fresh with every town
|
||||
if (tryBuildStructure(t))
|
||||
didWeBuildSomething = true;
|
||||
else if (potentialBuildings.size())
|
||||
//start fresh with every town
|
||||
ah->getBuildingOptions(t);
|
||||
auto ib = ah->immediateBuilding();
|
||||
if (ib.is_initialized())
|
||||
{
|
||||
auto pb = potentialBuildings.front(); //gather resources for any we can't afford
|
||||
auto goal = ah->whatToDo(pb.price, sptr(Goals::BuildThis(pb.bid, t)));
|
||||
if (goal->goalType == Goals::BUILD_STRUCTURE)
|
||||
buildStructure(t, ib.get().bid); //do it right now
|
||||
didWeBuildSomething = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto eb = ah->expensiveBuilding();
|
||||
if (eb.is_initialized())
|
||||
{
|
||||
logAi->error("We were supposed to NOT afford any building");
|
||||
buildStructure(t, pb.bid); //do it right now
|
||||
didWeBuildSomething = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: right now we do that for every town in order. Consider comparison of all potential goals.
|
||||
striveToGoal(goal); //gather resources, or something else?
|
||||
auto pb = eb.get(); //gather resources for any we can't afford
|
||||
auto goal = ah->whatToDo(pb.price, sptr(Goals::BuildThis(pb.bid, t)));
|
||||
if (goal->goalType == Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
logAi->error("We were supposed to NOT afford any building");
|
||||
buildStructure(t, pb.bid); //do it right now
|
||||
didWeBuildSomething = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: right now we do that for every town in order. Consider comparison of all potential goals.
|
||||
striveToGoal(goal); //gather resources, or something else?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,30 +129,10 @@ struct SectorMap
|
||||
class DLL_EXPORT VCAI : public CAdventureAI
|
||||
{
|
||||
public:
|
||||
//internal methods for town development
|
||||
//TODO: refactor to separate class BuildManager
|
||||
|
||||
//try build anything in given town, and execute resulting Goal if any
|
||||
bool tryBuildStructure(const CGTownInstance * t);
|
||||
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
|
||||
//try build first unbuilt structure
|
||||
|
||||
bool tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays = 7);
|
||||
//try build ANY unbuilt structure
|
||||
BuildingID canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7) const;
|
||||
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
|
||||
void buildStructure(const CGTownInstance * t, BuildingID building); //actually execute build operation
|
||||
|
||||
struct PotentialBuilding
|
||||
{
|
||||
BuildingID bid;
|
||||
TResources price;
|
||||
//days to build?
|
||||
};
|
||||
std::vector<PotentialBuilding> potentialBuildings; //what we can build in current town
|
||||
|
||||
friend class FuzzyHelper;
|
||||
friend class ResourceManager;
|
||||
friend class BuildingManager;
|
||||
|
||||
std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
|
||||
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
|
||||
@ -289,6 +269,7 @@ public:
|
||||
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
|
||||
|
||||
bool moveHeroToTile(int3 dst, HeroPtr h);
|
||||
void buildStructure(const CGTownInstance * t, BuildingID building); //TODO: move to BuildingManager
|
||||
|
||||
void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)
|
||||
void waitTillFree();
|
||||
|
@ -141,6 +141,7 @@
|
||||
<ClCompile Include="Fuzzy.cpp" />
|
||||
<ClCompile Include="Goals.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="MapObjectsEvaluator.cpp" />
|
||||
<ClCompile Include="StdInc.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
@ -153,6 +154,7 @@
|
||||
<ClInclude Include="AIUtility.h" />
|
||||
<ClInclude Include="Fuzzy.h" />
|
||||
<ClInclude Include="Goals.h" />
|
||||
<ClInclude Include="MapObjectsEvaluator.h" />
|
||||
<ClInclude Include="StdInc.h" />
|
||||
<ClInclude Include="VCAI.h" />
|
||||
</ItemGroup>
|
||||
|
@ -12,6 +12,7 @@
|
||||
{
|
||||
"index" : 0,
|
||||
"name" : "Cyclops Stockpile",
|
||||
"aiValue" : 3000,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPCAVE"]
|
||||
},
|
||||
@ -120,6 +121,7 @@
|
||||
"index" : 1,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Dwarven Treasury",
|
||||
"aiValue" : 2000,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPDWAR"]
|
||||
},
|
||||
@ -210,6 +212,7 @@
|
||||
"index" : 2,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Griffin Conservatory",
|
||||
"aiValue" : 9000,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPGRIF"]
|
||||
},
|
||||
@ -284,6 +287,7 @@
|
||||
"index" : 3,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Imp Cache",
|
||||
"aiValue" : 1500,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPFIRE"]
|
||||
},
|
||||
@ -373,6 +377,7 @@
|
||||
"index" : 4,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Medusa Stores",
|
||||
"aiValue" : 1500,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPMEDU"]
|
||||
},
|
||||
@ -463,6 +468,7 @@
|
||||
"index" : 5,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Naga Bank",
|
||||
"aiValue" : 3000,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPNAGA"]
|
||||
},
|
||||
@ -553,6 +559,7 @@
|
||||
"index" : 6,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Dragon Fly Hive",
|
||||
"aiValue" : 9000,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPLEAR"]
|
||||
},
|
||||
@ -633,6 +640,7 @@
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Shipwreck",
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
"value" : 2000,
|
||||
"rarity" : 100
|
||||
@ -724,6 +732,7 @@
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Derelict Ship",
|
||||
"aiValue" : 4000,
|
||||
"rmg" : {
|
||||
"value" : 4000,
|
||||
"rarity" : 20
|
||||
@ -822,6 +831,7 @@
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Crypt",
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1000,
|
||||
"rarity" : 100
|
||||
@ -917,6 +927,7 @@
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Dragon Utopia",
|
||||
"aiValue" : 11000,
|
||||
"rmg" : {
|
||||
"value" : 10000,
|
||||
"rarity" : 100
|
||||
@ -1031,6 +1042,7 @@
|
||||
"index" : 0,
|
||||
"resetDuration" : 0,
|
||||
"name" : "Pyramid",
|
||||
"aiValue" : 8000,
|
||||
"rmg" : {
|
||||
"value" : 5000,
|
||||
"rarity" : 20
|
||||
|
@ -10,7 +10,7 @@
|
||||
}
|
||||
},
|
||||
"types" : {
|
||||
"prison" : { "index" : 0 }
|
||||
"prison" : { "index" : 0, "aiValue" : 5000 }
|
||||
}
|
||||
},
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -45,6 +46,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -65,6 +67,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -79,6 +82,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -100,6 +104,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 8000,
|
||||
"rmg" : {
|
||||
"value" : 8000,
|
||||
"rarity" : 20
|
||||
@ -120,6 +125,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 10000,
|
||||
"templates" : {
|
||||
"normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
|
||||
},
|
||||
@ -151,6 +157,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 750,
|
||||
"templates" :
|
||||
{
|
||||
"base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] },
|
||||
@ -176,6 +183,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 750,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 750,
|
||||
@ -195,6 +203,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -246,6 +255,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
"value" : 5000,
|
||||
"rarity" : 20
|
||||
@ -265,6 +275,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 1500,
|
||||
@ -285,6 +296,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"value" : 500,
|
||||
"rarity" : 100
|
||||
@ -304,6 +316,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
"value" : 2000,
|
||||
"rarity" : 100
|
||||
@ -323,6 +336,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 3000,
|
||||
"rmg" : {
|
||||
"value" : 3000,
|
||||
"rarity" : 100
|
||||
@ -344,6 +358,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 0,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -360,6 +375,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 750,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -376,6 +392,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -392,6 +409,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 350,
|
||||
"rmg" : {
|
||||
"mapLimit" : 48,
|
||||
"value" : 3500,
|
||||
@ -411,6 +429,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 0,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -428,6 +447,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 100
|
||||
@ -447,6 +467,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1000,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -463,6 +484,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 0,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -480,6 +502,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"mapLimit" : 32,
|
||||
"value" : 100,
|
||||
@ -500,6 +523,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"templates" :
|
||||
{
|
||||
"green" : { "animation" : "avxdent.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "BA"], "allowedTerrains":["grass", "swamp", "dirt"] },
|
||||
@ -524,6 +548,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2500,
|
||||
"rmg" : {
|
||||
"value" : 2500,
|
||||
"rarity" : 20
|
||||
@ -542,6 +567,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 3,
|
||||
"value" : 1500,
|
||||
@ -565,6 +591,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 10000,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -581,6 +608,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 250,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 250,
|
||||
@ -819,6 +847,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 7000,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 7000,
|
||||
@ -833,6 +862,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 0,
|
||||
"rmg" : {
|
||||
}
|
||||
}
|
||||
@ -850,6 +880,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -870,6 +901,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
|
@ -23,6 +23,7 @@
|
||||
"hero" : {
|
||||
"index" :34,
|
||||
"handler": "hero",
|
||||
"defaultAiValue" : 5000,
|
||||
"base" : {
|
||||
"base" : {
|
||||
"visitableFrom" : [ "+++", "+-+", "+++" ],
|
||||
@ -63,14 +64,14 @@
|
||||
}
|
||||
},
|
||||
"types" : {
|
||||
"wood" : { "index" : 0, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
|
||||
"mercury" : { "index" : 1, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
|
||||
"ore" : { "index" : 2, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } },
|
||||
"sulfur" : { "index" : 3, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
|
||||
"crystal" : { "index" : 4, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
|
||||
"gems" : { "index" : 5, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
|
||||
"gold" : { "index" : 6, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
|
||||
"mithril" : { "index" : 7 } // TODO: move to WoG?
|
||||
"wood" : { "index" : 0, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
|
||||
"mercury" : { "index" : 1, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
|
||||
"ore" : { "index" : 2, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } },
|
||||
"sulfur" : { "index" : 3, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
|
||||
"crystal" : { "index" : 4, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
|
||||
"gems" : { "index" : 5, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
|
||||
"gold" : { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
|
||||
"mithril" : { "index" : 7, "aiValue" : 3500 } // TODO: move to WoG?
|
||||
}
|
||||
},
|
||||
|
||||
@ -78,6 +79,7 @@
|
||||
"town" : {
|
||||
"index" :98,
|
||||
"handler": "town",
|
||||
"defaultAiValue" : 20000,
|
||||
"base" : {
|
||||
"filters" : {
|
||||
// village image - fort not present
|
||||
@ -107,6 +109,7 @@
|
||||
"boat" : {
|
||||
"index" :8,
|
||||
"handler": "boat",
|
||||
"defaultAiValue" : 0,
|
||||
"base" : {
|
||||
"base" : {
|
||||
"visitableFrom" : [ "+++", "+-+", "+++" ],
|
||||
@ -124,6 +127,7 @@
|
||||
"borderGuard" : {
|
||||
"index" :9,
|
||||
"handler": "borderGuard",
|
||||
"defaultAiValue" : 0,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["CAVEHEAD"],
|
||||
@ -144,6 +148,7 @@
|
||||
"borderGate" : {
|
||||
"index" :212,
|
||||
"handler": "borderGate",
|
||||
"defaultAiValue" : 0,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["CAVEHEAD"]
|
||||
@ -163,6 +168,7 @@
|
||||
"keymasterTent" : {
|
||||
"index" :10,
|
||||
"handler": "keymaster",
|
||||
"defaultAiValue" : 10000,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["CAVEHEAD"]
|
||||
@ -183,6 +189,7 @@
|
||||
"seerHut" : {
|
||||
"index" :83,
|
||||
"handler": "seerHut",
|
||||
"defaultAiValue" : 10000,
|
||||
"base" : {
|
||||
"base" : {
|
||||
"visitableFrom" : [ "---", "+++", "+++" ],
|
||||
@ -209,9 +216,9 @@
|
||||
}
|
||||
},
|
||||
"types" : {
|
||||
"water" : { "index" : 0, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } },
|
||||
"land" : { "index" : 1, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } },
|
||||
"subterra" : { "index" : 2, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } }
|
||||
"water" : { "index" : 0, "aiValue" : 5000, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } },
|
||||
"land" : { "index" : 1, "aiValue": 10000, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } },
|
||||
"subterra" : { "index" : 2, "aiValue" : 7500, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } }
|
||||
}
|
||||
},
|
||||
|
||||
@ -227,6 +234,7 @@
|
||||
"types" : {
|
||||
"sawmill" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500
|
||||
},
|
||||
@ -236,6 +244,7 @@
|
||||
},
|
||||
"alchemistLab" : {
|
||||
"index" : 1,
|
||||
"aiValue" : 3500,
|
||||
"rmg" : {
|
||||
"value" : 3500
|
||||
},
|
||||
@ -245,6 +254,7 @@
|
||||
},
|
||||
"orePit" : {
|
||||
"index" : 2,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500
|
||||
},
|
||||
@ -254,6 +264,7 @@
|
||||
},
|
||||
"sulfurDune" : {
|
||||
"index" : 3,
|
||||
"aiValue" : 3500,
|
||||
"rmg" : {
|
||||
"value" : 3500
|
||||
},
|
||||
@ -263,6 +274,7 @@
|
||||
},
|
||||
"crystalCavern" : {
|
||||
"index" : 4,
|
||||
"aiValue" : 3500,
|
||||
"rmg" : {
|
||||
"value" : 3500
|
||||
},
|
||||
@ -272,6 +284,7 @@
|
||||
},
|
||||
"gemPond" : {
|
||||
"index" : 5,
|
||||
"aiValue" : 3500,
|
||||
"rmg" : {
|
||||
"value" : 3500
|
||||
},
|
||||
@ -281,6 +294,7 @@
|
||||
},
|
||||
"goldMine" : {
|
||||
"index" : 6,
|
||||
"aiValue" : 7000,
|
||||
"rmg" : {
|
||||
"value" : 7000
|
||||
},
|
||||
@ -290,6 +304,7 @@
|
||||
},
|
||||
"abandoned" : {
|
||||
"index" : 7,
|
||||
"aiValue" : 3500,
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPCAVE"],
|
||||
"visit" : ["MYSTERY"]
|
||||
@ -300,6 +315,7 @@
|
||||
"abandonedMine" : {
|
||||
"index" :220,
|
||||
"handler": "mine",
|
||||
"defaultAiValue" : 3500,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"ambient" : ["LOOPCAVE"]
|
||||
@ -313,6 +329,7 @@
|
||||
"garrisonHorizontal": {
|
||||
"index" :33,
|
||||
"handler": "garrison",
|
||||
"defaultAiValue" : 0,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["MILITARY"]
|
||||
@ -336,6 +353,7 @@
|
||||
"garrisonVertical" : {
|
||||
"index" :219,
|
||||
"handler": "garrison",
|
||||
"defaultAiValue" : 0,
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["MILITARY"]
|
||||
@ -454,6 +472,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"templates" : {
|
||||
"normal" : {
|
||||
"visitableFrom" : [ "+++", "+-+", "+++" ],
|
||||
|
@ -12,7 +12,8 @@
|
||||
},
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0//,
|
||||
"index" : 0,
|
||||
"aiValue" : 500//,
|
||||
//"rmg" : {
|
||||
// "zoneLimit" : 1,
|
||||
// "value" : 500,
|
||||
@ -35,6 +36,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"value" : 500,
|
||||
"rarity" : 50
|
||||
@ -54,6 +56,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 80
|
||||
@ -73,6 +76,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 750,
|
||||
"rmg" : {
|
||||
"value" : 750,
|
||||
"rarity" : 50
|
||||
@ -92,6 +96,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"value" : 500,
|
||||
"rarity" : 100
|
||||
@ -110,6 +115,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"value" : 500,
|
||||
"rarity" : 100
|
||||
@ -128,6 +134,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"value" : 500,
|
||||
"rarity" : 50
|
||||
@ -146,6 +153,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 6000,
|
||||
"rmg" : {
|
||||
"value" : 6000,
|
||||
"rarity" : 20
|
||||
@ -167,6 +175,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
"value" : 2000,
|
||||
"rarity" : 500
|
||||
@ -186,6 +195,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2000,
|
||||
"rmg" : {
|
||||
"value" : 2000,
|
||||
"rarity" : 100
|
||||
@ -205,6 +215,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 500
|
||||
@ -224,6 +235,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 50
|
||||
@ -243,6 +255,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 1000
|
||||
@ -263,6 +276,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 3000,
|
||||
"rmg" : {
|
||||
"value" : 3000,
|
||||
"rarity" : 50
|
||||
@ -282,6 +296,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 100
|
||||
@ -301,6 +316,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 100
|
||||
@ -319,6 +335,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 12000,
|
||||
"rmg" : {
|
||||
"value" : 12000,
|
||||
"rarity" : 20
|
||||
@ -338,6 +355,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 100
|
||||
@ -357,6 +375,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 100
|
||||
@ -375,6 +394,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 2500,
|
||||
"rmg" : {
|
||||
"mapLimit" : 100,
|
||||
"value" : 2500,
|
||||
@ -395,6 +415,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1000,
|
||||
"rmg" : {
|
||||
"value" : 1000,
|
||||
"rarity" : 50
|
||||
@ -414,6 +435,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1000,
|
||||
"rmg" : {
|
||||
"value" : 1000,
|
||||
"rarity" : 50
|
||||
@ -433,6 +455,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 1500,
|
||||
"rmg" : {
|
||||
"value" : 1500,
|
||||
"rarity" : 200
|
||||
@ -454,6 +477,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"value" : 100,
|
||||
"rarity" : 100
|
||||
@ -472,6 +496,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -492,6 +517,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -512,6 +538,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -532,6 +559,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -551,6 +579,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -573,6 +602,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"value" : 100,
|
||||
"rarity" : 20
|
||||
@ -591,6 +621,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -611,6 +642,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 200,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 200,
|
||||
@ -631,6 +663,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -651,6 +684,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 100,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 100,
|
||||
@ -670,6 +704,7 @@
|
||||
"types" : {
|
||||
"object" : {
|
||||
"index" : 0,
|
||||
"aiValue" : 500,
|
||||
"rmg" : {
|
||||
"zoneLimit" : 1,
|
||||
"value" : 500,
|
||||
|
@ -13,6 +13,9 @@
|
||||
"name": {
|
||||
"type":"string",
|
||||
},
|
||||
"defaultAiValue": {
|
||||
"type":"number",
|
||||
},
|
||||
|
||||
"handler": {
|
||||
"type":"string",
|
||||
|
@ -13,6 +13,9 @@
|
||||
"name": {
|
||||
"type":"string",
|
||||
},
|
||||
"aiValue": {
|
||||
"type":"number",
|
||||
},
|
||||
|
||||
"sounds": {
|
||||
"type":"object",
|
||||
|
@ -206,6 +206,11 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
|
||||
obj->handlerName = json["handler"].String();
|
||||
obj->base = json["base"];
|
||||
obj->id = selectNextID(json["index"], objects, 256);
|
||||
if(json["defaultAiValue"].isNull())
|
||||
obj->groupDefaultAiValue = boost::none;
|
||||
else
|
||||
obj->groupDefaultAiValue = json["defaultAiValue"].Integer();
|
||||
|
||||
for (auto entry : json["types"].Struct())
|
||||
{
|
||||
loadObjectEntry(entry.first, entry.second, obj);
|
||||
@ -285,6 +290,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::s
|
||||
throw std::runtime_error("Object type handler not found");
|
||||
}
|
||||
|
||||
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const
|
||||
{
|
||||
return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
|
||||
}
|
||||
|
||||
std::set<si32> CObjectClassesHandler::knownObjects() const
|
||||
{
|
||||
std::set<si32> ret;
|
||||
@ -382,6 +392,11 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
|
||||
return objects.at(type)->handlerName;
|
||||
}
|
||||
|
||||
boost::optional<si32> CObjectClassesHandler::getObjGroupAiValue(si32 primaryID) const
|
||||
{
|
||||
return objects.at(primaryID)->groupDefaultAiValue;
|
||||
}
|
||||
|
||||
AObjectTypeHandler::AObjectTypeHandler():
|
||||
type(-1), subtype(-1)
|
||||
{
|
||||
@ -452,6 +467,11 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
|
||||
for(const JsonNode & node : input["sounds"]["removal"].Vector())
|
||||
sounds.removal.push_back(node.String());
|
||||
|
||||
if(input["aiValue"].isNull())
|
||||
aiValue = boost::none;
|
||||
else
|
||||
aiValue = input["aiValue"].Integer();
|
||||
|
||||
initTypeData(input);
|
||||
}
|
||||
|
||||
@ -540,6 +560,11 @@ const RandomMapInfo & AObjectTypeHandler::getRMGInfo()
|
||||
return rmgInfo;
|
||||
}
|
||||
|
||||
boost::optional<si32> AObjectTypeHandler::getAiValue() const
|
||||
{
|
||||
return aiValue;
|
||||
}
|
||||
|
||||
bool AObjectTypeHandler::isStaticObject()
|
||||
{
|
||||
return false; // most of classes are not static
|
||||
|
@ -65,6 +65,27 @@ struct DLL_LINKAGE RandomMapInfo
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE CompoundMapObjectID
|
||||
{
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
|
||||
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
|
||||
|
||||
bool operator<(const CompoundMapObjectID& other) const
|
||||
{
|
||||
if(this->primaryID != other.primaryID)
|
||||
return this->primaryID < other.primaryID;
|
||||
else
|
||||
return this->secondaryID < other.secondaryID;
|
||||
}
|
||||
|
||||
bool operator==(const CompoundMapObjectID& other) const
|
||||
{
|
||||
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE IObjectInfo
|
||||
{
|
||||
public:
|
||||
@ -125,6 +146,8 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
|
||||
std::vector<ObjectTemplate> templates;
|
||||
|
||||
SObjectSounds sounds;
|
||||
|
||||
boost::optional<si32> aiValue;
|
||||
protected:
|
||||
void preInitObject(CGObjectInstance * obj) const;
|
||||
virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
|
||||
@ -163,6 +186,8 @@ public:
|
||||
|
||||
const RandomMapInfo & getRMGInfo();
|
||||
|
||||
boost::optional<si32> getAiValue() const;
|
||||
|
||||
virtual bool isStaticObject();
|
||||
|
||||
virtual void afterLoadFinalization();
|
||||
@ -194,6 +219,10 @@ public:
|
||||
{
|
||||
h & sounds;
|
||||
}
|
||||
if(version >= 789)
|
||||
{
|
||||
h & aiValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -216,6 +245,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
|
||||
|
||||
SObjectSounds sounds;
|
||||
|
||||
boost::optional<si32> groupDefaultAiValue;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & name;
|
||||
@ -231,6 +262,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
|
||||
{
|
||||
h & sounds;
|
||||
}
|
||||
if(version >= 789)
|
||||
{
|
||||
h & groupDefaultAiValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -274,6 +309,7 @@ public:
|
||||
/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
|
||||
TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
|
||||
|
||||
std::string getObjectName(si32 type) const;
|
||||
std::string getObjectName(si32 type, si32 subtype) const;
|
||||
@ -284,7 +320,7 @@ public:
|
||||
/// Returns handler string describing the handler (for use in client)
|
||||
std::string getObjectHandlerName(si32 type) const;
|
||||
|
||||
|
||||
boost::optional<si32> getObjGroupAiValue(si32 primaryID) const; //default AI value of objects belonging to particular primaryID
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../ConstTransitivePtr.h"
|
||||
#include "../GameConstants.h"
|
||||
|
||||
const ui32 SERIALIZATION_VERSION = 788;
|
||||
const ui32 SERIALIZATION_VERSION = 789;
|
||||
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
||||
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user