mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Feature: Army Management Shortcuts should work as in HD+ Mod
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							7faa691f42
						
					
				
				
					commit
					8cae3398ba
				
			| @@ -109,6 +109,7 @@ int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, S | ||||
| 	sendRequest(&pack); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val) | ||||
| { | ||||
| 	ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val); | ||||
| @@ -116,6 +117,34 @@ int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, Sl | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) | ||||
| { | ||||
| 	BulkMoveArmy pack(srcArmy, destArmy, srcSlot); | ||||
| 	sendRequest(&pack); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany) | ||||
| { | ||||
| 	BulkSplitStack pack(armyId, srcSlot, howMany); | ||||
| 	sendRequest(&pack); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) | ||||
| { | ||||
| 	BulkSmartSplitStack pack(armyId, srcSlot); | ||||
| 	sendRequest(&pack); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) | ||||
| { | ||||
| 	BulkMergeStacks pack(armyId, srcSlot); | ||||
| 	sendRequest(&pack); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| bool CCallback::dismissHero(const CGHeroInstance *hero) | ||||
| { | ||||
| 	if(player!=hero->tempOwner) return false; | ||||
|   | ||||
							
								
								
									
										14
									
								
								CCallback.h
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								CCallback.h
									
									
									
									
									
								
							| @@ -78,6 +78,12 @@ public: | ||||
| 	virtual void save(const std::string &fname) = 0; | ||||
| 	virtual void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) = 0; | ||||
| 	virtual void buildBoat(const IShipyard *obj) = 0; | ||||
|  | ||||
| 	// To implement high-level army management bulk actions | ||||
| 	virtual int bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) = 0; | ||||
| 	virtual int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) = 0; | ||||
| 	virtual int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) = 0; | ||||
| 	virtual int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) = 0; | ||||
| }; | ||||
|  | ||||
| struct CPackForServer; | ||||
| @@ -99,7 +105,9 @@ public: | ||||
| 	friend class CClient; | ||||
| }; | ||||
|  | ||||
| class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback | ||||
| class CCallback : public CPlayerSpecificInfoCallback, | ||||
| 	public IGameActionCallback, | ||||
| 	public CBattleCallback | ||||
| { | ||||
| public: | ||||
| 	CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient *C); | ||||
| @@ -125,6 +133,10 @@ public: | ||||
| 	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 | ||||
| 	int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val) override; | ||||
| 	int bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) override; | ||||
| 	int bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany = 1) override; | ||||
| 	int bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) override; | ||||
| 	int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override; | ||||
| 	bool dismissHero(const CGHeroInstance * hero) override; | ||||
| 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override; | ||||
| 	bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override; | ||||
|   | ||||
| @@ -35,4 +35,4 @@ Platform support is constantly tested by continuous integration and CMake config | ||||
| VCMI Project source code is licensed under GPL version 2 or later. | ||||
| VCMI Project assets are licensed under CC-BY-SA 4.0. Assets sources and information about contributors are available under following link: [https://github.com/vcmi/vcmi-assets] | ||||
|  | ||||
| Copyright (C) 2007-2020  VCMI Team (check AUTHORS file for the contributors list) | ||||
| Copyright (C) 2007-2022  VCMI Team (check AUTHORS file for the contributors list) | ||||
|   | ||||
| @@ -238,6 +238,30 @@ void RebalanceStacks::applyCl(CClient * cl) | ||||
| 	dispatchGarrisonChange(cl, srcArmy, dstArmy); | ||||
| } | ||||
|  | ||||
| void BulkRebalanceStacks::applyCl(CClient * cl) | ||||
| { | ||||
| 	if(!moves.empty()) | ||||
| 	{ | ||||
| 		auto destArmy = moves[0].srcArmy == moves[0].dstArmy | ||||
| 			? ObjectInstanceID() | ||||
| 			: moves[0].dstArmy; | ||||
| 		dispatchGarrisonChange(cl, moves[0].srcArmy, destArmy); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void BulkSmartRebalanceStacks::applyCl(CClient * cl) | ||||
| { | ||||
| 	if(!moves.empty()) | ||||
| 	{ | ||||
| 		assert(moves[0].srcArmy == moves[0].dstArmy); | ||||
| 		dispatchGarrisonChange(cl, moves[0].srcArmy, ObjectInstanceID()); | ||||
| 	} | ||||
| 	else if(!changes.empty()) | ||||
| 	{ | ||||
| 		dispatchGarrisonChange(cl, changes[0].army, ObjectInstanceID()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void PutArtifact::applyCl(CClient *cl) | ||||
| { | ||||
| 	callInterfaceIfPresent(cl, al.owningPlayer(), &IGameEventsReceiver::artifactPut, al); | ||||
|   | ||||
| @@ -74,18 +74,23 @@ void CGarrisonSlot::hover (bool on) | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				if(upg == EGarrisonType::UP) | ||||
| 				const bool isHeroOnMap = owner->armedObjs[0] // Hero is not a visitor and not a garrison defender | ||||
| 					&& owner->armedObjs[0]->ID == Obj::HERO | ||||
| 					&& (!owner->armedObjs[1] || owner->armedObjs[1]->ID == Obj::HERO) // one hero or we are in the Heroes exchange window | ||||
| 					&& !(static_cast<const CGHeroInstance*>(owner->armedObjs[0]))->inTownGarrison; | ||||
|  | ||||
| 				if(isHeroOnMap) | ||||
| 				{ | ||||
| 					temp = CGI->generaltexth->allTexts[481]; //Select %s | ||||
| 				} | ||||
| 				else if(upg == EGarrisonType::UP) | ||||
| 				{ | ||||
| 					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison) | ||||
| 				} | ||||
| 				else if(owner->armedObjs[0] && (owner->armedObjs[0]->ID == Obj::TOWN || owner->armedObjs[0]->ID == Obj::HERO)) | ||||
| 				else // Hero is visiting some object (town, mine, etc) | ||||
| 				{ | ||||
| 					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting) | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					temp = CGI->generaltexth->allTexts[481]; //Select %s | ||||
| 				} | ||||
| 				boost::algorithm::replace_first(temp,"%s",creature->nameSing); | ||||
| 			} | ||||
| 		} | ||||
| @@ -140,6 +145,18 @@ bool CGarrisonSlot::ally() const | ||||
| 	return PlayerRelations::ALLIES == LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, getObj()->tempOwner); | ||||
| } | ||||
|  | ||||
| std::function<void()> CGarrisonSlot::getDismiss() const | ||||
| { | ||||
| 	const bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID | ||||
| 		&& (getObj()->stacksCount() > 1 || | ||||
| 			!getObj()->needsLastStack()); | ||||
|  | ||||
| 	return canDismiss ? [=]() | ||||
| 	{ | ||||
| 		LOCPLINT->cb->dismissCreature(getObj(), ID); | ||||
| 	} : (std::function<void()>)nullptr; | ||||
| } | ||||
|  | ||||
| /// The creature slot has been clicked twice, therefore the creature info should be shown | ||||
| /// @return Whether the view should be refreshed | ||||
| bool CGarrisonSlot::viewInfo() | ||||
| @@ -148,11 +165,9 @@ bool CGarrisonSlot::viewInfo() | ||||
| 	LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom); | ||||
|  | ||||
| 	bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible | ||||
| 	bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1  || !getObj()->needsLastStack()); | ||||
| 	std::function<void(CreatureID)> upgr = nullptr; | ||||
| 	std::function<void()> dism = nullptr; | ||||
| 	auto dism = getDismiss(); | ||||
| 	if(canUpgrade) upgr = [=] (CreatureID newID) { LOCPLINT->cb->upgradeCreature(getObj(), ID, newID); }; | ||||
| 	if(canDismiss) dism = [=](){ LOCPLINT->cb->dismissCreature(getObj(), ID); }; | ||||
|  | ||||
| 	owner->selectSlot(nullptr); | ||||
| 	owner->setSplittingMode(false); | ||||
| @@ -288,13 +303,17 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState) | ||||
| 	{ | ||||
| 		bool refr = false; | ||||
| 		const CGarrisonSlot * selection = owner->getSelection(); | ||||
|  | ||||
| 		if(!selection) | ||||
| 		{ | ||||
| 			refr = highlightOrDropArtifact(); | ||||
| 			refr = highlightOrDropArtifact(); // Affects selection | ||||
| 			handleSplittingShortcuts(); | ||||
| 		} | ||||
| 		else if(selection == this) | ||||
| 			refr = viewInfo(); | ||||
| 		{ | ||||
| 			if(!handleSplittingShortcuts()) | ||||
| 				refr = viewInfo(); // Affects selection | ||||
| 		} | ||||
| 		// Re-highlight if troops aren't removable or not ours. | ||||
| 		else if (mustForceReselection()) | ||||
| 		{ | ||||
| @@ -403,34 +422,67 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGa | ||||
| 	update(); | ||||
| } | ||||
|  | ||||
| void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount, int maxOfSplittedSlots) | ||||
| void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount) | ||||
| { | ||||
| 	auto empty = owner->getEmptySlot(type); | ||||
|  | ||||
| 	if(empty == SlotID()) | ||||
| 		return; | ||||
|  | ||||
| 	owner->pb = type; | ||||
| 	for(CGarrisonSlot * slot : owner->getEmptySlots(type)) | ||||
| 	{ | ||||
| 		owner->p2 = slot->ID; | ||||
| 		owner->splitStacks(1, amount); | ||||
| 		maxOfSplittedSlots--; | ||||
| 		if(!maxOfSplittedSlots || owner->getSelection()->myStack->count <= 1) | ||||
| 			break; | ||||
| 	} | ||||
| 	owner->p2 = empty; | ||||
| 	owner->splitStacks(1, amount); | ||||
| } | ||||
|  | ||||
| void CGarrisonSlot::handleSplittingShortcuts() | ||||
| bool CGarrisonSlot::handleSplittingShortcuts() | ||||
| { | ||||
| 	const Uint8 * state = SDL_GetKeyboardState(NULL); | ||||
| 	if(owner->getSelection() && owner->getEmptySlots(owner->getSelection()->upg).size() && owner->getSelection()->myStack->count > 1) | ||||
| 	const bool isAlt = !!state[SDL_SCANCODE_LALT]; | ||||
| 	const bool isLShift = !!state[SDL_SCANCODE_LSHIFT]; | ||||
| 	const bool isLCtrl = !!state[SDL_SCANCODE_LCTRL]; | ||||
|  | ||||
| 	if(!isAlt && !isLShift && !isLCtrl) | ||||
| 		return false; // This is only case when return false | ||||
|  | ||||
| 	auto selected = owner->getSelection(); | ||||
| 	if(!selected) | ||||
| 		return true; // Some Shortcusts are pressed but there are no appropriate actions | ||||
|  | ||||
| 	auto units = selected->myStack->count; | ||||
| 	if(units < 1) | ||||
| 		return true; | ||||
|  | ||||
| 	if (isLShift && isLCtrl && isAlt) | ||||
| 	{ | ||||
| 		if(state[SDL_SCANCODE_LCTRL] && state[SDL_SCANCODE_LSHIFT]) | ||||
| 			splitIntoParts(owner->getSelection()->upg, 1, 7); | ||||
| 		else if(state[SDL_SCANCODE_LCTRL]) | ||||
| 			splitIntoParts(owner->getSelection()->upg, 1, 1); | ||||
| 		else if(state[SDL_SCANCODE_LSHIFT]) | ||||
| 			splitIntoParts(owner->getSelection()->upg, owner->getSelection()->myStack->count/2 , 1); | ||||
| 		else | ||||
| 			return; | ||||
| 		owner->selectSlot(nullptr); | ||||
| 		owner->bulkMoveArmy(selected); | ||||
| 	} | ||||
| 	else if(isLCtrl && isAlt) | ||||
| 	{ | ||||
| 		owner->moveStackToAnotherArmy(selected); | ||||
| 	} | ||||
| 	else if(isLShift && isAlt) | ||||
| 	{ | ||||
| 		auto dismiss = getDismiss(); | ||||
| 		if(dismiss) | ||||
| 			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[12], dismiss, nullptr); | ||||
| 	} | ||||
| 	else if(isAlt) | ||||
| 	{ | ||||
| 		owner->bulkMergeStacks(selected); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if(units <= 1) | ||||
| 			return true; | ||||
|  | ||||
| 		if(isLCtrl && isLShift) | ||||
| 			owner->bulkSplitStack(selected); | ||||
| 		else if(isLShift) | ||||
| 			owner->bulkSmartSplitStack(selected); | ||||
| 		else | ||||
| 			splitIntoParts(selected->upg, 1); // LCtrl | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::addSplitBtn(std::shared_ptr<CButton> button) | ||||
| @@ -492,6 +544,115 @@ void CGarrisonInt::splitStacks(int, int amountRight) | ||||
| 	LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight); | ||||
| } | ||||
|  | ||||
| bool CGarrisonInt::checkSelected(const CGarrisonSlot * selected, TQuantity min) const | ||||
| { | ||||
| 	return selected && selected->myStack && selected->myStack->count > min && selected->creature; | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::moveStackToAnotherArmy(const CGarrisonSlot * selected) | ||||
| { | ||||
| 	if(!checkSelected(selected)) | ||||
| 		return; | ||||
|  | ||||
| 	const auto srcArmyType = selected->upg; | ||||
| 	const auto destArmyType = srcArmyType == CGarrisonSlot::UP | ||||
| 		? CGarrisonSlot::DOWN | ||||
| 		: CGarrisonSlot::UP; | ||||
|  | ||||
| 	auto srcArmy = armedObjs[srcArmyType]; | ||||
| 	auto destArmy = armedObjs[destArmyType]; | ||||
|  | ||||
| 	if(!destArmy) | ||||
| 		return; | ||||
|  | ||||
| 	auto destSlot = destArmy->getSlotFor(selected->creature); | ||||
|  | ||||
| 	if(destSlot == SlotID()) | ||||
| 		return; | ||||
|  | ||||
| 	const auto srcSlot = selected->ID; | ||||
| 	const bool isDestSlotEmpty = !destArmy->getStackCount(destSlot); | ||||
|  | ||||
| 	if(isDestSlotEmpty && !destArmy->getStackCount(srcSlot)) | ||||
| 		destSlot = srcSlot; // Same place is more preferable | ||||
|  | ||||
| 	const bool isLastStack = srcArmy->stacksCount() == 1 && srcArmy->needsLastStack(); | ||||
| 	auto srcAmount = selected->myStack->count - (isLastStack ? 1 : 0); | ||||
|  | ||||
| 	if(!srcAmount) | ||||
| 		return; | ||||
|  | ||||
| 	if(!isDestSlotEmpty || isLastStack) | ||||
| 	{ | ||||
| 		srcAmount += destArmy->getStackCount(destSlot); // Due to 'split' implementation in the 'CGameHandler::arrangeStacks' | ||||
| 		LOCPLINT->cb->splitStack(srcArmy, destArmy, srcSlot, destSlot, srcAmount); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		LOCPLINT->cb->swapCreatures(srcArmy, destArmy, srcSlot, destSlot); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::bulkMoveArmy(const CGarrisonSlot * selected) | ||||
| { | ||||
| 	if(!checkSelected(selected)) | ||||
| 		return; | ||||
|  | ||||
| 	const auto srcArmyType = selected->upg; | ||||
| 	const auto destArmyType = (srcArmyType == CGarrisonSlot::UP) | ||||
| 		? CGarrisonSlot::DOWN | ||||
| 		: CGarrisonSlot::UP; | ||||
|  | ||||
| 	auto srcArmy = armedObjs[srcArmyType]; | ||||
| 	auto destArmy = armedObjs[destArmyType]; | ||||
|  | ||||
| 	if(!destArmy) | ||||
| 		return; | ||||
|  | ||||
| 	const auto srcSlot = selected->ID; | ||||
| 	LOCPLINT->cb->bulkMoveArmy(srcArmy->id, destArmy->id, srcSlot); | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::bulkMergeStacks(const CGarrisonSlot * selected) | ||||
| { | ||||
| 	if(!checkSelected(selected)) | ||||
| 		return; | ||||
|  | ||||
| 	const auto type = selected->upg; | ||||
|  | ||||
| 	if(!armedObjs[type]->hasCreatureSlots(selected->creature, selected->ID)) | ||||
| 		return; | ||||
|  | ||||
| 	LOCPLINT->cb->bulkMergeStacks(armedObjs[type]->id, selected->ID); | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::bulkSplitStack(const CGarrisonSlot * selected) | ||||
| { | ||||
| 	if(!checkSelected(selected, 1)) // check if > 1 | ||||
| 		return; | ||||
|  | ||||
| 	const auto type = selected->upg; | ||||
|  | ||||
| 	if(!hasEmptySlot(type)) | ||||
| 		return; | ||||
|  | ||||
| 	LOCPLINT->cb->bulkSplitStack(armedObjs[type]->id, selected->ID); | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::bulkSmartSplitStack(const CGarrisonSlot * selected) | ||||
| { | ||||
| 	if(!checkSelected(selected, 1)) | ||||
| 		return; | ||||
|  | ||||
| 	const auto type = selected->upg; | ||||
|  | ||||
| 	// Do not disturb the server if the creature is already balanced | ||||
| 	if(!hasEmptySlot(type) && armedObjs[type]->isCreatureBalanced(selected->creature)) | ||||
| 		return; | ||||
|  | ||||
| 	LOCPLINT->cb->bulkSmartSplitStack(armedObjs[type]->id, selected->ID); | ||||
| } | ||||
|  | ||||
| CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset, | ||||
| 		const CArmedInstance * s1, const CArmedInstance * s2, | ||||
| 		bool _removableUnits, bool smallImgs, bool _twoRows) | ||||
| @@ -513,7 +674,7 @@ CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset, | ||||
| 	createSlots(); | ||||
| } | ||||
|  | ||||
| const CGarrisonSlot * CGarrisonInt::getSelection() | ||||
| const CGarrisonSlot * CGarrisonInt::getSelection() const | ||||
| { | ||||
| 	return highlighted; | ||||
| } | ||||
| @@ -554,15 +715,15 @@ bool CGarrisonInt::getSplittingMode() | ||||
| 	return inSplittingMode; | ||||
| } | ||||
|  | ||||
| std::vector<CGarrisonSlot *> CGarrisonInt::getEmptySlots(CGarrisonSlot::EGarrisonType type) | ||||
| SlotID CGarrisonInt::getEmptySlot(CGarrisonSlot::EGarrisonType type) const | ||||
| { | ||||
| 	std::vector<CGarrisonSlot *> emptySlots; | ||||
| 	for(auto slot : availableSlots) | ||||
| 	{ | ||||
| 		if(type == slot->upg && ((slot->our() || slot->ally()) && slot->creature == nullptr)) | ||||
| 			emptySlots.push_back(slot.get()); | ||||
| 	} | ||||
| 	return emptySlots; | ||||
| 	assert(armedObjs[type]); | ||||
| 	return armedObjs[type] ? armedObjs[type]->getFreeSlot() : SlotID(); | ||||
| } | ||||
|  | ||||
| bool CGarrisonInt::hasEmptySlot(CGarrisonSlot::EGarrisonType type) const | ||||
| { | ||||
| 	return getEmptySlot(type) != SlotID(); | ||||
| } | ||||
|  | ||||
| void CGarrisonInt::setArmy(const CArmedInstance * army, bool bottomGarrison) | ||||
|   | ||||
| @@ -45,6 +45,8 @@ class CGarrisonSlot : public CIntObject | ||||
| 	bool mustForceReselection() const; | ||||
|  | ||||
| 	void setHighlight(bool on); | ||||
| 	std::function<void()> getDismiss() const; | ||||
|  | ||||
| public: | ||||
| 	virtual void hover (bool on) override; //call-in | ||||
| 	const CArmedInstance * getObj() const; | ||||
| @@ -55,8 +57,8 @@ public: | ||||
| 	void update(); | ||||
| 	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg=EGarrisonType::UP, const CStackInstance * creature_ = nullptr); | ||||
|  | ||||
| 	void splitIntoParts(EGarrisonType type, int amount, int maxOfSplittedSlots); | ||||
| 	void handleSplittingShortcuts(); | ||||
| 	void splitIntoParts(EGarrisonType type, int amount); | ||||
| 	bool handleSplittingShortcuts(); /// Returns true when some shortcut is pressed, false otherwise | ||||
|  | ||||
| 	friend class CGarrisonInt; | ||||
| }; | ||||
| @@ -70,6 +72,8 @@ class CGarrisonInt :public CIntObject | ||||
| 	std::vector<std::shared_ptr<CGarrisonSlot>> availableSlots;  ///< Slots of upper and lower garrison | ||||
|  | ||||
| 	void createSlots(); | ||||
| 	bool checkSelected(const CGarrisonSlot * selected, TQuantity min = 0) const; | ||||
|  | ||||
| public: | ||||
| 	int interx;  ///< Space between slots | ||||
| 	Point garOffset;  ///< Offset between garrisons (not used if only one hero) | ||||
| @@ -83,12 +87,13 @@ public: | ||||
| 		 owned[2];        ///< player Owns up or down army ([0] upper, [1] lower) | ||||
|  | ||||
| 	void selectSlot(CGarrisonSlot * slot); ///< @param slot null = deselect | ||||
| 	const CGarrisonSlot * getSelection(); | ||||
| 	const CGarrisonSlot * getSelection() const; | ||||
|  | ||||
| 	void setSplittingMode(bool on); | ||||
| 	bool getSplittingMode(); | ||||
|  | ||||
| 	std::vector<CGarrisonSlot *> getEmptySlots(CGarrisonSlot::EGarrisonType type); | ||||
| 	bool hasEmptySlot(CGarrisonSlot::EGarrisonType type) const; | ||||
| 	SlotID getEmptySlot(CGarrisonSlot::EGarrisonType type) const; | ||||
|  | ||||
| 	const CArmedInstance * armedObjs[2];  ///< [0] is upper, [1] is down | ||||
|  | ||||
| @@ -99,6 +104,11 @@ public: | ||||
|  | ||||
| 	void splitClick();  ///< handles click on split button | ||||
| 	void splitStacks(int amountLeft, int amountRight);  ///< TODO: comment me | ||||
| 	void moveStackToAnotherArmy(const CGarrisonSlot * selected); | ||||
| 	void bulkMoveArmy(const CGarrisonSlot * selected); | ||||
| 	void bulkMergeStacks(const CGarrisonSlot * selected); // Gather all creatures of selected type to the selected slot from other hero/garrison slots | ||||
| 	void bulkSplitStack(const CGarrisonSlot * selected); // Used to separate one-creature troops from main stack | ||||
| 	void bulkSmartSplitStack(const CGarrisonSlot * selected); | ||||
|  | ||||
| 	/// Constructor | ||||
| 	/// @param x, y Position | ||||
|   | ||||
| @@ -23,6 +23,12 @@ | ||||
| #include "serializer/JsonSerializeFormat.h" | ||||
| #include "NetPacksBase.h" | ||||
|  | ||||
|  | ||||
| bool CreatureSlotComparer::operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs) | ||||
| { | ||||
| 	return lhs.first->getAIValue() < rhs.first->getAIValue(); // Descendant order sorting | ||||
| } | ||||
|  | ||||
| const CStackInstance &CCreatureSet::operator[](SlotID slot) const | ||||
| { | ||||
| 	auto i = stacks.find(slot); | ||||
| @@ -72,7 +78,7 @@ SlotID CCreatureSet::getSlotFor(CreatureID creature, ui32 slotsAmount) const /*r | ||||
|  | ||||
| SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const | ||||
| { | ||||
| 	assert(c->valid()); | ||||
| 	assert(c && c->valid()); | ||||
| 	for(auto & elem : stacks) | ||||
| 	{ | ||||
| 		assert(elem.second->type->valid()); | ||||
| @@ -84,6 +90,75 @@ SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const | ||||
| 	return getFreeSlot(slotsAmount); | ||||
| } | ||||
|  | ||||
| bool CCreatureSet::hasCreatureSlots(const CCreature * c, SlotID exclude) const | ||||
| { | ||||
| 	assert(c && c->valid()); | ||||
| 	for(auto & elem : stacks) // elem is const | ||||
| 	{ | ||||
| 		if(elem.first == exclude) // Check slot | ||||
| 			continue; | ||||
|  | ||||
| 		if(!elem.second || !elem.second->type) // Check creature | ||||
| 			continue; | ||||
|  | ||||
| 		assert(elem.second->type->valid()); | ||||
|  | ||||
| 		if(elem.second->type == c) | ||||
| 			return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, SlotID exclude, TQuantity ignoreAmount) const | ||||
| { | ||||
| 	assert(c && c->valid()); | ||||
| 	std::vector<SlotID> result; | ||||
|  | ||||
| 	for(auto & elem : stacks) | ||||
| 	{ | ||||
| 		if(elem.first == exclude) | ||||
| 			continue; | ||||
|  | ||||
| 		if(!elem.second || !elem.second->type || elem.second->type != c) | ||||
| 			continue; | ||||
|  | ||||
| 		if(elem.second->count == ignoreAmount || elem.second->count < 1) | ||||
| 			continue; | ||||
|  | ||||
| 		assert(elem.second->type->valid()); | ||||
| 		result.push_back(elem.first); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount) const | ||||
| { | ||||
| 	assert(c && c->valid()); | ||||
| 	TQuantity max = 0; | ||||
| 	TQuantity min = std::numeric_limits<TQuantity>::max(); | ||||
|  | ||||
| 	for(auto & elem : stacks) | ||||
| 	{ | ||||
| 		if(!elem.second || !elem.second->type || elem.second->type != c) | ||||
| 			continue; | ||||
|  | ||||
| 		const auto count = elem.second->count; | ||||
|  | ||||
| 		if(count == ignoreAmount || count < 1) | ||||
| 			continue; | ||||
|  | ||||
| 		assert(elem.second->type->valid()); | ||||
|  | ||||
| 		if(count > max) | ||||
| 			max = count; | ||||
| 		if(count < min) | ||||
| 			min = count; | ||||
| 		if(max - min > 1) | ||||
| 			return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const | ||||
| { | ||||
| 	for(ui32 i=0; i<slotsAmount; i++) | ||||
| @@ -96,6 +171,68 @@ SlotID CCreatureSet::getFreeSlot(ui32 slotsAmount) const | ||||
| 	return SlotID(); //no slot available | ||||
| } | ||||
|  | ||||
| std::vector<SlotID> CCreatureSet::getFreeSlots(ui32 slotsAmount) const | ||||
| { | ||||
| 	std::vector<SlotID> freeSlots; | ||||
|  | ||||
| 	for(ui32 i = 0; i < slotsAmount; i++) | ||||
| 	{ | ||||
| 		auto slot = SlotID(i); | ||||
|  | ||||
| 		if(!vstd::contains(stacks, slot)) | ||||
| 			freeSlots.push_back(slot); | ||||
| 	} | ||||
| 	return freeSlots; | ||||
| } | ||||
|  | ||||
| std::queue<SlotID> CCreatureSet::getFreeSlotsQueue(ui32 slotsAmount) const | ||||
| { | ||||
| 	std::queue<SlotID> freeSlots; | ||||
|  | ||||
| 	for (ui32 i = 0; i < slotsAmount; i++) | ||||
| 	{ | ||||
| 		auto slot = SlotID(i); | ||||
|  | ||||
| 		if(!vstd::contains(stacks, slot)) | ||||
| 			freeSlots.push(slot); | ||||
| 	} | ||||
| 	return freeSlots; | ||||
| } | ||||
|  | ||||
| TMapCreatureSlot CCreatureSet::getCreatureMap() const | ||||
| { | ||||
| 	TMapCreatureSlot creatureMap; | ||||
| 	TMapCreatureSlot::key_compare keyComp = creatureMap.key_comp(); | ||||
|  | ||||
| 	// https://stackoverflow.com/questions/97050/stdmap-insert-or-stdmap-find | ||||
| 	// https://www.cplusplus.com/reference/map/map/key_comp/ | ||||
| 	for(auto pair : stacks) | ||||
| 	{ | ||||
| 		auto creature = pair.second->type; | ||||
| 		auto slot = pair.first; | ||||
| 		TMapCreatureSlot::iterator lb = creatureMap.lower_bound(creature); | ||||
|  | ||||
| 		if(lb != creatureMap.end() && !(keyComp(creature, lb->first))) | ||||
| 			continue; | ||||
|  | ||||
| 		creatureMap.insert(lb, TMapCreatureSlot::value_type(creature, slot)); | ||||
| 	} | ||||
| 	return creatureMap; | ||||
| } | ||||
|  | ||||
| TCreatureQueue CCreatureSet::getCreatureQueue(SlotID exclude) const | ||||
| { | ||||
| 	TCreatureQueue creatureQueue; | ||||
|  | ||||
| 	for(auto pair : stacks) | ||||
| 	{ | ||||
| 		if(pair.first == exclude) | ||||
| 			continue; | ||||
| 		creatureQueue.push(std::make_pair(pair.second->type, pair.first)); | ||||
| 	} | ||||
| 	return creatureQueue; | ||||
| } | ||||
|  | ||||
| TQuantity CCreatureSet::getStackCount(SlotID slot) const | ||||
| { | ||||
| 	auto i = stacks.find(slot); | ||||
|   | ||||
| @@ -142,6 +142,20 @@ public: | ||||
| typedef std::map<SlotID, CStackInstance*> TSlots; | ||||
| typedef std::map<SlotID, std::pair<CreatureID, TQuantity>> TSimpleSlots; | ||||
|  | ||||
| typedef std::pair<const CCreature*, SlotID> TPairCreatureSlot; | ||||
| typedef std::map<const CCreature*, SlotID> TMapCreatureSlot; | ||||
|  | ||||
| struct DLL_LINKAGE CreatureSlotComparer | ||||
| { | ||||
| 	bool operator()(const TPairCreatureSlot & lhs, const TPairCreatureSlot & rhs); | ||||
| }; | ||||
|  | ||||
| typedef std::priority_queue< | ||||
| 	TPairCreatureSlot, | ||||
| 	std::vector<TPairCreatureSlot>, | ||||
| 	CreatureSlotComparer | ||||
| > TCreatureQueue; | ||||
|  | ||||
| class IArmyDescriptor | ||||
| { | ||||
| public: | ||||
| @@ -209,7 +223,17 @@ public: | ||||
| 	SlotID findStack(const CStackInstance *stack) const; //-1 if none | ||||
| 	SlotID getSlotFor(CreatureID creature, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available | ||||
| 	SlotID getSlotFor(const CCreature *c, ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns -1 if no slot available | ||||
| 	SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; | ||||
| 	bool hasCreatureSlots(const CCreature * c, SlotID exclude) const; | ||||
| 	std::vector<SlotID> getCreatureSlots(const CCreature * c, SlotID exclude, TQuantity ignoreAmount = -1) const; | ||||
| 	bool isCreatureBalanced(const CCreature* c, TQuantity ignoreAmount = 1) const; // Check if the creature is evenly distributed across slots | ||||
|  | ||||
| 	SlotID getFreeSlot(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; //returns first free slot | ||||
| 	std::vector<SlotID> getFreeSlots(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; | ||||
| 	std::queue<SlotID> getFreeSlotsQueue(ui32 slotsAmount = GameConstants::ARMY_SIZE) const; | ||||
|  | ||||
| 	TMapCreatureSlot getCreatureMap() const; | ||||
| 	TCreatureQueue getCreatureQueue(SlotID exclude) const; | ||||
|  | ||||
| 	bool mergableStacks(std::pair<SlotID, SlotID> &out, SlotID preferable = SlotID()) const; //looks for two same stacks, returns slot positions; | ||||
| 	bool validTypes(bool allowUnrandomized = false) const; //checks if all types of creatures are set properly | ||||
| 	bool slotEmpty(SlotID slot) const; | ||||
|   | ||||
							
								
								
									
										128
									
								
								lib/NetPacks.h
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								lib/NetPacks.h
									
									
									
									
									
								
							| @@ -886,7 +886,7 @@ struct RebalanceStacks : CGarrisonOperationPack | ||||
| 	TQuantity count; | ||||
|  | ||||
| 	void applyCl(CClient *cl); | ||||
| 	DLL_LINKAGE void applyGs(CGameState *gs); | ||||
| 	DLL_LINKAGE void applyGs(CGameState * gs); | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler &h, const int version) | ||||
| 	{ | ||||
| @@ -898,6 +898,36 @@ struct RebalanceStacks : CGarrisonOperationPack | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkRebalanceStacks : CGarrisonOperationPack | ||||
| { | ||||
| 	std::vector<RebalanceStacks> moves; | ||||
|  | ||||
| 	void applyCl(CClient * cl); | ||||
| 	DLL_LINKAGE void applyGs(CGameState * gs); | ||||
|  | ||||
| 	template <typename Handler>  | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & moves; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkSmartRebalanceStacks : CGarrisonOperationPack | ||||
| { | ||||
| 	std::vector<RebalanceStacks> moves; | ||||
| 	std::vector<ChangeStackCount> changes; | ||||
|  | ||||
| 	void applyCl(CClient * cl); | ||||
| 	DLL_LINKAGE void applyGs(CGameState * gs); | ||||
|  | ||||
| 	template <typename Handler>  | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & moves; | ||||
| 		h & changes; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct GetEngagedHeroIds : boost::static_visitor<boost::optional<ObjectInstanceID>> | ||||
| { | ||||
| 	boost::optional<ObjectInstanceID> operator()(const ConstTransitivePtr<CGHeroInstance> &h) const | ||||
| @@ -1946,6 +1976,102 @@ struct ArrangeStacks : public CPackForServer | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkMoveArmy : public CPackForServer | ||||
| { | ||||
| 	SlotID srcSlot; | ||||
| 	ObjectInstanceID srcArmy; | ||||
| 	ObjectInstanceID destArmy; | ||||
|  | ||||
| 	BulkMoveArmy() | ||||
| 	{}; | ||||
|  | ||||
| 	BulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) | ||||
| 		: srcArmy(srcArmy), destArmy(destArmy), srcSlot(srcSlot) | ||||
| 	{}; | ||||
|  | ||||
| 	bool applyGh(CGameHandler * gh); | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & static_cast<CPackForServer&>(*this); | ||||
| 		h & srcSlot; | ||||
| 		h & srcArmy; | ||||
| 		h & destArmy; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkSplitStack : public CPackForServer | ||||
| { | ||||
| 	SlotID src; | ||||
| 	ObjectInstanceID srcOwner; | ||||
| 	si32 amount; | ||||
|  | ||||
| 	BulkSplitStack() : amount(0) | ||||
| 	{}; | ||||
|  | ||||
| 	BulkSplitStack(ObjectInstanceID srcOwner, SlotID src, si32 howMany) | ||||
| 		: src(src), srcOwner(srcOwner), amount(howMany)  | ||||
| 	{}; | ||||
|  | ||||
| 	bool applyGh(CGameHandler * gh); | ||||
|  | ||||
| 	template <typename Handler>  | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & static_cast<CPackForServer&>(*this); | ||||
| 		h & src; | ||||
| 		h & srcOwner; | ||||
| 		h & amount; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkMergeStacks : public CPackForServer | ||||
| { | ||||
| 	SlotID src; | ||||
| 	ObjectInstanceID srcOwner; | ||||
|  | ||||
| 	BulkMergeStacks() | ||||
| 	{}; | ||||
|  | ||||
| 	BulkMergeStacks(ObjectInstanceID srcOwner, SlotID src) | ||||
| 		: src(src), srcOwner(srcOwner) | ||||
| 	{}; | ||||
|  | ||||
| 	bool applyGh(CGameHandler * gh); | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & static_cast<CPackForServer&>(*this); | ||||
| 		h & src; | ||||
| 		h & srcOwner; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct BulkSmartSplitStack : public CPackForServer | ||||
| { | ||||
| 	SlotID src; | ||||
| 	ObjectInstanceID srcOwner; | ||||
|  | ||||
| 	BulkSmartSplitStack() | ||||
| 	{}; | ||||
|  | ||||
| 	BulkSmartSplitStack(ObjectInstanceID srcOwner, SlotID src) | ||||
| 		: src(src), srcOwner(srcOwner) | ||||
| 	{}; | ||||
|  | ||||
| 	bool applyGh(CGameHandler * gh); | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & static_cast<CPackForServer&>(*this); | ||||
| 		h & src; | ||||
| 		h & srcOwner; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct DisbandCreature : public CPackForServer | ||||
| { | ||||
| 	DisbandCreature(){}; | ||||
|   | ||||
| @@ -1020,6 +1020,21 @@ DLL_LINKAGE void RebalanceStacks::applyGs(CGameState * gs) | ||||
| 	CBonusSystemNode::treeHasChanged(); | ||||
| } | ||||
|  | ||||
| DLL_LINKAGE void BulkRebalanceStacks::applyGs(CGameState * gs) | ||||
| { | ||||
| 	for(auto & move : moves) | ||||
| 		move.applyGs(gs); | ||||
| } | ||||
|  | ||||
| DLL_LINKAGE void BulkSmartRebalanceStacks::applyGs(CGameState * gs) | ||||
| { | ||||
| 	for(auto & move : moves) | ||||
| 		move.applyGs(gs); | ||||
|  | ||||
| 	for(auto & change : changes) | ||||
| 		change.applyGs(gs); | ||||
| } | ||||
|  | ||||
| DLL_LINKAGE void PutArtifact::applyGs(CGameState *gs) | ||||
| { | ||||
| 	assert(art->canBePutAt(al)); | ||||
|   | ||||
| @@ -317,6 +317,8 @@ void registerTypesClientPacks2(Serializer &s) | ||||
|  | ||||
| 	s.template registerType<CPackForClient, SaveGameClient>(); | ||||
| 	s.template registerType<CPackForClient, PlayerMessageClient>(); | ||||
| 	s.template registerType<CGarrisonOperationPack, BulkRebalanceStacks>(); | ||||
| 	s.template registerType<CGarrisonOperationPack, BulkSmartRebalanceStacks>(); | ||||
| } | ||||
|  | ||||
| template<typename Serializer> | ||||
| @@ -347,6 +349,10 @@ void registerTypesServerPacks(Serializer &s) | ||||
| 	s.template registerType<CPackForServer, CastleTeleportHero>(); | ||||
| 	s.template registerType<CPackForServer, SaveGame>(); | ||||
| 	s.template registerType<CPackForServer, PlayerMessage>(); | ||||
| 	s.template registerType<CPackForServer, BulkSplitStack>(); | ||||
| 	s.template registerType<CPackForServer, BulkMergeStacks>(); | ||||
| 	s.template registerType<CPackForServer, BulkSmartSplitStack>(); | ||||
| 	s.template registerType<CPackForServer, BulkMoveArmy>(); | ||||
| } | ||||
|  | ||||
| template<typename Serializer> | ||||
|   | ||||
| @@ -1629,6 +1629,9 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
|  | ||||
| CGameHandler::CGameHandler(CVCMIServer * lobby) | ||||
| 	: lobby(lobby) | ||||
| 	, complainNoCreatures("No creatures to split") | ||||
| 	, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!") | ||||
| 	, complainInvalidSlot("Invalid slot accessed!") | ||||
| { | ||||
| 	QID = 1; | ||||
| 	IObjectInterface::cb = this; | ||||
| @@ -2962,6 +2965,259 @@ void CGameHandler::load(const std::string & filename) | ||||
| 	gs->updateOnLoad(lobby->si.get()); | ||||
| } | ||||
|  | ||||
| bool CGameHandler::bulkSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner, si32 howMany) | ||||
| { | ||||
| 	if(!slotSrc.validSlot() && complain(complainInvalidSlot)) | ||||
| 		return false; | ||||
|  | ||||
| 	const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner)); | ||||
| 	const CCreatureSet & creatureSet = *army; | ||||
|  | ||||
| 	if((!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures)) | ||||
| 		|| (howMany < 1 && complain("Invalid split parameter!"))) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	auto actualAmount = army->getStackCount(slotSrc); | ||||
|  | ||||
| 	if(actualAmount <= howMany && complain(complainNotEnoughCreatures)) // '<=' because it's not intended just for moving a stack | ||||
| 		return false; | ||||
|  | ||||
| 	auto freeSlots = creatureSet.getFreeSlots(); | ||||
|  | ||||
| 	if(freeSlots.empty() && complain("No empty stacks")) | ||||
| 		return false; | ||||
|  | ||||
| 	BulkRebalanceStacks bulkRS; | ||||
|  | ||||
| 	for(auto slot : freeSlots) | ||||
| 	{ | ||||
| 		RebalanceStacks rs; | ||||
| 		rs.srcArmy = army->id; | ||||
| 		rs.dstArmy = army->id; | ||||
| 		rs.srcSlot = slotSrc; | ||||
| 		rs.dstSlot = slot; | ||||
| 		rs.count = howMany; | ||||
|  | ||||
| 		bulkRS.moves.push_back(rs); | ||||
| 		actualAmount -= howMany; | ||||
|  | ||||
| 		if(actualAmount <= howMany) | ||||
| 			break; | ||||
| 	} | ||||
| 	sendAndApply(&bulkRS); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner) | ||||
| { | ||||
| 	if(!slotSrc.validSlot() && complain(complainInvalidSlot)) | ||||
| 		return false; | ||||
|  | ||||
| 	const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner)); | ||||
| 	const CCreatureSet & creatureSet = *army; | ||||
|  | ||||
| 	if(!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto actualAmount = creatureSet.getStackCount(slotSrc); | ||||
|  | ||||
| 	if(actualAmount < 1 && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto currentCreature = creatureSet.getCreature(slotSrc); | ||||
|  | ||||
| 	if(!currentCreature && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto creatureSlots = creatureSet.getCreatureSlots(currentCreature, slotSrc); | ||||
|  | ||||
| 	if(!creatureSlots.size()) | ||||
| 		return false; | ||||
|  | ||||
| 	BulkRebalanceStacks bulkRS; | ||||
|  | ||||
| 	for(auto slot : creatureSlots) | ||||
| 	{ | ||||
| 		RebalanceStacks rs; | ||||
| 		rs.srcArmy = army->id; | ||||
| 		rs.dstArmy = army->id; | ||||
| 		rs.srcSlot = slot; | ||||
| 		rs.dstSlot = slotSrc; | ||||
| 		rs.count = creatureSet.getStackCount(slot); | ||||
| 		bulkRS.moves.push_back(rs); | ||||
| 	} | ||||
| 	sendAndApply(&bulkRS); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) | ||||
| { | ||||
| 	if(!srcSlot.validSlot() && complain(complainInvalidSlot)) | ||||
| 		return false; | ||||
|  | ||||
| 	const CArmedInstance * armySrc = static_cast<const CArmedInstance*>(getObjInstance(srcArmy)); | ||||
| 	const CCreatureSet & setSrc = *armySrc; | ||||
|  | ||||
| 	if(!vstd::contains(setSrc.stacks, srcSlot) && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	const CArmedInstance * armyDest = static_cast<const CArmedInstance*>(getObjInstance(destArmy)); | ||||
| 	const CCreatureSet & setDest = *armyDest; | ||||
| 	auto freeSlots = setDest.getFreeSlotsQueue(); | ||||
|  | ||||
| 	typedef std::map<SlotID, std::pair<SlotID, TQuantity>> TRebalanceMap; | ||||
| 	TRebalanceMap moves; | ||||
|  | ||||
| 	auto srcQueue = setSrc.getCreatureQueue(srcSlot); // Exclude srcSlot, it should be moved last | ||||
| 	auto slotsLeft = setSrc.stacksCount(); | ||||
| 	auto destMap = setDest.getCreatureMap(); | ||||
| 	TMapCreatureSlot::key_compare keyComp = destMap.key_comp(); | ||||
|  | ||||
| 	while(!srcQueue.empty()) | ||||
| 	{ | ||||
| 		auto pair = srcQueue.top(); | ||||
| 		srcQueue.pop(); | ||||
|  | ||||
| 		auto currCreature = pair.first; | ||||
| 		auto currSlot = pair.second; | ||||
| 		const auto quantity = setSrc.getStackCount(currSlot); | ||||
|  | ||||
| 		TMapCreatureSlot::iterator lb = destMap.lower_bound(currCreature); | ||||
| 		const bool alreadyExists = (lb != destMap.end() && !(keyComp(currCreature, lb->first))); | ||||
|  | ||||
| 		if(!alreadyExists) | ||||
| 		{ | ||||
| 			if(freeSlots.empty()) | ||||
| 				continue; | ||||
|  | ||||
| 			auto currFreeSlot = freeSlots.front(); | ||||
| 			freeSlots.pop(); | ||||
| 			destMap.insert(lb, TMapCreatureSlot::value_type(currCreature, currFreeSlot)); | ||||
| 		} | ||||
| 		moves.insert(std::make_pair(currSlot, std::make_pair(destMap[currCreature], quantity))); | ||||
| 		slotsLeft--; | ||||
| 	} | ||||
| 	if(slotsLeft == 1) | ||||
| 	{ | ||||
| 		auto lastCreature = setSrc.getCreature(srcSlot); | ||||
| 		auto slotToMove = SlotID(); | ||||
| 		// Try to find a slot for last creature | ||||
| 		if(destMap.find(lastCreature) == destMap.end()) | ||||
| 		{ | ||||
| 			if(!freeSlots.empty()) | ||||
| 				slotToMove = freeSlots.front(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			slotToMove = destMap[lastCreature]; | ||||
| 		} | ||||
|  | ||||
| 		if(slotToMove != SlotID()) | ||||
| 		{ | ||||
| 			const bool needsLastStack = armySrc->needsLastStack(); | ||||
| 			const auto quantity = setSrc.getStackCount(srcSlot) - (needsLastStack ? 1 : 0); | ||||
| 			moves.insert(std::make_pair(srcSlot, std::make_pair(slotToMove, quantity))); | ||||
| 		} | ||||
| 	} | ||||
| 	BulkRebalanceStacks bulkRS; | ||||
|  | ||||
| 	for(auto & move : moves) | ||||
| 	{ | ||||
| 		RebalanceStacks rs; | ||||
| 		rs.srcArmy = armySrc->id; | ||||
| 		rs.dstArmy = armyDest->id; | ||||
| 		rs.srcSlot = move.first; | ||||
| 		rs.dstSlot = move.second.first; | ||||
| 		rs.count = move.second.second; | ||||
| 		bulkRS.moves.push_back(rs); | ||||
| 	} | ||||
| 	sendAndApply(&bulkRS); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner) | ||||
| { | ||||
| 	if(!slotSrc.validSlot() && complain(complainInvalidSlot)) | ||||
| 		return false; | ||||
|  | ||||
| 	const CArmedInstance * army = static_cast<const CArmedInstance*>(getObjInstance(srcOwner)); | ||||
| 	const CCreatureSet & creatureSet = *army; | ||||
|  | ||||
| 	if(!vstd::contains(creatureSet.stacks, slotSrc) && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto actualAmount = creatureSet.getStackCount(slotSrc); | ||||
|  | ||||
| 	if(actualAmount <= 1 && complain(complainNoCreatures)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto freeSlot = creatureSet.getFreeSlot(); | ||||
| 	auto currentCreature = creatureSet.getCreature(slotSrc); | ||||
|  | ||||
| 	if(freeSlot == SlotID() && creatureSet.isCreatureBalanced(currentCreature)) | ||||
| 		return true; | ||||
|  | ||||
| 	auto creatureSlots = creatureSet.getCreatureSlots(currentCreature, SlotID(-1), 1); // Ignore slots where's only 1 creature, don't ignore slotSrc | ||||
| 	TQuantity totalCreatures = 0; | ||||
|  | ||||
| 	for(auto slot : creatureSlots) | ||||
| 		totalCreatures += creatureSet.getStackCount(slot); | ||||
|  | ||||
| 	if(totalCreatures <= 1 && complain("Total creatures number is invalid")) | ||||
| 		return false; | ||||
|  | ||||
| 	if(freeSlot != SlotID()) | ||||
| 		creatureSlots.push_back(freeSlot); | ||||
|  | ||||
| 	if(creatureSlots.empty() && complain("No available slots for smart rebalancing")) | ||||
| 		return false; | ||||
|  | ||||
| 	const auto totalCreatureSlots = creatureSlots.size(); | ||||
| 	const auto rem = totalCreatures % totalCreatureSlots; | ||||
| 	const auto quotient = totalCreatures / totalCreatureSlots; | ||||
|  | ||||
| 	// totalCreatures == rem * (quotient + 1) + (totalCreatureSlots - rem) * quotient; | ||||
| 	// Proof: r(q+1)+(s-r)q = rq+r+qs-rq = r+qs = total, where total/s = q+r/s | ||||
|  | ||||
| 	BulkSmartRebalanceStacks bulkSRS; | ||||
|  | ||||
| 	if(freeSlot != SlotID()) | ||||
| 	{ | ||||
| 		RebalanceStacks rs; | ||||
| 		rs.srcArmy = rs.dstArmy = army->id; | ||||
| 		rs.srcSlot = slotSrc; | ||||
| 		rs.dstSlot = freeSlot; | ||||
| 		rs.count = 1; | ||||
| 		bulkSRS.moves.push_back(rs); | ||||
| 	} | ||||
| 	auto currSlot = 0; | ||||
| 	auto check = 0; | ||||
|  | ||||
| 	for(auto slot : creatureSlots) | ||||
| 	{ | ||||
| 		ChangeStackCount csc; | ||||
|  | ||||
| 		csc.army = army->id; | ||||
| 		csc.slot = slot; | ||||
| 		csc.count = (currSlot < rem) | ||||
| 			? quotient + 1 | ||||
| 			: quotient; | ||||
| 		csc.absoluteValue = true; | ||||
| 		bulkSRS.changes.push_back(csc); | ||||
| 		currSlot++; | ||||
| 		check += csc.count; | ||||
| 	} | ||||
|  | ||||
| 	if(check != totalCreatures) | ||||
| 	{ | ||||
| 		complain((boost::format("Failure: totalCreatures=%d but check=%d") % totalCreatures % check).str()); | ||||
| 		return false; | ||||
| 	} | ||||
| 	sendAndApply(&bulkSRS); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player) | ||||
| { | ||||
| 	const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObjInstance(id1)), | ||||
| @@ -2970,7 +3226,7 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 | ||||
| 	StackLocation sl1(s1, p1), sl2(s2, p2); | ||||
| 	if (!sl1.slot.validSlot()  ||  !sl2.slot.validSlot()) | ||||
| 	{ | ||||
| 		complain("Invalid slot accessed!"); | ||||
| 		complain(complainInvalidSlot); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| @@ -3051,8 +3307,8 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 | ||||
| 		} | ||||
|  | ||||
| 		//general conditions checking | ||||
| 		if ((!vstd::contains(S1.stacks,p1) && complain("no creatures to split")) | ||||
| 			|| (val<1  && complain("no creatures to split")) ) | ||||
| 		if ((!vstd::contains(S1.stacks,p1) && complain(complainNoCreatures)) | ||||
| 			|| (val<1  && complain(complainNoCreatures)) ) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| @@ -3087,7 +3343,7 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 | ||||
| 		{ | ||||
| 			if (s1->getStackCount(p1) < val)//not enough creatures | ||||
| 			{ | ||||
| 				complain("Cannot split that stack, not enough creatures!"); | ||||
| 				complain(complainNotEnoughCreatures); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| @@ -6793,7 +7049,11 @@ bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player) | ||||
| 	auto query = queries.topQuery(player); | ||||
| 	if (query && query->blocksPack(pack)) | ||||
| 	{ | ||||
| 		complain(boost::str(boost::format("Player %s has to answer queries  before attempting any further actions. Top query is %s!") % player % query->toString())); | ||||
| 		complain(boost::str(boost::format( | ||||
| 			"\r\n| Player \"%s\" has to answer queries before attempting any further actions.\r\n| Top Query: \"%s\"\r\n") | ||||
| 			% boost::to_upper_copy<std::string>(player.getStr()) | ||||
| 			% query->toString() | ||||
| 		)); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -249,6 +249,10 @@ public: | ||||
| 	bool razeStructure(ObjectInstanceID tid, BuildingID bid); | ||||
| 	bool disbandCreature( ObjectInstanceID id, SlotID pos ); | ||||
| 	bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); | ||||
| 	bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); | ||||
| 	bool bulkSplitStack(SlotID src, ObjectInstanceID srcOwner, si32 howMany); | ||||
| 	bool bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner); | ||||
| 	bool bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner); | ||||
| 	void save(const std::string &fname); | ||||
| 	void load(const std::string &fname); | ||||
|  | ||||
| @@ -353,7 +357,9 @@ private: | ||||
| 	void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors); | ||||
| 	void checkVictoryLossConditionsForAll(); | ||||
|  | ||||
|  | ||||
| 	const std::string complainNoCreatures; | ||||
| 	const std::string complainNotEnoughCreatures; | ||||
| 	const std::string complainInvalidSlot; | ||||
| }; | ||||
|  | ||||
| class ExceptionNotAllowedAction : public std::exception | ||||
|   | ||||
| @@ -69,7 +69,25 @@ void CQuery::addPlayer(PlayerColor color) | ||||
|  | ||||
| std::string CQuery::toString() const | ||||
| { | ||||
| 	std::string ret = boost::str(boost::format("A query of type %s and qid=%d affecting players %s") % typeid(*this).name() % queryID % formatContainer(players)); | ||||
| 	const auto size = players.size(); | ||||
| 	const std::string plural = size > 1 ? "s" : ""; | ||||
| 	std::string names; | ||||
|  | ||||
| 	for(size_t i = 0; i < size; i++) | ||||
| 	{ | ||||
| 		names += boost::to_upper_copy<std::string>(players[i].getStr()); | ||||
|  | ||||
| 		if(i < size - 2) | ||||
| 			names += ", "; | ||||
| 		else if(size > 1 && i == size - 2) | ||||
| 			names += " and "; | ||||
| 	} | ||||
| 	std::string ret = boost::str(boost::format("A query of type '%s' and qid = %d affecting player%s %s") | ||||
| 		% typeid(*this).name() | ||||
| 		% queryID  | ||||
| 		% plural | ||||
| 		% names | ||||
| 	); | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| @@ -327,6 +345,18 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const | ||||
| 	if(auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack)) | ||||
| 		return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2); | ||||
|  | ||||
| 	if(auto stacks = dynamic_ptr_cast<BulkSplitStack>(pack)) | ||||
| 		return !vstd::contains(ourIds, stacks->srcOwner); | ||||
|  | ||||
| 	if(auto stacks = dynamic_ptr_cast<BulkMergeStacks>(pack)) | ||||
| 		return !vstd::contains(ourIds, stacks->srcOwner); | ||||
|  | ||||
| 	if(auto stacks = dynamic_ptr_cast<BulkSmartSplitStack>(pack)) | ||||
| 		return !vstd::contains(ourIds, stacks->srcOwner); | ||||
|  | ||||
| 	if(auto stacks = dynamic_ptr_cast<BulkMoveArmy>(pack)) | ||||
| 		return !vstd::contains(ourIds, stacks->srcArmy) || !vstd::contains(ourIds, stacks->destArmy); | ||||
|  | ||||
| 	if(auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack)) | ||||
| 	{ | ||||
| 		if(auto id1 = boost::apply_visitor(GetEngagedHeroIds(), arts->src.artHolder)) | ||||
|   | ||||
| @@ -123,6 +123,26 @@ bool ArrangeStacks::applyGh(CGameHandler * gh) | ||||
| 	return gh->arrangeStacks(id1, id2, what, p1, p2, val, gh->getPlayerAt(c)); | ||||
| } | ||||
|  | ||||
| bool BulkMoveArmy::applyGh(CGameHandler * gh) | ||||
| { | ||||
| 	return gh->bulkMoveArmy(srcArmy, destArmy, srcSlot); | ||||
| } | ||||
|  | ||||
| bool BulkSplitStack::applyGh(CGameHandler * gh) | ||||
| { | ||||
| 	return gh->bulkSplitStack(src, srcOwner, amount); | ||||
| } | ||||
|  | ||||
| bool BulkMergeStacks::applyGh(CGameHandler* gh) | ||||
| { | ||||
| 	return gh->bulkMergeStacks(src, srcOwner); | ||||
| } | ||||
|  | ||||
| bool BulkSmartSplitStack::applyGh(CGameHandler * gh) | ||||
| { | ||||
| 	return gh->bulkSmartSplitStack(src, srcOwner); | ||||
| } | ||||
|  | ||||
| bool DisbandCreature::applyGh(CGameHandler * gh) | ||||
| { | ||||
| 	throwOnWrongOwner(gh, id); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user