Bläddra i källkod

Fix handling of creatures in configurable town buildings

- Fixed removal of partial stacks of creatures
- It is now possible to give units via town building, but only if
visitor has slots to take them
Ivan Savenko 5 månader sedan
förälder
incheckning
95ce9ce509

+ 2 - 1
docs/modders/Map_Objects/Rewardable.md

@@ -635,7 +635,8 @@ List of supported slots names:
 
 - Can be used as limiter
 - Can be used as reward, to give new creatures to a hero
-- If hero does not have enough free slots, game will show selection dialog to pick troops to keep
+- For map objects, if hero does not have enough free slots, game will show selection dialog to pick troops to keep
+- For town buildings, hero must either have free slot(s) to take them, or have creatures of this type. Othervice reward would fail to give any creatures
 - It is possible to specify probability to receive upgraded creature
 
 ```json

+ 1 - 0
lib/callback/IGameEventCallback.h

@@ -76,6 +76,7 @@ public:
 	virtual void giveResource(PlayerColor player, GameResID which, int val)=0;
 	virtual void giveResources(PlayerColor player, ResourceSet resources)=0;
 
+	virtual void giveCreatures(const CGHeroInstance * h, const CCreatureSet &creatures) =0;
 	virtual void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) =0;
 	virtual void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval = false) =0;
 	virtual bool changeStackCount(const StackLocation &sl, TQuantity count, ChangeValueMode mode) =0;

+ 4 - 1
lib/rewardable/Interface.cpp

@@ -254,8 +254,11 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameEventCallback & gameEve
 		for(const auto & crea : info.reward.creatures)
 			creatures.addToSlot(creatures.getFreeSlot(), std::make_unique<CStackInstance>(cb, crea.getId(), crea.getCount()));
 
-		if(auto * army = dynamic_cast<const CArmedInstance*>(this)) //TODO: to fix that, CArmedInstance must be split on map instance part and interface part
+		auto * army = dynamic_cast<const CArmedInstance*>(this);
+		if (army)
 			gameEvents.giveCreatures(army, hero, creatures, false);
+		else
+			gameEvents.giveCreatures(hero, creatures);
 	}
 	
 	if(info.reward.spellCast.first != SpellID::NONE)

+ 33 - 2
server/CGameHandler.cpp

@@ -1139,6 +1139,37 @@ void CGameHandler::giveResources(PlayerColor player, TResources resources)
 	sendAndApply(sr);
 }
 
+void CGameHandler::giveCreatures(const CGHeroInstance * hero, const CCreatureSet &creatures)
+{
+	if (!hero->canBeMergedWith(creatures, true))
+	{
+		complain("Unable to give creatures! Hero does not have enough free slots to receive them!");
+		return;
+	}
+
+	for (const auto & unit : creatures.Slots())
+	{
+		SlotID pos = hero->getSlotFor(unit.second->getCreature());
+		if (!pos.validSlot())
+		{
+			//try to merge two other stacks to make place
+			std::pair<SlotID, SlotID> toMerge;
+			if (hero->mergeableStacks(toMerge))
+			{
+				moveStack(StackLocation(hero->id, toMerge.first), StackLocation(hero->id, toMerge.second)); //merge toMerge.first into toMerge.second
+				pos = toMerge.first;
+			}
+		}
+		assert(pos.validSlot());
+		assert(hero->slotEmpty(pos) || hero->getCreature(pos) == unit.second->getCreature());
+
+		if (hero->hasStackAtSlot(pos))
+			changeStackCount(StackLocation(hero->id, pos), unit.second->getCount(), ChangeValueMode::RELATIVE);
+		else
+			insertNewStack(StackLocation(hero->id, pos), unit.second->getCreature(), unit.second->getCount());
+	}
+}
+
 void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove)
 {
 	COMPLAIN_RET_IF(!creatures.stacksCount(), "Strange, giveCreatures called without args!");
@@ -1181,8 +1212,8 @@ void CGameHandler::takeCreatures(ObjectInstanceID objid, const std::vector<CStac
 					else
 					{
 						// take part of the stack
-						collected = stackToTake.getCount();
 						changeStackCount(StackLocation(army->id, armySlot.first), collected - stackToTake.getCount(), ChangeValueMode::RELATIVE);
+						collected = stackToTake.getCount();
 					}
 					foundSth = true;
 					break;
@@ -3811,7 +3842,7 @@ bool CGameHandler::changeStackCount(const StackLocation &sl, TQuantity count, Ch
 	auto army = dynamic_cast<const CArmedInstance*>(getObj(sl.army));
 
 	TQuantity currentCount = army->getStackCount(sl.slot);
-	if ((mode == ChangeValueMode::RELATIVE && count < 0)
+	if ((mode == ChangeValueMode::ABSOLUTE && count < 0)
 		|| (mode == ChangeValueMode::RELATIVE && -count > currentCount))
 	{
 		COMPLAIN_RET("Cannot take more stacks than present!");

+ 1 - 0
server/CGameHandler.h

@@ -125,6 +125,7 @@ public:
 	void giveResource(PlayerColor player, GameResID which, int val) override;
 	void giveResources(PlayerColor player, TResources resources) override;
 
+	void giveCreatures(const CGHeroInstance * h, const CCreatureSet &creatures) override;
 	void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override;
 	void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval) override;
 	bool changeStackType(const StackLocation &sl, const CCreature *c) override;

+ 1 - 0
test/mock/mock_IGameCallback.h

@@ -60,6 +60,7 @@ public:
 	void giveResource(PlayerColor player, GameResID which, int val) override {}
 	void giveResources(PlayerColor player, ResourceSet resources) override {}
 
+	void giveCreatures(const CGHeroInstance * h, const CCreatureSet &creatures) override{}
 	void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {}
 	void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval) override {}
 	bool changeStackCount(const StackLocation &sl, TQuantity count, ChangeValueMode mode) override {return false;}