mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge branch 'develop' into feature/VCMIMapFormat1
This commit is contained in:
		| @@ -30,7 +30,7 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vector<Com | ||||
| 	cb->selectionMade(0, askID); | ||||
| } | ||||
|  | ||||
| void CEmptyAI::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) | ||||
| void CEmptyAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) | ||||
| { | ||||
| 	cb->selectionMade(0, askID); | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ public: | ||||
| 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override; | ||||
| 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; | ||||
| 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; | ||||
| 	void showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) override; | ||||
| 	void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; | ||||
| 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -98,6 +98,7 @@ VCAI::VCAI(void) | ||||
| 	LOG_TRACE(logAi); | ||||
| 	makingTurn = nullptr; | ||||
| 	destinationTeleport = ObjectInstanceID(); | ||||
| 	destinationTeleportPos = int3(-1); | ||||
| } | ||||
|  | ||||
| VCAI::~VCAI(void) | ||||
| @@ -616,33 +617,46 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector<Compone | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void VCAI::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) | ||||
| void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) | ||||
| { | ||||
| 	LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); | ||||
| //	LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); | ||||
| 	NET_EVENT_HANDLER; | ||||
| 	status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") | ||||
| 																			% exits.size())); | ||||
|  | ||||
| 	ObjectInstanceID choosenExit; | ||||
| 	int choosenExit = -1; | ||||
| 	if(impassable) | ||||
| 		knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE; | ||||
| 	else | ||||
| 	else if(destinationTeleport != ObjectInstanceID() && destinationTeleportPos.valid()) | ||||
| 	{ | ||||
| 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) | ||||
| 			choosenExit = destinationTeleport; | ||||
| 		auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); | ||||
| 		if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) | ||||
| 			choosenExit = vstd::find_pos(exits, neededExit); | ||||
| 	} | ||||
|  | ||||
| 		if(!status.channelProbing()) | ||||
| 	for(auto exit : exits) | ||||
| 	{ | ||||
| 		if(status.channelProbing() && exit.first == destinationTeleport) | ||||
| 		{ | ||||
| 			vstd::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool | ||||
| 			choosenExit = vstd::find_pos(exits, exit); | ||||
| 			break; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// TODO: Implement checking if visiting that teleport will uncovert any FoW | ||||
| 			// So far this is the best option to handle decision about probing | ||||
| 			auto obj = cb->getObj(exit.first, false); | ||||
| 			if(obj == nullptr && !vstd::contains(teleportChannelProbingList, exit.first) && | ||||
| 				exit.first != destinationTeleport) | ||||
| 			{ | ||||
| 				return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit); | ||||
| 			}); | ||||
| 				teleportChannelProbingList.push_back(exit.first); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	requestActionASAP([=]() | ||||
| 	{ | ||||
| 		answerQuery(askID, choosenExit.getNum()); | ||||
| 		answerQuery(askID, choosenExit); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @@ -1876,25 +1890,29 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| 			cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit); | ||||
| 		}; | ||||
|  | ||||
| 		auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId) | ||||
| 		auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) | ||||
| 		{ | ||||
| 			destinationTeleport = exitId; | ||||
| 			cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true)); | ||||
| 			if(exitPos.valid()) | ||||
| 				destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); | ||||
| 			cb->moveHero(*h, h->pos); | ||||
| 			destinationTeleport = ObjectInstanceID(); | ||||
| 			destinationTeleportPos = int3(-1); | ||||
| 			afterMovementCheck(); | ||||
| 		}; | ||||
|  | ||||
| 		auto doChannelProbing = [&]() -> void | ||||
| 		{ | ||||
| 			auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false), false); | ||||
| 			assert(currentExit); | ||||
| 			auto currentPos = CGHeroInstance::convertPosition(h->pos,false); | ||||
| 			auto currentExit = getObj(currentPos, true)->id; | ||||
|  | ||||
| 			status.setChannelProbing(true); | ||||
| 			for(auto exit : teleportChannelProbingList) | ||||
| 				doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), exit); | ||||
| 				doTeleportMovement(exit, int3(-1)); | ||||
| 			teleportChannelProbingList.clear(); | ||||
| 			doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), currentExit->id); | ||||
| 			status.setChannelProbing(false); | ||||
|  | ||||
| 			doTeleportMovement(currentExit, currentPos); | ||||
| 		}; | ||||
|  | ||||
| 		int i=path.nodes.size()-1; | ||||
| @@ -1907,7 +1925,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) | ||||
| 			auto nextObject = getObj(nextCoord, false); | ||||
| 			if(CGTeleport::isConnected(currentObject, nextObject)) | ||||
| 			{ //we use special login if hero standing on teleporter it's mean we need | ||||
| 				doTeleportMovement(currentCoord, nextObject->id); | ||||
| 				doTeleportMovement(nextObject->id, nextCoord); | ||||
| 				if(teleportChannelProbingList.size()) | ||||
| 					doChannelProbing(); | ||||
|  | ||||
| @@ -2934,7 +2952,7 @@ void AIStatus::setMove(bool ongoing) | ||||
| void AIStatus::setChannelProbing(bool ongoing) | ||||
| { | ||||
| 	boost::unique_lock<boost::mutex> lock(mx); | ||||
| 	ongoingHeroMovement = ongoing; | ||||
| 	ongoingChannelProbing = ongoing; | ||||
| 	cv.notify_all(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -132,6 +132,7 @@ public: | ||||
| 	std::map<TeleportChannelID, shared_ptr<TeleportChannel> > knownTeleportChannels; | ||||
| 	std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates; | ||||
| 	ObjectInstanceID destinationTeleport; | ||||
| 	int3 destinationTeleportPos; | ||||
| 	std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored | ||||
| 	//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs | ||||
| 	std::map<HeroPtr, std::set<const CGTownInstance *> > townVisitsThisWeek; | ||||
| @@ -186,7 +187,7 @@ public: | ||||
| 	virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO | ||||
| 	virtual void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. | ||||
| 	virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done | ||||
| 	virtual void showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) override; | ||||
| 	virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; | ||||
| 	virtual void saveGame(COSer & h, const int version) override; //saving | ||||
| 	virtual void loadGame(CISer & h, const int version) override; //loading | ||||
| 	virtual void finish() override; | ||||
|   | ||||
| @@ -7,11 +7,13 @@ GENERAL: | ||||
| * New artifacts supported | ||||
| - Angel Wings | ||||
| - Boots of Levitation | ||||
| * Implemented rumors in tavern window | ||||
|  | ||||
| ADVETURE AI: | ||||
| * Fixed AI trying to go through underground rock | ||||
| * Fixed several cases causing AI wandering aimlessly | ||||
| * AI can again pick best artifacts and exchange artifacts between heroes | ||||
| * AI heroes with patrol enabled won't leave patrol area anymore | ||||
|  | ||||
| RANDOM MAP GENERATOR: | ||||
| * Changed fractalization algorithm so it can create cycles | ||||
|   | ||||
| @@ -98,6 +98,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) | ||||
| { | ||||
| 	logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed"; | ||||
| 	destinationTeleport = ObjectInstanceID(); | ||||
| 	destinationTeleportPos = int3(-1); | ||||
| 	observerInDuelMode = false; | ||||
| 	howManyPeople++; | ||||
| 	GH.defActionsDef = 0; | ||||
| @@ -1148,14 +1149,15 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v | ||||
|  | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) | ||||
| void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) | ||||
| { | ||||
| 	EVENT_HANDLER_CALLED_BY_CLIENT; | ||||
| 	ObjectInstanceID choosenExit; | ||||
| 	if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) | ||||
| 		choosenExit = destinationTeleport; | ||||
| 	int choosenExit = -1; | ||||
| 	auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); | ||||
| 	if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) | ||||
| 		choosenExit = vstd::find_pos(exits, neededExit); | ||||
|  | ||||
| 	cb->selectionMade(choosenExit.getNum(), askID); | ||||
| 	cb->selectionMade(choosenExit, askID); | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::tileRevealed(const std::unordered_set<int3, ShashInt3> &pos) | ||||
| @@ -1415,6 +1417,7 @@ void CPlayerInterface::requestRealized( PackageApplied *pa ) | ||||
| 	   && stillMoveHero.get() == DURING_MOVE) | ||||
| 	{ // After teleportation via CGTeleport object is finished | ||||
| 		destinationTeleport = ObjectInstanceID(); | ||||
| 		destinationTeleportPos = int3(-1); | ||||
| 		stillMoveHero.setn(CONTINUE_MOVE); | ||||
| 	} | ||||
| } | ||||
| @@ -2663,6 +2666,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) | ||||
| 			{ | ||||
| 				CCS->soundh->stopSound(sh); | ||||
| 				destinationTeleport = nextObject->id; | ||||
| 				destinationTeleportPos = nextCoord; | ||||
| 				doMovement(h->pos, false); | ||||
| 				sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); | ||||
| 				continue; | ||||
|   | ||||
| @@ -89,6 +89,7 @@ class CPlayerInterface : public CGameInterface, public IUpdateable | ||||
| public: | ||||
| 	bool observerInDuelMode; | ||||
| 	ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation | ||||
| 	int3 destinationTeleportPos; | ||||
|  | ||||
| 	//minor interfaces | ||||
| 	CondSh<bool> *showingDialog; //indicates if dialog box is displayed | ||||
| @@ -167,7 +168,7 @@ public: | ||||
| 	void showRecruitmentDialog(const CGDwelling *dwelling, const CArmedInstance *dst, int level) override; | ||||
| 	void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard; | ||||
| 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. | ||||
| 	void showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) override; | ||||
| 	void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; | ||||
| 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; | ||||
| 	void showPuzzleMap() override; | ||||
| 	void viewWorldMap() override; | ||||
|   | ||||
| @@ -705,7 +705,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj): | ||||
|  | ||||
| 	new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); | ||||
| 	new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500"); | ||||
| 	new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); | ||||
|  | ||||
| 	auto rumorText = boost::str(boost::format(CGI->generaltexth->allTexts[216]) % LOCPLINT->cb->getTavernRumor(tavernObj)); | ||||
| 	new CTextBox(rumorText, Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); | ||||
|  | ||||
| 	new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); | ||||
| 	cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), SDLK_ESCAPE); | ||||
| @@ -1628,9 +1630,9 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): | ||||
| 	int counter = 0; | ||||
| 	for(auto & iter : tgi.colorToBestHero) | ||||
| 	{ | ||||
| 		new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); | ||||
| 		if(iter.second.portrait >= 0) | ||||
| 		{ | ||||
| 			new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); | ||||
| 			new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360); | ||||
| 			//TODO: r-click info: | ||||
| 			// - r-click on hero | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| #include "CGameInfoCallback.h" | ||||
|  | ||||
| #include "CGameState.h" // PlayerState | ||||
| #include "CGeneralTextHandler.h" | ||||
| #include "mapObjects/CObjectHandler.h" // for CGObjectInstance | ||||
| #include "StartInfo.h" // for StartInfo | ||||
| #include "BattleState.h" // for BattleInfo | ||||
| @@ -198,7 +199,14 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj | ||||
|  | ||||
| 	if(obj->ID == Obj::TOWN  ||  obj->ID == Obj::TAVERN) | ||||
| 	{ | ||||
| 		gs->obtainPlayersStats(thi, gs->players[obj->tempOwner].towns.size()); | ||||
| 		int taverns = 0; | ||||
| 		for(auto town : gs->players[*player].towns) | ||||
| 		{ | ||||
| 			if(town->hasBuilt(BuildingID::TAVERN)) | ||||
| 				taverns++; | ||||
| 		} | ||||
|  | ||||
| 		gs->obtainPlayersStats(thi, taverns); | ||||
| 	} | ||||
| 	else if(obj->ID == Obj::DEN_OF_THIEVES) | ||||
| 	{ | ||||
| @@ -566,9 +574,34 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo | ||||
| 	return ps->status; | ||||
| } | ||||
|  | ||||
| std::string CGameInfoCallback::getTavernGossip(const CGObjectInstance * townOrTavern) const | ||||
| std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const | ||||
| { | ||||
| 	return "GOSSIP TEST"; | ||||
| 	std::string text = "", extraText = ""; | ||||
| 	if(gs->rumor.type == RumorState::TYPE_NONE) // (version < 755 backward compatability | ||||
| 		return text; | ||||
|  | ||||
| 	auto rumor = gs->rumor.last[gs->rumor.type]; | ||||
| 	switch(gs->rumor.type) | ||||
| 	{ | ||||
| 	case RumorState::TYPE_SPECIAL: | ||||
| 		if(rumor.first == RumorState::RUMOR_GRAIL) | ||||
| 			extraText = VLC->generaltexth->arraytxt[158 + rumor.second]; | ||||
| 		else | ||||
| 			extraText = VLC->generaltexth->capColors[rumor.second]; | ||||
|  | ||||
| 		text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText); | ||||
|  | ||||
| 		break; | ||||
| 	case RumorState::TYPE_MAP: | ||||
| 		text = gs->map->rumors[rumor.first].text; | ||||
| 		break; | ||||
|  | ||||
| 	case RumorState::TYPE_RAND: | ||||
| 		text = VLC->generaltexth->tavernRumors[rumor.first]; | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	return text; | ||||
| } | ||||
|  | ||||
| PlayerRelations::PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const | ||||
| @@ -855,7 +888,7 @@ std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std:: | ||||
| { | ||||
| 	vstd::erase_if(ids, [&](ObjectInstanceID id) -> bool | ||||
| 	{ | ||||
| 		auto obj = getObj(id); | ||||
| 		auto obj = getObj(id, false); | ||||
| 		return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); | ||||
| 	}); | ||||
| 	return ids; | ||||
|   | ||||
| @@ -99,7 +99,7 @@ public: | ||||
| 	int howManyTowns(PlayerColor Player) const; | ||||
| 	const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial) | ||||
| 	std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited | ||||
| 	std::string getTavernGossip(const CGObjectInstance * townOrTavern) const;  | ||||
| 	std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; | ||||
| 	EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements | ||||
| 	virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; | ||||
| 	const CTown *getNativeTown(PlayerColor color) const; | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
|  | ||||
| #include "spells/ViewSpellInt.h" | ||||
|  | ||||
| #include "mapObjects/CObjectHandler.h" | ||||
|  | ||||
| /* | ||||
|  * CGameInterface.h, part of VCMI engine | ||||
|  * | ||||
| @@ -94,7 +96,7 @@ public: | ||||
|  | ||||
| 	// all stacks operations between these objects become allowed, interface has to call onEnd when done | ||||
| 	virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0; | ||||
| 	virtual void showTeleportDialog(TeleportChannelID channel, std::vector<ObjectInstanceID> exits, bool impassable, QueryID askID) = 0; | ||||
| 	virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) = 0; | ||||
| 	virtual void finish(){}; //if for some reason we want to end | ||||
| 	 | ||||
| 	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){}; | ||||
|   | ||||
| @@ -2134,6 +2134,76 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const | ||||
| 	return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z]; | ||||
| } | ||||
|  | ||||
| void CGameState::updateRumor() | ||||
| { | ||||
| 	static std::vector<RumorState::ERumorType> rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND}; | ||||
| 	std::vector<RumorState::ERumorTypeSpecial> sRumorTypes = { | ||||
| 		RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME}; | ||||
| 	if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce | ||||
| 		sRumorTypes.push_back(RumorState::RUMOR_GRAIL); | ||||
|  | ||||
| 	int rumorId = -1, rumorExtra = -1; | ||||
| 	auto & rand = getRandomGenerator(); | ||||
| 	rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); | ||||
| 	if(!map->rumors.size() && rumor.type == RumorState::TYPE_MAP) | ||||
| 		rumor.type = RumorState::TYPE_RAND; | ||||
|  | ||||
| 	do | ||||
| 	{ | ||||
| 		switch(rumor.type) | ||||
| 		{ | ||||
| 		case RumorState::TYPE_SPECIAL: | ||||
| 		{ | ||||
| 			SThievesGuildInfo tgi; | ||||
| 			obtainPlayersStats(tgi, 20); | ||||
| 			rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); | ||||
| 			if(rumorId == RumorState::RUMOR_GRAIL) | ||||
| 			{ | ||||
| 				rumorExtra = getTile(map->grailPos)->terType; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			std::vector<PlayerColor> players = {}; | ||||
| 			switch(rumorId) | ||||
| 			{ | ||||
| 			case RumorState::RUMOR_OBELISKS: | ||||
| 				players = tgi.obelisks[0]; | ||||
| 				break; | ||||
|  | ||||
| 			case RumorState::RUMOR_ARTIFACTS: | ||||
| 				players = tgi.artifacts[0]; | ||||
| 				break; | ||||
|  | ||||
| 			case RumorState::RUMOR_ARMY: | ||||
| 				players = tgi.army[0]; | ||||
| 				break; | ||||
|  | ||||
| 			case RumorState::RUMOR_INCOME: | ||||
| 				players = tgi.income[0]; | ||||
| 				break; | ||||
| 			} | ||||
| 			rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum(); | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 		case RumorState::TYPE_MAP: | ||||
| 			rumorId = rand.nextInt(map->rumors.size() - 1); | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		case RumorState::TYPE_RAND: | ||||
| 			do | ||||
| 			{ | ||||
| 				rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); | ||||
| 			} | ||||
| 			while(!VLC->generaltexth->tavernRumors[rumorId].length()); | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	while(!rumor.update(rumorId, rumorExtra)); | ||||
| } | ||||
|  | ||||
| bool CGameState::isVisible(int3 pos, PlayerColor player) | ||||
| { | ||||
| 	if(player == PlayerColor::NEUTRAL) | ||||
| @@ -2455,6 +2525,58 @@ struct statsHLP | ||||
| 		} | ||||
| 		return str; | ||||
| 	} | ||||
|  | ||||
| 	// get total gold income | ||||
| 	static int getIncome(const PlayerState * ps) | ||||
| 	{ | ||||
| 		int totalIncome = 0; | ||||
| 		const CGObjectInstance * heroOrTown = nullptr; | ||||
|  | ||||
| 		//Heroes can produce gold as well - skill, specialty or arts | ||||
| 		for(auto & h : ps->heroes) | ||||
| 		{ | ||||
| 			totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ESTATES)); | ||||
| 			totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, Res::GOLD)); | ||||
|  | ||||
| 			if(!heroOrTown) | ||||
| 				heroOrTown = h; | ||||
| 		} | ||||
|  | ||||
| 		//Add town income of all towns | ||||
| 		for(auto & t : ps->towns) | ||||
| 		{ | ||||
| 			totalIncome += t->dailyIncome()[Res::GOLD]; | ||||
|  | ||||
| 			if(!heroOrTown) | ||||
| 				heroOrTown = t; | ||||
| 		} | ||||
|  | ||||
| 		/// FIXME: Dirty dirty hack | ||||
| 		/// Stats helper need some access to gamestate. | ||||
| 		std::vector<const CGObjectInstance *> ownedObjects; | ||||
| 		for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) | ||||
| 		{ | ||||
| 			if(obj && obj->tempOwner == ps->color) | ||||
| 				ownedObjects.push_back(obj); | ||||
| 		} | ||||
| 		/// This is code from CPlayerSpecificInfoCallback::getMyObjects | ||||
| 		/// I'm really need to find out about callback interface design... | ||||
|  | ||||
| 		for(auto object : ownedObjects) | ||||
| 		{ | ||||
| 			//Mines | ||||
| 			if ( object->ID == Obj::MINE ) | ||||
| 			{ | ||||
| 				const CGMine *mine = dynamic_cast<const CGMine*>(object); | ||||
| 				assert(mine); | ||||
|  | ||||
| 				if (mine->producedResource == Res::GOLD) | ||||
| 					totalIncome += mine->producedQuantity; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return totalIncome; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) | ||||
| @@ -2485,20 +2607,22 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) | ||||
| 			tgi.playerColors.push_back(elem.second.color); | ||||
| 	} | ||||
|  | ||||
| 	if(level >= 1) //num of towns & num of heroes | ||||
| 	if(level >= 0) //num of towns & num of heroes | ||||
| 	{ | ||||
| 		//num of towns | ||||
| 		FILL_FIELD(numOfTowns, g->second.towns.size()) | ||||
| 		//num of heroes | ||||
| 		FILL_FIELD(numOfHeroes, g->second.heroes.size()) | ||||
| 		//best hero's portrait | ||||
| 	} | ||||
| 	if(level >= 1) //best hero's portrait | ||||
| 	{ | ||||
| 		for(auto g = players.cbegin(); g != players.cend(); ++g) | ||||
| 		{ | ||||
| 			if(playerInactive(g->second.color)) | ||||
| 				continue; | ||||
| 			const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color); | ||||
| 			InfoAboutHero iah; | ||||
| 			iah.initFromHero(best, level >= 8); | ||||
| 			iah.initFromHero(best, level >= 2); | ||||
| 			iah.army.clear(); | ||||
| 			tgi.colorToBestHero[g->second.color] = iah; | ||||
| 		} | ||||
| @@ -2515,27 +2639,27 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) | ||||
| 	{ | ||||
| 		FILL_FIELD(mercSulfCrystGems, g->second.resources[Res::MERCURY] + g->second.resources[Res::SULFUR] + g->second.resources[Res::CRYSTAL] + g->second.resources[Res::GEMS]) | ||||
| 	} | ||||
| 	if(level >= 4) //obelisks found | ||||
| 	if(level >= 3) //obelisks found | ||||
| 	{ | ||||
| 		FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id]) | ||||
| 	} | ||||
| 	if(level >= 5) //artifacts | ||||
| 	if(level >= 4) //artifacts | ||||
| 	{ | ||||
| 		FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) | ||||
| 	} | ||||
| 	if(level >= 6) //army strength | ||||
| 	if(level >= 4) //army strength | ||||
| 	{ | ||||
| 		FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) | ||||
| 	} | ||||
| 	if(level >= 7) //income | ||||
| 	if(level >= 5) //income | ||||
| 	{ | ||||
| 		//TODO:obtainPlayersStats - income | ||||
| 		FILL_FIELD(income, statsHLP::getIncome(&g->second)) | ||||
| 	} | ||||
| 	if(level >= 8) //best hero's stats | ||||
| 	if(level >= 2) //best hero's stats | ||||
| 	{ | ||||
| 		//already set in  lvl 1 handling | ||||
| 	} | ||||
| 	if(level >= 9) //personality | ||||
| 	if(level >= 3) //personality | ||||
| 	{ | ||||
| 		for(auto g = players.cbegin(); g != players.cend(); ++g) | ||||
| 		{ | ||||
| @@ -2552,7 +2676,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| 	if(level >= 10) //best creature | ||||
| 	if(level >= 4) //best creature | ||||
| 	{ | ||||
| 		//best creatures belonging to player (highest AI value) | ||||
| 		for(auto g = players.cbegin(); g != players.cend(); ++g) | ||||
| @@ -2803,6 +2927,25 @@ std::string PlayerState::nodeName() const | ||||
| 	return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast<std::string>(color)); | ||||
| } | ||||
|  | ||||
|  | ||||
| bool RumorState::update(int id, int extra) | ||||
| { | ||||
| 	if(vstd::contains(last, type)) | ||||
| 	{ | ||||
| 		if(last[type].first != id) | ||||
| 		{ | ||||
| 			last[type].first = id; | ||||
| 			last[type].second = extra; | ||||
| 		} | ||||
| 		else | ||||
| 			return false; | ||||
| 	} | ||||
| 	else | ||||
| 		last[type] = std::make_pair(id, extra); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| InfoAboutArmy::InfoAboutArmy(): | ||||
|     owner(PlayerColor::NEUTRAL) | ||||
| {} | ||||
|   | ||||
| @@ -81,6 +81,34 @@ struct DLL_LINKAGE SThievesGuildInfo | ||||
|  | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE RumorState | ||||
| { | ||||
| 	enum ERumorType : ui8 | ||||
| 	{ | ||||
| 		TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP | ||||
| 	}; | ||||
|  | ||||
| 	enum ERumorTypeSpecial : ui8 | ||||
| 	{ | ||||
| 		RUMOR_OBELISKS = 208, | ||||
| 		RUMOR_ARTIFACTS = 209, | ||||
| 		RUMOR_ARMY = 210, | ||||
| 		RUMOR_INCOME = 211, | ||||
| 		RUMOR_GRAIL = 212 | ||||
| 	}; | ||||
|  | ||||
| 	ERumorType type; | ||||
| 	std::map<ERumorType, std::pair<int, int>> last; | ||||
|  | ||||
| 	RumorState(){type = TYPE_NONE; last = {};}; | ||||
| 	bool update(int id, int extra); | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & type & last; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct UpgradeInfo | ||||
| { | ||||
| 	CreatureID oldID; //creature to be upgraded | ||||
| @@ -183,6 +211,7 @@ public: | ||||
| 	std::map<PlayerColor, PlayerState> players; | ||||
| 	std::map<TeamID, TeamState> teams; | ||||
| 	CBonusSystemNode globalEffects; | ||||
| 	RumorState rumor; | ||||
|  | ||||
| 	boost::shared_mutex *mx; | ||||
|  | ||||
| @@ -196,6 +225,7 @@ public: | ||||
| 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //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; | ||||
| 	void updateRumor(); | ||||
|  | ||||
| 	// ----- victory, loss condition checks ----- | ||||
|  | ||||
| @@ -219,6 +249,13 @@ public: | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects & rand; | ||||
| 		if(version >= 755) | ||||
| 		{ | ||||
| 			h & rumor; | ||||
| 		} | ||||
| 		else if(!h.saving) | ||||
| 			rumor = RumorState(); | ||||
|  | ||||
| 		BONUS_TREE_DESERIALIZATION_FIX | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -327,6 +327,7 @@ CGeneralTextHandler::CGeneralTextHandler() | ||||
| 	readToVector("DATA/PRISKILL.TXT", primarySkillNames); | ||||
| 	readToVector("DATA/JKTEXT.TXT",   jktexts); | ||||
| 	readToVector("DATA/TVRNINFO.TXT", tavernInfo); | ||||
| 	readToVector("DATA/RANDTVRN.TXT", tavernRumors); | ||||
| 	readToVector("DATA/TURNDUR.TXT",  turnDurations); | ||||
| 	readToVector("DATA/HEROSCRN.TXT", heroscrn); | ||||
| 	readToVector("DATA/TENTCOLR.TXT", tentColors); | ||||
|   | ||||
| @@ -109,6 +109,7 @@ public: | ||||
| 	//towns | ||||
| 	std::vector<std::string> tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen | ||||
| 	std::vector<std::string> tavernInfo; | ||||
| 	std::vector<std::string> tavernRumors; | ||||
|  | ||||
| 	std::vector<std::pair<std::string,std::string> > zelp; | ||||
| 	std::vector<std::string> lossCondtions; | ||||
|   | ||||
| @@ -38,7 +38,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() | ||||
| } | ||||
|  | ||||
| CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) | ||||
| 	: CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) | ||||
| 	: CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({}) | ||||
| { | ||||
| 	assert(hero); | ||||
| 	assert(hero == getHero(hero->id)); | ||||
| @@ -53,6 +53,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan | ||||
|  | ||||
| 	hlp = make_unique<CPathfinderHelper>(hero, options); | ||||
|  | ||||
| 	initializePatrol(); | ||||
| 	initializeGraph(); | ||||
| 	neighbourTiles.reserve(8); | ||||
| 	neighbours.reserve(16); | ||||
| @@ -96,8 +97,10 @@ void CPathfinder::calculatePaths() | ||||
| 	CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); | ||||
| 	initialNode->turns = 0; | ||||
| 	initialNode->moveRemains = hero->movement; | ||||
| 	pq.push(initialNode); | ||||
| 	if(isHeroPatrolLocked()) | ||||
| 		return; | ||||
|  | ||||
| 	pq.push(initialNode); | ||||
| 	while(!pq.empty()) | ||||
| 	{ | ||||
| 		cp = pq.top(); | ||||
| @@ -120,6 +123,9 @@ void CPathfinder::calculatePaths() | ||||
| 		addNeighbours(); | ||||
| 		for(auto & neighbour : neighbours) | ||||
| 		{ | ||||
| 			if(!isPatrolMovementAllowed(neighbour)) | ||||
| 				continue; | ||||
|  | ||||
| 			dt = &gs->map->getTile(neighbour); | ||||
| 			dtObj = dt->topVisitableObj(); | ||||
| 			for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) | ||||
| @@ -216,7 +222,9 @@ void CPathfinder::addNeighbours() | ||||
| void CPathfinder::addTeleportExits() | ||||
| { | ||||
| 	neighbours.clear(); | ||||
| 	if(!isSourceVisitableObj()) | ||||
| 	/// For now we disable teleports usage for patrol movement | ||||
| 	/// VCAI not aware about patrol and may stuck while attempt to use teleport | ||||
| 	if(!isSourceVisitableObj() || patrolState == PATROL_RADIUS) | ||||
| 		return; | ||||
|  | ||||
| 	const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(ctObj); | ||||
| @@ -257,6 +265,22 @@ void CPathfinder::addTeleportExits() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool CPathfinder::isHeroPatrolLocked() const | ||||
| { | ||||
| 	return patrolState == PATROL_LOCKED; | ||||
| } | ||||
|  | ||||
| bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const | ||||
| { | ||||
| 	if(patrolState == PATROL_RADIUS) | ||||
| 	{ | ||||
| 		if(!vstd::contains(patrolTiles, dst)) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const | ||||
| { | ||||
| 	/// No layer transition allowed when previous node action is BATTLE | ||||
| @@ -565,6 +589,23 @@ bool CPathfinder::isDestinationGuardian() const | ||||
| 	return gs->guardingCreaturePosition(cp->coord) == dp->coord; | ||||
| } | ||||
|  | ||||
| void CPathfinder::initializePatrol() | ||||
| { | ||||
| 	auto state = PATROL_NONE; | ||||
| 	if(hero->patrol.patrolling && !getPlayer(hero->tempOwner)->human) | ||||
| 	{ | ||||
| 		if(hero->patrol.patrolRadious) | ||||
| 		{ | ||||
| 			state = PATROL_RADIUS; | ||||
| 			gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, boost::optional<PlayerColor>(), 0, true); | ||||
| 		} | ||||
| 		else | ||||
| 			state = PATROL_LOCKED; | ||||
| 	} | ||||
|  | ||||
| 	patrolState = state; | ||||
| } | ||||
|  | ||||
| void CPathfinder::initializeGraph() | ||||
| { | ||||
| 	auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) | ||||
|   | ||||
| @@ -161,6 +161,13 @@ private: | ||||
| 	const std::vector<std::vector<std::vector<ui8> > > &FoW; | ||||
| 	unique_ptr<CPathfinderHelper> hlp; | ||||
|  | ||||
| 	enum EPatrolState { | ||||
| 		PATROL_NONE = 0, | ||||
| 		PATROL_LOCKED = 1, | ||||
| 		PATROL_RADIUS | ||||
| 	} patrolState; | ||||
| 	std::unordered_set<int3, ShashInt3> patrolTiles; | ||||
|  | ||||
| 	struct NodeComparer | ||||
| 	{ | ||||
| 		bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const | ||||
| @@ -187,6 +194,9 @@ private: | ||||
| 	void addNeighbours(); | ||||
| 	void addTeleportExits(); | ||||
|  | ||||
| 	bool isHeroPatrolLocked() const; | ||||
| 	bool isPatrolMovementAllowed(const int3 & dst) const; | ||||
|  | ||||
| 	bool isLayerTransitionPossible(const ELayer dstLayer) const; | ||||
| 	bool isLayerTransitionPossible() const; | ||||
| 	bool isMovementToDestPossible() const; | ||||
| @@ -200,6 +210,7 @@ private: | ||||
| 	bool isDestinationGuarded(const bool ignoreAccessibility = true) const; | ||||
| 	bool isDestinationGuardian() const; | ||||
|  | ||||
| 	void initializePatrol(); | ||||
| 	void initializeGraph(); | ||||
|  | ||||
| 	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; | ||||
|   | ||||
| @@ -47,7 +47,7 @@ void CPrivilagedInfoCallback::getFreeTiles (std::vector<int3> &tiles) const | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player/*=uninit*/, int mode/*=0*/ ) const | ||||
| void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player/*=uninit*/, int mode/*=0*/, bool patrolDistance/*=false*/) const | ||||
| { | ||||
| 	if(!!player && *player >= PlayerColor::PLAYER_LIMIT) | ||||
| 	{ | ||||
| @@ -63,7 +63,13 @@ void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set<int3, ShashInt | ||||
| 		{ | ||||
| 			for (int yd = std::max<int>(pos.y - radious, 0); yd <= std::min<int>(pos.y + radious, gs->map->height - 1); yd++) | ||||
| 			{ | ||||
| 				double distance = pos.dist2d(int3(xd,yd,pos.z)) - 0.5; | ||||
| 				int3 tilePos(xd,yd,pos.z); | ||||
| 				double distance; | ||||
| 				if(patrolDistance) | ||||
| 					distance = pos.mandist2d(tilePos); | ||||
| 				else | ||||
| 					distance = pos.dist2d(tilePos) - 0.5; | ||||
|  | ||||
| 				if(distance <= radious) | ||||
| 				{ | ||||
| 					if(!player | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class DLL_LINKAGE CPrivilagedInfoCallback : public CGameInfoCallback | ||||
| public: | ||||
| 	CGameState * gameState(); | ||||
| 	void getFreeTiles (std::vector<int3> &tiles) const; //used for random spawns | ||||
| 	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode=0) const;  //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only unrevealed | ||||
| 	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode = 0, bool patrolDistance = false) const;  //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only unrevealed | ||||
| 	void getAllTiles (std::unordered_set<int3, ShashInt3> &tiles, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water | ||||
| 	void pickAllowedArtsSet(std::vector<const CArtifact*> &out); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant | ||||
| 	void getAllowedSpells(std::vector<SpellID> &out, ui16 level); | ||||
|   | ||||
| @@ -1222,7 +1222,7 @@ struct TeleportDialog : public Query//2006 | ||||
|  | ||||
| 	const CGHeroInstance *hero; | ||||
| 	TeleportChannelID channel; | ||||
| 	std::vector<ObjectInstanceID> exits; | ||||
| 	TTeleportExitsList exits; | ||||
| 	bool impassable; | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
|   | ||||
| @@ -1033,6 +1033,9 @@ DLL_LINKAGE void NewTurn::applyGs( CGameState *gs ) | ||||
|  | ||||
| 	for(CGTownInstance* t : gs->map->towns) | ||||
| 		t->builded = 0; | ||||
|  | ||||
| 	if(gs->getDate(Date::DAY_OF_WEEK) == 1) | ||||
| 		gs->updateRumor(); | ||||
| } | ||||
|  | ||||
| DLL_LINKAGE void SetObjectProperty::applyGs( CGameState *gs ) | ||||
|   | ||||
| @@ -105,6 +105,11 @@ public: | ||||
| 	{ | ||||
| 		return std::sqrt((double)dist2dSQ(o)); | ||||
| 	} | ||||
| 	//manhattan distance used for patrol radius (z coord is not used) | ||||
| 	double mandist2d(const int3 & o) const | ||||
| 	{ | ||||
| 		return abs(o.x - x) + abs(o.y - y); | ||||
| 	} | ||||
|  | ||||
| 	bool areNeighbours(const int3 & o) const | ||||
| 	{ | ||||
|   | ||||
| @@ -72,12 +72,23 @@ public: | ||||
|  | ||||
| 	struct DLL_LINKAGE Patrol | ||||
| 	{ | ||||
| 		Patrol(){patrolling=false;patrolRadious=-1;}; | ||||
| 		Patrol(){patrolling=false;initialPos=int3();patrolRadious=-1;}; | ||||
| 		bool patrolling; | ||||
| 		int3 initialPos; | ||||
| 		ui32 patrolRadious; | ||||
| 		template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 		{ | ||||
| 			h & patrolling & patrolRadious; | ||||
| 			h & patrolling; | ||||
| 			if(version >= 755) | ||||
| 			{ | ||||
| 				h & initialPos; | ||||
| 			} | ||||
| 			else if(!h.saving) | ||||
| 			{ | ||||
| 				patrolling = false; | ||||
| 				initialPos = int3(); | ||||
| 			} | ||||
| 			h & patrolRadious; | ||||
| 		} | ||||
| 	} patrol; | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,10 @@ class CGObjectInstance; | ||||
| struct MetaString; | ||||
| struct BattleResult; | ||||
|  | ||||
| // This one teleport-specific, but has to be available everywhere in callbacks and netpacks | ||||
| // For now it's will be there till teleports code refactored and moved into own file | ||||
| typedef std::vector<std::pair<ObjectInstanceID, int3>> TTeleportExitsList; | ||||
|  | ||||
| class DLL_LINKAGE IObjectInterface | ||||
| { | ||||
| public: | ||||
|   | ||||
| @@ -869,7 +869,7 @@ bool CGTeleport::isTeleport(const CGObjectInstance * obj) | ||||
|  | ||||
| bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst) | ||||
| { | ||||
| 	return src && dst && src != dst && src->isChannelExit(dst->id); | ||||
| 	return src && dst && src->isChannelExit(dst->id); | ||||
| } | ||||
|  | ||||
| bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstance * dst) | ||||
| @@ -948,7 +948,13 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const | ||||
| 	if(isEntrance()) | ||||
| 	{ | ||||
| 		if(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size()) | ||||
| 			td.exits = cb->getTeleportChannelExits(channel); | ||||
| 		{ | ||||
| 			auto exits = cb->getTeleportChannelExits(channel); | ||||
| 			for(auto exit : exits) | ||||
| 			{ | ||||
| 				td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(cb->isTeleportChannelImpassable(channel)) | ||||
| 		{ | ||||
| @@ -964,9 +970,9 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const | ||||
| 	cb->showTeleportDialog(&td); | ||||
| } | ||||
|  | ||||
| void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const | ||||
| void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const | ||||
| { | ||||
| 	ObjectInstanceID objId(answer); | ||||
| 	int3 dPos; | ||||
| 	auto realExits = getAllExits(true); | ||||
| 	if(!isEntrance() // Do nothing if hero visited exit only object | ||||
| 		|| (!exits.size() && !realExits.size()) // Do nothing if there no exits on this channel | ||||
| @@ -974,14 +980,12 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	else if(objId == ObjectInstanceID()) | ||||
| 		objId = getRandomExit(hero); | ||||
| 	else if(vstd::isValidIndex(exits, answer)) | ||||
| 		dPos = exits[answer].second; | ||||
| 	else | ||||
| 		assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list | ||||
| 		dPos = CGHeroInstance::convertPosition(cb->getObj(getRandomExit(hero))->visitablePos(), true); | ||||
|  | ||||
| 	auto obj = cb->getObj(objId); | ||||
| 	if(obj) | ||||
| 		cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); | ||||
| 	cb->moveHero(hero->id, dPos, true); | ||||
| } | ||||
|  | ||||
| void CGMonolith::initObj() | ||||
| @@ -1021,7 +1025,10 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const | ||||
| 		td.impassable = true; | ||||
| 	} | ||||
| 	else | ||||
| 		td.exits.push_back(getRandomExit(h)); | ||||
| 	{ | ||||
| 		auto exit = getRandomExit(h); | ||||
| 		td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); | ||||
| 	} | ||||
|  | ||||
| 	cb->showTeleportDialog(&td); | ||||
| } | ||||
| @@ -1120,29 +1127,35 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const | ||||
| 		cb->changeStackCount(StackLocation(h, targetstack), -countToTake); | ||||
| 	} | ||||
| 	else | ||||
| 		 td.exits = getAllExits(true); | ||||
| 	{ | ||||
| 		auto exits = getAllExits(); | ||||
| 		for(auto exit : exits) | ||||
| 		{ | ||||
| 			auto blockedPosList = cb->getObj(exit)->getBlockedPos(); | ||||
| 			for(auto bPos : blockedPosList) | ||||
| 				td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(bPos, true))); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	cb->showTeleportDialog(&td); | ||||
| } | ||||
|  | ||||
| void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const | ||||
| void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const | ||||
| { | ||||
| 	ObjectInstanceID objId(answer); | ||||
| 	int3 dPos; | ||||
| 	auto realExits = getAllExits(); | ||||
| 	if(!exits.size() && !realExits.size()) | ||||
| 		return; | ||||
| 	else if(objId == ObjectInstanceID()) | ||||
| 		objId = getRandomExit(hero); | ||||
| 	else if(vstd::isValidIndex(exits, answer)) | ||||
| 		dPos = exits[answer].second; | ||||
| 	else | ||||
| 		assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list | ||||
|  | ||||
| 	auto obj = cb->getObj(objId); | ||||
| 	if(obj) | ||||
| 	{ | ||||
| 		auto obj = cb->getObj(getRandomExit(hero)); | ||||
| 		std::set<int3> tiles = obj->getBlockedPos(); | ||||
| 		auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); | ||||
| 		cb->moveHero(hero->id, CGHeroInstance::convertPosition(tile, true), true); | ||||
| 		dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true); | ||||
| 	} | ||||
|  | ||||
| 	cb->moveHero(hero->id, dPos, true); | ||||
| } | ||||
|  | ||||
| bool CGWhirlpool::isProtected( const CGHeroInstance * h ) | ||||
|   | ||||
| @@ -312,7 +312,7 @@ public: | ||||
| 	bool isEntrance() const; | ||||
| 	bool isExit() const; | ||||
|  | ||||
| 	virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const = 0; | ||||
| 	virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const = 0; | ||||
|  | ||||
| 	static bool isTeleport(const CGObjectInstance * dst); | ||||
| 	static bool isConnected(const CGTeleport * src, const CGTeleport * dst); | ||||
| @@ -333,7 +333,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport | ||||
|  | ||||
| protected: | ||||
| 	void onHeroVisit(const CGHeroInstance * h) const override; | ||||
| 	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const override; | ||||
| 	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; | ||||
| 	void initObj() override; | ||||
|  | ||||
| public: | ||||
| @@ -360,7 +360,7 @@ public: | ||||
| class DLL_LINKAGE CGWhirlpool : public CGMonolith | ||||
| { | ||||
| 	void onHeroVisit(const CGHeroInstance * h) const override; | ||||
| 	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector<ObjectInstanceID> exits) const override; | ||||
| 	void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; | ||||
| 	static bool isProtected( const CGHeroInstance * h ); | ||||
|  | ||||
| public: | ||||
|   | ||||
| @@ -1060,7 +1060,7 @@ void CMapLoaderH3M::readObjects() | ||||
| 		case Obj::RANDOM_HERO: | ||||
| 		case Obj::PRISON: | ||||
| 			{ | ||||
| 				nobj = readHero(idToBeGiven); | ||||
| 				nobj = readHero(idToBeGiven, objPos); | ||||
| 				break; | ||||
| 			} | ||||
| 		case Obj::MONSTER:  //Monster | ||||
| @@ -1549,7 +1549,7 @@ void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number) | ||||
| 	out->validTypes(true); | ||||
| } | ||||
|  | ||||
| CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) | ||||
| CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos) | ||||
| { | ||||
| 	auto nhi = new CGHeroInstance(); | ||||
|  | ||||
| @@ -1658,6 +1658,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) | ||||
| 	else | ||||
| 	{ | ||||
| 		nhi->patrol.patrolling = true; | ||||
| 		nhi->patrol.initialPos = CGHeroInstance::convertPosition(initialPos, false); | ||||
| 	} | ||||
|  | ||||
| 	if(map->version > EMapFormat::ROE) | ||||
|   | ||||
| @@ -172,7 +172,7 @@ private: | ||||
| 	 * @param idToBeGiven the object id which should be set for the hero | ||||
| 	 * @return a object instance | ||||
| 	 */ | ||||
| 	CGObjectInstance * readHero(ObjectInstanceID idToBeGiven); | ||||
| 	CGObjectInstance * readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos); | ||||
|  | ||||
| 	/** | ||||
| 	 * Reads a seer hut. | ||||
|   | ||||
| @@ -1755,7 +1755,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo | ||||
| 	} | ||||
|  | ||||
|     logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst; | ||||
| 	const int3 hmpos = dst + int3(-1,0,0); | ||||
| 	const int3 hmpos = CGHeroInstance::convertPosition(dst, false); | ||||
|  | ||||
| 	if(!gs->map->isInTheMap(hmpos)) | ||||
| 	{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user