mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Win/loss conditions based on logical expressions, yet another large
changeset: - victory/defeat will be detected using triggered events - vcmi will convert h3 conditions into set of triggered events - it is possible to either change number of days without towns or even remove this loss condition completely - possibility of custom win/loss text and icons in pregame (no longer connected to win/loss conditions) Still missing: - No interface to pass custom events/victory conditions into the game - AI would benefit from improvemets (handle all victory conditions, select best one to fulfill) - You have X days till defeat message still hardcoded to 7 days
This commit is contained in:
		| @@ -135,86 +135,140 @@ namespace Goals | ||||
|  | ||||
| TSubgoal Win::whatToDoToAchieve() | ||||
| { | ||||
| 	const VictoryCondition &vc = cb->getMapHeader()->victoryCondition; | ||||
| 	EVictoryConditionType::EVictoryConditionType cond = vc.condition; | ||||
|  | ||||
| 	if(!vc.appliesToAI) | ||||
| 	auto toBool = [=](const EventCondition &) | ||||
| 	{ | ||||
| 		//TODO deduce victory from human loss condition | ||||
| 		cond = EVictoryConditionType::WINSTANDARD; | ||||
| 		// TODO: proper implementation | ||||
| 		// Right now even already fulfilled goals will be included into generated list | ||||
| 		// Proper check should test if event condition is already fulfilled | ||||
| 		// Easiest way to do this is to call CGameState::checkForVictory but this function should not be | ||||
| 		// used on client side or in AI code | ||||
| 		return false; | ||||
| 	}; | ||||
|  | ||||
| 	std::vector<EventCondition> goals; | ||||
|  | ||||
| 	for (const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents) | ||||
| 	{ | ||||
| 		//TODO: try to eliminate human player(s) using loss conditions that have isHuman element | ||||
|  | ||||
| 		if (event.effect.type == EventEffect::VICTORY) | ||||
| 		{ | ||||
| 			boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch(cond) | ||||
| 	//TODO: instead of returning first encountered goal AI should generate list of possible subgoals | ||||
| 	for (const EventCondition & goal : goals) | ||||
| 	{ | ||||
| 	case EVictoryConditionType::ARTIFACT: | ||||
| 		return sptr (Goals::GetArtOfType(vc.objectId)); | ||||
| 	case EVictoryConditionType::BEATHERO: | ||||
| 		return sptr (Goals::GetObj(vc.obj->id.getNum())); | ||||
| 	case EVictoryConditionType::BEATMONSTER: | ||||
| 		return sptr (Goals::GetObj(vc.obj->id.getNum())); | ||||
| 	case EVictoryConditionType::BUILDCITY: | ||||
| 		//TODO build castle/capitol | ||||
| 		break; | ||||
| 	case EVictoryConditionType::BUILDGRAIL: | ||||
| 		switch(goal.condition) | ||||
| 		{ | ||||
| 			if(auto h = ai->getHeroWithGrail()) | ||||
| 		case EventCondition::HAVE_ARTIFACT: | ||||
| 			return sptr (Goals::GetArtOfType(goal.objectType)); | ||||
| 		case EventCondition::DESTROY: | ||||
| 			{ | ||||
| 				//hero is in a town that can host Grail | ||||
| 				if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) | ||||
| 				if (goal.object) | ||||
| 				{ | ||||
| 					const CGTownInstance *t = h->visitedTown; | ||||
| 					return sptr (Goals::BuildThis(BuildingID::GRAIL, t)); | ||||
| 					return sptr (Goals::GetObj(goal.object->id.getNum())); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					auto towns = cb->getTownsInfo(); | ||||
| 					towns.erase(boost::remove_if(towns, | ||||
| 										[](const CGTownInstance *t) -> bool | ||||
| 										{ | ||||
| 											return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); | ||||
| 										}), | ||||
| 								towns.end()); | ||||
| 					boost::sort(towns, isCloser); | ||||
| 					if(towns.size()) | ||||
| 					{ | ||||
| 						return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); | ||||
| 					} | ||||
| 					// TODO: destroy all objects of type goal.objectType | ||||
| 					// This situation represents "kill all creatures" condition from H3 | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 			double ratio = 0; | ||||
| 			int3 grailPos = cb->getGrailPos(ratio); | ||||
| 			if(ratio > 0.99) | ||||
| 		case EventCondition::HAVE_BUILDING: | ||||
| 			{ | ||||
| 				return sptr (Goals::DigAtTile(grailPos)); | ||||
| 			} //TODO: use FIND_OBJ | ||||
| 			else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks | ||||
| 			{ | ||||
| 				return sptr (Goals::GetObj(obj->id.getNum())); | ||||
| 			// TODO build other buildings apart from Grail | ||||
| 			// goal.objectType = buidingID to build | ||||
| 			// goal.object = optional, town in which building should be built | ||||
| 			// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions) | ||||
|  | ||||
| 				if (goal.objectType == BuildingID::GRAIL) | ||||
| 				{ | ||||
| 					if(auto h = ai->getHeroWithGrail()) | ||||
| 					{ | ||||
| 						//hero is in a town that can host Grail | ||||
| 						if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) | ||||
| 						{ | ||||
| 							const CGTownInstance *t = h->visitedTown; | ||||
| 							return sptr (Goals::BuildThis(BuildingID::GRAIL, t)); | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							auto towns = cb->getTownsInfo(); | ||||
| 							towns.erase(boost::remove_if(towns, | ||||
| 												[](const CGTownInstance *t) -> bool | ||||
| 												{ | ||||
| 													return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); | ||||
| 												}), | ||||
| 										towns.end()); | ||||
| 							boost::sort(towns, isCloser); | ||||
| 							if(towns.size()) | ||||
| 							{ | ||||
| 								return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 					double ratio = 0; | ||||
| 					// maybe make this check a bit more complex? For example: | ||||
| 					// 0.75 -> dig randomly withing 3 tiles radius | ||||
| 					// 0.85 -> radius now 2 tiles | ||||
| 					// 0.95 -> 1 tile radius, position is fully known | ||||
| 					// AFAIK H3 AI does something like this | ||||
| 					int3 grailPos = cb->getGrailPos(ratio); | ||||
| 					if(ratio > 0.99) | ||||
| 					{ | ||||
| 						return sptr (Goals::DigAtTile(grailPos)); | ||||
| 					} //TODO: use FIND_OBJ | ||||
| 					else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks | ||||
| 						return sptr (Goals::GetObj(obj->id.getNum())); | ||||
| 					else | ||||
| 						return sptr (Goals::Explore()); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			else | ||||
| 				return sptr (Goals::Explore()); | ||||
| 		case EventCondition::CONTROL: | ||||
| 			{ | ||||
| 				if (goal.object) | ||||
| 				{ | ||||
| 					return sptr (Goals::GetObj(goal.object->id.getNum())); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					//TODO: control all objects of type "goal.objectType" | ||||
| 					// Represents H3 condition "Flag all mines" | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		case EventCondition::HAVE_RESOURCES: | ||||
| 			//TODO mines? piles? marketplace? | ||||
| 			//save? | ||||
| 			return sptr (Goals::CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value)); | ||||
| 		case EventCondition::HAVE_CREATURES: | ||||
| 			return sptr (Goals::GatherTroops(goal.objectType, goal.value)); | ||||
| 		case EventCondition::TRANSPORT: | ||||
| 			{ | ||||
| 				//TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it | ||||
| 				// Represents "transport artifact" condition: | ||||
| 				// goal.objectType = type of artifact | ||||
| 				// goal.object = destination-town where artifact should be transported | ||||
| 				break; | ||||
| 			} | ||||
| 		case EventCondition::STANDARD_WIN: | ||||
| 			return sptr (Goals::Conquer()); | ||||
|  | ||||
| 		// Conditions that likely don't need any implementation | ||||
| 		case EventCondition::DAYS_PASSED: | ||||
| 			break; // goal.value = number of days for condition to trigger | ||||
| 		case EventCondition::DAYS_WITHOUT_TOWN: | ||||
| 			break; // goal.value = number of days to trigger this | ||||
| 		case EventCondition::IS_HUMAN: | ||||
| 			break; // Should be only used in calculation of candidates (see toBool lambda) | ||||
| 		default: | ||||
| 			assert(0); | ||||
| 		} | ||||
| 		break; | ||||
| 	case EVictoryConditionType::CAPTURECITY: | ||||
| 		return sptr (Goals::GetObj(vc.obj->id.getNum())); | ||||
| 	case EVictoryConditionType::GATHERRESOURCE: | ||||
|         return sptr (Goals::CollectRes(static_cast<Res::ERes>(vc.objectId), vc.count)); | ||||
| 		//TODO mines? piles? marketplace? | ||||
| 		//save? | ||||
| 		break; | ||||
| 	case EVictoryConditionType::GATHERTROOP: | ||||
| 		return sptr (Goals::GatherTroops(vc.objectId, vc.count)); | ||||
| 		break; | ||||
| 	case EVictoryConditionType::TAKEDWELLINGS: | ||||
| 		break; | ||||
| 	case EVictoryConditionType::TAKEMINES: | ||||
| 		break; | ||||
| 	case EVictoryConditionType::TRANSPORTITEM: | ||||
| 		break; | ||||
| 	case EVictoryConditionType::WINSTANDARD: | ||||
| 		return sptr (Goals::Conquer()); | ||||
| 	default: | ||||
| 		assert(0); | ||||
| 	} | ||||
| 	return sptr (Goals::Invalid()); | ||||
| } | ||||
|   | ||||
| @@ -2107,7 +2107,8 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul | ||||
|  | ||||
| 	if(player == playerID) | ||||
| 	{ | ||||
| 		if(victoryLossCheckResult.loss()) showInfoDialog(CGI->generaltexth->allTexts[95]); | ||||
| 		if(victoryLossCheckResult.loss()) | ||||
| 			showInfoDialog(CGI->generaltexth->allTexts[95]); | ||||
|  | ||||
| 		if(LOCPLINT == this) | ||||
| 		{ | ||||
| @@ -2154,18 +2155,9 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul | ||||
| 	{ | ||||
| 		if(victoryLossCheckResult.loss() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME) //enemy has lost | ||||
| 		{ | ||||
| 			std::string txt; | ||||
| 			if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) | ||||
| 			{ | ||||
| 				txt = CGI->generaltexth->allTexts[8]; // %s's heroes have abandoned him, and he is banished from this land. | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				txt = CGI->generaltexth->allTexts[5]; // %s has been vanquished! | ||||
| 			} | ||||
|  | ||||
| 			boost::algorithm::replace_first(txt, "%s", CGI->generaltexth->capColors[player.getNum()]); | ||||
| 			showInfoDialog(txt,std::vector<CComponent*>(1, new CComponent(CComponent::flag, player.getNum(), 0))); | ||||
| 			std::string str = victoryLossCheckResult.messageToSelf; | ||||
| 			boost::algorithm::replace_first(str, "%s", CGI->generaltexth->capColors[player.getNum()]); | ||||
| 			showInfoDialog(str, std::vector<CComponent*>(1, new CComponent(CComponent::flag, player.getNum(), 0))); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1437,18 +1437,10 @@ void SelectionTab::printMaps(SDL_Surface *to) | ||||
| 			blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to); | ||||
|  | ||||
| 			//victory conditions | ||||
| 			if (currentItem->mapHeader->victoryCondition.condition == EVictoryConditionType::WINSTANDARD) | ||||
| 				temp = 11; | ||||
| 			else | ||||
| 				temp = currentItem->mapHeader->victoryCondition.condition; | ||||
| 			blitAtLoc(CGP->victory->ourImages[temp].bitmap, 306, 117 + line * 25, to); | ||||
| 			blitAtLoc(CGP->victory->ourImages[currentItem->mapHeader->victoryIconIndex].bitmap, 306, 117 + line * 25, to); | ||||
|  | ||||
| 			//loss conditions | ||||
| 			if (currentItem->mapHeader->lossCondition.typeOfLossCon == ELossConditionType::LOSSSTANDARD) | ||||
| 				temp=3; | ||||
| 			else | ||||
| 				temp=currentItem->mapHeader->lossCondition.typeOfLossCon; | ||||
| 			blitAtLoc(CGP->loss->ourImages[temp].bitmap, 339, 117 + line * 25, to); | ||||
| 			blitAtLoc(CGP->loss->ourImages[currentItem->mapHeader->defeatIconIndex].bitmap, 339, 117 + line * 25, to); | ||||
| 		} | ||||
| 		else //if campaign | ||||
| 		{ | ||||
| @@ -2023,26 +2015,14 @@ void InfoCard::showAll(SDL_Surface * to) | ||||
| 				CDefHandler * loss    = CGP ? CGP->loss    : CDefHandler::giveDef("SCNRLOSS.DEF"); | ||||
| 				CDefHandler * victory = CGP ? CGP->victory : CDefHandler::giveDef("SCNRVICT.DEF"); | ||||
|  | ||||
| 				CMapHeader * header = SEL->current->mapHeader.get(); | ||||
| 				//victory conditions | ||||
| 				temp = SEL->current->mapHeader->victoryCondition.condition+1; | ||||
| 				if (temp>20) temp=0; | ||||
| 				std::string sss = CGI->generaltexth->victoryConditions[temp]; | ||||
| 				if (temp && SEL->current->mapHeader->victoryCondition.allowNormalVictory) sss+= "/" + CGI->generaltexth->victoryConditions[0]; | ||||
| 				printAtLoc(sss, 60, 307, FONT_SMALL, Colors::WHITE, to); | ||||
|  | ||||
| 				temp = SEL->current->mapHeader->victoryCondition.condition; | ||||
| 				if (temp>12) temp=11; | ||||
| 				blitAtLoc(victory->ourImages[temp].bitmap, 24, 302, to); //victory cond descr | ||||
| 				printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to); | ||||
| 				blitAtLoc(victory->ourImages[header->victoryIconIndex].bitmap, 24, 302, to); //victory cond descr | ||||
|  | ||||
| 				//loss conditoins | ||||
| 				temp = SEL->current->mapHeader->lossCondition.typeOfLossCon+1; | ||||
| 				if (temp>20) temp=0; | ||||
| 				sss = CGI->generaltexth->lossCondtions[temp]; | ||||
| 				printAtLoc(sss, 60, 366, FONT_SMALL, Colors::WHITE, to); | ||||
|  | ||||
| 				temp=SEL->current->mapHeader->lossCondition.typeOfLossCon; | ||||
| 				if (temp>12) temp=3; | ||||
| 				blitAtLoc(loss->ourImages[temp].bitmap, 24, 359, to); //loss cond | ||||
| 				printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to); | ||||
| 				blitAtLoc(loss->ourImages[header->defeatIconIndex].bitmap, 24, 359, to); //loss cond | ||||
|  | ||||
| 				if (!CGP) | ||||
| 				{ | ||||
| @@ -2987,7 +2967,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb) | ||||
| 			return (a->version<b->version); | ||||
| 			break; | ||||
| 		case _loscon: //by loss conditions | ||||
| 			return (a->lossCondition.typeOfLossCon<b->lossCondition.typeOfLossCon); | ||||
| 			return (a->defeatMessage < b->defeatMessage); | ||||
| 			break; | ||||
| 		case _playerAm: //by player amount | ||||
| 			int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA; | ||||
| @@ -3008,7 +2988,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb) | ||||
| 			return (a->width<b->width); | ||||
| 			break; | ||||
| 		case _viccon: //by victory conditions | ||||
| 			return (a->victoryCondition.condition < b->victoryCondition.condition); | ||||
| 			return (a->victoryMessage < b->victoryMessage); | ||||
| 			break; | ||||
| 		case _name: //by name | ||||
| 			return boost::ilexicographical_compare(a->name, b->name); | ||||
|   | ||||
| @@ -2225,141 +2225,151 @@ bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom, | ||||
|  | ||||
| EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const | ||||
| { | ||||
| 	auto result = checkForVictory(player); | ||||
| 	if (result == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) result = checkForLoss(player); | ||||
| 	return result; | ||||
| 	const std::string & messageWonSelf = VLC->generaltexth->allTexts[659]; | ||||
| 	const std::string & messageWonOther = VLC->generaltexth->allTexts[5]; | ||||
| 	const std::string & messageLostSelf = VLC->generaltexth->allTexts[7]; | ||||
| 	const std::string & messageLostOther = VLC->generaltexth->allTexts[8]; | ||||
|  | ||||
| 	auto evaluateEvent = [=](const EventCondition & condition) | ||||
| 	{ | ||||
| 		return this->checkForVictory(player, condition); | ||||
| 	}; | ||||
|  | ||||
| 	const PlayerState *p = CGameInfoCallback::getPlayer(player); | ||||
|  | ||||
| 	//cheater or tester, but has entered the code... | ||||
| 	if (p->enteredWinningCheatCode) | ||||
| 		return EVictoryLossCheckResult::victory(messageWonSelf, messageWonOther); | ||||
|  | ||||
| 	if (p->enteredLosingCheatCode) | ||||
| 		return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); | ||||
|  | ||||
| 	for (const TriggeredEvent & event : map->triggeredEvents) | ||||
| 	{ | ||||
| 		if ((event.trigger.test(evaluateEvent))) | ||||
| 		{ | ||||
| 			if (event.effect.type == EventEffect::VICTORY) | ||||
| 				return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage); | ||||
|  | ||||
| 			if (event.effect.type == EventEffect::DEFEAT) | ||||
| 				return EVictoryLossCheckResult::defeat(event.onFulfill, event.effect.toOtherMessage); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (checkForStandardLoss(player)) | ||||
| 	{ | ||||
| 		return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); | ||||
| 	} | ||||
| 	return EVictoryLossCheckResult(); | ||||
| } | ||||
|  | ||||
| EVictoryLossCheckResult CGameState::checkForVictory( PlayerColor player ) const | ||||
| bool CGameState::checkForVictory( PlayerColor player, const EventCondition & condition ) const | ||||
| { | ||||
| 	const PlayerState *p = CGameInfoCallback::getPlayer(player); | ||||
| 	if(map->victoryCondition.condition == EVictoryConditionType::WINSTANDARD  ||  map->victoryCondition.allowNormalVictory | ||||
| 		|| (!p->human && !map->victoryCondition.appliesToAI)) //if the special victory condition applies only to human, AI has the standard) | ||||
| 	switch (condition.condition) | ||||
| 	{ | ||||
| 		if(player == checkForStandardWin()) | ||||
| 			return EVictoryLossCheckResult::VICTORY_STANDARD; | ||||
| 	} | ||||
|  | ||||
| 	if (p->enteredWinningCheatCode) | ||||
| 	{ //cheater or tester, but has entered the code... | ||||
| 		if(map->victoryCondition.condition == EVictoryConditionType::WINSTANDARD) | ||||
| 			return EVictoryLossCheckResult::VICTORY_STANDARD; | ||||
| 		else | ||||
| 			return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 	} | ||||
|  | ||||
| 	if(p->human || map->victoryCondition.appliesToAI) | ||||
| 	{ | ||||
|  		switch(map->victoryCondition.condition) | ||||
| 		case EventCondition::STANDARD_WIN: | ||||
| 		{ | ||||
| 			return player == checkForStandardWin(); | ||||
| 		} | ||||
| 		case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact | ||||
| 		{ | ||||
| 		case EVictoryConditionType::ARTIFACT: | ||||
| 			//check if any hero has winning artifact | ||||
| 			for(auto & elem : p->heroes) | ||||
|                 if(elem->hasArt(map->victoryCondition.objectId)) | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		case EVictoryConditionType::GATHERTROOP: | ||||
| 				if(elem->hasArt(condition.objectType)) | ||||
| 					return true; | ||||
| 			return false; | ||||
| 		} | ||||
| 		case EventCondition::HAVE_CREATURES: | ||||
| 		{ | ||||
| 			//check if in players armies there is enough creatures | ||||
| 			int total = 0; //creature counter | ||||
| 			for(size_t i = 0; i < map->objects.size(); i++) | ||||
| 			{ | ||||
| 				//check if in players armies there is enough creatures | ||||
| 				int total = 0; //creature counter | ||||
| 				for(size_t i = 0; i < map->objects.size(); i++) | ||||
| 				const CArmedInstance *ai = nullptr; | ||||
| 				if(map->objects[i] | ||||
| 					&& map->objects[i]->tempOwner == player //object controlled by player | ||||
| 					&&  (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army | ||||
| 				{ | ||||
| 					const CArmedInstance *ai = nullptr; | ||||
| 					if(map->objects[i] | ||||
| 						&& map->objects[i]->tempOwner == player //object controlled by player | ||||
| 						&&  (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army | ||||
| 					{ | ||||
| 						for(auto & elem : ai->Slots()) //iterate through army | ||||
|                             if(elem.second->type->idNumber == map->victoryCondition.objectId) //it's searched creature | ||||
| 								total += elem.second->count; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if(total >= map->victoryCondition.count) | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			} | ||||
| 			break; | ||||
|  | ||||
| 		case EVictoryConditionType::GATHERRESOURCE: | ||||
|             if(p->resources[map->victoryCondition.objectId] >= map->victoryCondition.count) | ||||
| 				return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		case EVictoryConditionType::BUILDCITY: | ||||
| 			{ | ||||
| 				const CGTownInstance *t = static_cast<const CGTownInstance *>(map->victoryCondition.obj); | ||||
|                 if(t->tempOwner == player && t->fortLevel()-1 >= map->victoryCondition.objectId && t->hallLevel()-1 >= map->victoryCondition.count) | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			} | ||||
| 			break; | ||||
|  | ||||
| 		case EVictoryConditionType::BUILDGRAIL: | ||||
| 			for(const CGTownInstance *t : map->towns) | ||||
| 				if((t == map->victoryCondition.obj || !map->victoryCondition.obj) | ||||
| 					&& t->tempOwner == player | ||||
| 					&& t->hasBuilt(BuildingID::GRAIL)) | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			break; | ||||
|  | ||||
| 		case EVictoryConditionType::BEATHERO: | ||||
| 			if(map->victoryCondition.obj->tempOwner >= PlayerColor::PLAYER_LIMIT) //target hero not present on map | ||||
| 				return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			break; | ||||
| 		case EVictoryConditionType::CAPTURECITY: | ||||
| 			{ | ||||
| 				if(map->victoryCondition.obj->tempOwner == player) | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			} | ||||
| 			break; | ||||
| 		case EVictoryConditionType::BEATMONSTER: | ||||
| 			if(!getObj(map->victoryCondition.obj->id)) //target monster not present on map | ||||
| 				return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 			break; | ||||
| 		case EVictoryConditionType::TAKEDWELLINGS: | ||||
| 			for(auto & elem : map->objects) | ||||
| 			{ | ||||
| 				if(elem && elem->tempOwner != player) //check not flagged objs | ||||
| 				{ | ||||
| 					switch(elem->ID) | ||||
| 					{ | ||||
| 					case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR2: | ||||
| 					case Obj::CREATURE_GENERATOR3: case Obj::CREATURE_GENERATOR4: | ||||
| 					case Obj::RANDOM_DWELLING: case Obj::RANDOM_DWELLING_LVL: case Obj::RANDOM_DWELLING_FACTION: | ||||
| 						return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; //found not flagged dwelling - player not won | ||||
| 					} | ||||
| 					for(auto & elem : ai->Slots()) //iterate through army | ||||
| 						if(elem.second->type->idNumber == condition.objectType) //it's searched creature | ||||
| 							total += elem.second->count; | ||||
| 				} | ||||
| 			} | ||||
| 			return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 		case EVictoryConditionType::TAKEMINES: | ||||
| 			for(auto & elem : map->objects) | ||||
| 			return total >= condition.value; | ||||
| 		} | ||||
| 		case EventCondition::HAVE_RESOURCES: | ||||
| 		{ | ||||
| 			return p->resources[condition.objectType] >= condition.value; | ||||
| 		} | ||||
| 		case EventCondition::HAVE_BUILDING: | ||||
| 		{ | ||||
| 			const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object); | ||||
| 			return (t->tempOwner == player && t->hasBuilt(BuildingID(condition.objectType))); | ||||
| 		} | ||||
| 		case EventCondition::DESTROY: | ||||
| 		{ | ||||
| 			if (condition.object) // mode A - destroy specific object of this type | ||||
| 			{ | ||||
| 				if(elem && elem->tempOwner != player) //check not flagged objs | ||||
| 				{ | ||||
| 					switch(elem->ID) | ||||
| 					{ | ||||
| 					case Obj::MINE: case Obj::ABANDONED_MINE: | ||||
| 						return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; //found not flagged mine - player not won | ||||
| 					} | ||||
| 				} | ||||
| 				if (auto hero = dynamic_cast<const CGHeroInstance*>(condition.object)) | ||||
| 					return boost::range::find(gs->map->heroesOnMap, hero) == gs->map->heroesOnMap.end(); | ||||
| 				else | ||||
| 					return getObj(condition.object->id) == nullptr; | ||||
| 			} | ||||
| 			return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 		case EVictoryConditionType::TRANSPORTITEM: | ||||
| 			else | ||||
| 			{ | ||||
| 				const CGTownInstance *t = static_cast<const CGTownInstance *>(map->victoryCondition.obj); | ||||
|                 if((t->visitingHero && t->visitingHero->hasArt(map->victoryCondition.objectId)) | ||||
|                     || (t->garrisonHero && t->garrisonHero->hasArt(map->victoryCondition.objectId))) | ||||
| 				for(auto & elem : map->objects) // mode B - destroy all objects of this type | ||||
| 				{ | ||||
| 					return EVictoryLossCheckResult::VICTORY_SPECIAL; | ||||
| 					if(elem && elem->ID == condition.objectType) | ||||
| 						return false; | ||||
| 				} | ||||
| 				return true; | ||||
| 			} | ||||
| 			break; | ||||
|  		} | ||||
| 		} | ||||
| 		case EventCondition::CONTROL: | ||||
| 		{ | ||||
| 			if (condition.object) // mode A - flag one specific object, like town | ||||
| 			{ | ||||
| 				return condition.object->tempOwner == player; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				for(auto & elem : map->objects) // mode B - flag all objects of this type | ||||
| 				{ | ||||
| 					 //check not flagged objs | ||||
| 					if(elem && elem->tempOwner != player && elem->ID == condition.objectType) | ||||
| 						return false; | ||||
| 				} | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 		case EventCondition::TRANSPORT: | ||||
| 		{ | ||||
| 			const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object); | ||||
| 			if((t->visitingHero && t->visitingHero->hasArt(condition.objectType)) | ||||
| 				|| (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType))) | ||||
| 			{ | ||||
| 				return true; | ||||
| 			} | ||||
| 			return false; | ||||
| 		} | ||||
| 		case EventCondition::DAYS_PASSED: | ||||
| 		{ | ||||
| 			return gs->day > condition.value; | ||||
| 		} | ||||
| 		case EventCondition::IS_HUMAN: | ||||
| 		{ | ||||
| 			return p->human ? condition.value == 1 : condition.value == 0; | ||||
| 		} | ||||
| 		case EventCondition::DAYS_WITHOUT_TOWN: | ||||
| 		{ | ||||
| 			if (p->daysWithoutCastle) | ||||
| 				return p->daysWithoutCastle.get() >= condition.value; | ||||
| 			else | ||||
| 				return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; | ||||
| 	assert(0); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| PlayerColor CGameState::checkForStandardWin() const | ||||
| @@ -2597,46 +2607,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) | ||||
| #undef FILL_FIELD | ||||
| } | ||||
|  | ||||
| EVictoryLossCheckResult CGameState::checkForLoss( PlayerColor player ) const | ||||
| { | ||||
| 	const PlayerState *p = CGameInfoCallback::getPlayer(player); | ||||
| 	//if(map->lossCondition.typeOfLossCon == lossStandard) | ||||
| 		if(checkForStandardLoss(player)) | ||||
| 			return EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS; | ||||
|  | ||||
| 	if (p->enteredLosingCheatCode) | ||||
| 	{ | ||||
| 		return EVictoryLossCheckResult::LOSS_SPECIAL; | ||||
| 	} | ||||
|  | ||||
| 	if(p->human) //special loss condition applies only to human player | ||||
| 	{ | ||||
| 		switch(map->lossCondition.typeOfLossCon) | ||||
| 		{ | ||||
| 		case ELossConditionType::LOSSCASTLE: | ||||
| 		case ELossConditionType::LOSSHERO: | ||||
| 			{ | ||||
| 				const CGObjectInstance *obj = map->lossCondition.obj; | ||||
| 				assert(obj); | ||||
| 				if(obj->tempOwner != player) | ||||
| 					return EVictoryLossCheckResult::LOSS_SPECIAL; | ||||
| 			} | ||||
| 			break; | ||||
| 		case ELossConditionType::TIMEEXPIRES: | ||||
| 			if(map->lossCondition.timeLimit < day) | ||||
| 				return EVictoryLossCheckResult::LOSS_SPECIAL; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(p->towns.empty() && p->daysWithoutCastle && *p->daysWithoutCastle >= 6 && currentPlayer != player) | ||||
| 	{ | ||||
| 		return EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER; | ||||
| 	} | ||||
|  | ||||
| 	return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; | ||||
| } | ||||
|  | ||||
| std::map<ui32, ConstTransitivePtr<CGHeroInstance> > CGameState::unusedHeroesFromPool() | ||||
| { | ||||
| 	std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = hpool.heroesPool; | ||||
| @@ -3479,21 +3449,16 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance | ||||
| 	allowEmbarkAndDisembark = true; | ||||
| } | ||||
|  | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::NO_VICTORY_OR_LOSS = EVictoryLossCheckResult(0); | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::VICTORY_STANDARD = EVictoryLossCheckResult(1); | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::VICTORY_SPECIAL = EVictoryLossCheckResult(2); | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS = EVictoryLossCheckResult(3); | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER = EVictoryLossCheckResult(4); | ||||
| const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_SPECIAL = EVictoryLossCheckResult(5); | ||||
|  | ||||
| EVictoryLossCheckResult::EVictoryLossCheckResult() : intValue(0) | ||||
| EVictoryLossCheckResult::EVictoryLossCheckResult() : | ||||
| 	intValue(0) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue) : intValue(intValue) | ||||
| EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers) : | ||||
| 	messageToSelf(toSelf), | ||||
| 	messageToOthers(toOthers), | ||||
| 	intValue(intValue) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| bool EVictoryLossCheckResult::operator==(EVictoryLossCheckResult const & other) const | ||||
| @@ -3508,27 +3473,31 @@ bool EVictoryLossCheckResult::operator!=(EVictoryLossCheckResult const & other) | ||||
|  | ||||
| bool EVictoryLossCheckResult::victory() const | ||||
| { | ||||
| 	return *this == VICTORY_STANDARD || *this == VICTORY_SPECIAL; | ||||
| 	return intValue == VICTORY; | ||||
| } | ||||
|  | ||||
| bool EVictoryLossCheckResult::loss() const | ||||
| { | ||||
| 	return !victory(); | ||||
| 	return intValue == DEFEAT; | ||||
| } | ||||
|  | ||||
| std::string EVictoryLossCheckResult::toString() const | ||||
| EVictoryLossCheckResult EVictoryLossCheckResult::invert() | ||||
| { | ||||
| 	if(*this == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) return "No victory or loss"; | ||||
| 	else if(*this == EVictoryLossCheckResult::VICTORY_STANDARD) return "Victory standard"; | ||||
| 	else if(*this == EVictoryLossCheckResult::VICTORY_SPECIAL) return "Victory special"; | ||||
| 	else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS) return "Loss standard heroes and towns"; | ||||
| 	else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) return "Loss standard towns and time over"; | ||||
| 	else if(*this == EVictoryLossCheckResult::LOSS_SPECIAL) return "Loss special"; | ||||
| 	else return "Unknown type"; | ||||
| 	return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf); | ||||
| } | ||||
|  | ||||
| EVictoryLossCheckResult EVictoryLossCheckResult::victory(std::string toSelf, std::string toOthers) | ||||
| { | ||||
| 	return EVictoryLossCheckResult(VICTORY, toSelf, toOthers); | ||||
| } | ||||
|  | ||||
| EVictoryLossCheckResult EVictoryLossCheckResult::defeat(std::string toSelf, std::string toOthers) | ||||
| { | ||||
| 	return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers); | ||||
| } | ||||
|  | ||||
| std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult) | ||||
| { | ||||
| 	os << victoryLossCheckResult.toString(); | ||||
| 	os << victoryLossCheckResult.messageToSelf; | ||||
| 	return os; | ||||
| } | ||||
|   | ||||
| @@ -357,28 +357,34 @@ struct BattleInfo; | ||||
| class DLL_LINKAGE EVictoryLossCheckResult | ||||
| { | ||||
| public: | ||||
| 	static const EVictoryLossCheckResult NO_VICTORY_OR_LOSS; | ||||
| 	static const EVictoryLossCheckResult VICTORY_STANDARD; | ||||
| 	static const EVictoryLossCheckResult VICTORY_SPECIAL; | ||||
| 	static const EVictoryLossCheckResult LOSS_STANDARD_HEROES_AND_TOWNS; | ||||
| 	static const EVictoryLossCheckResult LOSS_STANDARD_TOWNS_AND_TIME_OVER; | ||||
| 	static const EVictoryLossCheckResult LOSS_SPECIAL; | ||||
| 	static EVictoryLossCheckResult victory(std::string toSelf, std::string toOthers); | ||||
| 	static EVictoryLossCheckResult defeat(std::string toSelf, std::string toOthers); | ||||
|  | ||||
| 	EVictoryLossCheckResult(); | ||||
| 	bool operator==(EVictoryLossCheckResult const & other) const; | ||||
| 	bool operator!=(EVictoryLossCheckResult const & other) const; | ||||
| 	bool victory() const; | ||||
| 	bool loss() const; | ||||
| 	std::string toString() const; | ||||
|  | ||||
| 	EVictoryLossCheckResult invert(); | ||||
|  | ||||
| 	std::string messageToSelf; | ||||
| 	std::string messageToOthers; | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & intValue; | ||||
| 		h & intValue & messageToSelf & messageToOthers; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	EVictoryLossCheckResult(si32 intValue); | ||||
| 	si32 intValue; | ||||
| 	enum EResult | ||||
| 	{ | ||||
| 		DEFEAT = -1, | ||||
| 		INGAME =  0, | ||||
| 		VICTORY= +1 | ||||
| 	}; | ||||
|  | ||||
| 	EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers); | ||||
| 	si32 intValue; // uses EResult | ||||
| }; | ||||
|  | ||||
| DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult); | ||||
| @@ -426,7 +432,13 @@ public: | ||||
| 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists | ||||
| 	int3 guardingCreaturePosition (int3 pos) const; | ||||
| 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const; | ||||
|  | ||||
| 	// ----- victory, loss condition checks ----- | ||||
|  | ||||
| 	EVictoryLossCheckResult checkForVictoryAndLoss(PlayerColor player) const; | ||||
| 	bool checkForVictory(PlayerColor player, const EventCondition & condition) const; //checks if given player is winner | ||||
| 	PlayerColor checkForStandardWin() const; //returns color of player that accomplished standard victory conditions or 255 (NEUTRAL) if no winner | ||||
| 	bool checkForStandardLoss(PlayerColor player) const; //checks if given player lost the game | ||||
|  | ||||
| 	void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild | ||||
| 	std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns | ||||
| @@ -476,13 +488,6 @@ private: | ||||
| 	void initMapObjects(); | ||||
| 	void initVisitingAndGarrisonedHeroes(); | ||||
|  | ||||
| 	// ----- victory, loss condition checks ----- | ||||
|  | ||||
| 	EVictoryLossCheckResult checkForVictory(PlayerColor player) const; //checks if given player is winner | ||||
| 	EVictoryLossCheckResult checkForLoss(PlayerColor player) const; //checks if given player is loser | ||||
| 	PlayerColor checkForStandardWin() const; //returns color of player that accomplished standard victory conditions or 255 (NEUTRAL) if no winner | ||||
| 	bool checkForStandardLoss(PlayerColor player) const; //checks if given player lost the game | ||||
|  | ||||
| 	// ----- bonus system handling ----- | ||||
|  | ||||
| 	void buildBonusSystemTree(); | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| #include "CDefObjInfoHandler.h" | ||||
| #include "JsonNode.h" | ||||
| #include "filesystem/Filesystem.h" | ||||
| #include "filesystem/AdapterLoaders.h" | ||||
| #include "filesystem/CFilesystemLoader.h" | ||||
|  | ||||
| #include "CCreatureHandler.h" | ||||
| #include "CArtHandler.h" | ||||
|   | ||||
| @@ -290,17 +290,6 @@ public: | ||||
|  | ||||
| ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill) | ||||
|  | ||||
| namespace EVictoryConditionType | ||||
| { | ||||
| 	enum EVictoryConditionType { ARTIFACT, GATHERTROOP, GATHERRESOURCE, BUILDCITY, BUILDGRAIL, BEATHERO, | ||||
| 		CAPTURECITY, BEATMONSTER, TAKEDWELLINGS, TAKEMINES, TRANSPORTITEM, WINSTANDARD = 255 }; | ||||
| } | ||||
|  | ||||
| namespace ELossConditionType | ||||
| { | ||||
| 	enum ELossConditionType { LOSSCASTLE, LOSSHERO, TIMEEXPIRES, LOSSSTANDARD = 255 }; | ||||
| } | ||||
|  | ||||
| namespace EAlignment | ||||
| { | ||||
| 	enum EAlignment { GOOD, EVIL, NEUTRAL }; | ||||
|   | ||||
| @@ -9,6 +9,7 @@ namespace LogicalExpressionDetail | ||||
| 	template<typename ContainedClass> | ||||
| 	class ExpressionBase | ||||
| 	{ | ||||
| 	public: | ||||
| 		/// Possible logical operations, mostly needed to create different types for boost::variant | ||||
| 		enum EOperations | ||||
| 		{ | ||||
| @@ -16,7 +17,6 @@ namespace LogicalExpressionDetail | ||||
| 			ALL_OF, | ||||
| 			NONE_OF | ||||
| 		}; | ||||
| 	public: | ||||
| 		template<EOperations tag> class Element; | ||||
|  | ||||
| 		typedef Element<ANY_OF> OperatorAny; | ||||
| @@ -145,6 +145,44 @@ namespace LogicalExpressionDetail | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	/// Simple foreach visitor | ||||
| 	template <typename ContainedClass> | ||||
| 	class ForEachVisitor : public boost::static_visitor<void> | ||||
| 	{ | ||||
| 		typedef ExpressionBase<ContainedClass> Base; | ||||
|  | ||||
| 		std::function<void(typename Base::Value &)> visitor; | ||||
|  | ||||
| 	public: | ||||
| 		ForEachVisitor(std::function<void(typename Base::Value &)> visitor): | ||||
| 			visitor(visitor) | ||||
| 		{} | ||||
|  | ||||
| 		//FIXME: duplicated code | ||||
| 		void operator()(typename Base::OperatorAny & element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 		} | ||||
|  | ||||
| 		void operator()(typename Base::OperatorAll & element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 		} | ||||
|  | ||||
| 		void operator()(typename Base::OperatorNone & element) const | ||||
| 		{ | ||||
| 			for (auto & entry : element.expressions) | ||||
| 				boost::apply_visitor(*this, entry); | ||||
| 		} | ||||
|  | ||||
| 		void operator()(typename Base::Value & element) const | ||||
| 		{ | ||||
| 			visitor(element); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	/// Json parser for expressions | ||||
| 	template <typename ContainedClass> | ||||
| 	class Reader | ||||
| @@ -289,6 +327,18 @@ public: | ||||
| 		std::swap(data, expr.data); | ||||
| 	} | ||||
|  | ||||
| 	Variant get() | ||||
| 	{ | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| 	/// Simple visitor that visits all entries in expression | ||||
| 	void forEach(std::function<void(Value &)> visitor) | ||||
| 	{ | ||||
| 		LogicalExpressionDetail::ForEachVisitor<Value> testVisitor(visitor); | ||||
| 		boost::apply_visitor(testVisitor, data); | ||||
| 	} | ||||
|  | ||||
| 	/// calculates if expression evaluates to "true". | ||||
| 	/// Note: empty expressions always return true | ||||
| 	bool test(std::function<bool(const Value &)> toBool) const | ||||
|   | ||||
| @@ -1612,7 +1612,8 @@ DLL_LINKAGE void YourTurn::applyGs( CGameState *gs ) | ||||
| 	auto & playerState = gs->players[player]; | ||||
| 	if(playerState.towns.empty()) | ||||
| 	{ | ||||
| 		if(playerState.daysWithoutCastle) ++(*playerState.daysWithoutCastle); | ||||
| 		if(playerState.daysWithoutCastle) | ||||
| 			++(*playerState.daysWithoutCastle); | ||||
| 		else playerState.daysWithoutCastle = 0; | ||||
| 	} | ||||
| 	else | ||||
|   | ||||
| @@ -46,18 +46,18 @@ std::unordered_set<ResourceID> CMappedFileLoader::getFilteredFiles(std::function | ||||
|  | ||||
| CFilesystemList::CFilesystemList() | ||||
| { | ||||
| 	loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >; | ||||
| 	//loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >; | ||||
| } | ||||
|  | ||||
| CFilesystemList::~CFilesystemList() | ||||
| { | ||||
| 	delete loaders; | ||||
| 	//delete loaders; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceName) const | ||||
| { | ||||
| 	// load resource from last loader that have it (last overriden version) | ||||
| 	for (auto & loader : boost::adaptors::reverse(*loaders)) | ||||
| 	for (auto & loader : boost::adaptors::reverse(loaders)) | ||||
| 	{ | ||||
| 		if (loader->existsResource(resourceName)) | ||||
| 			return loader->load(resourceName); | ||||
| @@ -69,7 +69,7 @@ std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceN | ||||
|  | ||||
| bool CFilesystemList::existsResource(const ResourceID & resourceName) const | ||||
| { | ||||
| 	for (auto & loader : *loaders) | ||||
| 	for (auto & loader : loaders) | ||||
| 		if (loader->existsResource(resourceName)) | ||||
| 			return true; | ||||
| 	return false; | ||||
| @@ -91,7 +91,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b | ||||
| { | ||||
| 	std::unordered_set<ResourceID> ret; | ||||
|  | ||||
| 	for (auto & loader : *loaders) | ||||
| 	for (auto & loader : loaders) | ||||
| 		for (auto & entry : loader->getFilteredFiles(filter)) | ||||
| 			ret.insert(entry); | ||||
|  | ||||
| @@ -101,7 +101,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b | ||||
| bool CFilesystemList::createResource(std::string filename, bool update) | ||||
| { | ||||
| 	logGlobal->traceStream()<< "Creating " << filename; | ||||
| 	for (auto & loader : boost::adaptors::reverse(*loaders)) | ||||
| 	for (auto & loader : boost::adaptors::reverse(loaders)) | ||||
| 	{ | ||||
| 		if (writeableLoaders.count(loader.get()) != 0                       // writeable, | ||||
| 			&& loader->createResource(filename, update))          // successfully created | ||||
| @@ -123,7 +123,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName | ||||
| { | ||||
| 	std::vector<const ISimpleResourceLoader *> ret; | ||||
|  | ||||
| 	for (auto & loader : *loaders) | ||||
| 	for (auto & loader : loaders) | ||||
| 		boost::range::copy(loader->getResourcesWithName(resourceName), std::back_inserter(ret)); | ||||
|  | ||||
| 	return ret; | ||||
| @@ -131,7 +131,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName | ||||
|  | ||||
| void CFilesystemList::addLoader(ISimpleResourceLoader * loader, bool writeable) | ||||
| { | ||||
| 	loaders->push_back(std::unique_ptr<ISimpleResourceLoader>(loader)); | ||||
| 	loaders.push_back(std::unique_ptr<ISimpleResourceLoader>(loader)); | ||||
| 	if (writeable) | ||||
| 		writeableLoaders.insert(loader); | ||||
| } | ||||
|   | ||||
| @@ -54,7 +54,7 @@ private: | ||||
|  | ||||
| class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader | ||||
| { | ||||
| 	std::vector<std::unique_ptr<ISimpleResourceLoader> >* loaders; | ||||
| 	std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders; | ||||
|  | ||||
| 	std::set<ISimpleResourceLoader *> writeableLoaders; | ||||
|  | ||||
|   | ||||
| @@ -3,9 +3,11 @@ | ||||
|  | ||||
| #include "../CArtHandler.h" | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../CCreatureHandler.h" | ||||
| #include "../CTownHandler.h" | ||||
| #include "../CHeroHandler.h" | ||||
| #include "../CDefObjInfoHandler.h" | ||||
| #include "../CGeneralTextHandler.h" | ||||
| #include "../CSpellHandler.h" | ||||
| #include "CMapEditManager.h" | ||||
|  | ||||
| @@ -59,17 +61,13 @@ bool PlayerInfo::hasCustomMainHero() const | ||||
| 	return !mainCustomHeroName.empty() && mainCustomHeroPortrait != -1; | ||||
| } | ||||
|  | ||||
| LossCondition::LossCondition() : typeOfLossCon(ELossConditionType::LOSSSTANDARD), | ||||
| 	pos(int3(-1, -1, -1)), timeLimit(-1), obj(nullptr) | ||||
| EventCondition::EventCondition(EWinLoseType condition): | ||||
| 	object(nullptr), | ||||
| 	value(-1), | ||||
| 	objectType(-1), | ||||
| 	position(-1, -1, -1), | ||||
| 	condition(condition) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| VictoryCondition::VictoryCondition() : condition(EVictoryConditionType::WINSTANDARD), | ||||
| 	allowNormalVictory(false), appliesToAI(false), pos(int3(-1, -1, -1)), objectId(0), | ||||
| 	count(0), obj(nullptr) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| DisposedHero::DisposedHero() : heroId(0), portrait(255), players(0) | ||||
| @@ -146,9 +144,44 @@ const int CMapHeader::MAP_SIZE_MIDDLE = 72; | ||||
| const int CMapHeader::MAP_SIZE_LARGE = 108; | ||||
| const int CMapHeader::MAP_SIZE_XLARGE = 144; | ||||
|  | ||||
| void CMapHeader::setupEvents() | ||||
| { | ||||
| 	EventCondition victoryCondition(EventCondition::STANDARD_WIN); | ||||
| 	EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN); | ||||
| 	defeatCondition.value = 7; | ||||
|  | ||||
| 	//Victory condition - defeat all | ||||
| 	TriggeredEvent standardVictory; | ||||
| 	standardVictory.effect.type = EventEffect::VICTORY; | ||||
| 	standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; | ||||
| 	standardVictory.identifier = "standardVictory"; | ||||
| 	standardVictory.description = ""; // TODO: display in quest window | ||||
| 	standardVictory.onFulfill = VLC->generaltexth->allTexts[659]; | ||||
| 	standardVictory.trigger = EventExpression(victoryCondition); | ||||
|  | ||||
| 	//Loss condition - 7 days without town | ||||
| 	TriggeredEvent standardDefeat; | ||||
| 	standardDefeat.effect.type = EventEffect::DEFEAT; | ||||
| 	standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8]; | ||||
| 	standardDefeat.identifier = "standardDefeat"; | ||||
| 	standardDefeat.description = ""; // TODO: display in quest window | ||||
| 	standardDefeat.onFulfill = VLC->generaltexth->allTexts[7]; | ||||
| 	standardDefeat.trigger = EventExpression(defeatCondition); | ||||
|  | ||||
| 	triggeredEvents.push_back(standardVictory); | ||||
| 	triggeredEvents.push_back(standardDefeat); | ||||
|  | ||||
| 	victoryIconIndex = 11; | ||||
| 	victoryMessage = VLC->generaltexth->victoryConditions[0]; | ||||
|  | ||||
| 	defeatIconIndex = 3; | ||||
| 	defeatMessage = VLC->generaltexth->lossCondtions[0]; | ||||
| } | ||||
|  | ||||
| CMapHeader::CMapHeader() : version(EMapFormat::SOD), height(72), width(72), | ||||
| 	twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false) | ||||
| { | ||||
| 	setupEvents(); | ||||
| 	allowedHeroes = VLC->heroh->getDefaultAllowed(); | ||||
| 	players.resize(PlayerColor::PLAYER_LIMIT_I); | ||||
| } | ||||
| @@ -272,29 +305,75 @@ bool CMap::isWaterTile(const int3 &pos) const | ||||
| 	return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER; | ||||
| } | ||||
|  | ||||
| const CGObjectInstance * CMap::getObjectiveObjectFrom(int3 pos, bool lookForHero) | ||||
| const CGObjectInstance * CMap::getObjectiveObjectFrom(int3 pos, Obj::EObj type) | ||||
| { | ||||
| 	const std::vector<CGObjectInstance *> & objs = getTile(pos).visitableObjects; | ||||
| 	assert(objs.size()); | ||||
| 	if(objs.size() > 1 && lookForHero && objs.front()->ID != Obj::HERO) | ||||
| 	for (CGObjectInstance * object : getTile(pos).visitableObjects) | ||||
| 	{ | ||||
| 		assert(objs.back()->ID == Obj::HERO); | ||||
| 		return objs.back(); | ||||
| 		if (object->ID == type) | ||||
| 			return object; | ||||
| 	} | ||||
| 	else | ||||
| 		return objs.front(); | ||||
| 	// possibly may trigger for empty placeholders in campaigns | ||||
| 	logGlobal->warnStream() << "Failed to find object of type " << int(type) << " at " << pos; | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| void CMap::checkForObjectives() | ||||
| { | ||||
| 	if(isInTheMap(victoryCondition.pos)) | ||||
| 	// NOTE: probably should be moved to MapFormatH3M.cpp | ||||
| 	for (TriggeredEvent & event : triggeredEvents) | ||||
| 	{ | ||||
| 		victoryCondition.obj = getObjectiveObjectFrom(victoryCondition.pos, victoryCondition.condition == EVictoryConditionType::BEATHERO); | ||||
| 	} | ||||
| 		auto patcher = [&](EventCondition & cond) | ||||
| 		{ | ||||
| 			switch (cond.condition) | ||||
| 			{ | ||||
| 				break; case EventCondition::HAVE_ARTIFACT: | ||||
| 					boost::algorithm::replace_first(event.onFulfill, "%s", VLC->arth->artifacts[cond.objectType]->Name()); | ||||
|  | ||||
| 	if(isInTheMap(lossCondition.pos)) | ||||
| 	{ | ||||
| 		lossCondition.obj = getObjectiveObjectFrom(lossCondition.pos, lossCondition.typeOfLossCon == ELossConditionType::LOSSHERO); | ||||
| 				break; case EventCondition::HAVE_CREATURES: | ||||
| 					boost::algorithm::replace_first(event.onFulfill, "%s", VLC->creh->creatures[cond.objectType]->nameSing); | ||||
| 					boost::algorithm::replace_first(event.onFulfill, "%d", boost::lexical_cast<std::string>(cond.value)); | ||||
|  | ||||
| 				break; case EventCondition::HAVE_RESOURCES: | ||||
| 					boost::algorithm::replace_first(event.onFulfill, "%s", VLC->generaltexth->restypes[cond.objectType]); | ||||
| 					boost::algorithm::replace_first(event.onFulfill, "%d", boost::lexical_cast<std::string>(cond.value)); | ||||
|  | ||||
| 				break; case EventCondition::HAVE_BUILDING: | ||||
| 					if (isInTheMap(cond.position)) | ||||
| 						cond.object = getObjectiveObjectFrom(cond.position, Obj::TOWN); | ||||
|  | ||||
| 				break; case EventCondition::CONTROL: | ||||
| 					if (isInTheMap(cond.position)) | ||||
| 						cond.object = getObjectiveObjectFrom(cond.position, Obj::EObj(cond.objectType)); | ||||
|  | ||||
| 					if (cond.object) | ||||
| 					{ | ||||
| 						const CGTownInstance *town = dynamic_cast<const CGTownInstance*>(cond.object); | ||||
| 						if (town) | ||||
| 							boost::algorithm::replace_first(event.onFulfill, "%s", town->name); | ||||
| 						const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object); | ||||
| 						if (hero) | ||||
| 							boost::algorithm::replace_first(event.onFulfill, "%s", hero->name); | ||||
| 					} | ||||
|  | ||||
| 				break; case EventCondition::DESTROY: | ||||
| 					if (isInTheMap(cond.position)) | ||||
| 						cond.object = getObjectiveObjectFrom(cond.position, Obj::EObj(cond.objectType)); | ||||
|  | ||||
| 					if (cond.object) | ||||
| 					{ | ||||
| 						const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object); | ||||
| 						if (hero) | ||||
| 							boost::algorithm::replace_first(event.onFulfill, "%s", hero->name); | ||||
| 					} | ||||
| 				break; case EventCondition::TRANSPORT: | ||||
| 					cond.object = getObjectiveObjectFrom(cond.position, Obj::TOWN); | ||||
| 				//break; case EventCondition::DAYS_PASSED: | ||||
| 				//break; case EventCondition::IS_HUMAN: | ||||
| 				//break; case EventCondition::DAYS_WITHOUT_TOWN: | ||||
| 				//break; case EventCondition::STANDARD_WIN: | ||||
| 			} | ||||
| 		}; | ||||
| 		event.trigger.forEach(patcher); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
| #include "../ResourceSet.h" | ||||
| #include "../int3.h" | ||||
| #include "../GameConstants.h" | ||||
| #include "../LogicalExpression.h" | ||||
|  | ||||
| class CArtifactInstance; | ||||
| class CGDefInfo; | ||||
| @@ -104,45 +105,82 @@ struct DLL_LINKAGE PlayerInfo | ||||
| }; | ||||
|  | ||||
| /// The loss condition describes the condition to lose the game. (e.g. lose all own heroes/castles) | ||||
| struct DLL_LINKAGE LossCondition | ||||
| struct DLL_LINKAGE EventCondition | ||||
| { | ||||
| 	LossCondition(); | ||||
| 	enum EWinLoseType { | ||||
| 		HAVE_ARTIFACT,     // type - required artifact | ||||
| 		HAVE_CREATURES,    // type - creatures to collect, value - amount to collect | ||||
| 		HAVE_RESOURCES,    // type - resource ID, value - amount to collect | ||||
| 		HAVE_BUILDING,     // position - town, optional, type - building to build | ||||
| 		CONTROL,           // position - position of object, optional, type - type of object | ||||
| 		DESTROY,           // position - position of object, optional, type - type of object | ||||
| 		TRANSPORT,         // position - where artifact should be transported, type - type of artifact | ||||
| 		DAYS_PASSED,       // value - number of days from start of the game | ||||
| 		IS_HUMAN,          // value - 0 = player is AI, 1 = player is human | ||||
| 		DAYS_WITHOUT_TOWN, // value - how long player can live without town, 0=instakill | ||||
| 		STANDARD_WIN       // normal defeat all enemies condition | ||||
| 	}; | ||||
|  | ||||
| 	ELossConditionType::ELossConditionType typeOfLossCon; | ||||
| 	int3 pos; /// the position of an object which mustn't be lost | ||||
| 	si32 timeLimit; /// time limit in days, -1 if not used | ||||
| 	const CGObjectInstance * obj; | ||||
| 	EventCondition(EWinLoseType condition = STANDARD_WIN); | ||||
|  | ||||
| 	const CGObjectInstance * object; // object that was at specified position on start | ||||
| 	si32 value; | ||||
| 	si32 objectType; | ||||
| 	int3 position; | ||||
| 	EWinLoseType condition; | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & typeOfLossCon & pos & timeLimit & obj; | ||||
| 		h & object & value & objectType & position & condition; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /// The victory condition describes the condition to win the game. (e.g. defeat all enemy heroes/castles, | ||||
| /// receive a specific artifact, ...) | ||||
| struct DLL_LINKAGE VictoryCondition | ||||
| { | ||||
| 	VictoryCondition(); | ||||
| typedef LogicalExpression<EventCondition> EventExpression; | ||||
|  | ||||
| 	EVictoryConditionType::EVictoryConditionType condition; | ||||
| 	bool allowNormalVictory; /// true if a normal victory is allowed (defeat all enemy towns, heroes) | ||||
| 	bool appliesToAI; | ||||
| 	/// pos of city to upgrade (3); pos of town to build grail, {-1,-1,-1} if not relevant (4); hero pos (5); town pos(6); | ||||
| 	///	monster pos (7); destination pos(8) | ||||
| 	int3 pos; | ||||
| 	/// artifact ID (0); monster ID (1); resource ID (2); needed fort level in upgraded town (3); artifact ID (8) | ||||
| 	si32 objectId; | ||||
| 	/// needed count for creatures (1) / resource (2); upgraded town hall level (3); | ||||
| 	si32 count; | ||||
| 	/// object of specific monster / city / hero instance (nullptr if not used); set during map parsing | ||||
| 	const CGObjectInstance * obj; | ||||
| struct EventEffect | ||||
| { | ||||
| 	enum EType | ||||
| 	{ | ||||
| 		VICTORY, | ||||
| 		DEFEAT | ||||
| 	}; | ||||
|  | ||||
| 	/// effect type, using EType enum | ||||
| 	si8 type; | ||||
|  | ||||
| 	/// message that will be sent to other players | ||||
| 	std::string toOtherMessage; | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & condition & allowNormalVictory & appliesToAI & pos & objectId & count & obj; | ||||
| 		h & type & toOtherMessage; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct TriggeredEvent | ||||
| { | ||||
|  | ||||
| 	/// base condition that must be evaluated | ||||
| 	EventExpression trigger; | ||||
|  | ||||
| 	/// string identifier read from config file (e.g. captureKreelah) | ||||
| 	std::string identifier; | ||||
|  | ||||
| 	/// string-description, for use in UI (capture town to win) | ||||
| 	std::string description; | ||||
|  | ||||
| 	/// Message that will be displayed when this event is triggered (You captured town. You won!) | ||||
| 	std::string onFulfill; | ||||
|  | ||||
| 	/// Effect of this event. TODO: refactor into something more flexible | ||||
| 	EventEffect effect; | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & identifier & trigger & description & onFulfill & effect; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -287,6 +325,7 @@ enum EMapFormat | ||||
| 	ROE = 0x0e, // 14 | ||||
| 	AB  = 0x15, // 21 | ||||
| 	SOD = 0x1c, // 28 | ||||
| // HOTA = 0x1e ... 0x20 // 28 ... 30 | ||||
| 	WOG = 0x33  // 51 | ||||
| }; | ||||
| } | ||||
| @@ -294,6 +333,7 @@ enum EMapFormat | ||||
| /// The map header holds information about loss/victory condition,map format, version, players, height, width,... | ||||
| class DLL_LINKAGE CMapHeader | ||||
| { | ||||
| 	void setupEvents(); | ||||
| public: | ||||
| 	static const int MAP_SIZE_SMALL; | ||||
| 	static const int MAP_SIZE_MIDDLE; | ||||
| @@ -313,19 +353,27 @@ public: | ||||
| 	/// Specifies the maximum level to reach for a hero. A value of 0 states that there is no | ||||
| 	///	maximum level for heroes. This is the default value. | ||||
| 	ui8 levelLimit; | ||||
| 	LossCondition lossCondition; /// The default value is lose all your towns and heroes. | ||||
| 	VictoryCondition victoryCondition; /// The default value is defeat all enemies. | ||||
|  | ||||
| 	std::string victoryMessage; | ||||
| 	std::string defeatMessage; | ||||
| 	ui16 victoryIconIndex; | ||||
| 	ui16 defeatIconIndex; | ||||
|  | ||||
| 	std::vector<PlayerInfo> players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT. | ||||
| 	ui8 howManyTeams; | ||||
| 	std::vector<bool> allowedHeroes; | ||||
| 	std::vector<ui16> placeholdedHeroes; | ||||
| 	bool areAnyPlayers; /// Unused. True if there are any playable players on the map. | ||||
|  | ||||
| 	/// "main quests" of the map that describe victory and loss conditions | ||||
| 	std::vector<TriggeredEvent> triggeredEvents; | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int Version) | ||||
| 	{ | ||||
| 		h & version & name & description & width & height & twoLevel & difficulty & levelLimit & areAnyPlayers; | ||||
| 		h & players & lossCondition & victoryCondition & howManyTeams & allowedHeroes; | ||||
| 		h & players & howManyTeams & allowedHeroes & triggeredEvents; | ||||
| 		h & victoryMessage & victoryIconIndex & defeatMessage & defeatIconIndex; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -350,8 +398,8 @@ public: | ||||
| 	void eraseArtifactInstance(CArtifactInstance * art); | ||||
| 	void addQuest(CGObjectInstance * quest); | ||||
|  | ||||
| 	/// Gets the topmost object or the lowermost object depending on the flag lookForHero from the specified position. | ||||
| 	const CGObjectInstance * getObjectiveObjectFrom(int3 pos, bool lookForHero); | ||||
| 	/// Gets object of specified type on requested position | ||||
| 	const CGObjectInstance * getObjectiveObjectFrom(int3 pos, Obj::EObj type); | ||||
| 	CGHeroInstance * getHero(int heroId); | ||||
|  | ||||
| 	/// Sets the victory/loss condition objectives ?? | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
|  | ||||
| #include "../CSpellHandler.h" | ||||
| #include "../CCreatureHandler.h" | ||||
| #include "../CGeneralTextHandler.h" | ||||
| #include "../CHeroHandler.h" | ||||
| #include "../CObjectHandler.h" | ||||
| #include "../CDefObjInfoHandler.h" | ||||
| @@ -281,98 +282,292 @@ void CMapLoaderH3M::readPlayerInfo() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| namespace EVictoryConditionType | ||||
| { | ||||
| 	enum EVictoryConditionType { ARTIFACT, GATHERTROOP, GATHERRESOURCE, BUILDCITY, BUILDGRAIL, BEATHERO, | ||||
| 		CAPTURECITY, BEATMONSTER, TAKEDWELLINGS, TAKEMINES, TRANSPORTITEM, WINSTANDARD = 255 }; | ||||
| } | ||||
|  | ||||
| namespace ELossConditionType | ||||
| { | ||||
| 	enum ELossConditionType { LOSSCASTLE, LOSSHERO, TIMEEXPIRES, LOSSSTANDARD = 255 }; | ||||
| } | ||||
|  | ||||
| void CMapLoaderH3M::readVictoryLossConditions() | ||||
| { | ||||
| 	mapHeader->victoryCondition.obj = nullptr; | ||||
| 	mapHeader->victoryCondition.condition = (EVictoryConditionType::EVictoryConditionType)reader.readUInt8(); | ||||
| 	mapHeader->triggeredEvents.clear(); | ||||
|  | ||||
| 	auto vicCondition = (EVictoryConditionType::EVictoryConditionType)reader.readUInt8(); | ||||
|  | ||||
| 	EventCondition victoryCondition(EventCondition::STANDARD_WIN); | ||||
| 	EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN); | ||||
| 	defeatCondition.value = 7; | ||||
|  | ||||
| 	TriggeredEvent standardVictory; | ||||
| 	standardVictory.effect.type = EventEffect::VICTORY; | ||||
| 	standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; | ||||
| 	standardVictory.identifier = "standardVictory"; | ||||
| 	standardVictory.description = ""; // TODO: display in quest window | ||||
| 	standardVictory.onFulfill = VLC->generaltexth->allTexts[659]; | ||||
| 	standardVictory.trigger = EventExpression(victoryCondition); | ||||
|  | ||||
| 	TriggeredEvent standardDefeat; | ||||
| 	standardDefeat.effect.type = EventEffect::DEFEAT; | ||||
| 	standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8]; | ||||
| 	standardDefeat.identifier = "standardDefeat"; | ||||
| 	standardDefeat.description = ""; // TODO: display in quest window | ||||
| 	standardDefeat.onFulfill = VLC->generaltexth->allTexts[7]; | ||||
| 	standardDefeat.trigger = EventExpression(defeatCondition); | ||||
|  | ||||
| 	// Specific victory conditions | ||||
| 	if(mapHeader->victoryCondition.condition != EVictoryConditionType::WINSTANDARD) | ||||
| 	if(vicCondition == EVictoryConditionType::WINSTANDARD) | ||||
| 	{ | ||||
| 		mapHeader->victoryCondition.allowNormalVictory = reader.readBool(); | ||||
| 		mapHeader->victoryCondition.appliesToAI = reader.readBool(); | ||||
| 		// create normal condition | ||||
| 		mapHeader->triggeredEvents.push_back(standardVictory); | ||||
| 		mapHeader->victoryIconIndex = 11; | ||||
| 		mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[0]; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		TriggeredEvent specialVictory; | ||||
| 		specialVictory.effect.type = EventEffect::VICTORY; | ||||
| 		specialVictory.identifier = "specialVictory"; | ||||
| 		specialVictory.description = ""; // TODO: display in quest window | ||||
|  | ||||
| 		// Read victory conditions | ||||
| //		int nr = 0; | ||||
| 		switch(mapHeader->victoryCondition.condition) | ||||
| 		mapHeader->victoryIconIndex = ui16(vicCondition); | ||||
| 		mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1]; | ||||
|  | ||||
| 		bool allowNormalVictory = reader.readBool(); | ||||
| 		bool appliesToAI = reader.readBool(); | ||||
|  | ||||
| 		switch(vicCondition) | ||||
| 		{ | ||||
| 		case EVictoryConditionType::ARTIFACT: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.objectId = reader.readUInt8(); | ||||
| 				EventCondition cond(EventCondition::HAVE_ARTIFACT); | ||||
| 				cond.objectType = reader.readUInt8(); | ||||
| 				if (mapHeader->version != EMapFormat::ROE) | ||||
| 					reader.skip(1); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[281]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[280]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::GATHERTROOP: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.objectId = reader.readUInt8(); | ||||
| 				EventCondition cond(EventCondition::HAVE_CREATURES); | ||||
| 				cond.objectType = reader.readUInt8(); | ||||
| 				if (mapHeader->version != EMapFormat::ROE) | ||||
| 					reader.skip(1); | ||||
| 				mapHeader->victoryCondition.count = reader.readUInt32(); | ||||
| 				cond.value = reader.readUInt32(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[277]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[276]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::GATHERRESOURCE: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.objectId = reader.readUInt8(); | ||||
| 				mapHeader->victoryCondition.count = reader.readUInt32(); | ||||
| 				EventCondition cond(EventCondition::HAVE_RESOURCES); | ||||
| 				cond.objectType = reader.readUInt8(); | ||||
| 				cond.value = reader.readUInt32(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[279]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[278]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::BUILDCITY: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.pos = readInt3(); | ||||
| 				mapHeader->victoryCondition.count = reader.readUInt8(); | ||||
| 				mapHeader->victoryCondition.objectId = reader.readUInt8(); | ||||
| 				EventExpression::OperatorAll oper; | ||||
| 				EventCondition cond(EventCondition::HAVE_BUILDING); | ||||
| 				cond.position = readInt3(); | ||||
| 				cond.objectType = BuildingID::VILLAGE_HALL + reader.readUInt8(); | ||||
| 				oper.expressions.push_back(cond); | ||||
| 				cond.objectType = BuildingID::FORT + reader.readUInt8(); | ||||
| 				oper.expressions.push_back(cond); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[283]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[282]; | ||||
| 				specialVictory.trigger = EventExpression(oper); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::BUILDGRAIL: | ||||
| 			{ | ||||
| 				int3 p = readInt3(); | ||||
| 				if(p.z > 2) | ||||
| 				{ | ||||
| 					p = int3(-1,-1,-1); | ||||
| 				} | ||||
| 				mapHeader->victoryCondition.pos = p; | ||||
| 				EventCondition cond(EventCondition::HAVE_BUILDING); | ||||
| 				cond.objectType = BuildingID::GRAIL; | ||||
| 				cond.position = readInt3(); | ||||
| 				if(cond.position.z > 2) | ||||
| 					cond.position = int3(-1,-1,-1); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[285]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[284]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::BEATHERO: | ||||
| 			{ | ||||
| 				EventCondition cond(EventCondition::DESTROY); | ||||
| 				cond.objectType = Obj::HERO; | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[253]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[252]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::CAPTURECITY: | ||||
| 			{ | ||||
| 				EventCondition cond(EventCondition::CONTROL); | ||||
| 				cond.objectType = Obj::TOWN; | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[250]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[249]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::BEATMONSTER: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.pos = readInt3(); | ||||
| 				EventCondition cond(EventCondition::DESTROY); | ||||
| 				cond.objectType = Obj::MONSTER; | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[287]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[286]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::TAKEDWELLINGS: | ||||
| 			{ | ||||
| 				EventCondition cond(EventCondition::CONTROL); | ||||
| 				cond.objectType = Obj::CREATURE_GENERATOR1; // FIXME: generators 2-4? | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[289]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[288]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::TAKEMINES: | ||||
| 			{ | ||||
| 				EventCondition cond(EventCondition::CONTROL); | ||||
| 				cond.objectType = Obj::MINE; | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[291]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[290]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		case EVictoryConditionType::TRANSPORTITEM: | ||||
| 			{ | ||||
| 				mapHeader->victoryCondition.objectId = reader.readUInt8(); | ||||
| 				mapHeader->victoryCondition.pos = readInt3(); | ||||
| 				EventCondition cond(EventCondition::TRANSPORT); | ||||
| 				cond.objectType = reader.readUInt8(); | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[293]; | ||||
| 				specialVictory.onFulfill = VLC->generaltexth->allTexts[292]; | ||||
| 				specialVictory.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		default: | ||||
| 			assert(0); | ||||
| 		} | ||||
| 		//bool allowNormalVictory = reader.readBool(); | ||||
| 		// if condition is human-only turn it into following construction: AllOf(human, condition) | ||||
| 		if (!appliesToAI) | ||||
| 		{ | ||||
| 			EventExpression::OperatorAll oper; | ||||
| 			EventCondition notAI(EventCondition::IS_HUMAN); | ||||
| 			notAI.value = 1; | ||||
| 			oper.expressions.push_back(notAI); | ||||
| 			oper.expressions.push_back(specialVictory.trigger.get()); | ||||
| 			specialVictory.trigger = EventExpression(oper); | ||||
| 		} | ||||
|  | ||||
| 		// if normal victory allowed - add one more quest | ||||
| 		if (allowNormalVictory) | ||||
| 		{ | ||||
| 			mapHeader->victoryMessage += " / "; | ||||
| 			mapHeader->victoryMessage += VLC->generaltexth->victoryConditions[0]; | ||||
| 			mapHeader->triggeredEvents.push_back(standardVictory); | ||||
| 		} | ||||
| 		mapHeader->triggeredEvents.push_back(specialVictory); | ||||
| 	} | ||||
|  | ||||
| 	// Read loss conditions | ||||
| 	mapHeader->lossCondition.typeOfLossCon = (ELossConditionType::ELossConditionType) reader.readUInt8(); | ||||
| 	switch(mapHeader->lossCondition.typeOfLossCon) | ||||
| 	auto lossCond = (ELossConditionType::ELossConditionType)reader.readUInt8(); | ||||
| 	if (lossCond == ELossConditionType::LOSSSTANDARD) | ||||
| 	{ | ||||
| 	case ELossConditionType::LOSSCASTLE: | ||||
| 	case ELossConditionType::LOSSHERO: | ||||
| 		{ | ||||
| 			mapHeader->lossCondition.pos = readInt3(); | ||||
| 			break; | ||||
| 		} | ||||
| 	case ELossConditionType::TIMEEXPIRES: | ||||
| 		{ | ||||
| 			mapHeader->lossCondition.timeLimit = reader.readUInt16(); | ||||
| 			break; | ||||
| 		} | ||||
| 		mapHeader->defeatIconIndex = 3; | ||||
| 		mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[0]; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		TriggeredEvent specialDefeat; | ||||
| 		specialDefeat.effect.type = EventEffect::DEFEAT; | ||||
| 		specialDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[5]; | ||||
| 		specialDefeat.identifier = "specialDefeat"; | ||||
| 		specialDefeat.description = ""; // TODO: display in quest window | ||||
|  | ||||
| 		mapHeader->defeatIconIndex = ui16(lossCond); | ||||
| 		mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[size_t(lossCond) + 1]; | ||||
|  | ||||
| 		switch(lossCond) | ||||
| 		{ | ||||
| 		case ELossConditionType::LOSSCASTLE: | ||||
| 			{ | ||||
| 				EventExpression::OperatorNone noneOf; | ||||
| 				EventCondition cond(EventCondition::CONTROL); | ||||
| 				cond.objectType = Obj::TOWN; | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				noneOf.expressions.push_back(cond); | ||||
| 				specialDefeat.onFulfill = VLC->generaltexth->allTexts[251]; | ||||
| 				specialDefeat.trigger = EventExpression(noneOf); | ||||
| 				break; | ||||
| 			} | ||||
| 		case ELossConditionType::LOSSHERO: | ||||
| 			{ | ||||
| 				EventExpression::OperatorNone noneOf; | ||||
| 				EventCondition cond(EventCondition::CONTROL); | ||||
| 				cond.objectType = Obj::HERO; | ||||
| 				cond.position = readInt3(); | ||||
|  | ||||
| 				noneOf.expressions.push_back(cond); | ||||
| 				specialDefeat.onFulfill = VLC->generaltexth->allTexts[253]; | ||||
| 				specialDefeat.trigger = EventExpression(noneOf); | ||||
| 				break; | ||||
| 			} | ||||
| 		case ELossConditionType::TIMEEXPIRES: | ||||
| 			{ | ||||
| 				EventCondition cond(EventCondition::DAYS_PASSED); | ||||
| 				cond.value = reader.readUInt16(); | ||||
|  | ||||
| 				specialDefeat.onFulfill = VLC->generaltexth->allTexts[254]; | ||||
| 				specialDefeat.trigger = EventExpression(cond); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		// turn simple loss condition into complete one that can be evaluated later: | ||||
| 		// - any of : | ||||
| 		//   - days without town: 7 | ||||
| 		//   - all of: | ||||
| 		//     - is human | ||||
| 		//     - (expression) | ||||
|  | ||||
| 		EventExpression::OperatorAll allOf; | ||||
| 		EventCondition isHuman(EventCondition::IS_HUMAN); | ||||
| 		isHuman.value = 1; | ||||
|  | ||||
| 		allOf.expressions.push_back(isHuman); | ||||
| 		allOf.expressions.push_back(specialDefeat.trigger.get()); | ||||
| 		specialDefeat.trigger = EventExpression(allOf); | ||||
|  | ||||
| 		mapHeader->triggeredEvents.push_back(specialDefeat); | ||||
| 	} | ||||
| 	mapHeader->triggeredEvents.push_back(standardDefeat); | ||||
| } | ||||
|  | ||||
| void CMapLoaderH3M::readTeamInfo() | ||||
| @@ -470,10 +665,18 @@ void CMapLoaderH3M::readAllowedArtifacts() | ||||
| 	} | ||||
|  | ||||
| 	// Messy, but needed | ||||
| 	if(map->victoryCondition.condition == EVictoryConditionType::ARTIFACT | ||||
| 			|| map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM) | ||||
| 	for (TriggeredEvent & event : map->triggeredEvents) | ||||
| 	{ | ||||
| 		map->allowedArtifact[map->victoryCondition.objectId] = false; | ||||
| 		auto patcher = [&](EventCondition & cond) | ||||
| 		{ | ||||
| 			if (cond.condition == EventCondition::HAVE_ARTIFACT || | ||||
| 				cond.condition == EventCondition::TRANSPORT) | ||||
| 			{ | ||||
| 				map->allowedArtifact[cond.objectType] = false; | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		event.trigger.forEach(patcher); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1923,8 +1923,7 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta | ||||
| 	vistiCastleObjects (obj, hero); | ||||
| 	giveSpells (obj, hero); | ||||
|  | ||||
| 	if(gs->map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM) | ||||
| 		checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact? | ||||
| 	checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact? | ||||
| } | ||||
|  | ||||
| void CGameHandler::vistiCastleObjects (const CGTownInstance *t, const CGHeroInstance *h) | ||||
| @@ -2168,29 +2167,25 @@ void CGameHandler::applyAndSend(CPackForClient * info) | ||||
| void CGameHandler::sendAndApply(CGarrisonOperationPack * info) | ||||
| { | ||||
| 	sendAndApply(static_cast<CPackForClient*>(info)); | ||||
| 	if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERTROOP) | ||||
| 		checkVictoryLossConditionsForAll(); | ||||
| 	checkVictoryLossConditionsForAll(); | ||||
| } | ||||
|  | ||||
| void CGameHandler::sendAndApply( SetResource * info ) | ||||
| { | ||||
| 	sendAndApply(static_cast<CPackForClient*>(info)); | ||||
| 	if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE) | ||||
| 		checkVictoryLossConditionsForPlayer(info->player); | ||||
| 	checkVictoryLossConditionsForPlayer(info->player); | ||||
| } | ||||
|  | ||||
| void CGameHandler::sendAndApply( SetResources * info ) | ||||
| { | ||||
| 	sendAndApply(static_cast<CPackForClient*>(info)); | ||||
| 	if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE) | ||||
| 		checkVictoryLossConditionsForPlayer(info->player); | ||||
| 	checkVictoryLossConditionsForPlayer(info->player); | ||||
| } | ||||
|  | ||||
| void CGameHandler::sendAndApply( NewStructures * info ) | ||||
| { | ||||
| 	sendAndApply(static_cast<CPackForClient*>(info)); | ||||
| 	if(gs->map->victoryCondition.condition == EVictoryConditionType::BUILDCITY) | ||||
| 		checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner); | ||||
| 	checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner); | ||||
| } | ||||
|  | ||||
| void CGameHandler::save(const std::string & filename ) | ||||
| @@ -5048,7 +5043,8 @@ void CGameHandler::checkVictoryLossConditions(const std::set<PlayerColor> & play | ||||
| { | ||||
| 	for(auto playerColor : playerColors) | ||||
| 	{ | ||||
| 		if(gs->getPlayer(playerColor)) checkVictoryLossConditionsForPlayer(playerColor); | ||||
| 		if(gs->getPlayer(playerColor)) | ||||
| 			checkVictoryLossConditionsForPlayer(playerColor); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -5069,7 +5065,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) | ||||
|  | ||||
| 	auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player); | ||||
|  | ||||
| 	if(victoryLossCheckResult != EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) | ||||
| 	if(victoryLossCheckResult.victory() || victoryLossCheckResult.loss()) | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		getVictoryLossMessage(player, victoryLossCheckResult, iw); | ||||
| @@ -5083,18 +5079,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) | ||||
| 		if(victoryLossCheckResult.victory()) | ||||
| 		{ | ||||
| 			//one player won -> all enemies lost | ||||
| 			iw.text.localStrings.front().second++; //message about losing because enemy won first is just after victory message | ||||
|  | ||||
| 			for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++) | ||||
| 			{ | ||||
| 				if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME) | ||||
| 				{ | ||||
| 					iw.player = i->first; | ||||
| 					sendAndApply(&iw); | ||||
|  | ||||
| 					peg.player = i->first; | ||||
| 					peg.victoryLossCheckResult = gameState()->getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ? | ||||
| 								victoryLossCheckResult : EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS; // ally of winner | ||||
| 								victoryLossCheckResult : victoryLossCheckResult.invert(); // ally of winner | ||||
|  | ||||
| 					InfoWindow iw; | ||||
| 					getVictoryLossMessage(player, peg.victoryLossCheckResult, iw); | ||||
| 					iw.player = i->first; | ||||
|  | ||||
| 					sendAndApply(&iw); | ||||
| 					sendAndApply(&peg); | ||||
| 				} | ||||
| 			} | ||||
| @@ -5122,7 +5119,6 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) | ||||
| 							connection->prepareForSendingHeroes(); | ||||
| 					} | ||||
|  | ||||
|  | ||||
| 					UpdateCampaignState ucs; | ||||
| 					ucs.camp = gs->scenarioOps->campState; | ||||
| 					sendAndApply(&ucs); | ||||
| @@ -5149,6 +5145,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) | ||||
| 				if(player.getNum() != i) playerColors.insert(PlayerColor(i)); | ||||
| 			} | ||||
|  | ||||
| 			//notify all players | ||||
| 			for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++) | ||||
| 			{ | ||||
| 				if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME) | ||||
| 				{ | ||||
| 					InfoWindow iw; | ||||
| 					getVictoryLossMessage(player, victoryLossCheckResult.invert(), iw); | ||||
| 					iw.player = i->first; | ||||
| 					sendAndApply(&iw); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
|  | ||||
| 			checkVictoryLossConditions(playerColors); | ||||
| 		} | ||||
|  | ||||
| @@ -5162,110 +5171,14 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) | ||||
|  | ||||
| void CGameHandler::getVictoryLossMessage(PlayerColor player, EVictoryLossCheckResult victoryLossCheckResult, InfoWindow & out) const | ||||
| { | ||||
| //	const PlayerState *p = gs->getPlayer(player); | ||||
| // 	if(!p->human) | ||||
| // 		return; //AI doesn't need text info of loss | ||||
|  | ||||
| 	out.player = player; | ||||
| 	out.text.clear(); | ||||
| 	out.text << victoryLossCheckResult.messageToSelf; | ||||
| 	// hackish, insert one player-specific string, if applicable | ||||
| 	if (victoryLossCheckResult.messageToSelf.find("%s") != std::string::npos) | ||||
| 		out.text.addReplacement(MetaString::COLOR, player.getNum()); | ||||
|  | ||||
| 	if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_SPECIAL) | ||||
| 	{ | ||||
| 		switch(gs->map->victoryCondition.condition) | ||||
| 		{ | ||||
| 		case EVictoryConditionType::ARTIFACT: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 280); //Congratulations! You have found the %s, and can claim victory! | ||||
| 			out.text.addReplacement(MetaString::ART_NAMES,gs->map->victoryCondition.objectId); //artifact name | ||||
| 			break; | ||||
| 		case EVictoryConditionType::GATHERTROOP: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 276); //Congratulations! You have over %d %s in your armies. Your enemies have no choice but to bow down before your power! | ||||
| 			out.text.addReplacement(gs->map->victoryCondition.count); | ||||
| 			out.text.addReplacement(MetaString::CRE_PL_NAMES, gs->map->victoryCondition.objectId); | ||||
| 			break; | ||||
| 		case EVictoryConditionType::GATHERRESOURCE: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 278); //Congratulations! You have collected over %d %s in your treasury. Victory is yours! | ||||
| 			out.text.addReplacement(gs->map->victoryCondition.count); | ||||
| 			out.text.addReplacement(MetaString::RES_NAMES, gs->map->victoryCondition.objectId); | ||||
| 			break; | ||||
| 		case EVictoryConditionType::BUILDCITY: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 282); //Congratulations! You have successfully upgraded your town, and can claim victory! | ||||
| 			break; | ||||
| 		case EVictoryConditionType::BUILDGRAIL: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 284); //Congratulations! You have constructed a permanent home for the Grail, and can claim victory! | ||||
| 			break; | ||||
| 		case EVictoryConditionType::BEATHERO: | ||||
| 			{ | ||||
| 				out.text.addTxt(MetaString::GENERAL_TXT, 252); //Congratulations! You have completed your quest to defeat the enemy hero %s. Victory is yours! | ||||
| 				const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->victoryCondition.obj); | ||||
| 				assert(h); | ||||
| 				out.text.addReplacement(h->name); | ||||
| 			} | ||||
| 			break; | ||||
| 		case EVictoryConditionType::CAPTURECITY: | ||||
| 			{ | ||||
| 				out.text.addTxt(MetaString::GENERAL_TXT, 249); //Congratulations! You captured %s, and are victorious! | ||||
| 				const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->victoryCondition.obj); | ||||
| 				assert(t); | ||||
| 				out.text.addReplacement(t->name); | ||||
| 			} | ||||
| 			break; | ||||
| 		case EVictoryConditionType::BEATMONSTER: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 286); //Congratulations! You have completed your quest to kill the fearsome beast, and can claim victory! | ||||
| 			break; | ||||
| 		case EVictoryConditionType::TAKEDWELLINGS: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 288); //Congratulations! Your flag flies on the dwelling of every creature. Victory is yours! | ||||
| 			break; | ||||
| 		case EVictoryConditionType::TAKEMINES: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 290); //Congratulations! Your flag flies on every mine. Victory is yours! | ||||
| 			break; | ||||
| 		case EVictoryConditionType::TRANSPORTITEM: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 292); //Congratulations! You have reached your destination, precious cargo intact, and can claim victory! | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	else if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_STANDARD) | ||||
| 	{ | ||||
| 		out.text.addTxt(MetaString::GENERAL_TXT, 659); // Congratulations! All your enemies have been defeated! Victory is yours! | ||||
| 	} | ||||
| 	else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) | ||||
| 	{ | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 7);//%s, your heroes abandon you, and you are banished from this land. | ||||
| 			out.text.addReplacement(MetaString::COLOR, player.getNum()); | ||||
| 			out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0)); | ||||
| 	} | ||||
| 	else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_SPECIAL) | ||||
| 	{ | ||||
| 		switch(gs->map->lossCondition.typeOfLossCon) | ||||
| 		{ | ||||
| 		case ELossConditionType::LOSSCASTLE: | ||||
| 			{ | ||||
| 				out.text.addTxt(MetaString::GENERAL_TXT, 251); //The town of %s has fallen - all is lost! | ||||
| 				const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->lossCondition.obj); | ||||
| 				assert(t); | ||||
| 				out.text.addReplacement(t->name); | ||||
| 			} | ||||
| 			break; | ||||
| 		case ELossConditionType::LOSSHERO: | ||||
| 			{ | ||||
| 				out.text.addTxt(MetaString::GENERAL_TXT, 253); //The hero, %s, has suffered defeat - your quest is over! | ||||
| 				const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->lossCondition.obj); | ||||
| 				assert(h); | ||||
| 				out.text.addReplacement(h->name); | ||||
| 			} | ||||
| 			break; | ||||
| 		case ELossConditionType::TIMEEXPIRES: | ||||
| 			out.text.addTxt(MetaString::GENERAL_TXT, 254); //Alas, time has run out on your quest. All is lost. | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS) | ||||
| 	{ | ||||
| 		out.text.addTxt(MetaString::GENERAL_TXT, 660); //All your forces have been defeated, and you are banished from this land! | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		assert(0); | ||||
| 		logGlobal->warnStream() << "Unknown victory loss check result"; | ||||
| 	} | ||||
| 	out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0)); | ||||
| } | ||||
|  | ||||
| bool CGameHandler::dig( const CGHeroInstance *h ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user