mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #2868 from IvanSavenko/simultaneous_turns
Simultaneous turns
This commit is contained in:
		| @@ -41,12 +41,10 @@ bool CCallback::moveHero(const CGHeroInstance *h, int3 dst, bool transit) | ||||
|  | ||||
| int CCallback::selectionMade(int selection, QueryID queryID) | ||||
| { | ||||
| 	JsonNode reply(JsonNode::JsonType::DATA_INTEGER); | ||||
| 	reply.Integer() = selection; | ||||
| 	return sendQueryReply(reply, queryID); | ||||
| 	return sendQueryReply(selection, queryID); | ||||
| } | ||||
|  | ||||
| int CCallback::sendQueryReply(const JsonNode & reply, QueryID queryID) | ||||
| int CCallback::sendQueryReply(std::optional<int32_t> reply, QueryID queryID) | ||||
| { | ||||
| 	ASSERT_IF_CALLED_WITH_PLAYER | ||||
| 	if(queryID == QueryID(-1)) | ||||
|   | ||||
| @@ -82,7 +82,7 @@ public: | ||||
| 	virtual void trade(const IMarket * market, EMarketMode mode, const std::vector<ui32> & id1, const std::vector<ui32> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0; | ||||
|  | ||||
| 	virtual int selectionMade(int selection, QueryID queryID) =0; | ||||
| 	virtual int sendQueryReply(const JsonNode & reply, QueryID queryID) =0; | ||||
| 	virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0; | ||||
| 	virtual int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//swaps creatures between two possibly different garrisons // TODO: AI-unsafe code - fix it! | ||||
| 	virtual int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//joins first stack to the second (creatures must be same type) | ||||
| 	virtual int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) =0; //first goes to the second | ||||
| @@ -159,7 +159,7 @@ public: | ||||
| 	bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile) | ||||
| 	bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where); | ||||
| 	int selectionMade(int selection, QueryID queryID) override; | ||||
| 	int sendQueryReply(const JsonNode & reply, QueryID queryID) override; | ||||
| 	int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) override; | ||||
| 	int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; | ||||
| 	int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second | ||||
| 	int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second | ||||
|   | ||||
| @@ -167,47 +167,61 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std:: | ||||
| 	CCS->musich->loadTerrainMusicThemes(); | ||||
|  | ||||
| 	initializeHeroTownList(); | ||||
|  | ||||
| 	// always recreate advmap interface to avoid possible memory-corruption bugs | ||||
| 	adventureInt.reset(new AdventureMapInterface()); | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::playerEndsTurn(PlayerColor player) | ||||
| { | ||||
| 	EVENT_HANDLER_CALLED_BY_CLIENT; | ||||
| 	if (player == playerID) | ||||
| 	{ | ||||
| 		makingTurn = false; | ||||
|  | ||||
| 		// remove all active dialogs that do not expect query answer | ||||
| 		for (;;) | ||||
| 		{ | ||||
| 			auto adventureWindow = GH.windows().topWindow<AdventureMapInterface>(); | ||||
| 			auto infoWindow = GH.windows().topWindow<CInfoWindow>(); | ||||
|  | ||||
| 			if(adventureWindow != nullptr) | ||||
| 				break; | ||||
|  | ||||
| 			if(infoWindow && infoWindow->ID != QueryID::NONE) | ||||
| 				break; | ||||
|  | ||||
| 			if (infoWindow) | ||||
| 				infoWindow->close(); | ||||
| 			else | ||||
| 				GH.windows().popWindows(1); | ||||
| 		} | ||||
|  | ||||
| 		// remove all pending dialogs that do not expect query answer | ||||
| 		vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){ | ||||
| 			return window->ID == QueryID::NONE; | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::playerStartsTurn(PlayerColor player) | ||||
| { | ||||
| 	EVENT_HANDLER_CALLED_BY_CLIENT; | ||||
|  | ||||
| 	movementController->onPlayerTurnStarted(); | ||||
|  | ||||
| 	if(GH.windows().findWindows<AdventureMapInterface>().empty()) | ||||
| 	{ | ||||
| 		// after map load - remove all active windows and replace them with adventure map | ||||
| 		// always recreate advmap interface to avoid possible memory-corruption bugs | ||||
| 		adventureInt.reset(new AdventureMapInterface()); | ||||
|  | ||||
| 		GH.windows().clear(); | ||||
| 		GH.windows().pushWindow(adventureInt); | ||||
| 	} | ||||
|  | ||||
| 	// close window from another player | ||||
| 	if(auto w = GH.windows().topWindow<CInfoWindow>()) | ||||
| 		if(w->ID == QueryID::NONE && player != playerID) | ||||
| 			w->close(); | ||||
| 	 | ||||
| 	// remove all dialogs that do not expect query answer | ||||
| 	while (!GH.windows().topWindow<AdventureMapInterface>() && !GH.windows().topWindow<CInfoWindow>()) | ||||
| 		GH.windows().popWindows(1); | ||||
|  | ||||
| 	EVENT_HANDLER_CALLED_BY_CLIENT; | ||||
| 	if (player != playerID && LOCPLINT == this) | ||||
| 	{ | ||||
| 		waitWhileDialog(); | ||||
|  | ||||
| 		bool isHuman = cb->getStartInfo()->playerInfos.count(player) && cb->getStartInfo()->playerInfos.at(player).isControlledByHuman(); | ||||
|  | ||||
| 		adventureInt->onEnemyTurnStarted(player, isHuman); | ||||
| 		if (makingTurn == false) | ||||
| 			adventureInt->onEnemyTurnStarted(player, isHuman); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -335,6 +349,7 @@ void CPlayerInterface::acceptTurn(QueryID queryID) | ||||
| 	} | ||||
| 	 | ||||
| 	cb->selectionMade(0, queryID); | ||||
| 	movementController->onPlayerTurnStarted(); | ||||
| } | ||||
|  | ||||
| void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) | ||||
| @@ -1058,15 +1073,12 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component | ||||
|  | ||||
| 	auto selectCallback = [=](int selection) | ||||
| 	{ | ||||
| 		JsonNode reply(JsonNode::JsonType::DATA_INTEGER); | ||||
| 		reply.Integer() = selection; | ||||
| 		cb->sendQueryReply(reply, askID); | ||||
| 		cb->sendQueryReply(selection, askID); | ||||
| 	}; | ||||
|  | ||||
| 	auto cancelCallback = [=]() | ||||
| 	{ | ||||
| 		JsonNode reply(JsonNode::JsonType::DATA_NULL); | ||||
| 		cb->sendQueryReply(reply, askID); | ||||
| 		cb->sendQueryReply(std::nullopt, askID); | ||||
| 	}; | ||||
|  | ||||
| 	const std::string localTitle = title.toString(); | ||||
|   | ||||
| @@ -477,6 +477,13 @@ void CServerHandler::setDifficulty(int to) const | ||||
| 	sendLobbyPack(lsd); | ||||
| } | ||||
|  | ||||
| void CServerHandler::setSimturnsInfo(const SimturnsInfo & info) const | ||||
| { | ||||
| 	LobbySetSimturns pack; | ||||
| 	pack.simturnsInfo = info; | ||||
| 	sendLobbyPack(pack); | ||||
| } | ||||
|  | ||||
| void CServerHandler::setTurnTimerInfo(const TurnTimerInfo & info) const | ||||
| { | ||||
| 	LobbySetTurnTime lstt; | ||||
|   | ||||
| @@ -66,6 +66,7 @@ public: | ||||
| 	virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0; | ||||
| 	virtual void setDifficulty(int to) const = 0; | ||||
| 	virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0; | ||||
| 	virtual void setSimturnsInfo(const SimturnsInfo &) const = 0; | ||||
| 	virtual void sendMessage(const std::string & txt) const = 0; | ||||
| 	virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it? | ||||
| 	virtual void sendStartGame(bool allowOnlyAI = false) const = 0; | ||||
| @@ -148,6 +149,7 @@ public: | ||||
| 	void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override; | ||||
| 	void setDifficulty(int to) const override; | ||||
| 	void setTurnTimerInfo(const TurnTimerInfo &) const override; | ||||
| 	void setSimturnsInfo(const SimturnsInfo &) const override; | ||||
| 	void sendMessage(const std::string & txt) const override; | ||||
| 	void sendGuiAction(ui8 action) const override; | ||||
| 	void sendRestartGame() const override; | ||||
|   | ||||
| @@ -71,7 +71,7 @@ std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts() | ||||
| 		{ EShortcut::ADVENTURE_GAME_OPTIONS,     optionInMapView(),      [this]() { this->adventureOptions(); } }, | ||||
| 		{ EShortcut::GLOBAL_OPTIONS,             optionInMapView(),      [this]() { this->systemOptions(); } }, | ||||
| 		{ EShortcut::ADVENTURE_NEXT_HERO,        optionHasNextHero(),    [this]() { this->nextHero(); } }, | ||||
| 		{ EShortcut::GAME_END_TURN,              optionInMapView(),      [this]() { this->endTurn(); } }, | ||||
| 		{ EShortcut::GAME_END_TURN,              optionCanEndTurn(),     [this]() { this->endTurn(); } }, | ||||
| 		{ EShortcut::ADVENTURE_THIEVES_GUILD,    optionInMapView(),      [this]() { this->showThievesGuild(); } }, | ||||
| 		{ EShortcut::ADVENTURE_VIEW_SCENARIO,    optionInMapView(),      [this]() { this->showScenarioInfo(); } }, | ||||
| 		{ EShortcut::GAME_SAVE_GAME,             optionInMapView(),      [this]() { this->saveGame(); } }, | ||||
| @@ -453,6 +453,11 @@ bool AdventureMapShortcuts::optionHasNextHero() | ||||
| 	return optionInMapView() && nextSuitableHero != nullptr; | ||||
| } | ||||
|  | ||||
| bool AdventureMapShortcuts::optionCanEndTurn() | ||||
| { | ||||
| 	return optionInMapView() && LOCPLINT->makingTurn; | ||||
| } | ||||
|  | ||||
| bool AdventureMapShortcuts::optionSpellcasting() | ||||
| { | ||||
| 	return state == EAdventureState::CASTING_SPELL; | ||||
|   | ||||
| @@ -78,6 +78,7 @@ public: | ||||
| 	bool optionHeroCanMove(); | ||||
| 	bool optionHasNextHero(); | ||||
| 	bool optionCanVisitObject(); | ||||
| 	bool optionCanEndTurn(); | ||||
| 	bool optionSpellcasting(); | ||||
| 	bool optionInMapView(); | ||||
| 	bool optionInWorldView(); | ||||
|   | ||||
| @@ -59,6 +59,7 @@ public: | ||||
| 	std::shared_ptr<CButton> buttonOptions; | ||||
| 	std::shared_ptr<CButton> buttonStart; | ||||
| 	std::shared_ptr<CButton> buttonBack; | ||||
| 	std::shared_ptr<CButton> buttonSimturns; | ||||
|  | ||||
| 	std::shared_ptr<SelectionTab> tabSel; | ||||
| 	std::shared_ptr<OptionsTab> tabOpt; | ||||
|   | ||||
| @@ -58,6 +58,12 @@ OptionsTab::OptionsTab() : humanPlayers(0) | ||||
| 			CSH->setTurnTimerInfo(tinfo); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	addCallback("setSimturnDuration", [&](int index){ | ||||
| 		SimturnsInfo info; | ||||
| 		info.optionalTurns = index; | ||||
| 		CSH->setSimturnsInfo(info); | ||||
| 	}); | ||||
| 	 | ||||
| 	//helper function to parse string containing time to integer reflecting time in seconds | ||||
| 	//assumed that input string can be modified by user, function shall support user's intention | ||||
| @@ -214,6 +220,18 @@ void OptionsTab::recreate() | ||||
|  | ||||
| 		entries.insert(std::make_pair(pInfo.first, std::make_shared<PlayerOptionsEntry>(pInfo.second, * this))); | ||||
| 	} | ||||
|  | ||||
| 	//Simultaneous turns | ||||
| 	if(auto turnSlider = widget<CSlider>("labelSimturnsDurationValue")) | ||||
| 		turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); | ||||
|  | ||||
| 	if(auto w = widget<CLabel>("labelSimturnsDurationValue")) | ||||
| 	{ | ||||
| 		MetaString message; | ||||
| 		message.appendRawString("Simturns: up to %d days"); | ||||
| 		message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); | ||||
| 		w->setText(message.toString()); | ||||
| 	} | ||||
| 	 | ||||
| 	const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; | ||||
| 	 | ||||
|   | ||||
| @@ -73,16 +73,41 @@ | ||||
| 			"adoptHeight": true | ||||
| 		}, | ||||
| 		 | ||||
| 		// timer | ||||
| 		{ | ||||
| 			"name": "simturnsDuration", | ||||
| 			"type": "slider", | ||||
| 			"orientation": "horizontal", | ||||
| 			"position": {"x": 55, "y": 537}, | ||||
| 			"size": 194, | ||||
| 			"callback": "setSimturnDuration", | ||||
| 			"itemsVisible": 1, | ||||
| 			"itemsTotal": 28, | ||||
| 			"selected": 0, | ||||
| 			"style": "blue", | ||||
| 			"scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, | ||||
| 			"panningStep": 20 | ||||
| 		}, | ||||
| 		 | ||||
| 		{ | ||||
| 			"name": "labelSimturnsDurationValue", | ||||
| 			"type": "label", | ||||
| 			"font": "small", | ||||
| 			"alignment": "center", | ||||
| 			"color": "yellow", | ||||
| 			"text": "core.genrltxt.521", | ||||
| 			"position": {"x": 222, "y": 544} | ||||
| 			"color": "white", | ||||
| 			"text": "", | ||||
| 			"position": {"x": 319, "y": 545} | ||||
| 		}, | ||||
| 		 | ||||
| 		// timer | ||||
| 		//{ | ||||
| 		//	"type": "label", | ||||
| 		//	"font": "small", | ||||
| 		//	"alignment": "center", | ||||
| 		//	"color": "yellow", | ||||
| 		//	"text": "core.genrltxt.521", | ||||
| 		//	"position": {"x": 222, "y": 544} | ||||
| 		//}, | ||||
| 		 | ||||
| 		{ | ||||
| 			"name": "labelTurnDurationValue", | ||||
| 			"type": "label", | ||||
| @@ -104,7 +129,8 @@ | ||||
| 			"itemsTotal": 11, | ||||
| 			"selected": 11, | ||||
| 			"style": "blue", | ||||
| 			"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, | ||||
| 			"scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, | ||||
| 			//"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, | ||||
| 			"panningStep": 20 | ||||
| 		}, | ||||
| 	], | ||||
|   | ||||
| @@ -158,6 +158,7 @@ public: | ||||
| 	virtual void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) {} | ||||
| 	virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) {} | ||||
| 	virtual void visitLobbySetPlayer(LobbySetPlayer & pack) {} | ||||
| 	virtual void visitLobbySetSimturns(LobbySetSimturns & pack) {} | ||||
| 	virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) {} | ||||
| 	virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) {} | ||||
| 	virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {} | ||||
|   | ||||
| @@ -2609,14 +2609,14 @@ struct DLL_LINKAGE BuildBoat : public CPackForServer | ||||
| struct DLL_LINKAGE QueryReply : public CPackForServer | ||||
| { | ||||
| 	QueryReply() = default; | ||||
| 	QueryReply(const QueryID & QID, const JsonNode & Reply) | ||||
| 	QueryReply(const QueryID & QID, std::optional<int32_t> Reply) | ||||
| 		: qid(QID) | ||||
| 		, reply(Reply) | ||||
| 	{ | ||||
| 	} | ||||
| 	QueryID qid; | ||||
| 	PlayerColor player; | ||||
| 	JsonNode reply; | ||||
| 	std::optional<int32_t> reply; | ||||
|  | ||||
| 	virtual void visitTyped(ICPackVisitor & visitor) override; | ||||
|  | ||||
|   | ||||
| @@ -750,6 +750,11 @@ void LobbySetPlayer::visitTyped(ICPackVisitor & visitor) | ||||
| 	visitor.visitLobbySetPlayer(*this); | ||||
| } | ||||
|  | ||||
| void LobbySetSimturns::visitTyped(ICPackVisitor & visitor) | ||||
| { | ||||
| 	visitor.visitLobbySetSimturns(*this); | ||||
| } | ||||
|  | ||||
| void LobbySetTurnTime::visitTyped(ICPackVisitor & visitor) | ||||
| { | ||||
| 	visitor.visitLobbySetTurnTime(*this); | ||||
|   | ||||
| @@ -250,6 +250,18 @@ struct DLL_LINKAGE LobbySetPlayer : public CLobbyPackToServer | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE LobbySetSimturns : public CLobbyPackToServer | ||||
| { | ||||
| 	SimturnsInfo simturnsInfo; | ||||
|  | ||||
| 	virtual void visitTyped(ICPackVisitor & visitor) override; | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & simturnsInfo; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE LobbySetTurnTime : public CLobbyPackToServer | ||||
| { | ||||
| 	TurnTimerInfo turnTimerInfo; | ||||
|   | ||||
| @@ -23,6 +23,24 @@ class CMapInfo; | ||||
| struct PlayerInfo; | ||||
| class PlayerColor; | ||||
|  | ||||
| struct DLL_LINKAGE SimturnsInfo | ||||
| { | ||||
| 	/// Minimal number of turns that must be played simultaneously even if contact has been detected | ||||
| 	int requiredTurns = 0; | ||||
| 	/// Maximum number of turns that might be played simultaneously unless contact is detected | ||||
| 	int optionalTurns = 0; | ||||
| 	/// If set to true, human and 1 AI can act at the same time | ||||
| 	bool allowHumanWithAI = true; | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| 		h & requiredTurns; | ||||
| 		h & optionalTurns; | ||||
| 		h & allowHumanWithAI; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /// Struct which describes the name, the color, the starting bonus of a player | ||||
| struct DLL_LINKAGE PlayerSettings | ||||
| { | ||||
| @@ -84,6 +102,7 @@ struct DLL_LINKAGE StartInfo | ||||
| 	ui32 seedPostInit; //so we know that game is correctly synced at the start; 0 if not known yet | ||||
| 	ui32 mapfileChecksum; //0 if not relevant | ||||
| 	std::string startTimeIso8601; | ||||
| 	SimturnsInfo simturnsInfo; | ||||
| 	TurnTimerInfo turnTimerInfo; | ||||
| 	std::string mapname; // empty for random map, otherwise name of the map or savegame | ||||
| 	bool createRandomMap() const { return mapGenOptions != nullptr; } | ||||
| @@ -108,6 +127,7 @@ struct DLL_LINKAGE StartInfo | ||||
| 		h & seedPostInit; | ||||
| 		h & mapfileChecksum; | ||||
| 		h & startTimeIso8601; | ||||
| 		h & simturnsInfo; | ||||
| 		h & turnTimerInfo; | ||||
| 		h & mapname; | ||||
| 		h & mapGenOptions; | ||||
|   | ||||
| @@ -372,7 +372,7 @@ ObjectInstanceID CGTeleport::getRandomExit(const CGHeroInstance * h) const | ||||
|  | ||||
| bool CGTeleport::isTeleport(const CGObjectInstance * obj) | ||||
| { | ||||
| 	return ((dynamic_cast<const CGTeleport *>(obj))); | ||||
| 	return dynamic_cast<const CGTeleport *>(obj) != nullptr; | ||||
| } | ||||
|  | ||||
| bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst) | ||||
|   | ||||
| @@ -121,6 +121,8 @@ void CPathfinder::calculatePaths() | ||||
| 			movement = hlp->getMaxMovePoints(source.node->layer); | ||||
| 			if(!hlp->passOneTurnLimitCheck(source)) | ||||
| 				continue; | ||||
| 			if(turn >= hlp->options.turnLimit) | ||||
| 				continue; | ||||
| 		} | ||||
|  | ||||
| 		source.isInitialPosition = source.nodeHero == hlp->hero; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class CGWhirlpool; | ||||
| struct TurnInfo; | ||||
| struct PathfinderOptions; | ||||
|  | ||||
| class CPathfinder | ||||
| class DLL_LINKAGE CPathfinder | ||||
| { | ||||
| public: | ||||
| 	friend class CPathfinderHelper; | ||||
|   | ||||
| @@ -30,6 +30,7 @@ PathfinderOptions::PathfinderOptions() | ||||
| 	, lightweightFlyingMode(false) | ||||
| 	, oneTurnSpecialLayersLimit(true) | ||||
| 	, originalMovementRules(false) | ||||
| 	, turnLimit(std::numeric_limits<uint8_t>::max()) | ||||
| { | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,9 @@ struct DLL_LINKAGE PathfinderOptions | ||||
| 	///   I find it's reasonable limitation, but it's will make some movements more expensive than in H3. | ||||
| 	bool originalMovementRules; | ||||
|  | ||||
| 	/// Max number of turns to compute. Default = infinite | ||||
| 	uint8_t turnLimit; | ||||
|  | ||||
| 	PathfinderOptions(); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -399,6 +399,7 @@ void registerTypesLobbyPacks(Serializer &s) | ||||
| 	s.template registerType<CLobbyPackToServer, LobbySetCampaignBonus>(); | ||||
| 	s.template registerType<CLobbyPackToServer, LobbySetPlayer>(); | ||||
| 	s.template registerType<CLobbyPackToServer, LobbySetTurnTime>(); | ||||
| 	s.template registerType<CLobbyPackToServer, LobbySetSimturns>(); | ||||
| 	s.template registerType<CLobbyPackToServer, LobbySetDifficulty>(); | ||||
| 	s.template registerType<CLobbyPackToServer, LobbyForceSetPlayer>(); | ||||
| } | ||||
|   | ||||
| @@ -482,11 +482,11 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons | ||||
|  | ||||
| 	if(!parameters.pos.valid() && parameters.caster->getSpellSchoolLevel(owner) >= 2) | ||||
| 	{ | ||||
| 		auto queryCallback = [=](const JsonNode & reply) -> void | ||||
| 		auto queryCallback = [=](std::optional<int32_t> reply) -> void | ||||
| 		{ | ||||
| 			if(reply.getType() == JsonNode::JsonType::DATA_INTEGER) | ||||
| 			if(reply.has_value()) | ||||
| 			{ | ||||
| 				ObjectInstanceID townId(static_cast<si32>(reply.Integer())); | ||||
| 				ObjectInstanceID townId(*reply); | ||||
|  | ||||
| 				const CGObjectInstance * o = env->getCb()->getObj(townId, true); | ||||
| 				if(o == nullptr) | ||||
|   | ||||
| @@ -56,7 +56,7 @@ public: | ||||
|  | ||||
| 	virtual bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) = 0;	//TODO: remove | ||||
|  | ||||
| 	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented | ||||
| 	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented | ||||
| }; | ||||
|  | ||||
| namespace spells | ||||
|   | ||||
| @@ -1086,8 +1086,18 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
|  | ||||
| 	const TerrainTile t = *getTile(hmpos); | ||||
| 	const int3 guardPos = gs->guardingCreaturePosition(hmpos); | ||||
| 	CGObjectInstance * objectToVisit = nullptr; | ||||
| 	CGObjectInstance * guardian = nullptr; | ||||
|  | ||||
| 	const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; | ||||
| 	if (!t.visitableObjects.empty()) | ||||
| 		objectToVisit = t.visitableObjects.back(); | ||||
|  | ||||
| 	if (isInTheMap(guardPos)) | ||||
| 		guardian = getTile(guardPos)->visitableObjects.back(); | ||||
|  | ||||
| 	assert(guardian == nullptr || dynamic_cast<CGCreature*>(guardian) != nullptr); | ||||
|  | ||||
| 	const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT; | ||||
| 	const bool disembarking = h->boat | ||||
| 		&& t.terType->isLand() | ||||
| 		&& (dst == h->pos | ||||
| @@ -1110,7 +1120,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 	const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining()); | ||||
|  | ||||
| 	const bool standAtObstacle = t.blocked && !t.visitable; | ||||
| 	const bool standAtWater = !h->boat && t.terType->isWater() && (t.visitableObjects.empty() || !t.visitableObjects.back()->isCoastVisitable()); | ||||
| 	const bool standAtWater = !h->boat && t.terType->isWater() && (objectToVisit || !objectToVisit->isCoastVisitable()); | ||||
|  | ||||
| 	const auto complainRet = [&](const std::string & message) | ||||
| 	{ | ||||
| @@ -1120,29 +1130,41 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 		return false; | ||||
| 	}; | ||||
|  | ||||
| 	if (guardian && getVisitingHero(guardian) != nullptr) | ||||
| 		return complainRet("Cannot move hero, destination monster is busy!"); | ||||
|  | ||||
| 	if (objectToVisit && getVisitingHero(objectToVisit) != nullptr) | ||||
| 		return complainRet("Cannot move hero, destination object is busy!"); | ||||
|  | ||||
| 	if (objectToVisit && | ||||
| 		objectToVisit->getOwner().isValidPlayer() && | ||||
| 		getPlayerRelations(objectToVisit->getOwner(), h->getOwner()) == PlayerRelations::ENEMIES && | ||||
| 		!turnOrder->isContactAllowed(objectToVisit->getOwner(), h->getOwner())) | ||||
| 		return complainRet("Cannot move hero, destination player is busy!"); | ||||
|  | ||||
| 	//it's a rock or blocked and not visitable tile | ||||
| 	//OR hero is on land and dest is water and (there is not present only one object - boat) | ||||
| 	if (!t.terType->isPassable() || (standAtObstacle && !canFly)) | ||||
| 		complainRet("Cannot move hero, destination tile is blocked!"); | ||||
| 		return complainRet("Cannot move hero, destination tile is blocked!"); | ||||
|  | ||||
| 	//hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) | ||||
| 	if(standAtWater && !canFly && !canWalkOnSea) | ||||
| 		complainRet("Cannot move hero, destination tile is on water!"); | ||||
| 		return complainRet("Cannot move hero, destination tile is on water!"); | ||||
|  | ||||
| 	if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked) | ||||
| 		complainRet("Cannot disembark hero, tile is blocked!"); | ||||
| 		return complainRet("Cannot disembark hero, tile is blocked!"); | ||||
|  | ||||
| 	if(distance(h->pos, dst) >= 1.5 && !teleporting) | ||||
| 		complainRet("Tiles are not neighboring!"); | ||||
| 		return complainRet("Tiles are not neighboring!"); | ||||
|  | ||||
| 	if(h->inTownGarrison) | ||||
| 		complainRet("Can not move garrisoned hero!"); | ||||
| 		return complainRet("Can not move garrisoned hero!"); | ||||
|  | ||||
| 	if(h->movementPointsRemaining() < cost && dst != h->pos && !teleporting) | ||||
| 		complainRet("Hero doesn't have any movement points left!"); | ||||
| 		return complainRet("Hero doesn't have any movement points left!"); | ||||
|  | ||||
| 	if (transit && !canFly && !(canWalkOnSea && t.terType->isWater())) | ||||
| 		complainRet("Hero cannot transit over this tile!"); | ||||
| 	if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()) && !CGTeleport::isTeleport(objectToVisit)) | ||||
| 		return complainRet("Hero cannot transit over this tile!"); | ||||
|  | ||||
| 	//several generic blocks of code | ||||
|  | ||||
| @@ -1173,14 +1195,13 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 		tmh.result = result; | ||||
| 		sendAndApply(&tmh); | ||||
|  | ||||
| 		if (visitDest == VISIT_DEST && t.topVisitableObj() && t.topVisitableObj()->id == h->id) | ||||
| 		if (visitDest == VISIT_DEST && objectToVisit && objectToVisit->id == h->id) | ||||
| 		{ // Hero should be always able to visit any object he staying on even if there guards around | ||||
| 			visitObjectOnTile(t, h); | ||||
| 		} | ||||
| 		else if (lookForGuards == CHECK_FOR_GUARDS && isInTheMap(guardPos)) | ||||
| 		{ | ||||
| 			const TerrainTile &guardTile = *gs->getTile(guardPos); | ||||
| 			objectVisited(guardTile.visitableObjects.back(), h); | ||||
| 			objectVisited(guardian, h); | ||||
|  | ||||
| 			moveQuery->visitDestAfterVictory = visitDest==VISIT_DEST; | ||||
| 		} | ||||
| @@ -1238,9 +1259,9 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 		// visit town for town portal \ castle gates | ||||
| 		// do not use generic visitObjectOnTile to avoid double-teleporting | ||||
| 		// if this moveHero call was triggered by teleporter | ||||
| 		if (!t.visitableObjects.empty()) | ||||
| 		if (objectToVisit) | ||||
| 		{ | ||||
| 			if (CGTownInstance * town = dynamic_cast<CGTownInstance *>(t.visitableObjects.back())) | ||||
| 			if (CGTownInstance * town = dynamic_cast<CGTownInstance *>(objectToVisit)) | ||||
| 				town->onHeroVisit(h); | ||||
| 		} | ||||
|  | ||||
| @@ -1258,7 +1279,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 		EVisitDest visitDest = VISIT_DEST; | ||||
| 		if (transit) | ||||
| 		{ | ||||
| 			if (CGTeleport::isTeleport(t.topVisitableObj())) | ||||
| 			if (CGTeleport::isTeleport(objectToVisit)) | ||||
| 				visitDest = DONT_VISIT_DEST; | ||||
|  | ||||
| 			if (canFly || (canWalkOnSea && t.terType->isWater())) | ||||
| @@ -1271,7 +1292,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo | ||||
| 			return true; | ||||
| 		 | ||||
| 		if(h->boat && !h->boat->onboardAssaultAllowed) | ||||
| 		   lookForGuards = IGNORE_GUARDS; | ||||
| 			lookForGuards = IGNORE_GUARDS; | ||||
|  | ||||
| 		turnTimerHandler.setEndTurnAllowed(h->getOwner(), !standAtWater && !standAtObstacle); | ||||
| 		doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); | ||||
| @@ -2197,6 +2218,11 @@ bool CGameHandler::hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> | ||||
| 	return connections.at(player).count(c); | ||||
| } | ||||
|  | ||||
| bool CGameHandler::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const | ||||
| { | ||||
| 	return connections.at(left) == connections.at(right); | ||||
| } | ||||
|  | ||||
| bool CGameHandler::disbandCreature(ObjectInstanceID id, SlotID pos) | ||||
| { | ||||
| 	const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObjInstance(id)); | ||||
| @@ -2422,6 +2448,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		COMPLAIN_RET_FALSE_IF(getVisitingHero(dwelling) != hero, "Cannot recruit: can only recruit by visiting hero!"); | ||||
| 		COMPLAIN_RET_FALSE_IF(!hero || hero->getOwner() != player, "Cannot recruit: can only recruit to owned hero!"); | ||||
| 	} | ||||
|  | ||||
| @@ -3124,12 +3151,13 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation) | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player) | ||||
| bool CGameHandler::queryReply(QueryID qid, std::optional<int32_t> answer, PlayerColor player) | ||||
| { | ||||
| 	boost::unique_lock<boost::recursive_mutex> lock(gsm); | ||||
|  | ||||
| 	logGlobal->trace("Player %s attempts answering query %d with answer:", player, qid); | ||||
| 	logGlobal->trace(answer.toJson()); | ||||
| 	if (answer) | ||||
| 		logGlobal->trace("%d", *answer); | ||||
|  | ||||
| 	auto topQuery = queries->topQuery(player); | ||||
|  | ||||
| @@ -3388,6 +3416,12 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta | ||||
|  | ||||
| 	logGlobal->debug("%s visits %s (%d:%d)", h->nodeName(), obj->getObjectName(), obj->ID, obj->subID); | ||||
|  | ||||
| 	if (getVisitingHero(obj) != nullptr) | ||||
| 	{ | ||||
| 		logGlobal->error("Attempt to visit object that is being visited by another hero!"); | ||||
| 		throw std::runtime_error("Can not visit object that is being visited"); | ||||
| 	} | ||||
|  | ||||
| 	std::shared_ptr<CObjectVisitQuery> visitQuery; | ||||
|  | ||||
| 	auto startVisit = [&](ObjectVisitStarted & event) | ||||
| @@ -4087,8 +4121,28 @@ void CGameHandler::changeFogOfWar(std::unordered_set<int3> &tiles, PlayerColor p | ||||
| 	sendAndApply(&fow); | ||||
| } | ||||
|  | ||||
| const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj) | ||||
| { | ||||
| 	assert(obj); | ||||
|  | ||||
| 	for (auto const & query : queries->allQueries()) | ||||
| 	{ | ||||
| 		auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(query); | ||||
| 		if (visit && visit->visitedObject == obj) | ||||
| 			return visit->visitingHero; | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) | ||||
| { | ||||
| 	assert(obj); | ||||
| 	assert(hero); | ||||
| 	assert(getVisitingHero(obj) == hero); | ||||
| 	// Check top query of targeted player: | ||||
| 	// If top query is NOT visit to targeted object then we assume that | ||||
| 	// visitation query is covered by other query that must be answered first | ||||
|  | ||||
| 	if (auto topQuery = queries->topQuery(hero->getOwner())) | ||||
| 		if (auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(topQuery)) | ||||
| 			return !(visit->visitedObject == obj && visit->visitingHero == hero); | ||||
|   | ||||
| @@ -153,6 +153,8 @@ public: | ||||
| 	 | ||||
| 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override; | ||||
|  | ||||
| 	/// Returns hero that is currently visiting this object, or nullptr if no visit is active | ||||
| 	const CGHeroInstance * getVisitingHero(const CGObjectInstance *obj); | ||||
| 	bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) override; | ||||
| 	void setObjProperty(ObjectInstanceID objid, int prop, si64 val) override; | ||||
| 	void showInfoDialog(InfoWindow * iw) override; | ||||
| @@ -176,8 +178,9 @@ public: | ||||
| 	void handleClientDisconnection(std::shared_ptr<CConnection> c); | ||||
| 	void handleReceivedPack(CPackForServer * pack); | ||||
| 	bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const; | ||||
| 	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const; | ||||
|  | ||||
| 	bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); | ||||
| 	bool queryReply( QueryID qid, std::optional<int32_t> reply, PlayerColor player ); | ||||
| 	bool buildBoat( ObjectInstanceID objid, PlayerColor player ); | ||||
| 	bool setFormation( ObjectInstanceID hid, ui8 formation ); | ||||
| 	bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2); | ||||
|   | ||||
| @@ -87,6 +87,7 @@ public: | ||||
| 	virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; | ||||
| 	virtual void visitLobbySetPlayer(LobbySetPlayer & pack) override; | ||||
| 	virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) override; | ||||
| 	virtual void visitLobbySetSimturns(LobbySetSimturns & pack) override; | ||||
| 	virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; | ||||
| 	virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -391,6 +391,12 @@ void ApplyOnServerNetPackVisitor::visitLobbySetPlayer(LobbySetPlayer & pack) | ||||
| 	result = true; | ||||
| } | ||||
|  | ||||
| void ApplyOnServerNetPackVisitor::visitLobbySetSimturns(LobbySetSimturns & pack) | ||||
| { | ||||
| 	srv.si->simturnsInfo = pack.simturnsInfo; | ||||
| 	result = true; | ||||
| } | ||||
|  | ||||
| void ApplyOnServerNetPackVisitor::visitLobbySetTurnTime(LobbySetTurnTime & pack) | ||||
| { | ||||
| 	srv.si->turnTimerInfo = pack.turnTimerInfo; | ||||
|   | ||||
| @@ -93,9 +93,9 @@ bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool t | ||||
| 	return gh->moveHero(hid, dst, teleporting, false); | ||||
| } | ||||
|  | ||||
| void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode&)> callback) | ||||
| void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) | ||||
| { | ||||
| 	auto query = std::make_shared<CGenericQuery>(gh->queries.get(), color, callback); | ||||
| 	auto query = std::make_shared<CGenericQuery>(gh, color, callback); | ||||
| 	request->queryID = query->queryID; | ||||
| 	gh->queries->addQuery(query); | ||||
| 	gh->sendAndApply(request); | ||||
|   | ||||
| @@ -36,7 +36,7 @@ public: | ||||
| 	const CMap * getMap() const override; | ||||
| 	const CGameInfoCallback * getCb() const override; | ||||
| 	bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override; | ||||
| 	void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) override; | ||||
| 	void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override; | ||||
| private: | ||||
| 	CGameHandler * gh; | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -174,6 +174,14 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy | ||||
|  | ||||
| 	if(mapObject->ID == Obj::TAVERN) | ||||
| 	{ | ||||
| 		const CGHeroInstance * visitor = gameHandler->getVisitingHero(mapObject); | ||||
|  | ||||
| 		if (!visitor || visitor->getOwner() != player) | ||||
| 		{ | ||||
| 			gameHandler->complain("Can't buy hero in tavern not being visited!"); | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if(gameHandler->getTile(mapObject->visitablePos())->visitableObjects.back() != mapObject && gameHandler->complain("Tavern entry must be unoccupied!")) | ||||
| 			return false; | ||||
| 	} | ||||
|   | ||||
| @@ -17,6 +17,8 @@ | ||||
|  | ||||
| #include "../../lib/CPlayerState.h" | ||||
| #include "../../lib/NetPacks.h" | ||||
| #include "../../lib/pathfinder/CPathfinder.h" | ||||
| #include "../../lib/pathfinder/PathfinderOptions.h" | ||||
|  | ||||
| TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner): | ||||
| 	gameHandler(owner) | ||||
| @@ -24,11 +26,125 @@ TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner): | ||||
|  | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::canActSimultaneously(PlayerColor active, PlayerColor waiting) const | ||||
| int TurnOrderProcessor::simturnsTurnsMaxLimit() const | ||||
| { | ||||
| 	return gameHandler->getStartInfo()->simturnsInfo.optionalTurns; | ||||
| } | ||||
|  | ||||
| int TurnOrderProcessor::simturnsTurnsMinLimit() const | ||||
| { | ||||
| 	return gameHandler->getStartInfo()->simturnsInfo.requiredTurns; | ||||
| } | ||||
|  | ||||
| void TurnOrderProcessor::updateContactStatus() | ||||
| { | ||||
| 	blockedContacts.clear(); | ||||
|  | ||||
| 	assert(actedPlayers.empty()); | ||||
| 	assert(actingPlayers.empty()); | ||||
|  | ||||
| 	for (auto left : awaitingPlayers) | ||||
| 	{ | ||||
| 		for(auto right : awaitingPlayers) | ||||
| 		{ | ||||
| 			if (left == right) | ||||
| 				continue; | ||||
|  | ||||
| 			if (computeCanActSimultaneously(left, right)) | ||||
| 				blockedContacts.push_back({left, right}); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) const | ||||
| { | ||||
| 	// TODO: refactor, cleanup and optimize | ||||
|  | ||||
| 	boost::multi_array<bool, 3> leftReachability; | ||||
| 	boost::multi_array<bool, 3> rightReachability; | ||||
|  | ||||
| 	int3 mapSize = gameHandler->getMapSize(); | ||||
|  | ||||
| 	leftReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]); | ||||
| 	rightReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]); | ||||
|  | ||||
| 	const auto * leftInfo = gameHandler->getPlayerState(left, false); | ||||
| 	const auto * rightInfo = gameHandler->getPlayerState(right, false); | ||||
|  | ||||
| 	for(const auto & hero : leftInfo->heroes) | ||||
| 	{ | ||||
| 		CPathsInfo out(mapSize, hero); | ||||
| 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero); | ||||
| 		CPathfinder pathfinder(gameHandler->gameState(), config); | ||||
| 		pathfinder.calculatePaths(); | ||||
|  | ||||
| 		for (int z = 0; z < mapSize.z; ++z) | ||||
| 			for (int y = 0; y < mapSize.y; ++y) | ||||
| 				for (int x = 0; x < mapSize.x; ++x) | ||||
| 					if (out.getNode({x,y,z})->reachable()) | ||||
| 						leftReachability[z][x][y] = true; | ||||
| 	} | ||||
|  | ||||
| 	for(const auto & hero : rightInfo->heroes) | ||||
| 	{ | ||||
| 		CPathsInfo out(mapSize, hero); | ||||
| 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero); | ||||
| 		CPathfinder pathfinder(gameHandler->gameState(), config); | ||||
| 		pathfinder.calculatePaths(); | ||||
|  | ||||
| 		for (int z = 0; z < mapSize.z; ++z) | ||||
| 			for (int y = 0; y < mapSize.y; ++y) | ||||
| 				for (int x = 0; x < mapSize.x; ++x) | ||||
| 					if (out.getNode({x,y,z})->reachable()) | ||||
| 						rightReachability[z][x][y] = true; | ||||
| 	} | ||||
|  | ||||
| 	for (int z = 0; z < mapSize.z; ++z) | ||||
| 		for (int y = 0; y < mapSize.y; ++y) | ||||
| 			for (int x = 0; x < mapSize.x; ++x) | ||||
| 				if (leftReachability[z][x][y] && rightReachability[z][x][y]) | ||||
| 					return true; | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::isContactAllowed(PlayerColor active, PlayerColor waiting) const | ||||
| { | ||||
| 	assert(active != waiting); | ||||
| 	return !vstd::contains(blockedContacts, PlayerPair{active, waiting}); | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::computeCanActSimultaneously(PlayerColor active, PlayerColor waiting) const | ||||
| { | ||||
| 	const auto * activeInfo = gameHandler->getPlayerState(active, false); | ||||
| 	const auto * waitingInfo = gameHandler->getPlayerState(waiting, false); | ||||
|  | ||||
| 	assert(active != waiting); | ||||
| 	assert(activeInfo); | ||||
| 	assert(waitingInfo); | ||||
|  | ||||
| 	if (gameHandler->hasBothPlayersAtSameConnection(active, waiting)) | ||||
| 	{ | ||||
| 		if (!gameHandler->getStartInfo()->simturnsInfo.allowHumanWithAI) | ||||
| 			return false; | ||||
|  | ||||
| 		// only one AI and one human can play simultaneoulsy from single connection | ||||
| 		if (activeInfo->human == waitingInfo->human) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	if (gameHandler->getDate(Date::DAY) < simturnsTurnsMinLimit()) | ||||
| 		return true; | ||||
|  | ||||
| 	if (gameHandler->getDate(Date::DAY) > simturnsTurnsMaxLimit()) | ||||
| 		return false; | ||||
|  | ||||
| 	if (playersInContact(active, waiting)) | ||||
| 		return false; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::mustActBefore(PlayerColor left, PlayerColor right) const | ||||
| { | ||||
| 	const auto * leftInfo = gameHandler->getPlayerState(left, false); | ||||
| @@ -61,7 +177,7 @@ bool TurnOrderProcessor::canStartTurn(PlayerColor which) const | ||||
|  | ||||
| 	for (auto player : actingPlayers) | ||||
| 	{ | ||||
| 		if (!canActSimultaneously(player, which)) | ||||
| 		if (player != which && isContactAllowed(player, which)) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| @@ -86,6 +202,7 @@ void TurnOrderProcessor::doStartNewDay() | ||||
| 	std::swap(actedPlayers, awaitingPlayers); | ||||
|  | ||||
| 	gameHandler->onNewTurn(); | ||||
| 	updateContactStatus(); | ||||
| 	tryStartTurnsForPlayers(); | ||||
| } | ||||
|  | ||||
| @@ -107,8 +224,7 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) | ||||
| 	pst.queryID = turnQuery->queryID; | ||||
| 	gameHandler->sendAndApply(&pst); | ||||
|  | ||||
| 	assert(actingPlayers.size() == 1); // No simturns yet :( | ||||
| 	assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin())); | ||||
| 	assert(!actingPlayers.empty()); | ||||
| } | ||||
|  | ||||
| void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) | ||||
| @@ -130,8 +246,6 @@ void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) | ||||
| 		doStartNewDay(); | ||||
|  | ||||
| 	assert(!actingPlayers.empty()); | ||||
| 	assert(actingPlayers.size() == 1); // No simturns yet :( | ||||
| 	assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin())); | ||||
| } | ||||
|  | ||||
| void TurnOrderProcessor::addPlayer(PlayerColor which) | ||||
| @@ -152,8 +266,6 @@ void TurnOrderProcessor::onPlayerEndsGame(PlayerColor which) | ||||
| 		doStartNewDay(); | ||||
|  | ||||
| 	assert(!actingPlayers.empty()); | ||||
| 	assert(actingPlayers.size() == 1); // No simturns yet :( | ||||
| 	assert(gameHandler->isPlayerMakingTurn(*actingPlayers.begin())); | ||||
| } | ||||
|  | ||||
| bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) | ||||
| @@ -188,6 +300,9 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) | ||||
|  | ||||
| void TurnOrderProcessor::onGameStarted() | ||||
| { | ||||
| 	if (actingPlayers.empty()) | ||||
| 		updateContactStatus(); | ||||
|  | ||||
| 	// this may be game load - send notification to players that they can act | ||||
| 	auto actingPlayersCopy = actingPlayers; | ||||
| 	for (auto player : actingPlayersCopy) | ||||
|   | ||||
| @@ -17,12 +17,41 @@ class TurnOrderProcessor : boost::noncopyable | ||||
| { | ||||
| 	CGameHandler * gameHandler; | ||||
|  | ||||
| 	struct PlayerPair | ||||
| 	{ | ||||
| 		PlayerColor a; | ||||
| 		PlayerColor b; | ||||
|  | ||||
| 		bool operator == (const PlayerPair & other) const | ||||
| 		{ | ||||
| 			return (a == other.a && b == other.b) || (a == other.b && b == other.a); | ||||
| 		} | ||||
|  | ||||
| 		template<typename Handler> | ||||
| 		void serialize(Handler & h, const int version) | ||||
| 		{ | ||||
| 			h & a; | ||||
| 			h & b; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	std::vector<PlayerPair> blockedContacts; | ||||
|  | ||||
| 	std::set<PlayerColor> awaitingPlayers; | ||||
| 	std::set<PlayerColor> actingPlayers; | ||||
| 	std::set<PlayerColor> actedPlayers; | ||||
|  | ||||
| 	/// Returns date on which simturns must end unconditionally | ||||
| 	int simturnsTurnsMaxLimit() const; | ||||
|  | ||||
| 	/// Returns date until which simturns must play unconditionally | ||||
| 	int simturnsTurnsMinLimit() const; | ||||
|  | ||||
| 	/// Returns true if players are close enough to each other for their heroes to meet on this turn | ||||
| 	bool playersInContact(PlayerColor left, PlayerColor right) const; | ||||
|  | ||||
| 	/// Returns true if waiting player can act alongside with currently acting player | ||||
| 	bool canActSimultaneously(PlayerColor active, PlayerColor waiting) const; | ||||
| 	bool computeCanActSimultaneously(PlayerColor active, PlayerColor waiting) const; | ||||
|  | ||||
| 	/// Returns true if left player must act before right player | ||||
| 	bool mustActBefore(PlayerColor left, PlayerColor right) const; | ||||
| @@ -33,6 +62,8 @@ class TurnOrderProcessor : boost::noncopyable | ||||
| 	/// Starts turn for all players that can start turn | ||||
| 	void tryStartTurnsForPlayers(); | ||||
|  | ||||
| 	void updateContactStatus(); | ||||
|  | ||||
| 	void doStartNewDay(); | ||||
| 	void doStartPlayerTurn(PlayerColor which); | ||||
| 	void doEndPlayerTurn(PlayerColor which); | ||||
| @@ -44,6 +75,8 @@ class TurnOrderProcessor : boost::noncopyable | ||||
| public: | ||||
| 	TurnOrderProcessor(CGameHandler * owner); | ||||
|  | ||||
| 	bool isContactAllowed(PlayerColor left, PlayerColor right) const; | ||||
|  | ||||
| 	/// Add new player to handle (e.g. on game start) | ||||
| 	void addPlayer(PlayerColor which); | ||||
|  | ||||
| @@ -59,6 +92,7 @@ public: | ||||
| 	template<typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & blockedContacts; | ||||
| 		h & awaitingPlayers; | ||||
| 		h & actingPlayers; | ||||
| 		h & actedPlayers; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisi | ||||
| } | ||||
|  | ||||
| CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): | ||||
| 	CGhQuery(owner), | ||||
| 	CQuery(owner), | ||||
| 	battleID(bi->getBattleID()) | ||||
| { | ||||
| 	belligerents[0] = bi->getSideArmy(0); | ||||
| @@ -37,7 +37,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): | ||||
| } | ||||
|  | ||||
| CBattleQuery::CBattleQuery(CGameHandler * owner): | ||||
| 	CGhQuery(owner) | ||||
| 	CQuery(owner) | ||||
| { | ||||
| 	belligerents[0] = belligerents[1] = nullptr; | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN | ||||
| class IBattleInfo; | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|  | ||||
| class CBattleQuery : public CGhQuery | ||||
| class CBattleQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	std::array<const CArmedInstance *,2> belligerents; | ||||
|   | ||||
| @@ -45,8 +45,9 @@ std::ostream & operator<<(std::ostream & out, QueryPtr query) | ||||
| 	return out << "[" << query.get() << "] " << query->toString(); | ||||
| } | ||||
|  | ||||
| CQuery::CQuery(QueriesProcessor * Owner): | ||||
| 	owner(Owner) | ||||
| CQuery::CQuery(CGameHandler * gameHandler) | ||||
| 	: owner(gameHandler->queries.get()) | ||||
| 	, gh(gameHandler) | ||||
| { | ||||
| 	boost::unique_lock<boost::mutex> l(QueriesProcessor::mx); | ||||
|  | ||||
| @@ -127,7 +128,7 @@ void CQuery::onAdded(PlayerColor color) | ||||
|  | ||||
| } | ||||
|  | ||||
| void CQuery::setReply(const JsonNode & reply) | ||||
| void CQuery::setReply(std::optional<int32_t> reply) | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -141,14 +142,8 @@ bool CQuery::blockAllButReply(const CPack * pack) const | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| CGhQuery::CGhQuery(CGameHandler * owner): | ||||
| 	CQuery(owner->queries.get()), gh(owner) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| CDialogQuery::CDialogQuery(CGameHandler * owner): | ||||
| 	CGhQuery(owner) | ||||
| 	CQuery(owner) | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -163,14 +158,14 @@ bool CDialogQuery::blocksPack(const CPack * pack) const | ||||
| 	return blockAllButReply(pack); | ||||
| } | ||||
|  | ||||
| void CDialogQuery::setReply(const JsonNode & reply) | ||||
| void CDialogQuery::setReply(std::optional<int32_t> reply) | ||||
| { | ||||
| 	if(reply.getType() == JsonNode::JsonType::DATA_INTEGER) | ||||
| 		answer = reply.Integer(); | ||||
| 	if(reply.has_value()) | ||||
| 		answer = *reply; | ||||
| } | ||||
|  | ||||
| CGenericQuery::CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback): | ||||
| 	CQuery(Owner), callback(Callback) | ||||
| CGenericQuery::CGenericQuery(CGameHandler * gh, PlayerColor color, std::function<void(std::optional<int32_t>)> Callback): | ||||
| 	CQuery(gh), callback(Callback) | ||||
| { | ||||
| 	addPlayer(color); | ||||
| } | ||||
| @@ -190,7 +185,7 @@ void CGenericQuery::onExposure(QueryPtr topQuery) | ||||
| 	//do nothing | ||||
| } | ||||
|  | ||||
| void CGenericQuery::setReply(const JsonNode & reply) | ||||
| void CGenericQuery::setReply(std::optional<int32_t> reply) | ||||
| { | ||||
| 	this->reply = reply; | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../../lib/GameConstants.h" | ||||
| #include "../../lib/JsonNode.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| @@ -39,8 +38,7 @@ public: | ||||
| 	std::vector<PlayerColor> players; //players that are affected (often "blocked") by query | ||||
| 	QueryID queryID; | ||||
|  | ||||
| 	CQuery(QueriesProcessor * Owner); | ||||
|  | ||||
| 	CQuery(CGameHandler * gh); | ||||
|  | ||||
| 	virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle. | ||||
|  | ||||
| @@ -53,11 +51,12 @@ public: | ||||
|  | ||||
| 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const; | ||||
|  | ||||
| 	virtual void setReply(const JsonNode & reply); | ||||
| 	virtual void setReply(std::optional<int32_t> reply); | ||||
|  | ||||
| 	virtual ~CQuery(); | ||||
| protected: | ||||
| 	QueriesProcessor * owner; | ||||
| 	CGameHandler * gh; | ||||
| 	void addPlayer(PlayerColor color); | ||||
| 	bool blockAllButReply(const CPack * pack) const; | ||||
| }; | ||||
| @@ -65,21 +64,13 @@ protected: | ||||
| std::ostream &operator<<(std::ostream &out, const CQuery &query); | ||||
| std::ostream &operator<<(std::ostream &out, QueryPtr query); | ||||
|  | ||||
| class CGhQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	CGhQuery(CGameHandler * owner); | ||||
| protected: | ||||
| 	CGameHandler * gh; | ||||
| }; | ||||
|  | ||||
| class CDialogQuery : public CGhQuery | ||||
| class CDialogQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	CDialogQuery(CGameHandler * owner); | ||||
| 	virtual bool endsByPlayerAnswer() const override; | ||||
| 	virtual bool blocksPack(const CPack *pack) const override; | ||||
| 	void setReply(const JsonNode & reply) override; | ||||
| 	void setReply(std::optional<int32_t> reply) override; | ||||
| protected: | ||||
| 	std::optional<ui32> answer; | ||||
| }; | ||||
| @@ -87,14 +78,14 @@ protected: | ||||
| class CGenericQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback); | ||||
| 	CGenericQuery(CGameHandler * gh, PlayerColor color, std::function<void(std::optional<int32_t>)> Callback); | ||||
|  | ||||
| 	bool blocksPack(const CPack * pack) const override; | ||||
| 	bool endsByPlayerAnswer() const override; | ||||
| 	void onExposure(QueryPtr topQuery) override; | ||||
| 	void setReply(const JsonNode & reply) override; | ||||
| 	void setReply(std::optional<int32_t> reply) override; | ||||
| 	void onRemoval(PlayerColor color) override; | ||||
| private: | ||||
| 	std::function<void(const JsonNode &)> callback; | ||||
| 	JsonNode reply; | ||||
| 	std::function<void(std::optional<int32_t>)> callback; | ||||
| 	std::optional<int32_t> reply; | ||||
| }; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
| #include "../../lib/serializer/Cast.h" | ||||
|  | ||||
| PlayerStartsTurnQuery::PlayerStartsTurnQuery(CGameHandler * owner, PlayerColor player): | ||||
| 	CGhQuery(owner) | ||||
| 	CQuery(owner) | ||||
| { | ||||
| 	addPlayer(player); | ||||
| } | ||||
| @@ -42,7 +42,7 @@ bool PlayerStartsTurnQuery::endsByPlayerAnswer() const | ||||
| } | ||||
|  | ||||
| CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile): | ||||
| 	CGhQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false) | ||||
| 	CQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false) | ||||
| { | ||||
| 	addPlayer(Hero->tempOwner); | ||||
| } | ||||
| @@ -213,7 +213,7 @@ void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQu | ||||
| } | ||||
|  | ||||
| CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory): | ||||
| 	CGhQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero) | ||||
| 	CQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero) | ||||
| { | ||||
| 	players.push_back(hero->tempOwner); | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class TurnTimerHandler; | ||||
|  | ||||
| //Created when player starts turn | ||||
| //Removed when player accepts a turn | ||||
| class PlayerStartsTurnQuery : public CGhQuery | ||||
| class PlayerStartsTurnQuery : public CQuery | ||||
| { | ||||
| public:	 | ||||
| 	PlayerStartsTurnQuery(CGameHandler * owner, PlayerColor player); | ||||
| @@ -30,7 +30,7 @@ public: | ||||
|  | ||||
| //Created when hero visits object. | ||||
| //Removed when query above is resolved (or immediately after visit if no queries were created) | ||||
| class CObjectVisitQuery : public CGhQuery | ||||
| class CObjectVisitQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	const CGObjectInstance *visitedObject; | ||||
| @@ -47,7 +47,7 @@ public: | ||||
|  | ||||
| //Created when hero attempts move and something happens | ||||
| //(not necessarily position change, could be just an object interaction). | ||||
| class CHeroMovementQuery : public CGhQuery | ||||
| class CHeroMovementQuery : public CQuery | ||||
| { | ||||
| public: | ||||
| 	TryMoveHero tmh; | ||||
|   | ||||
| @@ -126,7 +126,7 @@ public: | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) override | ||||
| 	void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override | ||||
| 	{ | ||||
| 		//todo: | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user