mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Nullkiller: stabilisation and fixes
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							bcf8db3d05
						
					
				
				
					commit
					e3c87fb58d
				
			| @@ -39,14 +39,14 @@ Goals::TGoalVec BuyArmyBehavior::getTasks() | ||||
|  | ||||
| 	if(heroes.size()) | ||||
| 	{ | ||||
| 		auto mainHero = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t | ||||
| 		auto mainArmy = vstd::maxElementByFun(heroes, [](const CGHeroInstance * hero) -> uint64_t | ||||
| 		{ | ||||
| 			return hero->getFightingStrength(); | ||||
| 			return hero->getTotalStrength(); | ||||
| 		}); | ||||
|  | ||||
| 		for(auto town : cb->getTownsInfo()) | ||||
| 		{ | ||||
| 			const CGHeroInstance * targetHero = *mainHero; | ||||
| 			const CGHeroInstance * targetHero = *mainArmy; | ||||
|  | ||||
| 			/*if(town->visitingHero) | ||||
| 			{ | ||||
| @@ -62,6 +62,9 @@ Goals::TGoalVec BuyArmyBehavior::getTasks() | ||||
|  | ||||
| 			auto reinforcement = ai->ah->howManyReinforcementsCanBuy(targetHero, town); | ||||
|  | ||||
| 			if(reinforcement) | ||||
| 				reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town); | ||||
|  | ||||
| 			if(reinforcement) | ||||
| 			{ | ||||
| 				tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5))); | ||||
|   | ||||
| @@ -79,6 +79,35 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town) | ||||
| 	return shortestPath.targetHero; | ||||
| } | ||||
|  | ||||
| bool needToRecruitHero(const CGTownInstance * startupTown) | ||||
| { | ||||
| 	if(!ai->canRecruitAnyHero(startupTown)) | ||||
| 		return false; | ||||
|  | ||||
| 	if(!startupTown->garrisonHero && !startupTown->visitingHero) | ||||
| 		return false; | ||||
|  | ||||
| 	auto heroToCheck = startupTown->garrisonHero ? startupTown->garrisonHero.get() : startupTown->visitingHero.get(); | ||||
| 	auto paths = cb->getPathsInfo(heroToCheck); | ||||
|  | ||||
| 	for(auto obj : ai->visitableObjs) | ||||
| 	{ | ||||
| 		if(obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD | ||||
| 			|| obj->ID == Obj::TREASURE_CHEST | ||||
| 			|| obj->ID == Obj::CAMPFIRE | ||||
| 			|| obj->ID == Obj::WATER_WHEEL) | ||||
| 		{ | ||||
| 			auto path = paths->getPathInfo(obj->visitablePos()); | ||||
| 			if(path->accessible == CGPathNode::BLOCKVIS && path->turns != 255) | ||||
| 			{ | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| Goals::TGoalVec StartupBehavior::getTasks() | ||||
| { | ||||
| 	Goals::TGoalVec tasks; | ||||
| @@ -88,8 +117,8 @@ Goals::TGoalVec StartupBehavior::getTasks() | ||||
| 		return tasks; | ||||
|  | ||||
| 	const CGTownInstance * startupTown = towns.front(); | ||||
| 	bool canRecruitHero = ai->canRecruitAnyHero(startupTown); | ||||
| 		 | ||||
| 	bool canRecruitHero = needToRecruitHero(startupTown); | ||||
|  | ||||
| 	if(towns.size() > 1) | ||||
| 	{ | ||||
| 		startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float | ||||
| @@ -131,14 +160,17 @@ Goals::TGoalVec StartupBehavior::getTasks() | ||||
| 				auto garrisonHero = startupTown->garrisonHero.get(); | ||||
| 				auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero); | ||||
|  | ||||
| 				if(garrisonHeroScore > visitingHeroScore) | ||||
| 				if(visitingHeroScore > garrisonHeroScore | ||||
| 					|| ai->ah->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->ah->getHeroRole(visitingHero) == HeroRole::MAIN) | ||||
| 				{ | ||||
| 					if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) | ||||
| 						tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100))); | ||||
| 					if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200) | ||||
| 					{ | ||||
| 						tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) | ||||
| 				{ | ||||
| 					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); | ||||
| 					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100))); | ||||
| 				} | ||||
| 			} | ||||
| 			else if(canRecruitHero) | ||||
| @@ -157,7 +189,7 @@ Goals::TGoalVec StartupBehavior::getTasks() | ||||
| 	{ | ||||
| 		for(const CGTownInstance * town : towns) | ||||
| 		{ | ||||
| 			if(town->garrisonHero) | ||||
| 			if(town->garrisonHero && town->garrisonHero->movement) | ||||
| 				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f))); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -99,7 +99,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| uint64_t getDwellingScore(const CGObjectInstance * target) | ||||
| uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold) | ||||
| { | ||||
| 	auto dwelling = dynamic_cast<const CGDwelling *>(target); | ||||
| 	uint64_t score = 0; | ||||
| @@ -109,17 +109,17 @@ uint64_t getDwellingScore(const CGObjectInstance * target) | ||||
| 		if(creLevel.first && creLevel.second.size()) | ||||
| 		{ | ||||
| 			auto creature = creLevel.second.back().toCreature(); | ||||
| 			if(cb->getResourceAmount().canAfford(creature->cost * creLevel.first)) | ||||
| 			{ | ||||
| 				score += creature->AIValue * creLevel.first; | ||||
| 			} | ||||
| 			if(checkGold &&	!cb->getResourceAmount().canAfford(creature->cost * creLevel.first)) | ||||
| 				continue; | ||||
|  | ||||
| 			score += creature->AIValue * creLevel.first; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return score; | ||||
| } | ||||
|  | ||||
| uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero) | ||||
| uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, bool checkGold) | ||||
| { | ||||
| 	if(!target) | ||||
| 		return 0; | ||||
| @@ -127,17 +127,20 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h | ||||
| 	switch(target->ID) | ||||
| 	{ | ||||
| 	case Obj::TOWN: | ||||
| 		return target->tempOwner == PlayerColor::NEUTRAL ? 5000 : 1000; | ||||
| 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; | ||||
| 	case Obj::CREATURE_BANK: | ||||
| 		return getCreatureBankArmyReward(target, hero); | ||||
| 	case Obj::CREATURE_GENERATOR1: | ||||
| 		return getDwellingScore(target); | ||||
| 	case Obj::CREATURE_GENERATOR2: | ||||
| 	case Obj::CREATURE_GENERATOR3: | ||||
| 	case Obj::CREATURE_GENERATOR4: | ||||
| 		return getDwellingScore(target, checkGold); | ||||
| 	case Obj::CRYPT: | ||||
| 	case Obj::SHIPWRECK: | ||||
| 	case Obj::SHIPWRECK_SURVIVOR: | ||||
| 		return 1500; | ||||
| 	case Obj::ARTIFACT: | ||||
| 		return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() == CArtifact::ART_MAJOR ? 3000 : 1500; | ||||
| 		return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() * 300; | ||||
| 	case Obj::DRAGON_UTOPIA: | ||||
| 		return 10000; | ||||
| 	default: | ||||
| @@ -259,11 +262,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) | ||||
| 	auto hero = heroPtr.get(); | ||||
| 	auto armyTotal = task->evaluationContext.heroStrength; | ||||
| 	double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal; | ||||
| 	int32_t goldReward = getGoldReward(target, hero); | ||||
| 	uint64_t armyReward = getArmyReward(target, hero); | ||||
| 	HeroRole heroRole = ai->ah->getHeroRole(heroPtr); | ||||
| 	float skillReward = getSkillReward(target, hero, heroRole); | ||||
| 	uint64_t danger = task->evaluationContext.danger; | ||||
| 	HeroRole heroRole = ai->ah->getHeroRole(heroPtr); | ||||
| 	int32_t goldReward = getGoldReward(target, hero); | ||||
| 	bool checkGold = danger == 0; | ||||
| 	uint64_t armyReward = getArmyReward(target, hero, checkGold); | ||||
| 	float skillReward = getSkillReward(target, hero, heroRole); | ||||
| 	double result = 0; | ||||
| 	int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0); | ||||
| 	 | ||||
|   | ||||
| @@ -82,6 +82,14 @@ void ExecuteHeroChain::accept(VCAI * ai) | ||||
| 			{ | ||||
| 				ai->nullkiller->setActive(hero); | ||||
|  | ||||
| 				if(node.specialAction) | ||||
| 				{ | ||||
| 					auto specialGoal = node.specialAction->whatToDo(hero); | ||||
|  | ||||
| 					if(specialGoal->isElementar) | ||||
| 						specialGoal->accept(ai); | ||||
| 				} | ||||
|  | ||||
| 				Goals::VisitTile(node.coord).sethero(hero).accept(ai); | ||||
| 			} | ||||
|  | ||||
| @@ -138,15 +146,13 @@ TSubgoal ExchangeSwapTownHeroes::whatToDoToAchieve() | ||||
|  | ||||
| void ExchangeSwapTownHeroes::accept(VCAI * ai) | ||||
| { | ||||
| 	if(!garrisonHero && town->garrisonHero && town->visitingHero) | ||||
| 		throw cannotFulfillGoalException("Invalid configuration. Garrison hero is null."); | ||||
|  | ||||
| 	if(!garrisonHero) | ||||
| 	{ | ||||
| 		if(!town->garrisonHero) | ||||
| 			throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison."); | ||||
| 		 | ||||
| 		cb->swapGarrisonHero(town); | ||||
| 		ai->buildArmyIn(town); | ||||
| 		ai->nullkiller->unlockHero(town->visitingHero.get()); | ||||
| 		logAi->debug("Extracted hero %s from garrison of %s", town->visitingHero->name, town->name); | ||||
|  | ||||
| @@ -162,7 +168,10 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai) | ||||
| 	ai->nullkiller->lockHero(town->garrisonHero.get()); | ||||
|  | ||||
| 	if(town->visitingHero) | ||||
| 	{ | ||||
| 		ai->nullkiller->unlockHero(town->visitingHero.get()); | ||||
| 		makePossibleUpgrades(town->visitingHero); | ||||
| 	} | ||||
|  | ||||
| 	logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name); | ||||
| } | ||||
| @@ -501,7 +501,7 @@ void AINodeStorage::setTownsAndDwellings( | ||||
| 	{ | ||||
| 		uint64_t mask = 1 << actors.size(); | ||||
|  | ||||
| 		if(town->getUpperArmy()->getArmyStrength()) | ||||
| 		if(!town->garrisonHero && town->getUpperArmy()->getArmyStrength()) | ||||
| 		{ | ||||
| 			actors.push_back(std::make_shared<TownGarrisonActor>(town, mask)); | ||||
| 		} | ||||
| @@ -563,7 +563,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( | ||||
|  | ||||
| 	if(source.isInitialPosition) | ||||
| 	{ | ||||
| 		calculateTownPortalTeleportations(source, neighbours); | ||||
| 		calculateTownPortalTeleportations(source, neighbours, pathfinderHelper); | ||||
| 	} | ||||
|  | ||||
| 	return neighbours; | ||||
| @@ -571,7 +571,8 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( | ||||
|  | ||||
| void AINodeStorage::calculateTownPortalTeleportations( | ||||
| 	const PathNodeInfo & source, | ||||
| 	std::vector<CGPathNode *> & neighbours) | ||||
| 	std::vector<CGPathNode *> & neighbours, | ||||
| 	const CPathfinderHelper * pathfinderHelper) | ||||
| { | ||||
| 	SpellID spellID = SpellID::TOWN_PORTAL; | ||||
| 	const CSpell * townPortal = spellID.toSpell(); | ||||
| @@ -594,9 +595,12 @@ void AINodeStorage::calculateTownPortalTeleportations( | ||||
|  | ||||
| 		// TODO: Copy/Paste from TownPortalMechanics | ||||
| 		auto skillLevel = hero->getSpellSchoolLevel(townPortal); | ||||
| 		auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3); | ||||
| 		auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3); | ||||
| 		float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND); | ||||
|  | ||||
| 		if(hero->movement < movementCost) | ||||
| 		movementCost += source.node->cost; | ||||
|  | ||||
| 		if(source.node->moveRemains < movementNeeded) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| @@ -605,7 +609,7 @@ void AINodeStorage::calculateTownPortalTeleportations( | ||||
| 		{ | ||||
| 			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int | ||||
| 			{ | ||||
| 				return hero->visitablePos().dist2dSQ(t->visitablePos()); | ||||
| 				return source.coord.dist2dSQ(t->visitablePos()); | ||||
| 			}); | ||||
|  | ||||
| 			towns = std::vector<const CGTownInstance *>{ nearestTown }; | ||||
| @@ -613,6 +617,7 @@ void AINodeStorage::calculateTownPortalTeleportations( | ||||
|  | ||||
| 		for(const CGTownInstance * targetTown : towns) | ||||
| 		{ | ||||
| 			// TODO: allow to hide visiting hero in garrison | ||||
| 			if(targetTown->visitingHero) | ||||
| 				continue; | ||||
|  | ||||
| @@ -626,9 +631,13 @@ void AINodeStorage::calculateTownPortalTeleportations( | ||||
|  | ||||
| 				AIPathNode * node = nodeOptional.get(); | ||||
|  | ||||
| 				node->theNodeBefore = source.node; | ||||
| 				node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown)); | ||||
| 				node->moveRemains = source.node->moveRemains; | ||||
| 				if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost) | ||||
| 				{ | ||||
| 					node->theNodeBefore = source.node; | ||||
| 					node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown)); | ||||
| 					node->moveRemains = source.node->moveRemains + movementNeeded; | ||||
| 					node->cost = movementCost; | ||||
| 				} | ||||
| 				 | ||||
| 				neighbours.push_back(node); | ||||
| 			} | ||||
| @@ -764,6 +773,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa | ||||
| 			AIPathNodeInfo pathNode; | ||||
| 			pathNode.cost = node->cost; | ||||
| 			pathNode.targetHero = node->actor->hero; | ||||
| 			pathNode.specialAction = node->specialAction; | ||||
| 			pathNode.turns = node->turns; | ||||
| 			pathNode.danger = node->danger; | ||||
| 			pathNode.coord = node->coord; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #define VCMI_TRACE_PATHFINDER 1 | ||||
| #define VCMI_TRACE_PATHFINDER 0 | ||||
|  | ||||
| #include "../../../lib/CPathfinder.h" | ||||
| #include "../../../lib/mapObjects/CGHeroInstance.h" | ||||
| @@ -39,6 +39,7 @@ struct AIPathNodeInfo | ||||
| 	uint64_t danger; | ||||
| 	const CGHeroInstance * targetHero; | ||||
| 	int parentIndex; | ||||
| 	std::shared_ptr<const ISpecialAction> specialAction; | ||||
| }; | ||||
|  | ||||
| struct AIPath | ||||
| @@ -163,7 +164,10 @@ private: | ||||
| 	void cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const; | ||||
| 	void addHeroChain(const std::vector<ExchangeCandidate> & result); | ||||
|  | ||||
| 	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours); | ||||
| 	void calculateTownPortalTeleportations( | ||||
| 		const PathNodeInfo & source, | ||||
| 		std::vector<CGPathNode *> & neighbours, | ||||
| 		const CPathfinderHelper * pathfinderHelper); | ||||
| 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; | ||||
| 	void commit( | ||||
| 		AIPathNode * destination,  | ||||
|   | ||||
| @@ -649,6 +649,12 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vector<Compon | ||||
| 	if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :) | ||||
| 		sel = 1; | ||||
|  | ||||
| 	// TODO: Find better way to understand it is Chest of Treasures | ||||
| 	if(components.size() == 2 && components.front().id == Component::RESOURCE) | ||||
| 	{ | ||||
| 		sel = 1; // for now lets pick gold from a chest. | ||||
| 	} | ||||
|  | ||||
| 	requestActionASAP([=]() | ||||
| 	{ | ||||
| 		answerQuery(askID, sel); | ||||
| @@ -1062,7 +1068,8 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) | ||||
| 		if(h->visitedTown) //we are inside, not just attacking | ||||
| 		{ | ||||
| 			townVisitsThisWeek[h].insert(h->visitedTown); | ||||
| 			if(!h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST) | ||||
| 			ah->updateHeroRoles(); | ||||
| 			if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST) | ||||
| 			{ | ||||
| 				if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1)) | ||||
| 					cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK); | ||||
| @@ -1807,6 +1814,9 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies) | ||||
|  | ||||
| bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| { | ||||
| 	if(h->inTownGarrison && h->visitedTown) | ||||
| 		cb->swapGarrisonHero(h->visitedTown); | ||||
|  | ||||
| 	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj() | ||||
|  | ||||
| 	auto afterMovementCheck = [&]() -> void | ||||
|   | ||||
		Reference in New Issue
	
	Block a user