mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Rebase
This commit is contained in:
		| @@ -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"; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user