Просмотр исходного кода

Merge remote-tracking branch 'upstream/develop' into cpp-map-editor

nordsoft 3 лет назад
Родитель
Сommit
d5403d3eb9

+ 25 - 3
AI/BattleAI/BattleAI.cpp

@@ -17,6 +17,7 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
+#include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/CStack.h" // TODO: remove
                               // Eventually only IBattleInfoCallback and battle::Unit should be used,
                               // CUnitState should be private and CStack should be removed completely
@@ -728,13 +729,34 @@ void CBattleAI::print(const std::string &text) const
 
 boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 {
-	if(cb->battleCanSurrender(playerID))
+	BattleStateInfoForRetreat bs;
+
+	bs.canFlee = cb->battleCanFlee();
+	bs.canSurrender = cb->battleCanSurrender(playerID);
+	bs.ourSide = cb->battleGetMySide();
+	bs.ourHero = cb->battleGetMyHero(); 
+	bs.enemyHero = nullptr;
+
+	for(auto stack : cb->battleGetAllStacks(false))
 	{
+		if(stack->alive())
+		{
+			if(stack->side == bs.ourSide)
+				bs.ourStacks.push_back(stack);
+			else
+			{
+				bs.enemyStacks.push_back(stack);
+				bs.enemyHero = cb->battleGetOwnerHero(stack);
+			}
+		}
 	}
-	if(cb->battleCanFlee())
+
+	if(!bs.canFlee || !bs.canSurrender)
 	{
+		return boost::none;
 	}
-	return boost::none;
+
+	return cb->makeSurrenderRetreatDecision(bs);
 }
 
 

+ 23 - 30
AI/Nullkiller/AIGateway.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/serializer/BinarySerializer.h"
 #include "../../lib/serializer/BinaryDeserializer.h"
+#include "../../lib/battle/BattleStateInfoForRetreat.h"
 
 #include "AIGateway.h"
 #include "Goals/Goals.h"
@@ -488,6 +489,24 @@ void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositio
 	NET_EVENT_HANDLER;
 }
 
+boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
+	const BattleStateInfoForRetreat & battleState)
+{
+	LOG_TRACE(logAi);
+	NET_EVENT_HANDLER;
+
+	double fightRatio = battleState.getOurStrength() / (double)battleState.getEnemyStrength();
+
+	// if we have no towns - things are already bad, so retreat is not an option.
+	if(cb->getTownsInfo().size() && fightRatio < 0.3 && battleState.canFlee)
+	{
+		return BattleAction::makeRetreat(battleState.ourSide);
+	}
+
+	return boost::none;
+}
+
+
 void AIGateway::init(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB)
 {
 	LOG_TRACE(logAi);
@@ -1017,8 +1036,10 @@ bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const
 	//TODO: make gathering gold, building tavern or conquering town (?) possible subgoals
 	if(!t)
 		t = findTownWithTavern();
-	if(!t)
+
+	if(!t || !townHasFreeTavern(t))
 		return false;
+
 	if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
 		return false;
 	if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
@@ -1383,7 +1404,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 const CGTownInstance * AIGateway::findTownWithTavern() const
 {
 	for(const CGTownInstance * t : cb->getTownsInfo())
-		if(t->hasBuilt(BuildingID::TAVERN) && !t->visitingHero)
+		if(townHasFreeTavern(t))
 			return t;
 
 	return nullptr;
@@ -1414,34 +1435,6 @@ void AIGateway::buildArmyIn(const CGTownInstance * t)
 	moveCreaturesToHero(t);
 }
 
-void AIGateway::recruitHero(const CGTownInstance * t, bool throwing)
-{
-	logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString());
-
-	auto heroes = cb->getAvailableHeroes(t);
-	if(heroes.size())
-	{
-		auto hero = heroes[0];
-		if(heroes.size() >= 2) //makes sense to recruit two heroes with starting amries in first week
-		{
-			if(heroes[1]->getTotalStrength() > hero->getTotalStrength())
-				hero = heroes[1];
-		}
-
-		cb->recruitHero(t, hero);
-		nullkiller->heroManager->update();
-
-		if(t->visitingHero)
-			moveHeroToTile(t->visitablePos(), t->visitingHero.get());
-
-		throw goalFulfilledException(sptr(Goals::RecruitHero(t)));
-	}
-	else if(throwing)
-	{
-		throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
-	}
-}
-
 void AIGateway::finish()
 {
 	//we want to lock to avoid multiple threads from calling makingTurn->join() at same time

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -166,6 +166,7 @@ public:
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
+	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 
 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
 	void battleEnd(const BattleResult * br) override;
@@ -176,7 +177,6 @@ public:
 	void endTurn();
 
 	// TODO: all the routines like recruiting hero or building army should be removed from here and extracted to elementar goals or whatever
-	void recruitHero(const CGTownInstance * t, bool throwing = false);
 	void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter);
 	void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
 	void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);

+ 10 - 0
AI/Nullkiller/AIUtility.cpp

@@ -450,4 +450,14 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	return true;
 }
 
+bool townHasFreeTavern(const CGTownInstance * town)
+{
+	if(!town->hasBuilt(BuildingID::TAVERN)) return false;
+	if(!town->visitingHero) return true;
+
+	bool canMoveVisitingHeroToGarnison = !town->getUpperArmy()->stacksCount();
+
+	return canMoveVisitingHeroToGarnison;
+}
+
 }

+ 1 - 0
AI/Nullkiller/AIUtility.h

@@ -238,6 +238,7 @@ bool isSafeToVisit(HeroPtr h, const CCreatureSet *, uint64_t dangerStrength);
 bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
 bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
 bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
+bool townHasFreeTavern(const CGTownInstance * town);
 
 uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start);
 

+ 31 - 1
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -34,10 +34,40 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
 	Goals::TGoalVec tasks;
 	auto towns = cb->getTownsInfo();
 
+	auto ourHeroes = ai->nullkiller->heroManager->getHeroRoles();
+	auto minScoreToHireMain = std::numeric_limits<float>::max();
+
+	for(auto hero : ourHeroes)
+	{
+		if(hero.second != HeroRole::MAIN)
+			continue;
+
+		auto newScore = ai->nullkiller->heroManager->evaluateHero(hero.first.get());
+
+		if(minScoreToHireMain > newScore)
+		{
+			// weakest main hero score
+			minScoreToHireMain = newScore;
+		}
+	}
+
 	for(auto town : towns)
 	{
-		if(!town->garrisonHero && !town->visitingHero && ai->canRecruitAnyHero(town))
+		if(ai->canRecruitAnyHero(town))
 		{
+			auto availableHeroes = cb->getAvailableHeroes(town);
+
+			for(auto hero : availableHeroes)
+			{
+				auto score = ai->nullkiller->heroManager->evaluateHero(hero);
+
+				if(score > minScoreToHireMain)
+				{
+					tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(200)));
+					break;
+				}
+			}
+
 			if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
 				|| (ai->nullkiller->getFreeResources()[Res::GOLD] > 10000
 					&& ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE))

+ 38 - 7
AI/Nullkiller/Goals/RecruitHero.cpp

@@ -35,16 +35,47 @@ void RecruitHero::accept(AIGateway * ai)
 
 	if(!t) t = ai->findTownWithTavern();
 
-	if(t)
+	if(!t)
 	{
-		ai->recruitHero(t, true);
-		//TODO try to free way to blocked town
-		//TODO: adventure map tavern or prison?
+		throw cannotFulfillGoalException("No town to recruit hero!");
 	}
-	else
+
+	logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString());
+
+	auto heroes = cb->getAvailableHeroes(t);
+
+	if(!heroes.size())
 	{
-		throw cannotFulfillGoalException("No town to recruit hero!");
+		throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
 	}
-}
 
+	auto heroToHire = heroes[0];
+
+	for(auto hero : heroes)
+	{
+		if(objid == hero->id.getNum())
+		{
+			heroToHire = hero;
+			break;
+		}
+
+		if(hero->getTotalStrength() > heroToHire->getTotalStrength())
+			heroToHire = hero;
+	}
+
+	if(t->visitingHero)
+	{
+		cb->swapGarrisonHero(t);
+	}
+
+	if(t->visitingHero)
+		throw cannotFulfillGoalException("Town " + t->nodeName() + " is occupied. Cannot recruit hero!");
+
+	cb->recruitHero(t, heroToHire);
+	ai->nullkiller->heroManager->update();
+
+	if(t->visitingHero)
+		ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get());
 }
+
+}

+ 6 - 0
CCallback.cpp

@@ -387,3 +387,9 @@ bool CBattleCallback::battleMakeTacticAction( BattleAction * action )
 	sendRequest(&ma);
 	return true;
 }
+
+boost::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(
+	const BattleStateInfoForRetreat & battleState)
+{
+	return cl->playerint[getPlayerID().get()]->makeSurrenderRetreatDecision(battleState);
+}

+ 3 - 0
CCallback.h

@@ -32,6 +32,7 @@ struct CPackForServer;
 class IBattleEventsReceiver;
 class IGameEventsReceiver;
 struct ArtifactLocation;
+class BattleStateInfoForRetreat;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -48,6 +49,7 @@ public:
 	//battle
 	virtual int battleMakeAction(const BattleAction * action) = 0;//for casting spells by hero - DO NOT use it for moving active stack
 	virtual bool battleMakeTacticAction(BattleAction * action) = 0; // performs tactic phase actions
+	virtual boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) = 0;
 };
 
 class IGameActionCallback
@@ -104,6 +106,7 @@ public:
 	CBattleCallback(boost::optional<PlayerColor> Player, CClient *C);
 	int battleMakeAction(const BattleAction * action) override;//for casting spells by hero - DO NOT use it for moving active stack
 	bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
+	boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
 
 #if SCRIPTING_ENABLED
 	scripting::Pool * getContextPool() const override;

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -12,6 +12,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/BattleHex.cpp
 		${MAIN_LIB_DIR}/battle/BattleInfo.cpp
 		${MAIN_LIB_DIR}/battle/BattleProxy.cpp
+		${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.cpp
 		${MAIN_LIB_DIR}/battle/CBattleInfoCallback.cpp
 		${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.cpp
 		${MAIN_LIB_DIR}/battle/CCallbackBase.cpp
@@ -241,6 +242,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/battle/BattleAttackInfo.h
 		${MAIN_LIB_DIR}/battle/BattleHex.h
 		${MAIN_LIB_DIR}/battle/BattleInfo.h
+		${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.h
 		${MAIN_LIB_DIR}/battle/BattleProxy.h
 		${MAIN_LIB_DIR}/battle/CBattleInfoCallback.h
 		${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.h

+ 8 - 1
lib/CGameInterface.h

@@ -58,7 +58,9 @@ class CLoadFile;
 class CSaveFile;
 class BinaryDeserializer;
 class BinarySerializer;
+class BattleStateInfo;
 struct ArtifactLocation;
+class BattleStateInfoForRetreat;
 
 #if SCRIPTING_ENABLED
 namespace scripting
@@ -93,7 +95,7 @@ public:
 
 	//pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
 	virtual void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)=0;
-	virtual	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID)=0;
+	virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID)=0;
 
 	// Show a dialog, player must take decision. If selection then he has to choose between one of given components,
 	// if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called
@@ -108,6 +110,11 @@ public:
 
 	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};
 
+	virtual boost::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+	{
+		return boost::none;
+	}
+
 	virtual void saveGame(BinarySerializer & h, const int version) = 0;
 	virtual void loadGame(BinaryDeserializer & h, const int version) = 0;
 };

+ 16 - 0
lib/battle/BattleAction.cpp

@@ -105,6 +105,22 @@ BattleAction BattleAction::makeEndOFTacticPhase(ui8 side)
 	return ba;
 }
 
+BattleAction BattleAction::makeSurrender(ui8 side)
+{
+	BattleAction ba;
+	ba.side = side;
+	ba.actionType = EActionType::SURRENDER;
+	return ba;
+}
+
+BattleAction BattleAction::makeRetreat(ui8 side)
+{
+	BattleAction ba;
+	ba.side = side;
+	ba.actionType = EActionType::RETREAT;
+	return ba;
+}
+
 std::string BattleAction::toString() const
 {
 	std::stringstream actionTypeStream;

+ 2 - 0
lib/battle/BattleAction.h

@@ -40,6 +40,8 @@ public:
 	static BattleAction makeCreatureSpellcast(const battle::Unit * stack, const battle::Target & target, SpellID spellID);
 	static BattleAction makeMove(const battle::Unit * stack, BattleHex dest);
 	static BattleAction makeEndOFTacticPhase(ui8 side);
+	static BattleAction makeRetreat(ui8 side);
+	static BattleAction makeSurrender(ui8 side);
 
 	std::string toString() const;
 

+ 52 - 0
lib/battle/BattleStateInfoForRetreat.cpp

@@ -0,0 +1,52 @@
+/*
+ * BattleStateInfoForRetreat.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "BattleStateInfoForRetreat.h"
+#include "Unit.h"
+#include "CBattleInfoCallback.h"
+#include "../CCreatureSet.h"
+#include "../mapObjects/CGHeroInstance.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+BattleStateInfoForRetreat::BattleStateInfoForRetreat()
+: canFlee(false), canSurrender(false), isLastTurnBeforeDie(false), ourStacks(), enemyStacks(), ourHero(nullptr), enemyHero(nullptr), ourSide(-1)
+{
+}
+
+uint64_t getFightingStrength(std::vector<const battle::Unit *> stacks, const CGHeroInstance * hero = nullptr)
+{
+	uint64_t result = 0;
+
+	for(const battle::Unit * stack : stacks)
+	{
+		result += stack->creatureId().toCreature()->AIValue * stack->getCount();
+	}
+
+	if(hero)
+	{
+		result = (uint64_t)(result * hero->getFightingStrength());
+	}
+
+	return result;
+}
+
+uint64_t BattleStateInfoForRetreat::getOurStrength() const
+{
+	return getFightingStrength(ourStacks, ourHero);
+}
+
+uint64_t BattleStateInfoForRetreat::getEnemyStrength() const
+{
+	return getFightingStrength(enemyStacks, enemyHero);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 38 - 0
lib/battle/BattleStateInfoForRetreat.h

@@ -0,0 +1,38 @@
+/*
+ * BattleStateInfoForRetreat.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace battle
+{
+	class Unit;
+}
+
+class CGHeroInstance;
+
+class DLL_LINKAGE BattleStateInfoForRetreat
+{
+public:
+	bool canFlee;
+	bool canSurrender;
+	bool isLastTurnBeforeDie;
+	ui8 ourSide;
+	std::vector<const battle::Unit *> ourStacks;
+	std::vector<const battle::Unit *> enemyStacks;
+	const CGHeroInstance * ourHero;
+	const CGHeroInstance * enemyHero;
+
+	BattleStateInfoForRetreat();
+	uint64_t getOurStrength() const;
+	uint64_t getEnemyStrength() const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 7 - 9
lib/mapObjects/CObjectClassesHandler.cpp

@@ -116,13 +116,15 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData(size_t dataSize)
 	size_t totalNumber = static_cast<size_t>(parser.readNumber()); // first line contains number of objects to read and nothing else
 	parser.endLine();
 
-	for (size_t i=0; i<totalNumber; i++)
+	for (size_t i = 0; i < totalNumber; i++)
 	{
-		auto templ = new ObjectTemplate;
-		templ->readTxt(parser);
+		auto tmpl = new ObjectTemplate;
+
+		tmpl->readTxt(parser);
 		parser.endLine();
-		std::pair<si32, si32> key(templ->id.num, templ->subid);
-		legacyTemplates.insert(std::make_pair(key, std::shared_ptr<const ObjectTemplate>(templ)));
+
+		std::pair<si32, si32> key(tmpl->id.num, tmpl->subid);
+		legacyTemplates.insert(std::make_pair(key, std::shared_ptr<const ObjectTemplate>(tmpl)));
 	}
 
 	std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
@@ -560,10 +562,6 @@ SObjectSounds AObjectTypeHandler::getSounds() const
 
 void AObjectTypeHandler::addTemplate(std::shared_ptr<const ObjectTemplate> templ)
 {
-	//Otherwise the template remains constant
-	auto ptr = const_cast<ObjectTemplate*>(templ.get());
-	ptr->id = Obj(type);
-	ptr->subid = subtype;
 	templates.push_back(templ);
 }
 

+ 6 - 4
lib/mapObjects/CObjectHandler.cpp

@@ -197,9 +197,6 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 {
 	const TerrainTile &tile = cb->gameState()->map->getTile(visitablePos());
 
-	this->ID = Obj(ID);
-	this->subID = subID;
-
 	//recalculate blockvis tiles - new appearance might have different blockmap than before
 	cb->gameState()->map->removeBlockVisTiles(this, true);
 	auto handler = VLC->objtypeh->getHandlerFor(ID, subID);
@@ -212,11 +209,16 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 		appearance = handler->getTemplates(tile.terType->id)[0];
 	else
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
-	if (ID == Obj::HERO)
+
+	if(this->ID == Obj::PRISON && ID == Obj::HERO)
 	{
 		//adjust for the prison offset
 		pos = visitablePos();
 	}
+
+	this->ID = Obj(ID);
+	this->subID = subID;
+
 	cb->gameState()->map->addBlockVisTiles(this);
 }
 

+ 1 - 9
lib/mapObjects/CommonConstructors.h

@@ -33,19 +33,11 @@ protected:
 		auto obj = new ObjectType();
 		preInitObject(obj);
 
+		//Set custom template or leave null
 		if (tmpl)
 		{
 			obj->appearance = tmpl;
 		}
-		else
-		{
-			auto templates = getTemplates();
-			if (templates.empty())
-			{
-				throw std::runtime_error("No handler for created object");
-			}
-			obj->appearance = templates.front(); //just any template for now, will be initialized later
-		}
 
 		return obj;
 	}

+ 1 - 0
lib/rmg/ObjectManager.cpp

@@ -490,6 +490,7 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
 	
 	auto & instance = object.addInstance(*guard);
 	instance.setPosition(guardPos - object.getPosition());
+	instance.setAnyTemplate(); //terrain is irrelevant for monsters, but monsters need some template now
 		
 	return true;
 }

+ 31 - 21
lib/rmg/RmgObject.cpp

@@ -105,20 +105,26 @@ void Object::Instance::setPositionRaw(const int3 & position)
 	dObject.pos = dPosition + dParent.getPosition();
 }
 
-void Object::Instance::setTemplate(const TerrainId & terrain)
+void Object::Instance::setAnyTemplate()
 {
-	if(dObject.appearance->id == Obj::NO_OBJ)
+	auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates();
+	if(templates.empty())
+		throw rmgException(boost::to_string(boost::format("Did not find any graphics for object (%d,%d)") % dObject.ID % dObject.subID));
+
+	dObject.appearance = templates.front();
+	dAccessibleAreaCache.clear();
+	setPosition(getPosition(false));
+}
+
+void Object::Instance::setTemplate(TerrainId terrain)
+{
+	auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
+	if (templates.empty())
 	{
-		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
 		auto terrainName = VLC->terrainTypeHandler->terrains()[terrain].name;
-		if (templates.empty())
-		{
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") %
-				dObject.ID % dObject.subID % terrainName));
-		}
-		
-		dObject.appearance = templates.front();
+		throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName));
 	}
+	dObject.appearance = templates.front();
 	dAccessibleAreaCache.clear();
 	setPosition(getPosition(false));
 }
@@ -283,6 +289,21 @@ void Object::Instance::finalize(RmgMap & map)
 	if(!map.isOnMap(getPosition(true)))
 		throw rmgException(boost::to_string(boost::format("Position of object %d at %s is outside the map") % dObject.id % getPosition(true).toString()));
 	
+	//If no specific template was defined for this object, select any matching
+	if (!dObject.appearance)
+	{
+		auto terrainType = map.map().getTile(getPosition(true)).terType;
+		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->id);
+		if (templates.empty())
+		{
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType));
+		}
+		else
+		{
+			setTemplate(terrainType->id);
+		}
+	}
+
 	if (dObject.isVisitable() && !map.isOnMap(dObject.visitablePos()))
 		throw rmgException(boost::to_string(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.pos.toString()));
 	
@@ -292,17 +313,6 @@ void Object::Instance::finalize(RmgMap & map)
 			throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString()));
 	}
 	
-	if (dObject.appearance->id == Obj::NO_OBJ)
-	{
-		auto terrainType = map.map().getTile(getPosition(true)).terType;
-		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->id);
-		if (templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") %
-				dObject.ID % dObject.subID % getPosition(true).toString() % terrainType->name));
-		
-		setTemplate(terrainType->id);
-	}
-	
 	for(auto & tile : getBlockedArea().getTilesVector())
 	{
 		map.setOccupied(tile, ETileType::ETileType::USED);

+ 2 - 1
lib/rmg/RmgObject.h

@@ -35,7 +35,8 @@ public:
 		int3 getVisitablePosition() const;
 		bool isVisitableFrom(const int3 & tile) const;
 		const Area & getAccessibleArea() const;
-		void setTemplate(const TerrainId & terrain); //cache invalidation
+		void setTemplate(TerrainId terrain); //cache invalidation
+		void setAnyTemplate(); //cache invalidation
 		
 		int3 getPosition(bool isAbsolute = false) const;
 		void setPosition(const int3 & position); //cache invalidation

+ 6 - 45
lib/serializer/BinaryDeserializer.h

@@ -391,56 +391,17 @@ public:
 		else
 			data.reset();
 	}
+
 	template <typename T>
-	void load(std::shared_ptr<const T> &data) //version of the above for const ptr
+	void load(std::shared_ptr<const T> & data)
 	{
-		typedef typename std::remove_const<T>::type NonConstT;
-		NonConstT *internalPtr;
-		load(internalPtr);
+		std::shared_ptr<T> nonConstData;
 
-		void *internalPtrDerived = typeList.castToMostDerived(internalPtr);
+		load(nonConstData);
 
-		if(internalPtr)
-		{
-			auto itr = loadedSharedPointers.find(internalPtrDerived);
-			if(itr != loadedSharedPointers.end())
-			{
-				// This pointer is already loaded. The "data" needs to be pointed to it,
-				// so their shared state is actually shared.
-				try
-				{
-					auto actualType = typeList.getTypeInfo(internalPtr);
-					auto typeWeNeedToReturn = typeList.getTypeInfo<T>();
-					if(*actualType == *typeWeNeedToReturn)
-					{
-						// No casting needed, just unpack already stored shared_ptr and return it
-						data = boost::any_cast<std::shared_ptr<const T>>(itr->second);
-					}
-					else
-					{
-						// We need to perform series of casts
-						auto ret = typeList.castShared(itr->second, actualType, typeWeNeedToReturn);
-						data = boost::any_cast<std::shared_ptr<const T>>(ret);
-					}
-				}
-				catch(std::exception &e)
-				{
-					logGlobal->error(e.what());
-					logGlobal->error("Failed to cast stored shared ptr. Real type: %s. Needed type %s. FIXME FIXME FIXME", itr->second.type().name(), typeid(std::shared_ptr<T>).name());
-					//TODO scenario with inheritance -> we can have stored ptr to base and load ptr to derived (or vice versa)
-					throw;
-				}
-			}
-			else
-			{
-				auto hlp = std::shared_ptr<const T>(internalPtr);
-				data = hlp; //possibly adds const
-				loadedSharedPointers[internalPtrDerived] = typeList.castSharedToMostDerived(hlp);
-			}
-		}
-		else
-			data.reset();
+		data = nonConstData;
 	}
+
 	template <typename T>
 	void load(std::unique_ptr<T> &data)
 	{