Explorar el Código

Large rewrite of adventure map objects:
- replaced CDefObjInfo with ObjectTemplate class
- ObjectTempate is a direct member of objects instead of pointer with
shared ownership across CMap, handler and game objects
- simplified handling of objects that can change appearance (e.g. towns)
- all object queries regarding object appearance/blockmaps use h3m pos
instead of relative positions
- removed need of modhandler::reload
- cleanup of some old code

Ivan Savenko hace 11 años
padre
commit
2c4c964a45

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -706,7 +706,7 @@ void VCAI::makeTurnInternal()
 			boost::sort (vec, isCloser);
 			for (auto obj : vec)
 			{
-				if(!obj || !obj->defInfo || !cb->getObj(obj->id))
+				if(!obj || !cb->getObj(obj->id))
 				{
 					logAi->errorStream() << "Error: there is wrong object on list for hero " << hero.first->name;
 					continue;

+ 0 - 5
client/CMT.cpp

@@ -954,11 +954,6 @@ void endGame()
 {
 	client->endGame();
 	vstd::clear_pointer(client);
-
-	delete CGI->dobjinfo.get();
-	const_cast<CGameInfo*>(CGI)->dobjinfo = new CDefObjInfoHandler;
-
-	const_cast<CGameInfo*>(CGI)->modh->reload(); //add info about new creatures to dobjinfo
 }
 
 void handleQuit()

+ 3 - 3
client/Graphics.cpp

@@ -335,12 +335,12 @@ void Graphics::loadFonts()
 
 CDefEssential * Graphics::getDef( const CGObjectInstance * obj )
 {
-	return advmapobjGraphics[obj->defInfo->name];
+	return advmapobjGraphics[obj->appearance.animationFile];
 }
 
-CDefEssential * Graphics::getDef( const CGDefInfo * info )
+CDefEssential * Graphics::getDef( const ObjectTemplate & info )
 {
-	return advmapobjGraphics[info->name];
+	return advmapobjGraphics[info.animationFile];
 }
 
 void Graphics::loadErmuToPicture()

+ 2 - 2
client/Graphics.h

@@ -25,7 +25,7 @@ struct SDL_Color;
 struct InfoAboutHero;
 struct InfoAboutTown;
 class CGObjectInstance;
-class CGDefInfo;
+class ObjectTemplate;
 
 enum EFonts
 {
@@ -60,7 +60,7 @@ public:
 
 	std::map<std::string, CDefEssential *> advmapobjGraphics;
 	CDefEssential * getDef(const CGObjectInstance * obj);
-	CDefEssential * getDef(const CGDefInfo * info);
+	CDefEssential * getDef(const ObjectTemplate & info);
 	//towns
 	std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
 	//for battles

+ 4 - 12
client/NetPacksClient.cpp

@@ -410,14 +410,8 @@ void NewStructures::applyCl( CClient *cl )
 	CGTownInstance *town = GS(cl)->getTown(tid);
 	for(const auto & id : bid)
 	{
-		if(id == BuildingID::CAPITOL) //fort or capitol
-		{
-			town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->capitols.at(town->subID).get());
-		}
-		if(id == BuildingID::FORT)
-		{
-			town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->gobjs.at(Obj::TOWN).at(town->subID).get());
-		}
+		town->updateAppearance();
+
 		if(vstd::contains(cl->playerint,town->tempOwner))
 			cl->playerint[town->tempOwner]->buildChanged(town,id,1);
 	}
@@ -427,10 +421,8 @@ void RazeStructures::applyCl (CClient *cl)
 	CGTownInstance *town = GS(cl)->getTown(tid);
 	for(const auto & id : bid)
 	{
-		if (id == BuildingID::CAPITOL) //fort or capitol
-		{
-			town->defInfo = const_cast<CGDefInfo*>(CGI->dobjinfo->gobjs.at(Obj::TOWN).at(town->subID).get());
-		}
+		town->updateAppearance();
+
 		if(vstd::contains (cl->playerint,town->tempOwner))
 			cl->playerint[town->tempOwner]->buildChanged (town,id,2);
 	}

+ 61 - 58
client/mapHandler.cpp

@@ -1,6 +1,7 @@
 #include "StdInc.h"
 #include "mapHandler.h"
 
+#include "CBitmapHandler.h"
 #include "gui/SDL_Extensions.h"
 #include "CGameInfo.h"
 #include "../lib/CDefObjInfoHandler.h"
@@ -229,26 +230,26 @@ void CMapHandler::borderAndTerrainBitmapInit()
 	delete bord;
 }
 
-static void processDef (const CGDefInfo* def)
+static void processDef (const ObjectTemplate & objTempl)
 {
-	if(def->id == Obj::EVENT)
+	if(objTempl.id == Obj::EVENT)
 	{
-		graphics->advmapobjGraphics[def->name] = nullptr;
+		graphics->advmapobjGraphics[objTempl.animationFile] = nullptr;
 		return;
 	}
-	CDefEssential * ourDef = graphics->getDef(def);
+	CDefEssential * ourDef = graphics->getDef(objTempl);
 	if(!ourDef) //if object has already set handler (eg. heroes) it should not be overwritten
 	{
-		if(def->name.size())
+		if(objTempl.animationFile.size())
 		{
-			graphics->advmapobjGraphics[def->name] = CDefHandler::giveDefEss(def->name);
+			graphics->advmapobjGraphics[objTempl.animationFile] = CDefHandler::giveDefEss(objTempl.animationFile);
 		}
 		else
 		{
-            logGlobal->warnStream() << "No def name for " << def->id << "  " << def->subid;
+			logGlobal->warnStream() << "No def name for " << objTempl.id << "  " << objTempl.subid;
 			return;
 		}
-		ourDef = graphics->getDef(def);
+		ourDef = graphics->getDef(objTempl);
 
 	}
 	//alpha transformation
@@ -266,44 +267,40 @@ void CMapHandler::initObjectRects()
 		const CGObjectInstance *obj = elem;
 		if(	!obj
 			|| (obj->ID==Obj::HERO && static_cast<const CGHeroInstance*>(obj)->inTownGarrison) //garrisoned hero
-			|| (obj->ID==Obj::BOAT && static_cast<const CGBoat*>(obj)->hero) //boat with hero (hero graphics is used)
-			|| !obj->defInfo )
+			|| (obj->ID==Obj::BOAT && static_cast<const CGBoat*>(obj)->hero)) //boat with hero (hero graphics is used)
 		{
 			continue;
 		}
 		if (!graphics->getDef(obj)) //try to load it
-			processDef(obj->defInfo);
+			processDef(obj->appearance);
 		if (!graphics->getDef(obj)) // stil no graphics? exit
 			continue;
 
 		const SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap;
-		for(int fx=0; fx<bitmap->w>>5; ++fx) //bitmap->w/32
+		for(int fx=0; fx < obj->getWidth(); ++fx)
 		{
-			for(int fy=0; fy<bitmap->h>>5; ++fy) //bitmap->h/32
+			for(int fy=0; fy < obj->getHeight(); ++fy)
 			{
+				int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
 				SDL_Rect cr;
 				cr.w = 32;
 				cr.h = 32;
-				cr.x = fx<<5; //fx*32
-				cr.y = fy<<5; //fy*32
+				cr.x = bitmap->w - fx * 32 - 32;
+				cr.y = bitmap->h - fy * 32 - 32;
 				std::pair<const CGObjectInstance*,SDL_Rect> toAdd = std::make_pair(obj,cr);
 				
-				if(    (obj->pos.x + fx - bitmap->w/32+1)  >=  0 
-					&& (obj->pos.x + fx - bitmap->w/32+1)  <  ttiles.size() - frameW 
-					&& (obj->pos.y + fy - bitmap->h/32+1)  >=  0 
-					&& (obj->pos.y + fy - bitmap->h/32+1)  <  ttiles[0].size() - frameH
+
+				if( map->isInTheMap(currTile) && // within map
+					cr.x + cr.w > 0 &&           // image has data on this tile
+					cr.y + cr.h > 0 &&
+					obj->coveringAt(currTile.x, currTile.y) // object is visible here
 				  )
 				{
-					//TerrainTile2 & curt =
-					//	ttiles
-					//	[obj->pos.x + fx - bitmap->w/32]
-					//[obj->pos.y + fy - bitmap->h/32]
-					//[obj->pos.z];
-					ttiles[obj->pos.x + fx - bitmap->w/32+1][obj->pos.y + fy - bitmap->h/32+1][obj->pos.z].objects.push_back(toAdd);
+					ttiles[currTile.x][currTile.y][currTile.z].objects.push_back(toAdd);
 				}
-			} // for(int fy=0; fy<bitmap->h/32; ++fy)
-		} //for(int fx=0; fx<bitmap->w/32; ++fx)
-	} // for(int f=0; f<map->objects.size(); ++f)
+			}
+		}
+	}
 
 	for(int ix=0; ix<ttiles.size()-frameW; ++ix)
 	{
@@ -319,7 +316,7 @@ void CMapHandler::initObjectRects()
 
 void CMapHandler::initHeroDef(const CGHeroInstance * h)
 {
-	graphics->advmapobjGraphics[h->defInfo->name] = graphics->flags1[0];
+	graphics->advmapobjGraphics[h->appearance.animationFile] = graphics->flags1[0];
 }
 
 void CMapHandler::init()
@@ -367,9 +364,6 @@ void CMapHandler::init()
 		}
 	}
 
-    std::for_each(map->customDefs.begin(),map->customDefs.end(),processDef); //load h3m defs
-    logGlobal->infoStream()<<"\tUnpacking and handling defs: "<<th.getDiff();
-
 	prepareFOWDefs();
 	roadsRiverTerrainInit();	//road's and river's DefHandlers; and simple values initialization
 	borderAndTerrainBitmapInit();
@@ -510,12 +504,16 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 			{
 				const CGObjectInstance *obj = object.first;
 				if (!graphics->getDef(obj))
-					processDef(obj->defInfo);
+					processDef(obj->appearance);
+				if (!graphics->getDef(obj) && !obj->appearance.animationFile.empty())
+				{
+					logGlobal->errorStream() << "Failed to load image " << obj->appearance.animationFile;
+				}
 
 				PlayerColor color = obj->tempOwner;
 
 				//checking if object has non-empty graphic on this tile
-				if(obj->ID != Obj::HERO && !obj->coveringAt(top_tile.x + bx - obj->pos.x, top_tile.y + by - obj->pos.y))
+				if(obj->ID != Obj::HERO && !obj->coveringAt(top_tile.x + bx, top_tile.y + by))
 					continue;
 
 				static const int notBlittedInPuzzleMode[] = {124};
@@ -640,10 +638,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 					if(color < PlayerColor::PLAYER_LIMIT || color==PlayerColor::NEUTRAL)
 						CSDL_Ext::setPlayerColor(bitmap, color);
 
-					if( obj->hasShadowAt(top_tile.x + bx - obj->pos.x, top_tile.y + by - obj->pos.y) )
-						CSDL_Ext::blit8bppAlphaTo24bpp(bitmap,&pp,extSurf,&sr2);
-					else
-						CSDL_Ext::blitSurface(bitmap,&pp,extSurf,&sr2);
+					CSDL_Ext::blit8bppAlphaTo24bpp(bitmap,&pp,extSurf,&sr2);
 				}
 			}
 			//objects blitted
@@ -708,33 +703,41 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 				
 				//FoW blitted
 
-				// TODO: these should be activable by the console
-#ifdef MARK_BLOCKED_POSITIONS
-				if(map->getTile(int3(pos.x, pos.y, top_tile.z)).blocked) //temporary hiding blocked positions
+				SDL_Rect tileRect = genRect(sr.h, sr.w, 0, 0);
+
+				if (settings["session"]["showBlock"].Bool())
 				{
-					SDL_Rect sr;
+					if(map->getTile(int3(pos.x, pos.y, top_tile.z)).blocked) //temporary hiding blocked positions
+					{
+						static SDL_Surface * block = nullptr;
+						if (!block)
+							block = BitmapHandler::loadBitmap("blocked");
+
+						SDL_Rect sr;
 
-					sr.x=srx;
-					sr.y=sry;
-					sr.h=sr.w=32;
+						sr.x=srx;
+						sr.y=sry;
+						sr.h=sr.w=32;
 
-					memset(rSurf->pixels, 128, rSurf->pitch * rSurf->h);
-					CSDL_Ext::blitSurface(rSurf,&genRect(sr.h, sr.w, 0, 0),extSurf,&sr);
+						CSDL_Ext::blitSurface(block, &tileRect, extSurf, &sr);
+					}
 				}
-#endif
-#ifdef MARK_VISITABLE_POSITIONS
-				if(map->getTile(int3(pos.x, pos.y, top_tile.z)).visitable) //temporary hiding visitable positions
+				if (settings["session"]["showVisit"].Bool())
 				{
-					SDL_Rect sr;
+					if(map->getTile(int3(pos.x, pos.y, top_tile.z)).visitable) //temporary hiding visitable positions
+					{
+						static SDL_Surface * visit = nullptr;
+						if (!visit)
+							visit = BitmapHandler::loadBitmap("visitable");
 
-					sr.x=srx;
-					sr.y=sry;
-					sr.h=sr.w=32;
+						SDL_Rect sr;
 
-					memset(rSurf->pixels, 128, rSurf->pitch * rSurf->h);
-					CSDL_Ext::blitSurface(rSurf,&genRect(sr.h, sr.w, 0, 0),extSurf,&sr);
+						sr.x=srx;
+						sr.y=sry;
+						sr.h=sr.w=32;
+						CSDL_Ext::blitSurface(visit, &tileRect, extSurf, &sr);
+					}
 				}
-#endif
 			}
 		}
 	}
@@ -849,7 +852,7 @@ std::pair<SDL_Surface *, bool> CMapHandler::getVisBitmap( const int3 & pos, cons
 bool CMapHandler::printObject(const CGObjectInstance *obj)
 {
     if (!graphics->getDef(obj))
-		processDef(obj->defInfo);
+		processDef(obj->appearance);
 
 	const SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap; 
 	const int tilesW = bitmap->w/32;

+ 30 - 14
lib/CArtHandler.cpp

@@ -657,6 +657,22 @@ void CArtHandler::afterLoadFinalization()
 			bonus->sid = art->id;
 		}
 	}
+
+	//Note: "10" is used here because H3 text files don't define any template for art with ID 0
+	ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::ARTIFACT, 10).front();
+	for (CArtifact * art : artifacts)
+	{
+		if (!art->advMapDef.empty())
+		{
+			base.animationFile = art->advMapDef;
+			base.subid = art->id;
+
+			// replace existing (if any) and add new template.
+			// Necessary for objects added via mods that don't have any templates in H3
+			VLC->dobjinfo->eraseAll(Obj::ARTIFACT, art->id);
+			VLC->dobjinfo->registerTemplate(base);
+		}
+	}
 }
 
 CArtifactInstance::CArtifactInstance()
@@ -695,13 +711,13 @@ void CArtifactInstance::init()
 	id = static_cast<ArtifactInstanceID>(ArtifactID::NONE); //to be randomized
 	setNodeType(ARTIFACT_INSTANCE);
 }
-
-ArtifactPosition CArtifactInstance::firstAvailableSlot(const CArtifactSet *h) const
-{
-	for(auto slot : artType->possibleSlots.at(h->bearerType()))
-	{
-		if(canBePutAt(h, slot)) //if(artType->fitsAt(h->artifWorn, slot))
-		{
+
+ArtifactPosition CArtifactInstance::firstAvailableSlot(const CArtifactSet *h) const
+{
+	for(auto slot : artType->possibleSlots.at(h->bearerType()))
+	{
+		if(canBePutAt(h, slot)) //if(artType->fitsAt(h->artifWorn, slot))
+		{
 			//we've found a free suitable slot.
 			return slot;
 		}
@@ -1101,13 +1117,13 @@ bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/) const
 	return getArtPos(aid, onlyWorn) != ArtifactPosition::PRE_FIRST;
 }
 
-const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const
-{
-	if(vstd::contains(artifactsWorn, pos))
-		return &artifactsWorn.at(pos);
-	if(pos >= ArtifactPosition::AFTER_LAST )
-	{
-		int backpackPos = (int)pos - GameConstants::BACKPACK_START;
+const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const
+{
+	if(vstd::contains(artifactsWorn, pos))
+		return &artifactsWorn.at(pos);
+	if(pos >= ArtifactPosition::AFTER_LAST )
+	{
+		int backpackPos = (int)pos - GameConstants::BACKPACK_START;
 		if(backpackPos < 0 || backpackPos >= artifactsInBackpack.size())
 			return nullptr;
 		else

+ 18 - 0
lib/CCreatureHandler.cpp

@@ -1112,6 +1112,24 @@ void CCreatureHandler::buildBonusTreeForTiers()
 		b.attachTo(&allCreatures);
 }
 
+void CCreatureHandler::afterLoadFinalization()
+{
+	ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::MONSTER, 0).front();
+	for (CCreature * crea : creatures)
+	{
+		if (!crea->advMapDef.empty())
+		{
+			base.animationFile = crea->advMapDef;
+			base.subid = crea->idNumber;
+
+			// replace existing (if any) and add new template.
+			// Necessary for objects added via mods that don't have any templates in H3
+			VLC->dobjinfo->eraseAll(Obj::MONSTER, crea->idNumber);
+			VLC->dobjinfo->registerTemplate(base);
+		}
+	}
+}
+
 void CCreatureHandler::deserializationFix()
 {
 	buildBonusTreeForTiers();

+ 2 - 0
lib/CCreatureHandler.h

@@ -198,6 +198,8 @@ public:
 	/// generates tier-specific bonus tree entries
 	void buildBonusTreeForTiers();
 
+	void afterLoadFinalization();
+
 	std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
 
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;

+ 342 - 114
lib/CDefObjInfoHandler.cpp

@@ -2,9 +2,14 @@
 #include "CDefObjInfoHandler.h"
 
 #include "filesystem/Filesystem.h"
-#include "../client/CGameInfo.h"
+#include "filesystem/CBinaryReader.h"
+//#include "../client/CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "GameConstants.h"
+#include "StringConstants.h"
+#include "CGeneralTextHandler.h"
+#include "CModHandler.h"
+#include "JsonNode.h"
 
 /*
  * CDefObjInfoHandler.cpp, part of VCMI engine
@@ -16,164 +21,387 @@
  *
  */
 
-bool CGDefInfo::isVisitable() const
+static bool isVisitableFromTop(int identifier, int type)
 {
-	for (auto & elem : visitMap)
-	{
-		if (elem)
-			return true;
-	}
+	if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource
+		return true;
+
+	static const Obj visitableFromTop[] =
+		{Obj::FLOTSAM,
+		Obj::SEA_CHEST,
+		Obj::SHIPWRECK_SURVIVOR,
+		Obj::BUOY,
+		Obj::OCEAN_BOTTLE,
+		Obj::BOAT,
+		Obj::WHIRLPOOL,
+		Obj::GARRISON,
+		Obj::SCHOLAR,
+		Obj::CAMPFIRE,
+		Obj::BORDERGUARD,
+		Obj::BORDER_GATE,
+		Obj::QUEST_GUARD,
+		Obj::CORPSE
+	};
+	if (vstd::find_pos(visitableFromTop, identifier) != -1)
+		return true;
 	return false;
 }
-CGDefInfo::CGDefInfo()
+
+ObjectTemplate::ObjectTemplate():
+	visitDir(8|16|32|64|128), // all but top
+	id(Obj::NO_OBJ),
+	subid(0),
+	printPriority(0)
+{
+}
+
+void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
 {
-	visitDir = (8|16|32|64|128); //4,5,6,7,8 - any not-from-up direction
+	std::string data = parser.readString();
+	std::vector<std::string> strings;
+	boost::split(strings, data, boost::is_any_of(" "));
+	assert(strings.size() == 9);
+
+	animationFile = strings[0];
+
+	std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked
+	std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable
+
+	assert(blockStr.size() == 6*8);
+	assert(visitStr.size() == 6*8);
+
+	setSize(8, 6);
+	for (size_t i=0; i<6; i++) // 6 rows
+	{
+		for (size_t j=0; j<8; j++) // 8 columns
+		{
+			auto & tile = usedTiles[i][j];
+			tile |= VISIBLE; // assume that all tiles are visible
+			if (blockStr[i*8 + j] == '0')
+				tile |= BLOCKED;
+
+			if (visitStr[i*8 + j] == '1')
+				tile |= VISITABLE;
+		}
+	}
+
+	// strings[3] most likely - terrains on which this object can be placed in editor.
+	// e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx
+	// so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains
+	std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain
+
+	assert(terrStr.size() == 9); // all terrains but rock
+	for (size_t i=0; i<9; i++)
+	{
+		if (terrStr[8-i] == '1')
+			allowedTerrains.insert(ETerrainType(i));
+	}
+
+	id    = Obj(boost::lexical_cast<int>(strings[5]));
+	subid = boost::lexical_cast<int>(strings[6]);
+	int type  = boost::lexical_cast<int>(strings[7]);
+	printPriority = boost::lexical_cast<int>(strings[8]) * 100; // to have some space in future
+
+	if (isVisitableFromTop(id, type))
+		visitDir = 0xff;
+	else
+		visitDir = (8|16|32|64|128);
 
-	width = height = -1;
+	readMsk();
 }
 
-void CGDefInfo::fetchInfoFromMSK()
+void ObjectTemplate::readMsk()
 {
-	ResourceID resID("SPRITES/" + name, EResType::MASK);
+	ResourceID resID("SPRITES/" + animationFile, EResType::MASK);
 
 	if (CResourceHandler::get()->existsResource(resID))
 	{
 		auto msk = CResourceHandler::get()->load(resID)->readAll();
+		setSize(msk.first.get()[0], msk.first.get()[1]);
+	}
+	else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system
+	{
+		setSize(8, 6);
+	}
+}
+
+void ObjectTemplate::readMap(CBinaryReader & reader)
+{
+	animationFile = reader.readString();
+
+	setSize(8, 6);
+	ui8 blockMask[6];
+	ui8 visitMask[6];
+	for(auto & byte : blockMask)
+		byte = reader.readUInt8();
+	for(auto & byte : visitMask)
+		byte = reader.readUInt8();
 
-		width = msk.first.get()[0];
-		height = msk.first.get()[1];
-		for(int i=0; i<6; ++i)
+	for (size_t i=0; i<6; i++) // 6 rows
+	{
+		for (size_t j=0; j<8; j++) // 8 columns
 		{
-			coverageMap[i] = msk.first.get()[i+2];
-			shadowCoverage[i] = msk.first.get()[i+8];
+			auto & tile = usedTiles[5 - i][7 - j];
+			tile |= VISIBLE; // assume that all tiles are visible
+			if (((blockMask[i] >> j) & 1 ) == 0)
+				tile |= BLOCKED;
+
+			if (((visitMask[i] >> j) & 1 ) != 0)
+				tile |= VISITABLE;
 		}
 	}
+
+	reader.readUInt16();
+	ui16 terrMask = reader.readUInt16();
+	for (size_t i=0; i<9; i++)
+	{
+		if (((terrMask >> i) & 1 ) != 0)
+			allowedTerrains.insert(ETerrainType(i));
+	}
+
+	id = Obj(reader.readUInt32());
+	subid = reader.readUInt32();
+	int type = reader.readUInt8();
+	printPriority = reader.readUInt8() * 100; // to have some space in future
+
+	if (isVisitableFromTop(id, type))
+		visitDir = 0xff;
 	else
+		visitDir = (8|16|32|64|128);
+
+	reader.skip(16);
+	readMsk();
+
+	if (id == Obj::EVENT)
 	{
-		//maximum possible size of H3 object
-		//TODO: remove hardcode and move this data into modding system
-		width = 8;
-		height = 6;
+		setSize(1,1);
+		usedTiles[0][0] = VISITABLE;
 	}
 }
 
-CDefObjInfoHandler::CDefObjInfoHandler()
+void ObjectTemplate::readJson(const JsonNode &node)
 {
-	VLC->dobjinfo = this;
+	id = Obj(node["basebase"].Float()); // temporary, should be replaced
+	subid = node["base"].Float();
+	animationFile = node["animation"].String();
 
-	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/OBJECTS.TXT"))->readAll();
-	std::istringstream inp(std::string((char*)textFile.first.get(), textFile.second));
-	int objNumber;
-	inp>>objNumber;
-	std::string mapStr;
-	for(int hh=0; hh<objNumber; ++hh)
+	const JsonVector & visitDirs = node["visitableFrom"].Vector();
+	if (!visitDirs.empty())
 	{
-		auto  nobj = new CGDefInfo();
-		std::string dump;
-		inp>>nobj->name;
+		if (visitDirs[0].String()[0] == '+') visitDir |= 1;
+		if (visitDirs[0].String()[1] == '+') visitDir |= 2;
+		if (visitDirs[0].String()[2] == '+') visitDir |= 4;
+		if (visitDirs[1].String()[2] == '+') visitDir |= 8;
+		if (visitDirs[2].String()[2] == '+') visitDir |= 16;
+		if (visitDirs[2].String()[1] == '+') visitDir |= 32;
+		if (visitDirs[2].String()[0] == '+') visitDir |= 64;
+		if (visitDirs[1].String()[0] == '+') visitDir |= 128;
+	}
+	else
+		visitDir = 0xff;
 
-		std::transform(nobj->name.begin(), nobj->name.end(), nobj->name.begin(), (int(*)(int))toupper);
+	for (auto & entry : node["allowedTerrains"].Vector())
+		allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String())));
 
-		for(int o=0; o<6; ++o)
+	auto charToTile = [&](const char & ch) -> ui8
+	{
+		switch (ch)
 		{
-			nobj->blockMap[o] = 0xff;
-			nobj->visitMap[o] = 0x00;
-			nobj->coverageMap[o] = 0x00;
-			nobj->shadowCoverage[o] = 0x00;
+			case '0' : return 0;
+			case 'V' : return VISIBLE;
+			case 'B' : return VISIBLE | BLOCKED;
+			case 'H' : return BLOCKED;
+			case 'A' : return VISIBLE | BLOCKED | VISITABLE;
+			case 'T' : return VISIBLE | VISITABLE;
+			default:
+				logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask";
+				return 0;
 		}
-		inp>>mapStr;
-		std::reverse(mapStr.begin(), mapStr.end());
-		for(int v=0; v<mapStr.size(); ++v)
-		{
-			if(mapStr[v]=='0')
-			{
-				nobj->blockMap[v/8] &= 255 - (128 >> (v%8));
-			}
-		}
-		inp>>mapStr;
-		std::reverse(mapStr.begin(), mapStr.end());
-		for(int v=0; v<mapStr.size(); ++v)
+	};
+
+	size_t maxHeight = 0, maxWidth = 0;
+	size_t width  = node["mask"].Vector()[0].String().size();
+	size_t height = node["mask"].Vector().size();
+	setSize(width, height);
+
+	for (size_t i=0; i<height; i++)
+	{
+		const std::string & line = node["mask"].Vector()[i].String();
+		assert(line.size() == width);
+		for (size_t j=0; j < width; j++)
 		{
-			if(mapStr[v]=='1')
+			ui8 tile = charToTile(line[j]);
+			if (tile != 0)
 			{
-				nobj->visitMap[v/8] |= (128 >> (v%8));
+				vstd::amax(maxHeight, i);
+				vstd::amax(maxWidth,  j);
+				usedTiles[i][j] = tile;
 			}
 		}
+	}
+	setSize(maxWidth, maxHeight);
 
-		for(int yy=0; yy<2; ++yy) //first - on which types of terrain object can be placed;
-			inp>>dump; //second -in which terrains' menus object in the editor will be available (?)
-		si32 id; inp >> id; nobj->id = Obj(id);
-		inp>>nobj->subid;
-		inp>>nobj->type;
+	printPriority = node["zIndex"].Float();
+}
 
-		nobj->visitDir = (8|16|32|64|128); //disabled visiting from the top
+ui32 ObjectTemplate::getWidth() const
+{
+	return usedTiles.empty() ? 0 : usedTiles.front().size();
+}
 
-		if(nobj->type == 2 || nobj->type == 3 || nobj->type == 4 || nobj->type == 5) //creature, hero, artifact, resource
-		{
-			nobj->visitDir = 0xff;
-		}
-		else
-		{
-			static const Obj visitableFromTop[] =
-				{Obj::FLOTSAM,
-				Obj::SEA_CHEST,
-				Obj::SHIPWRECK_SURVIVOR,
-				Obj::BUOY,
-				Obj::OCEAN_BOTTLE,
-				Obj::BOAT,
-				Obj::WHIRLPOOL,
-				Obj::GARRISON,
-				Obj::SCHOLAR,
-				Obj::CAMPFIRE,
-				Obj::BORDERGUARD,
-				Obj::BORDER_GATE,
-				Obj::QUEST_GUARD,
-				Obj::CORPSE};
-
-			for(auto & elem : visitableFromTop)
-			{
-				if(elem == nobj->id)
-				{
-					nobj->visitDir = 0xff;
-					break;
-				}
-			}
-		}
-		inp >> nobj->printPriority;
+ui32 ObjectTemplate::getHeight() const
+{
+	return usedTiles.size();
+}
 
-		//coverageMap calculating
-		nobj->fetchInfoFromMSK();
+void ObjectTemplate::setSize(ui32 width, ui32 height)
+{
+	usedTiles.resize(height);
+	for (auto & line : usedTiles)
+		line.resize(width);
+}
 
-		auto dest = nobj->id.toDefObjInfo();
-		if (dest.find(nobj->subid) != dest.end() && dest[nobj->subid] != nullptr)
-		{
-			// there is just too many of these. Note that this data is almost unused
-			// exceptions are: town(village-capitol) and creation of new objects (holes, creatures, heroes, etc)
-			//logGlobal->warnStream() << "Warning: overwriting def info for " << dest[nobj->subid]->name << " with " << nobj->name;
-			dest[nobj->subid].dellNull(); // do not leak
-		}
+bool ObjectTemplate::isVisitable() const
+{
+	for (auto & line : usedTiles)
+		for (auto & tile : line)
+			if (tile & VISITABLE)
+				return true;
+	return false;
+}
 
-		nobj->id.toDefObjInfo()[nobj->subid] = nobj;
+bool ObjectTemplate::isWithin(si32 X, si32 Y) const
+{
+	if (X < 0 || Y < 0)
+		return false;
+	if (X >= getWidth() || Y >= getHeight())
+		return false;
+	return true;
+}
 
-	}
+bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const
+{
+	if (isWithin(X, Y))
+		return usedTiles[Y][X] & VISITABLE;
+	return false;
+}
+
+bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const
+{
+	if (isWithin(X, Y))
+		return usedTiles[Y][X] & VISIBLE;
+	return false;
+}
+
+bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const
+{
+	if (isWithin(X, Y))
+		return usedTiles[Y][X] & BLOCKED;
+	return false;
+}
 
-	for (int i = 0; i < 8 ; i++)
+bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const
+{
+	// visitDir uses format
+	// 1 2 3
+	// 8   4
+	// 7 6 5
+	int dirMap[3][3] =
 	{
+		{ visitDir &   1, visitDir &   2, visitDir &   4 },
+		{ visitDir & 128,        1      , visitDir &   8 },
+		{ visitDir &  64, visitDir &  32, visitDir &  16 }
+	};
+	// map input values to range 0..2
+	int dx = X < 0 ? 0 : X == 0 ? 1 : 2;
+	int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2;
 
-		static const char *holeDefs[] = {"AVLHOLD0.DEF", "AVLHLDS0.DEF", "AVLHOLG0.DEF", "AVLHLSN0.DEF",
-			"AVLHOLS0.DEF", "AVLHOLR0.DEF", "AVLHOLX0.DEF", "AVLHOLL0.DEF"};
+	return dirMap[dy][dx] != 0;
+}
 
-		if(i)
-		{
-			gobjs[Obj::HOLE][i] = new CGDefInfo(*gobjs[Obj::HOLE][0]);
-		}
-		gobjs[Obj::HOLE][i]->name = holeDefs[i];
+bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const
+{
+	return allowedTerrains.count(terrain) != 0;
+}
+
+void CDefObjInfoHandler::readTextFile(std::string path)
+{
+	CLegacyConfigParser parser(path);
+	size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else
+	parser.endLine();
+
+	for (size_t i=0; i<totalNumber; i++)
+	{
+		ObjectTemplate templ;
+		templ.readTxt(parser);
+		parser.endLine();
+		objects.push_back(templ);
 	}
 }
 
-CDefObjInfoHandler::~CDefObjInfoHandler()
+CDefObjInfoHandler::CDefObjInfoHandler()
+{
+	readTextFile("Data/Objects.txt");
+	readTextFile("Data/Heroes.txt");
+/*
+	const JsonNode node = JsonUtils::assembleFromFiles("config/objectTemplates.json");
+	std::vector<ObjectTemplate> newTemplates;
+	newTemplates.reserve(node.Struct().size());
+
+	// load all new templates
+	for (auto & entry : node.Struct())
+	{
+		ObjectTemplate templ;
+		templ.readJson(entry.second);
+		newTemplates.push_back(templ);
+	}
+
+	// erase old ones to avoid conflicts
+	for (auto & entry : newTemplates)
+		eraseAll(entry.id, entry.subid);
+
+	// merge new templates into storage
+	objects.insert(objects.end(), newTemplates.begin(), newTemplates.end());
+*/
+}
+
+void CDefObjInfoHandler::eraseAll(Obj type, si32 subtype)
 {
-	for(auto & elem : gobjs)
-		for(auto j=elem.second.begin(); j!=elem.second.end(); j++)
-			j->second.dellNull();
+	auto it = std::remove_if(objects.begin(), objects.end(), [&](const ObjectTemplate & obj)
+	{
+		return obj.id == type && obj.subid == subtype;
+	});
+	objects.erase(it, objects.end());
+}
+
+void CDefObjInfoHandler::registerTemplate(ObjectTemplate obj)
+{
+	objects.push_back(obj);
+}
+
+std::vector<ObjectTemplate> CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype) const
+{
+	std::vector<ObjectTemplate> ret;
+
+	std::copy_if(objects.begin(), objects.end(), std::back_inserter(ret), [&](const ObjectTemplate & obj)
+	{
+		return obj.id == type && obj.subid == subtype;
+	});
+	if (ret.empty())
+		logGlobal->errorStream() << "Failed to find template for " << type << ":" << subtype;
+	assert(!ret.empty()); // Can't create object of this type/subtype
+	return ret;
+}
+
+std::vector<ObjectTemplate> CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const
+{
+	std::vector<ObjectTemplate> ret = pickCandidates(type, subtype);
+	std::vector<ObjectTemplate> filtered;
+
+	std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj)
+	{
+		return obj.canBePlacedAt(terrain);
+	});
+	// it is possible that there are no templates usable on specific terrain. In this case - return list before filtering
+	return filtered.empty() ? ret : filtered;
 }

+ 79 - 35
lib/CDefObjInfoHandler.h

@@ -13,52 +13,96 @@
  *
  */
 
-class CDefEssential;
-class DLL_LINKAGE CGDefInfo
+class CBinaryReader;
+class CLegacyConfigParser;
+class JsonNode;
+
+class DLL_LINKAGE ObjectTemplate
 {
-public:
-	std::string name;
+	enum EBlockMapBits
+	{
+		VISIBLE = 1,
+		VISITABLE = 2,
+		BLOCKED = 4
+	};
 
-	ui8 visitMap[6];
-	ui8 blockMap[6];
-	ui8 coverageMap[6], shadowCoverage[6]; //to determine which tiles are covered by picture of this object
-	ui8 visitDir; //directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7)
+	/// tiles that are covered by this object, uses EBlockMapBits enum as flags
+	std::vector<std::vector<ui8>> usedTiles;
+	/// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7)
+	ui8 visitDir;
+	/// list of terrains on which this object can be placed
+	std::set<ETerrainType> allowedTerrains;
+
+public:
+	/// H3 ID/subID of this object
 	Obj id;
-	si32 subid; //of object described by this defInfo
-	si32 terrainAllowed, //on which terrain it is possible to place object
-		 terrainMenu; //in which menus in map editor object will be showed
-	si32 width, height; //tiles
-	si32 type; //(0- ground, 1- towns, 2-creatures, 3- heroes, 4-artifacts, 5- resources)   
+	si32 subid;
+	/// print priority, objects with higher priority will be print first, below everything else
 	si32 printPriority;
+	/// animation file that should be used to display object
+	std::string animationFile;
+
+	ui32 getWidth() const;
+	ui32 getHeight() const;
+	void setSize(ui32 width, ui32 height);
+
 	bool isVisitable() const;
-	bool operator<(const CGDefInfo& por) const
-	{
-		if(id!=por.id)
-			return id<por.id;
-		else
-			return subid<por.subid;
-	}
+
+	// Checks object used tiles
+	// Position is relative to bottom-right corner of the object, can not be negative
+	bool isWithin(si32 X, si32 Y) const;
+	bool isVisitableAt(si32 X, si32 Y) const;
+	bool isVisibleAt(si32 X, si32 Y) const;
+	bool isBlockedAt(si32 X, si32 Y) const;
+
+	// Checks if object is visitable from certain direction. X and Y must be between -1..+1
+	bool isVisitableFrom(si8 X, si8 Y) const;
+
+	// Checks if object can be placed on specific terrain
+	bool canBePlacedAt(ETerrainType terrain) const;
+
+	ObjectTemplate();
+
+	void readTxt(CLegacyConfigParser & parser);
+	void readMsk();
+	void readMap(CBinaryReader & reader);
+	void readJson(const JsonNode & node);
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & name & visitMap & blockMap & visitDir & id & subid &terrainAllowed
-			& terrainMenu & width & height & type & printPriority & coverageMap & shadowCoverage;
+		h & usedTiles & allowedTerrains & animationFile;
+		h & id & subid & printPriority & visitDir;
 	}
-	CGDefInfo();
-void fetchInfoFromMSK();
 };
-class DLL_LINKAGE CDefObjInfoHandler
-{
-public:
-	std::map<int, std::map<int, ConstTransitivePtr<CGDefInfo> > > gobjs;
-
-	std::map<TFaction, ConstTransitivePtr<CGDefInfo> > capitols;
-	std::map<TFaction, ConstTransitivePtr<CGDefInfo> > villages;
-
-	CDefObjInfoHandler();
-	~CDefObjInfoHandler();
+
+class DLL_LINKAGE CDefObjInfoHandler
+{
+	/// list of all object templates loaded from text files
+	/// actual object have ObjectTemplate as member "appearance"
+	std::vector<ObjectTemplate> objects;
+
+	/// reads one of H3 text files that contain object templates description
+	void readTextFile(std::string path);
+public:
+
+	CDefObjInfoHandler();
+
+	/// Erases all templates with given type/subtype
+	void eraseAll(Obj type, si32 subtype);
+
+	/// Add new template into the list
+	void registerTemplate(ObjectTemplate obj);
+
+	/// picks all possible candidates for specific pair <type, subtype>
+	std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype) const;
+	/// picks all candidates for <type, subtype> and of possible - also filters them by terrain
+	std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const;
+
+	// TODO: as above, but also filters out templates that are not applicable according to accepted test
+	// std::vector<ObjectTemplate> pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function<bool(ObjectTemplate &)> accept) const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & gobjs & capitols & villages;
+		h & objects;
 	}
 };

+ 32 - 77
lib/CGameState.cpp

@@ -343,36 +343,23 @@ static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor
 	switch(id)
 	{
 	case Obj::HERO:
-		{
-			auto  nobj = new CGHeroInstance();
-			nobj->pos = pos;
-			nobj->tempOwner = owner;
-			nobj->subID = subid;
-			//nobj->initHero(ran);
-			return nobj;
-		}
+		nobj = new CGHeroInstance();
+		nobj->appearance = VLC->dobjinfo->pickCandidates(id, VLC->heroh->heroes[subid]->heroClass->id).front();
+		break;
 	case Obj::TOWN:
 		nobj = new CGTownInstance;
 		break;
 	default: //rest of objects
 		nobj = new CGObjectInstance;
-		nobj->defInfo = id.toDefObjInfo()[subid];
 		break;
 	}
 	nobj->ID = id;
 	nobj->subID = subid;
-	if(!nobj->defInfo)
-        logGlobal->warnStream() <<"No def declaration for " <<id <<" "<<subid;
 	nobj->pos = pos;
-	//nobj->state = nullptr;//new CLuaObjectScript();
 	nobj->tempOwner = owner;
-	nobj->defInfo->id = id;
-	nobj->defInfo->subid = subid;
+	if (id != Obj::HERO)
+		nobj->appearance = VLC->dobjinfo->pickCandidates(id, subid).front();
 
-	//assigning defhandler
-	if(nobj->ID==Obj::HERO || nobj->ID==Obj::TOWN)
-		return nobj;
-	nobj->defInfo = id.toDefObjInfo()[subid];
 	return nobj;
 }
 
@@ -641,14 +628,11 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
 	{
 		if(cur->ID==Obj::TOWN) //town - set def
 		{
+			const TerrainTile &tile = map->getTile(cur->pos);
 			CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
 			t->town = VLC->townh->factions[t->subID]->town;
-			if(t->hasCapitol())
-				t->defInfo = VLC->dobjinfo->capitols[t->subID];
-			else if(t->hasFort())
-				t->defInfo = VLC->dobjinfo->gobjs[Obj::TOWN][t->subID];
-			else
-				t->defInfo = VLC->dobjinfo->villages[t->subID];
+			t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN, t->subID, tile.terType).front();
+			t->updateAppearance();
 		}
 		return;
 	}
@@ -666,33 +650,33 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
 	}
 	else if(ran.first==Obj::TOWN)//special code for town
 	{
+		const TerrainTile &tile = map->getTile(cur->pos);
 		CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
         if(!t) {logGlobal->warnStream()<<"Wrong random town at "<<cur->pos; return;}
 		cur->ID = ran.first;
 		cur->subID = ran.second;
 		//FIXME: copy-pasted from above
 		t->town = VLC->townh->factions[t->subID]->town;
-		if(t->hasCapitol())
-			t->defInfo = VLC->dobjinfo->capitols[t->subID];
-		else if(t->hasFort())
-			t->defInfo = VLC->dobjinfo->gobjs[Obj::TOWN][t->subID];
-		else
-			t->defInfo = VLC->dobjinfo->villages[t->subID];
+		t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN,t->subID, tile.terType).front();
+		t->updateAppearance();
+
 		t->randomizeArmy(t->subID);
 		map->towns.push_back(t);
 		return;
 	}
+	else
+	{
+		if (ran.first  != cur->appearance.id ||
+			ran.second != cur->appearance.subid)
+		{
+			const TerrainTile &tile = map->getTile(cur->pos);
+			cur->appearance = VLC->dobjinfo->pickCandidates(Obj(ran.first),ran.second, tile.terType).front();
+		}
+	}
 	//we have to replace normal random object
 	cur->ID = ran.first;
 	cur->subID = ran.second;
-	map->removeBlockVisTiles(cur); //recalculate blockvis tiles - picked object might have different than random placeholder
-	map->customDefs.push_back(cur->defInfo = ran.first.toDefObjInfo()[ran.second]);
-	if(!cur->defInfo)
-	{
-        logGlobal->errorStream()<<"*BIG* WARNING: Missing def declaration for "<<cur->ID<<" "<<cur->subID;
-		return;
-	}
-
+	map->removeBlockVisTiles(cur, true); //recalculate blockvis tiles - picked object might have different than random placeholder
 	map->addBlockVisTiles(cur);
 }
 
@@ -1189,7 +1173,6 @@ void CGameState::placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeI
 	townPos.x += 1;
 
 	CGHeroInstance * hero =  static_cast<CGHeroInstance*>(createObject(Obj::HERO, heroTypeId.getNum(), townPos, playerColor));
-	hero->initHeroDefInfo();
 	map->getEditManager()->insertObject(hero, townPos);
 }
 
@@ -2205,14 +2188,15 @@ bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional<PlayerC
 	if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed?
 		return false;
 	//object is visible when at least one blocked tile is visible
-	for(int fx=0; fx<8; ++fx)
+	for(int fy=0; fy < obj->getHeight(); ++fy)
 	{
-		for(int fy=0; fy<6; ++fy)
+		for(int fx=0; fx < obj->getWidth(); ++fx)
 		{
-			int3 pos = obj->pos + int3(fx-7,fy-5,0);
-			if(map->isInTheMap(pos)
-				&& !((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1)
-				&& isVisible(pos, *player)  )
+			int3 pos = obj->pos + int3(-fx, -fy, 0);
+
+			if ( map->isInTheMap(pos) &&
+				 obj->coveringAt(pos.x, pos.y) &&
+				 isVisible(pos, *player))
 				return true;
 		}
 	}
@@ -2232,39 +2216,10 @@ bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom,
 		if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore
 			continue;
 
-		CGDefInfo * di = pom->visitableObjects[b]->defInfo;
-		if( (dst.x == src.x-1 && dst.y == src.y-1) && !(di->visitDir & (1<<4)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x && dst.y == src.y-1) && !(di->visitDir & (1<<5)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x+1 && dst.y == src.y-1) && !(di->visitDir & (1<<6)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x+1 && dst.y == src.y) && !(di->visitDir & (1<<7)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x+1 && dst.y == src.y+1) && !(di->visitDir & (1<<0)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x && dst.y == src.y+1) && !(di->visitDir & (1<<1)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x-1 && dst.y == src.y+1) && !(di->visitDir & (1<<2)) )
-		{
-			return false;
-		}
-		if( (dst.x == src.x-1 && dst.y == src.y) && !(di->visitDir & (1<<3)) )
-		{
+		const CGObjectInstance * obj = pom->visitableObjects[b];
+
+		if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y))
 			return false;
-		}
 	}
 	return true;
 }

+ 12 - 0
lib/CHeroHandler.cpp

@@ -223,6 +223,18 @@ void CHeroClassHandler::afterLoadFinalization()
 			heroClass->selectionProbability[faction->index] = static_cast<int>(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it
 		}
 	}
+
+	ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::HERO, 0).front();
+	for (CHeroClass * hc : heroClasses)
+	{
+		base.animationFile = hc->imageMapMale;
+		base.subid = hc->id;
+
+		// replace existing (if any) and add new template.
+		// Necessary for objects added via mods that don't have any templates in H3
+		VLC->dobjinfo->eraseAll(Obj::HERO, hc->id);
+		VLC->dobjinfo->registerTemplate(base);
+	}
 }
 
 std::vector<bool> CHeroClassHandler::getDefaultAllowed() const

+ 0 - 87
lib/CModHandler.cpp

@@ -789,91 +789,4 @@ void CModHandler::afterLoad()
 
 	std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
 	file << modSettings;
-	reload();
-}
-
-void CModHandler::reload()
-{
-	{
-		//recreate adventure map defs
-		assert(!VLC->dobjinfo->gobjs[Obj::MONSTER].empty()); //make sure that at least some def info was found
-
-		const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::MONSTER].begin()->second;
-
-		for(auto & crea : VLC->creh->creatures)
-		{
-			if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::MONSTER], crea->idNumber)) // no obj info for this type
-			{
-				auto  info = new CGDefInfo(*baseInfo);
-				info->subid = crea->idNumber;
-				info->name = crea->advMapDef;
-
-				VLC->dobjinfo->gobjs[Obj::MONSTER][crea->idNumber] = info;
-			}
-		}
-	}
-	{
-		assert(!VLC->dobjinfo->gobjs[Obj::ARTIFACT].empty());
-
-		const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::ARTIFACT].begin()->second;
-
-		for(auto & art : VLC->arth->artifacts)
-		{
-			if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::ARTIFACT], art->id)) // no obj info for this type
-			{
-				auto  info = new CGDefInfo(*baseInfo);
-				info->subid = art->id;
-				info->name = art->advMapDef;
-
-				VLC->dobjinfo->gobjs[Obj::ARTIFACT][art->id] = info;
-			}
-		}
-	}
-
-	{
-		assert(!VLC->dobjinfo->gobjs[Obj::TOWN].empty()); //make sure that at least some def info was found
-
-		const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::TOWN].begin()->second;
-		auto & townInfos = VLC->dobjinfo->gobjs[Obj::TOWN];
-
-		for(auto & faction : VLC->townh->factions)
-		{
-			TFaction index = faction->index;
-			CTown * town = faction->town;
-			if (town)
-			{
-				auto & cientInfo = town->clientInfo;
-
-				if (!vstd::contains(VLC->dobjinfo->gobjs[Obj::TOWN], index)) // no obj info for this type
-				{
-					auto  info = new CGDefInfo(*baseInfo);
-					info->subid = index;
-
-					townInfos[index] = info;
-				}
-				townInfos[index]->name = cientInfo.advMapCastle;
-
-				VLC->dobjinfo->villages[index] = new CGDefInfo(*townInfos[index]);
-				VLC->dobjinfo->villages[index]->name = cientInfo.advMapVillage;
-
-				VLC->dobjinfo->capitols[index] = new CGDefInfo(*townInfos[index]);
-				VLC->dobjinfo->capitols[index]->name = cientInfo.advMapCapitol;
-
-				for (int i = 0; i < town->dwellings.size(); ++i)
-				{
-					const CGDefInfo * baseInfo = VLC->dobjinfo->gobjs[Obj::CREATURE_GENERATOR1][i]; //get same blockmap as first dwelling of tier i
-
-					for (auto cre : town->creatures[i]) //both unupgraded and upgraded get same dwelling
-					{
-						auto  info = new CGDefInfo(*baseInfo);
-						info->subid = cre;
-						info->name = town->dwellings[i];
-						VLC->dobjinfo->gobjs[Obj::CREATURE_GENERATOR1][cre] = info;
-
-						VLC->objh->cregens[cre] = cre; //map of dwelling -> creature id
-					}
-				}
-			}
-		}
-	}
 }

+ 0 - 4
lib/CModHandler.h

@@ -213,10 +213,6 @@ public:
 	void load();
 	void afterLoad();
 
-	/// actions that should be triggered on map restart
-	/// TODO: merge into appropriate handlers?
-	void reload();
-
 	struct DLL_LINKAGE hardcodedFeatures
 	{
 		JsonNode data;

+ 38 - 99
lib/CObjectHandler.cpp

@@ -33,11 +33,6 @@
 
 using namespace boost::assign;
 
-// It looks that we can't rely on shadowCoverage correctness (Mantis #866). This may result
-// in notable performance decrease (SDL blit with custom alpha blit) not notable on my system (Ivan)
-#define USE_COVERAGE_MAP 0
-
-
 std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > CGTeleport::objs;
 std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > CGTeleport::gates;
 IGameCallback * IObjectInterface::cb = nullptr;
@@ -298,7 +293,6 @@ CGObjectInstance::CGObjectInstance():
 	pos(-1,-1,-1),
 	ID(Obj::NO_OBJ),
 	subID(-1),
-	defInfo(nullptr),
 	tempOwner(PlayerColor::UNFLAGGABLE),
 	blockVisit(false)
 {
@@ -323,62 +317,24 @@ void CGObjectInstance::setOwner(PlayerColor ow)
 }
 int CGObjectInstance::getWidth() const//returns width of object graphic in tiles
 {
-	return defInfo->width;
+	return appearance.getWidth();
 }
 int CGObjectInstance::getHeight() const //returns height of object graphic in tiles
 {
-	return defInfo->height;
+	return appearance.getHeight();
 }
 bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles)
 {
-	if(defInfo==nullptr)
-	{
-        logGlobal->warnStream() << "Warning: VisitableAt for obj "<< id.getNum() <<": nullptr defInfo!";
-		return false;
-	}
-
-	if((defInfo->visitMap[y] >> (7-x) ) & 1)
-	{
-		return true;
-	}
-
-	return false;
+	return appearance.isVisitableAt(pos.x - x, pos.y - y);
 }
 bool CGObjectInstance::blockingAt(int x, int y) const
 {
-	if(x<0 || y<0 || x>=getWidth() || y>=getHeight() || defInfo==nullptr)
-		return false;
-	if((defInfo->blockMap[y+6-getHeight()] >> (7-(8-getWidth()+x) )) & 1)
-		return false;
-	return true;
+	return appearance.isBlockedAt(pos.x - x, pos.y - y);
 }
 
 bool CGObjectInstance::coveringAt(int x, int y) const
 {
-	//input coordinates are always negative
-	x = -x;
-	y = -y;
-#if USE_COVERAGE_MAP
-	//NOTE: this code may be broken
-	if((defInfo->coverageMap[y] >> (7-(x) )) & 1
-		||  (defInfo->shadowCoverage[y] >> (7-(x) )) & 1)
-		return true;
-	return false;
-#else
-	return x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
-#endif
-}
-
-bool CGObjectInstance::hasShadowAt( int x, int y ) const
-{
-#if USE_COVERAGE_MAP
-	//NOTE: this code may be broken
-	if( (defInfo->shadowCoverage[y] >> (7-(x) )) & 1 )
-		return true;
-	return false;
-#else
-	return coveringAt(x,y);// ignore unreliable shadowCoverage map
-#endif
+	return appearance.isVisibleAt(pos.x - x, pos.y - y);
 }
 
 std::set<int3> CGObjectInstance::getBlockedPos() const
@@ -388,8 +344,8 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
 	{
 		for(int h=0; h<getHeight(); ++h)
 		{
-			if(blockingAt(w, h))
-				ret.insert(int3(pos.x - getWidth() + w + 1, pos.y - getHeight() + h + 1, pos.z));
+			if (appearance.isBlockedAt(-w, -h))
+				ret.insert(int3(pos.x - w, pos.y - h, pos.z));
 		}
 	}
 	return ret;
@@ -397,21 +353,20 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
 
 bool CGObjectInstance::operator<(const CGObjectInstance & cmp) const  //screen printing priority comparing
 {
-	if(defInfo->printPriority==1 && cmp.defInfo->printPriority==0)
-		return true;
-	if(cmp.defInfo->printPriority==1 && defInfo->printPriority==0)
-		return false;
-	if(this->pos.y<cmp.pos.y)
-		return true;
-	if(this->pos.y>cmp.pos.y)
-		return false;
+	if (appearance.printPriority != cmp.appearance.printPriority)
+		return appearance.printPriority > cmp.appearance.printPriority;
+
+	if(pos.y != cmp.pos.y)
+		return pos.y < cmp.pos.y;
+
 	if(cmp.ID==Obj::HERO && ID!=Obj::HERO)
 		return true;
 	if(cmp.ID!=Obj::HERO && ID==Obj::HERO)
 		return false;
-	if(!defInfo->isVisitable() && cmp.defInfo->isVisitable())
+
+	if(!isVisitable() && cmp.isVisitable())
 		return true;
-	if(!cmp.defInfo->isVisitable() && defInfo->isVisitable())
+	if(!cmp.isVisitable() && isVisitable())
 		return false;
 	if(this->pos.x<cmp.pos.x)
 		return true;
@@ -490,13 +445,13 @@ void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const
 }
 int3 CGObjectInstance::getVisitableOffset() const
 {
-	for(int y = 0; y < 6; y++)
-		for (int x = 0; x < 8; x++)
-			if((defInfo->visitMap[5-y] >> x) & 1)
+	for(int y = 0; y < appearance.getHeight(); y++)
+		for (int x = 0; x < appearance.getWidth(); x++)
+			if (appearance.isVisitableAt(x, y))
 				return int3(x,y,0);
 
     logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!";
-	return int3(-1,-1,-1);
+	return int3(0,0,0);
 }
 
 void CGObjectInstance::getNameVis( std::string &hname ) const
@@ -556,14 +511,7 @@ int3 CGObjectInstance::visitablePos() const
 
 bool CGObjectInstance::isVisitable() const
 {
-	for(int g=0; g<ARRAY_COUNT(defInfo->visitMap); ++g)
-	{
-		if(defInfo->visitMap[g] != 0)
-		{
-			return true;
-		}
-	}
-	return false;
+	return appearance.isVisitable();
 }
 
 bool CGObjectInstance::passableFor(PlayerColor color) const
@@ -766,11 +714,12 @@ void CGHeroInstance::initHero(HeroTypeID SUBID)
 void CGHeroInstance::initHero()
 {
 	assert(validTypes(true));
-	if(ID == Obj::HERO)
-		initHeroDefInfo();
 	if(!type)
 		type = VLC->heroh->heroes[subID];
 
+	if (ID == Obj::HERO)
+		appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, type->heroClass->id).front();
+
 	if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell
 	{
 		for(auto spellID : type->spells)
@@ -888,27 +837,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
 			dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
 	}
 }
-void CGHeroInstance::initHeroDefInfo()
-{
-	if(!defInfo  ||  defInfo->id != Obj::HERO)
-	{
-		defInfo = new CGDefInfo();
-		defInfo->id = Obj::HERO;
-		defInfo->subid = subID;
-		defInfo->printPriority = 0;
-		defInfo->visitDir = 0xff;
-	}
-	for(int i=0;i<6;i++)
-	{
-		defInfo->blockMap[i] = 255;
-		defInfo->visitMap[i] = 0;
-		defInfo->coverageMap[i] = 0;
-		defInfo->shadowCoverage[i] = 0;
-	}
-	defInfo->blockMap[5] = 253;
-	defInfo->visitMap[5] = 2;
-	defInfo->coverageMap[4] = defInfo->coverageMap[5] = 224;
-}
+
 CGHeroInstance::~CGHeroInstance()
 {
 	commander.dellNull();
@@ -1780,8 +1709,8 @@ void CGDwelling::initObj()
 			if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround
 			{
 				auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames;
-				assert (!dwellingNames.empty());
-				hoverName = dwellingNames[VLC->creh->creatures[subID]->level - 1];
+				assert (dwellingNames.size() > crs->level - 1);
+				hoverName = dwellingNames[crs->level - 1];
 			}
 			else
 				hoverName = VLC->generaltexth->creGens[subID];
@@ -2537,6 +2466,16 @@ std::vector<int> CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode
 		return IMarket::availableItemsIds(mode);
 }
 
+void CGTownInstance::updateAppearance()
+{
+	if (!hasFort())
+		appearance.animationFile = town->clientInfo.advMapVillage;
+	else if(hasCapitol())
+		appearance.animationFile = town->clientInfo.advMapCapitol;
+	else
+		appearance.animationFile = town->clientInfo.advMapCastle;
+}
+
 std::string CGTownInstance::nodeName() const
 {
 	return "Town (" + (town ? town->faction->name : "unknown") + ") of " +  name;
@@ -5378,7 +5317,7 @@ std::vector<int3> CGMagicSpring::getVisitableOffsets() const
 
 	for(int y = 0; y < 6; y++)
 		for (int x = 0; x < 8; x++) //starting from left
-			if((defInfo->visitMap[5-y] >> x) & 1)
+			if (appearance.isVisitableAt(x, y))
 				visitableTiles.push_back (int3(x, y , 0));
 
 	return visitableTiles;

+ 7 - 8
lib/CObjectHandler.h

@@ -2,6 +2,7 @@
 
 #include "../lib/CCreatureSet.h"
 #include "../lib/CTownHandler.h"
+#include "../lib/CDefObjInfoHandler.h"
 #include "CArtHandler.h"
 #include "../lib/ConstTransitivePtr.h"
 #include "int3.h"
@@ -36,7 +37,6 @@ class CSpell;
 class CGTownInstance;
 class CGTownBuilding;
 class CArtifact;
-class CGDefInfo;
 class CSpecObjInfo;
 class CCastleEvent;
 struct TerrainTile;
@@ -180,7 +180,7 @@ public:
 	Obj ID;
 	si32 subID; //normal subID (this one from OH3 maps ;])
 	ObjectInstanceID id;//number of object in map's vector
-	CGDefInfo * defInfo;
+	ObjectTemplate appearance;
 
 	PlayerColor tempOwner;
 	bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile
@@ -194,12 +194,11 @@ public:
 	void setOwner(PlayerColor ow);
 	int getWidth() const; //returns width of object graphic in tiles
 	int getHeight() const; //returns height of object graphic in tiles
-	virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles)
+	virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos)
 	virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos)
 	int3 visitablePos() const;
-	bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) form left top tile of image (x, y in tiles)
-	bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) form left top tile of maximal possible image (8 x 6 tiles) (x, y in tiles)
-	bool hasShadowAt(int x, int y) const;//returns true if object covers with shadow location (x, y) form left top tile of maximal possible image (8 x 6 tiles) (x, y in tiles)
+	bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos)
+	bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos)
 	std::set<int3> getBlockedPos() const; //returns set of positions blocked by this object
 	bool isVisitable() const; //returns true if object is visitable
 	bool operator<(const CGObjectInstance & cmp) const;  //screen printing priority comparing
@@ -220,7 +219,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & defInfo;
+		h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance;
 		//definfo is handled by map serializer
 	}
 protected:
@@ -446,7 +445,6 @@ public:
 	void initExp();
 	void initArmy(IArmyDescriptor *dst = nullptr);
 	//void giveArtifact (ui32 aid);
-	void initHeroDefInfo();
 	void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
 	ui8 maxlevelsToMagicSchool() const;
 	ui8 maxlevelsToWisdom() const;
@@ -688,6 +686,7 @@ public:
 	bool allowsTrade(EMarketMode::EMarketMode mode) const;
 	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
 
+	void updateAppearance();
 
 	//////////////////////////////////////////////////////////////////////////
 

+ 34 - 0
lib/CTownHandler.cpp

@@ -11,6 +11,8 @@
 #include "CArtHandler.h"
 #include "CSpellHandler.h"
 #include "filesystem/Filesystem.h"
+#include "CDefObjInfoHandler.h"
+#include "CObjectHandler.h"
 
 /*
  * CTownHandler.cpp, part of VCMI engine
@@ -709,6 +711,38 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 void CTownHandler::afterLoadFinalization()
 {
 	initializeRequirements();
+	ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::TOWN, 0).front();
+	for (CFaction * fact : factions)
+	{
+		if (fact->town)
+		{
+			base.animationFile = fact->town->clientInfo.advMapCastle;
+			base.subid = fact->index;
+
+			// replace existing (if any) and add new template.
+			// Necessary for objects added via mods that don't have any templates in H3
+			VLC->dobjinfo->eraseAll(Obj::TOWN, fact->index);
+			VLC->dobjinfo->registerTemplate(base);
+
+			assert(fact->town->dwellings.size() == fact->town->dwellingNames.size());
+			for (size_t i=0; i<fact->town->dwellings.size(); i++)
+			{
+				ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::CREATURE_GENERATOR1, 0).front();
+
+				//both unupgraded and upgraded get same dwelling
+				 for (auto cre : fact->town->creatures[i])
+				 {
+					base.subid = 80 + cre;
+					base.animationFile = fact->town->dwellings[i];
+					if (VLC->objh->cregens.count(cre) == 0)
+					{
+						VLC->dobjinfo->registerTemplate(base);
+						VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id
+					}
+				 }
+			}
+		}
+	}
 }
 
 void CTownHandler::initializeRequirements()

+ 1 - 7
lib/GameConstants.cpp

@@ -73,13 +73,7 @@ ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
 
 ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
 
-ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
-
-
-std::map<int, ConstTransitivePtr<CGDefInfo> > & Obj::toDefObjInfo() const
-{
-	return VLC->dobjinfo->gobjs[*this];
-}
+ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
 
 CArtifact * ArtifactID::toArtifact() const
 {

+ 0 - 3
lib/GameConstants.h

@@ -55,7 +55,6 @@ namespace GameConstants
 
 class CArtifact;
 class CArtifactInstance;
-class CGDefInfo;
 class CCreature;
 class CSpell;
 class CGameInfoCallback;
@@ -598,8 +597,6 @@ public:
 
 	ID_LIKE_CLASS_COMMON(Obj, EObj)
 
-	std::map<int, ConstTransitivePtr<CGDefInfo> > & toDefObjInfo() const;
-
 	EObj num;
 };
 

+ 3 - 21
lib/NetPacksLib.cpp

@@ -311,10 +311,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 	CGObjectInstance *obj = gs->getObjInstance(id);
 	logGlobal->debugStream() << "removing object id=" << id << "; address=" << (intptr_t)obj << "; name=" << obj->getHoverText();
 	//unblock tiles
-	if(obj->defInfo)
-	{
-		gs->map->removeBlockVisTiles(obj);
-	}
+	gs->map->removeBlockVisTiles(obj);
 
 	if(obj->ID==Obj::HERO)
 	{
@@ -534,7 +531,6 @@ DLL_LINKAGE void HeroRecruited::applyGs( CGameState *gs )
 	else
 		gs->map->objects[h->id.getNum()] = h;
 
-	h->initHeroDefInfo();
 	gs->map->heroesOnMap.push_back(h);
 	p->heroes.push_back(h);
 	h->attachTo(p);
@@ -558,7 +554,6 @@ DLL_LINKAGE void GiveHero::applyGs( CGameState *gs )
 	gs->map->removeBlockVisTiles(h,true);
 	h->setOwner(player);
 	h->movement =  h->maxMovePoints(true);
-	h->initHeroDefInfo();
 	gs->map->heroesOnMap.push_back(h);
 	gs->getPlayer(h->getOwner())->heroes.push_back(h);
 	gs->map->addBlockVisTiles(h);
@@ -594,27 +589,14 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
 	o->ID = ID;
 	o->subID = subID;
 	o->pos = pos;
-	o->defInfo = VLC->dobjinfo->gobjs[ID][subID];
+	const TerrainTile &t = gs->map->getTile(pos);
+	o->appearance = VLC->dobjinfo->pickCandidates(o->ID, o->subID, t.terType).front();
 	id = o->id = ObjectInstanceID(gs->map->objects.size());
 	o->hoverName = VLC->generaltexth->names[ID];
 
-	switch(ID)
-	{
-		case Obj::MONSTER:
-			o->defInfo = VLC->dobjinfo->gobjs[ID][subID];
-			assert(o->defInfo);
-			break;
-		case Obj::HOLE:
-			const TerrainTile &t = gs->map->getTile(pos);
-			o->defInfo = VLC->dobjinfo->gobjs[ID][t.terType];
-			assert(o->defInfo);
-		break;
-	}
-
 	gs->map->objects.push_back(o);
 	gs->map->addBlockVisTiles(o);
 	o->initObj();
-	assert(o->defInfo);
 
 	logGlobal->debugStream() << "added object id=" << id << "; address=" << (intptr_t)o << "; name=" << o->getHoverText();
 }

+ 12 - 12
lib/mapping/CMap.cpp

@@ -215,22 +215,22 @@ CMap::~CMap()
 
 void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 {
-	for(int fx=0; fx<8; ++fx)
+	for(int fx=0; fx<obj->getWidth(); ++fx)
 	{
-		for(int fy=0; fy<6; ++fy)
+		for(int fy=0; fy<obj->getHeight(); ++fy)
 		{
-			int xVal = obj->pos.x + fx - 7;
-			int yVal = obj->pos.y + fy - 5;
+			int xVal = obj->pos.x - fx;
+			int yVal = obj->pos.y - fy;
 			int zVal = obj->pos.z;
 			if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
 			{
 				TerrainTile & curt = terrain[xVal][yVal][zVal];
-				if(total || ((obj->defInfo->visitMap[fy] >> (7 - fx)) & 1))
+				if(total || obj->visitableAt(xVal, yVal))
 				{
 					curt.visitableObjects -= obj;
 					curt.visitable = curt.visitableObjects.size();
 				}
-				if(total || !((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1))
+				if(total || obj->blockingAt(xVal, yVal))
 				{
 					curt.blockingObjects -= obj;
 					curt.blocked = curt.blockingObjects.size();
@@ -242,22 +242,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 
 void CMap::addBlockVisTiles(CGObjectInstance * obj)
 {
-	for(int fx=0; fx<8; ++fx)
+	for(int fx=0; fx<obj->getWidth(); ++fx)
 	{
-		for(int fy=0; fy<6; ++fy)
+		for(int fy=0; fy<obj->getHeight(); ++fy)
 		{
-			int xVal = obj->pos.x + fx - 7;
-			int yVal = obj->pos.y + fy - 5;
+			int xVal = obj->pos.x - fx;
+			int yVal = obj->pos.y - fy;
 			int zVal = obj->pos.z;
 			if(xVal>=0 && xVal<width && yVal>=0 && yVal<height)
 			{
 				TerrainTile & curt = terrain[xVal][yVal][zVal];
-				if(((obj->defInfo->visitMap[fy] >> (7 - fx)) & 1))
+				if( obj->visitableAt(xVal, yVal))
 				{
 					curt.visitableObjects.push_back(obj);
 					curt.visitable = true;
 				}
-				if(!((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1))
+				if( obj->blockingAt(xVal, yVal))
 				{
 					curt.blockingObjects.push_back(obj);
 					curt.blocked = true;

+ 1 - 3
lib/mapping/CMap.h

@@ -19,7 +19,6 @@
 #include "../LogicalExpression.h"
 
 class CArtifactInstance;
-class CGDefInfo;
 class CGObjectInstance;
 class CGHeroInstance;
 class CCommanderInstance;
@@ -408,7 +407,6 @@ public:
 	std::vector<Rumor> rumors;
 	std::vector<DisposedHero> disposedHeroes;
 	std::vector<ConstTransitivePtr<CGHeroInstance> > predefinedHeroes;
-	std::vector<ConstTransitivePtr<CGDefInfo> > customDefs;
 	std::vector<bool> allowedSpell;
 	std::vector<bool> allowedArtifact;
 	std::vector<bool> allowedAbilities;
@@ -484,7 +482,7 @@ public:
 			}
 		}
 
-		h & customDefs & objects;
+		h & objects;
 		h & heroesOnMap & towns & artInstances;
 
 		// static members

+ 17 - 82
lib/mapping/MapFormatH3M.cpp

@@ -124,7 +124,6 @@ void CMapLoaderH3M::init()
 	// Calculate blocked / visitable positions
 	for(auto & elem : map->objects)
 	{
-		if(!elem->defInfo) continue;
 		map->addBlockVisTiles(elem);
 	}
 	times.push_back(MapLoadingTime("blocked/visitable tiles", sw.getDiff()));
@@ -945,78 +944,14 @@ void CMapLoaderH3M::readDefInfo()
 {
 	int defAmount = reader.readUInt32();
 
-	map->customDefs.reserve(defAmount + 8);
+	templates.reserve(defAmount);
 
 	// Read custom defs
 	for(int idd = 0; idd < defAmount; ++idd)
 	{
-		auto  defInfo = new CGDefInfo();
-
-		defInfo->name = reader.readString();
-		std::transform(defInfo->name.begin(),defInfo->name.end(),defInfo->name.begin(),(int(*)(int))toupper);
-
-		ui8 bytes[12];
-		for(auto & byte : bytes)
-		{
-			byte = reader.readUInt8();
-		}
-
-		defInfo->terrainAllowed = reader.readUInt16();
-		defInfo->terrainMenu = reader.readUInt16();
-		defInfo->id = Obj(reader.readUInt32());
-		defInfo->subid = reader.readUInt32();
-		defInfo->type = reader.readUInt8();
-		defInfo->printPriority = reader.readUInt8();
-
-		for(int zi = 0; zi < 6; ++zi)
-		{
-			defInfo->blockMap[zi] = reverse(bytes[zi]);
-		}
-		for(int zi = 0; zi < 6; ++zi)
-		{
-			defInfo->visitMap[zi] = reverse(bytes[6 + zi]);
-		}
-
-		reader.skip(16);
-		if(defInfo->id != Obj::HERO && defInfo->id != Obj::RANDOM_HERO)
-		{
-			CGDefInfo * h = VLC->dobjinfo->gobjs[defInfo->id][defInfo->subid];
-			if(!h)
-			{
-				//remove fake entry
-				VLC->dobjinfo->gobjs[defInfo->id].erase(defInfo->subid);
-				if(VLC->dobjinfo->gobjs[defInfo->id].size())
-				{
-					VLC->dobjinfo->gobjs.erase(defInfo->id);
-				}
-                logGlobal->warnStream() << "\t\tWarning: no defobjinfo entry for object ID="
-                      << defInfo->id << " subID=" << defInfo->subid;
-			}
-			else
-			{
-				defInfo->visitDir = VLC->dobjinfo->gobjs[defInfo->id][defInfo->subid]->visitDir;
-			}
-		}
-		else
-		{
-			defInfo->visitDir = 0xff;
-		}
-
-		if(defInfo->id == Obj::EVENT)
-		{
-			std::memset(defInfo->blockMap, 255, 6);
-		}
-
-		//calculating coverageMap
-		defInfo->fetchInfoFromMSK();
-
-		map->customDefs.push_back(defInfo);
-	}
-
-	//add holes - they always can appear
-	for(int i = 0; i < 8 ; ++i)
-	{
-		map->customDefs.push_back(VLC->dobjinfo->gobjs[Obj::HOLE][i]);
+		ObjectTemplate tmpl;
+		tmpl.readMap(reader);
+		templates.push_back(tmpl);
 	}
 }
 
@@ -1033,10 +968,10 @@ void CMapLoaderH3M::readObjects()
 		int defnum = reader.readUInt32();
 		ObjectInstanceID idToBeGiven = ObjectInstanceID(map->objects.size());
 
-		CGDefInfo * defInfo = map->customDefs.at(defnum);
+		ObjectTemplate & objTempl = templates.at(defnum);
 		reader.skip(5);
 
-		switch(defInfo->id)
+		switch(objTempl.id)
 		{
 		case Obj::EVENT:
 			{
@@ -1142,7 +1077,7 @@ void CMapLoaderH3M::readObjects()
 				break;
 			}
 		case Obj::TREASURE_CHEST:
-				if(defInfo->subid == 0)
+				if(objTempl.subid == 0)
 				{
 					nobj = new CGPickable();
 				}
@@ -1315,15 +1250,15 @@ void CMapLoaderH3M::readObjects()
 
 				readMessageAndGuards(art->message, art);
 
-				if(defInfo->id == Obj::SPELL_SCROLL)
+				if(objTempl.id == Obj::SPELL_SCROLL)
 				{
 					spellID = reader.readUInt32();
 					artID = 1;
 				}
-				else if(defInfo->id == Obj::ARTIFACT)
+				else if(objTempl.id == Obj::ARTIFACT)
 				{
 					//specific artifact
-					artID = defInfo->subid;
+					artID = objTempl.subid;
 				}
 
 				art->storedArtifact = createArtifact(artID, spellID);
@@ -1338,7 +1273,7 @@ void CMapLoaderH3M::readObjects()
 				readMessageAndGuards(res->message, res);
 
 				res->amount = reader.readUInt32();
-				if(defInfo->subid == Res::GOLD)
+				if(objTempl.subid == Res::GOLD)
 				{
 					// Gold is multiplied by 100.
 					res->amount *= 100;
@@ -1349,7 +1284,7 @@ void CMapLoaderH3M::readObjects()
 		case Obj::RANDOM_TOWN:
 		case Obj::TOWN:
 			{
-				nobj = readTown(defInfo->subid);
+				nobj = readTown(objTempl.subid);
 				break;
 			}
 		case Obj::MINE:
@@ -1455,7 +1390,7 @@ void CMapLoaderH3M::readObjects()
 			{
 				nobj = new CGDwelling();
 				CSpecObjInfo * spec = nullptr;
-				switch(defInfo->id)
+				switch(objTempl.id)
 				{
 					break; case Obj::RANDOM_DWELLING: spec = new CCreGenLeveledCastleInfo();
 					break; case Obj::RANDOM_DWELLING_LVL: spec = new CCreGenAsCastleInfo();
@@ -1607,7 +1542,7 @@ void CMapLoaderH3M::readObjects()
 			}
 		case Obj::PYRAMID: //Pyramid of WoG object
 			{
-				if(defInfo->subid == 0)
+				if(objTempl.subid == 0)
 				{
 					nobj = new CGPyramid();
 				}
@@ -1671,13 +1606,13 @@ void CMapLoaderH3M::readObjects()
 		}
 
 		nobj->pos = objPos;
-		nobj->ID = defInfo->id;
+		nobj->ID = objTempl.id;
 		nobj->id = idToBeGiven;
 		if(nobj->ID != Obj::HERO && nobj->ID != Obj::HERO_PLACEHOLDER && nobj->ID != Obj::PRISON)
 		{
-			nobj->subID = defInfo->subid;
+			nobj->subID = objTempl.subid;
 		}
-		nobj->defInfo = defInfo;
+		nobj->appearance = objTempl;
 		assert(idToBeGiven == ObjectInstanceID(map->objects.size()));
 		map->objects.push_back(nobj);
 		if(nobj->ID == Obj::TOWN)

+ 6 - 1
lib/mapping/MapFormatH3M.h

@@ -14,6 +14,7 @@
 #include "CMapService.h"
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
+#include "../CDefObjInfoHandler.h"
 
 #include "../int3.h"
 
@@ -250,6 +251,10 @@ private:
 		return p;
 	}
 
+	/** List of templates loaded from the map, used on later stage to create
+	 *  objects but not needed for fully functional CMap */
+	std::vector<ObjectTemplate> templates;
+
 	/** ptr to the map object which gets filled by data from the buffer */
 	CMap * map;
 
@@ -262,4 +267,4 @@ private:
 	CBinaryReader reader;
 	CInputStream * inputStream;
 
-};
+};

+ 1 - 1
lib/rmg/CMapGenerator.cpp

@@ -161,7 +161,7 @@ void CMapGenerator::genTowns()
 		if(townId == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) townId = gen.getInteger(0, 8); // Default towns
 		town->subID = townId;
 		town->tempOwner = owner;
-		town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID];
+		town->appearance = VLC->dobjinfo->pickCandidates(town->ID, town->subID, map->getTile(townPos[side]).terType).front();
 		town->builtBuildings.insert(BuildingID::FORT);
 		town->builtBuildings.insert(BuildingID::DEFAULT);
 		editManager->insertObject(town, int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0));

+ 1 - 1
server/CGameHandler.cpp

@@ -5221,7 +5221,7 @@ bool CGameHandler::dig( const CGHeroInstance *h )
 	NewObject no;
 	no.ID = Obj::HOLE;
 	no.pos = h->getPosition();
-	no.subID = getTile(no.pos)->terType;
+	no.subID = 0;
 	sendAndApply(&no);
 
 	//take MPs