mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	arts altar - arts holder
This commit is contained in:
		| @@ -20,14 +20,12 @@ | ||||
| #include "../../lib/networkPacks/ArtifactLocation.h" | ||||
|  | ||||
| CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position) | ||||
| 	: visibleArtSet(ArtBearer::ArtBearer::HERO) | ||||
| { | ||||
| 	init( | ||||
| 		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2), | ||||
| 		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2), | ||||
| 		position, | ||||
| 		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1)); | ||||
| 	pickedArtFromSlot = ArtifactPosition::PRE_FIRST; | ||||
|  | ||||
| 	// The backpack is in the altar window above and to the right | ||||
| 	for(auto & slot : backpack) | ||||
| @@ -41,80 +39,19 @@ CArtifactsOfHeroAltar::~CArtifactsOfHeroAltar() | ||||
| 	putBackPickedArtifact(); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::setHero(const CGHeroInstance * hero) | ||||
| { | ||||
| 	if(hero) | ||||
| 	{ | ||||
| 		visibleArtSet.artifactsWorn = hero->artifactsWorn; | ||||
| 		visibleArtSet.artifactsInBackpack = hero->artifactsInBackpack; | ||||
| 		CArtifactsOfHeroBase::setHero(hero); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::updateWornSlots() | ||||
| { | ||||
| 	for(auto place : artWorn) | ||||
| 		setSlotData(getArtPlace(place.first), place.first, visibleArtSet); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::updateBackpackSlots() | ||||
| { | ||||
| 	for(auto artPlace : backpack) | ||||
| 		setSlotData(getArtPlace(artPlace->slot), artPlace->slot, visibleArtSet); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::scrollBackpack(int offset) | ||||
| { | ||||
| 	CArtifactsOfHeroBase::scrollBackpackForArtSet(offset, visibleArtSet); | ||||
| 	CArtifactsOfHeroBase::scrollBackpackForArtSet(offset, *curHero); | ||||
| 	redraw(); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::pickUpArtifact(CArtPlace & artPlace) | ||||
| { | ||||
| 	if(const auto art = artPlace.getArt()) | ||||
| 	{ | ||||
| 		pickedArtFromSlot = artPlace.slot; | ||||
| 		artPlace.setArtifact(nullptr); | ||||
| 		deleteFromVisible(art); | ||||
| 		if(ArtifactUtils::isSlotBackpack(pickedArtFromSlot)) | ||||
| 			pickedArtFromSlot = curHero->getSlotByInstance(art); | ||||
| 		assert(pickedArtFromSlot != ArtifactPosition::PRE_FIRST); | ||||
| 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, pickedArtFromSlot), ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS)); | ||||
| 	} | ||||
| 	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot), | ||||
| 		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS)); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc) | ||||
| { | ||||
| 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc); | ||||
| 	const auto pickedArtInst = curHero->getArt(ArtifactPosition::TRANSITION_POS); | ||||
| 	assert(pickedArtInst); | ||||
| 	visibleArtSet.putArtifact(dstLoc.slot, const_cast<CArtifactInstance*>(pickedArtInst)); | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::pickedArtMoveToAltar(const ArtifactPosition & slot) | ||||
| { | ||||
| 	if(ArtifactUtils::isSlotBackpack(slot) || ArtifactUtils::isSlotEquipment(slot) || slot == ArtifactPosition::TRANSITION_POS) | ||||
| 	{ | ||||
| 		assert(curHero->getSlot(slot)->getArt()); | ||||
| 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, slot), ArtifactLocation(curHero->id, pickedArtFromSlot)); | ||||
| 		pickedArtFromSlot = ArtifactPosition::PRE_FIRST; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CArtifactsOfHeroAltar::deleteFromVisible(const CArtifactInstance * artInst) | ||||
| { | ||||
| 	const auto slot = visibleArtSet.getSlotByInstance(artInst); | ||||
| 	visibleArtSet.removeArtifact(slot); | ||||
| 	if(ArtifactUtils::isSlotBackpack(slot)) | ||||
| 	{ | ||||
| 		scrollBackpackForArtSet(0, visibleArtSet); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for(const auto & part : artInst->getPartsInfo()) | ||||
| 		{ | ||||
| 			if(part.slot != ArtifactPosition::PRE_FIRST) | ||||
| 				getArtPlace(part.slot)->setArtifact(nullptr); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -17,17 +17,10 @@ class CArtifactsOfHeroAltar : public CArtifactsOfHeroBase | ||||
| { | ||||
| public: | ||||
| 	std::set<const CArtifactInstance*> artifactsOnAltar; | ||||
| 	ArtifactPosition pickedArtFromSlot; | ||||
| 	CArtifactFittingSet visibleArtSet; | ||||
|  | ||||
| 	CArtifactsOfHeroAltar(const Point & position); | ||||
| 	~CArtifactsOfHeroAltar(); | ||||
| 	void setHero(const CGHeroInstance * hero) override; | ||||
| 	void updateWornSlots() override; | ||||
| 	void updateBackpackSlots() override; | ||||
| 	void scrollBackpack(int offset) override; | ||||
| 	void pickUpArtifact(CArtPlace & artPlace); | ||||
| 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc); | ||||
| 	void pickedArtMoveToAltar(const ArtifactPosition & slot); | ||||
| 	void deleteFromVisible(const CArtifactInstance * artInst); | ||||
| }; | ||||
|   | ||||
| @@ -31,6 +31,11 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * | ||||
| { | ||||
| 	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); | ||||
|  | ||||
| 	assert(market); | ||||
| 	auto altarObj = dynamic_cast<const CGArtifactsAltar*>(market); | ||||
| 	altarId = altarObj->id; | ||||
| 	altarArtifacts = altarObj; | ||||
|  | ||||
| 	deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("ALTSACR.DEF"), | ||||
| 		CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }); | ||||
| 	labels.emplace_back(std::make_shared<CLabel>(450, 34, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477])); | ||||
| @@ -117,11 +122,11 @@ void CAltarArtifacts::sacrificeAll() | ||||
|  | ||||
| void CAltarArtifacts::sacrificeBackpack() | ||||
| { | ||||
| 	while(!arts->visibleArtSet.artifactsInBackpack.empty()) | ||||
| 	/*while (!arts->visibleArtSet.artifactsInBackpack.empty()) | ||||
| 	{ | ||||
| 		if(!putArtOnAltar(nullptr, arts->visibleArtSet.artifactsInBackpack[0].artifact)) | ||||
| 			break; | ||||
| 	}; | ||||
| 	};*/ | ||||
| 	calcExpAltarForHero(); | ||||
| } | ||||
|  | ||||
| @@ -192,20 +197,21 @@ bool CAltarArtifacts::putArtOnAltar(std::shared_ptr<CTradeableItem> altarSlot, c | ||||
|  | ||||
| void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<CTradeableItem> & hCurSlot) | ||||
| { | ||||
| 	const auto pickedArtInst = arts->getPickedArtifact(); | ||||
| 	if(pickedArtInst) | ||||
| 	if(const auto pickedArtInst = arts->getPickedArtifact()) | ||||
| 	{ | ||||
| 		arts->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS); | ||||
| 		if(pickedArtInst->canBePutAt(altarArtifacts)) | ||||
| 		{ | ||||
| 			LOCPLINT->cb->swapArtifacts(ArtifactLocation(arts->getHero()->id, ArtifactPosition::TRANSITION_POS), | ||||
| 			ArtifactLocation(altarId, ArtifactPosition::ALTAR)); | ||||
| 			moveArtToAltar(newSlot, pickedArtInst); | ||||
| 		} | ||||
| 	} | ||||
| 	else if(const CArtifactInstance * art = newSlot->getArtInstance()) | ||||
| 	{ | ||||
| 		const auto hero = arts->getHero(); | ||||
| 		const auto slot = hero->getSlotByInstance(art); | ||||
| 		const auto slot = altarArtifacts->getSlotByInstance(art); | ||||
| 		assert(slot != ArtifactPosition::PRE_FIRST); | ||||
| 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, slot), | ||||
| 		LOCPLINT->cb->swapArtifacts(ArtifactLocation(altarId, slot), | ||||
| 			ArtifactLocation(hero->id, ArtifactPosition::TRANSITION_POS)); | ||||
| 		arts->pickedArtFromSlot = slot; | ||||
| 		arts->artifactsOnAltar.erase(art); | ||||
| 		newSlot->setID(-1); | ||||
| 		newSlot->subtitle.clear(); | ||||
|   | ||||
| @@ -25,6 +25,8 @@ public: | ||||
| 	std::shared_ptr<CArtifactsOfHeroAltar> getAOHset() const; | ||||
|  | ||||
| private: | ||||
| 	ObjectInstanceID altarId; | ||||
| 	const CArtifactSet * altarArtifacts; | ||||
| 	std::shared_ptr<CArtPlace> selectedArt; | ||||
| 	std::shared_ptr<CLabel> selectedCost; | ||||
| 	std::shared_ptr<CButton> sacrificeBackpackButton; | ||||
|   | ||||
| @@ -151,11 +151,16 @@ DLL_LINKAGE bool ArtifactUtils::isSlotEquipment(const ArtifactPosition & slot) | ||||
|  | ||||
| DLL_LINKAGE bool ArtifactUtils::isBackpackFreeSlots(const CArtifactSet * target, const size_t reqSlots) | ||||
| { | ||||
| 	if(target->bearerType() == ArtBearer::HERO) | ||||
| 	{ | ||||
| 		const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP); | ||||
| 		if(backpackCap < 0) | ||||
| 			return true; | ||||
| 		else | ||||
| 			return target->artifactsInBackpack.size() + reqSlots <= backpackCap; | ||||
| 	} | ||||
| 	else | ||||
| 		return false; | ||||
| } | ||||
|  | ||||
| DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities( | ||||
|   | ||||
| @@ -181,7 +181,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b | ||||
| { | ||||
| 	auto simpleArtCanBePutAt = [this](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool | ||||
| 	{ | ||||
| 		if(ArtifactUtils::isSlotBackpack(slot)) | ||||
| 		if(artSet->bearerType() == ArtBearer::HERO && ArtifactUtils::isSlotBackpack(slot)) | ||||
| 		{ | ||||
| 			if(isBig() || (!assumeDestRemoved && !ArtifactUtils::isBackpackFreeSlots(artSet))) | ||||
| 				return false; | ||||
| @@ -258,6 +258,7 @@ CArtifact::CArtifact() | ||||
| 	possibleSlots[ArtBearer::HERO]; //we want to generate map entry even if it will be empty | ||||
| 	possibleSlots[ArtBearer::CREATURE]; //we want to generate map entry even if it will be empty | ||||
| 	possibleSlots[ArtBearer::COMMANDER]; | ||||
| 	possibleSlots[ArtBearer::ALTAR].push_back(ArtifactPosition::ALTAR); | ||||
| } | ||||
|  | ||||
| //This destructor should be placed here to avoid side effects | ||||
| @@ -906,6 +907,9 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const | ||||
|  | ||||
| bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const | ||||
| { | ||||
| 	if(bearerType() == ArtBearer::ALTAR) | ||||
| 		return artifactsInBackpack.size() < 22; | ||||
|  | ||||
| 	if(const ArtSlotInfo *s = getSlot(pos)) | ||||
| 		return (onlyLockCheck || !s->artifact) && !s->locked; | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,8 @@ class JsonSerializeFormat; | ||||
| #define ART_BEARER_LIST \ | ||||
| 	ART_BEARER(HERO)\ | ||||
| 	ART_BEARER(CREATURE)\ | ||||
| 	ART_BEARER(COMMANDER) | ||||
| 	ART_BEARER(COMMANDER)\ | ||||
| 	ART_BEARER(ALTAR) | ||||
|  | ||||
| namespace ArtBearer | ||||
| { | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
| #include "mapObjects/CGHeroInstance.h" | ||||
| #include "mapObjects/CGTownInstance.h" | ||||
| #include "mapObjects/MiscObjects.h" | ||||
| #include "mapObjects/CGMarket.h" | ||||
| #include "networkPacks/ArtifactLocation.h" | ||||
| #include "CGeneralTextHandler.h" | ||||
| #include "StartInfo.h" // for StartInfo | ||||
| @@ -970,7 +971,8 @@ const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid | ||||
|  | ||||
| CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const | ||||
| { | ||||
| 	auto hero = const_cast<CGHeroInstance*>(getHero(loc.artHolder)); | ||||
| 	if(auto hero = const_cast<CGHeroInstance*>(getHero(loc.artHolder))) | ||||
| 	{ | ||||
| 		if(loc.creature.has_value()) | ||||
| 		{ | ||||
| 			if(loc.creature.value() == SlotID::COMMANDER_SLOT_PLACEHOLDER) | ||||
| @@ -982,6 +984,15 @@ CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const | ||||
| 		{ | ||||
| 			return hero; | ||||
| 		} | ||||
| 	} | ||||
| 	else if(auto market = dynamic_cast<const CGArtifactsAltar*>(getObj(loc.artHolder, false))) | ||||
| 	{ | ||||
| 		return const_cast<CGArtifactsAltar*>(market); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return nullptr; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const | ||||
|   | ||||
| @@ -610,7 +610,10 @@ public: | ||||
| 		CREATURE_SLOT = 0, | ||||
| 		 | ||||
| 		// Commander | ||||
| 		COMMANDER1 = 0, COMMANDER2, COMMANDER3, COMMANDER4, COMMANDER5, COMMANDER6 | ||||
| 		COMMANDER1 = 0, COMMANDER2, COMMANDER3, COMMANDER4, COMMANDER5, COMMANDER6, | ||||
|  | ||||
| 		// Altar | ||||
| 		ALTAR = BACKPACK_START | ||||
| 	}; | ||||
|  | ||||
| 	static_assert(MISC5 < BACKPACK_START, "incorrect number of artifact slots"); | ||||
|   | ||||
| @@ -238,6 +238,11 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const | ||||
| 				return new CGUniversity(cb); | ||||
| 		} | ||||
| 	} | ||||
| 	else if(marketModes.size() == 2) | ||||
| 	{ | ||||
| 		if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP)) | ||||
| 			return new CGArtifactsAltar(cb); | ||||
| 	} | ||||
| 	return new CGMarket(cb); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -113,4 +113,9 @@ void CGUniversity::onHeroVisit(const CGHeroInstance * h) const | ||||
| 	cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true); | ||||
| } | ||||
|  | ||||
| ArtBearer::ArtBearer CGArtifactsAltar::bearerType() const | ||||
| { | ||||
| 	return ArtBearer::ALTAR; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|  | ||||
| #include "CGObjectInstance.h" | ||||
| #include "IMarket.h" | ||||
| #include "../CArtHandler.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| @@ -80,4 +81,18 @@ public: | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class DLL_LINKAGE CGArtifactsAltar : public CGMarket, public CArtifactSet | ||||
| { | ||||
| public: | ||||
| 	using CGMarket::CGMarket; | ||||
|  | ||||
| 	ArtBearer::ArtBearer bearerType() const override; | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler & h) | ||||
| 	{ | ||||
| 		h & static_cast<CGMarket&>(*this); | ||||
| 		h & static_cast<CArtifactSet&>(*this); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -54,6 +54,7 @@ void registerTypesMapObjects(Serializer &s) | ||||
| 	s.template registerType<CGObjectInstance, CGMarket>(); | ||||
| 		s.template registerType<CGMarket, CGBlackMarket>(); | ||||
| 		s.template registerType<CGMarket, CGUniversity>(); | ||||
| 		s.template registerType<CGMarket, CGArtifactsAltar>(); | ||||
| 	s.template registerType<CGObjectInstance, CGHeroPlaceholder>(); | ||||
|  | ||||
| 	s.template registerType<CGObjectInstance, CArmedInstance>(); s.template registerType<CBonusSystemNode, CArmedInstance>(); s.template registerType<CCreatureSet, CArmedInstance>(); | ||||
|   | ||||
| @@ -2713,17 +2713,17 @@ bool CGameHandler::moveArtifact(const ArtifactLocation & src, const ArtifactLoca | ||||
| 		COMPLAIN_RET("That heroes cannot make any exchange!"); | ||||
|  | ||||
| 	const auto srcArtifact = srcArtSet->getArt(src.slot); | ||||
| 	const auto dstArtifact = dstArtSet->getArt(dst.slot); | ||||
| 	const bool isDstSlotOccupied = dstArtSet->bearerType() == ArtBearer::ALTAR ? false : dstArtSet->getArt(dst.slot) != nullptr; | ||||
| 	const bool isDstSlotBackpack = dstArtSet->bearerType() == ArtBearer::HERO ? ArtifactUtils::isSlotBackpack(dst.slot) : false; | ||||
|  | ||||
| 	if(srcArtifact == nullptr) | ||||
| 		COMPLAIN_RET("No artifact to move!"); | ||||
| 	if(dstArtifact && getHero(src.artHolder)->getOwner() != getHero(dst.artHolder)->getOwner() && !isDstSlotBackpack) | ||||
| 	if(isDstSlotOccupied && getOwner(src.artHolder) != getOwner(dst.artHolder) && !isDstSlotBackpack) | ||||
| 		COMPLAIN_RET("Can't touch artifact on hero of another player!"); | ||||
|  | ||||
| 	// Check if src/dest slots are appropriate for the artifacts exchanged. | ||||
| 	// Moving to the backpack is always allowed. | ||||
| 	if((!srcArtifact || !isDstSlotBackpack) && srcArtifact && !srcArtifact->canBePutAt(dstArtSet, dst.slot, true)) | ||||
| 	if((!srcArtifact || !isDstSlotBackpack) && !srcArtifact->canBePutAt(dstArtSet, dst.slot, true)) | ||||
| 		COMPLAIN_RET("Cannot move artifact!"); | ||||
|  | ||||
| 	auto srcSlotInfo = srcArtSet->getSlot(src.slot); | ||||
| @@ -2749,7 +2749,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation & src, const ArtifactLoca | ||||
| 	ma.dstCreature = dst.creature; | ||||
| 	 | ||||
| 	// Check if dst slot is occupied | ||||
| 	if(!isDstSlotBackpack && dstArtifact) | ||||
| 	if(!isDstSlotBackpack && isDstSlotOccupied) | ||||
| 	{ | ||||
| 		// Previous artifact must be removed | ||||
| 		ma.artsPack1.push_back(BulkMoveArtifacts::LinkedSlots(dstSlot, src.slot)); | ||||
| @@ -3432,6 +3432,20 @@ bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2) | ||||
| 				return true; | ||||
| 		} | ||||
|  | ||||
| 		if(o1->ID == Obj::ALTAR_OF_SACRIFICE) | ||||
| 		{ | ||||
| 			const auto visitingHero = getVisitingHero(o1); | ||||
| 			const auto thisHero = static_cast<const CGHeroInstance*>(o2); | ||||
| 			if(visitingHero == thisHero) | ||||
| 				return true; | ||||
| 		} | ||||
| 		if(o2->ID == Obj::ALTAR_OF_SACRIFICE) | ||||
| 		{ | ||||
| 			const auto visitingHero = getVisitingHero(o2); | ||||
| 			const auto thisHero = static_cast<const CGHeroInstance*>(o1); | ||||
| 			if(visitingHero == thisHero) | ||||
| 				return true; | ||||
| 		} | ||||
| 		if (o1->ID == Obj::HERO && o2->ID == Obj::HERO) | ||||
| 		{ | ||||
| 			const CGHeroInstance *h1 = static_cast<const CGHeroInstance*>(o1); | ||||
|   | ||||
| @@ -134,6 +134,7 @@ void ApplyGhNetPackVisitor::visitGarrisonHeroSwap(GarrisonHeroSwap & pack) | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitExchangeArtifacts(ExchangeArtifacts & pack) | ||||
| { | ||||
| 	if(gh.getHero(pack.src.artHolder)) | ||||
| 		gh.throwIfWrongPlayer(&pack, gh.getOwner(pack.src.artHolder)); //second hero can be ally | ||||
| 	result = gh.moveArtifact(pack.src, pack.dst); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user