Parcourir la source

Object class handler is now a proper "handler"
- Some changes in interfaces
- Fixed some missing fields in serialization
- Moved object names to new handler

Ivan Savenko il y a 11 ans
Parent
commit
6bd6be0835

+ 2 - 2
client/CGameInfo.h

@@ -23,7 +23,7 @@ class CBuildingHandler;
 class CObjectHandler;
 class CSoundHandler;
 class CMusicHandler;
-class CObjectTypesHandler;
+class CObjectClassesHandler;
 class CTownHandler;
 class CGeneralTextHandler;
 class CConsoleHandler;
@@ -57,7 +57,7 @@ public:
 	ConstTransitivePtr<CCreatureHandler> creh;
 	ConstTransitivePtr<CSpellHandler> spellh;
 	ConstTransitivePtr<CObjectHandler> objh;
-	ConstTransitivePtr<CObjectTypesHandler> objtypeh;
+	ConstTransitivePtr<CObjectClassesHandler> objtypeh;
 	CGeneralTextHandler * generaltexth;
 	CMapHandler * mh;
 	CTownHandler * townh;

+ 1 - 1
client/mapHandler.cpp

@@ -1073,7 +1073,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN
 	}
 
 	if(t.hasFavourableWinds())
-		out = CGI->generaltexth->names[Obj::FAVORABLE_WINDS];
+		out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS);
 	else if(terName)
         out = CGI->generaltexth->terrainNames[t.terType];
 }

+ 135 - 10
lib/CDefObjInfoHandler.cpp

@@ -11,6 +11,8 @@
 #include "CModHandler.h"
 #include "JsonNode.h"
 
+#include "CObjectConstructor.h"
+
 /*
  * CDefObjInfoHandler.cpp, part of VCMI engine
  *
@@ -350,29 +352,153 @@ CDefObjInfoHandler::CDefObjInfoHandler()
 	readTextFile("Data/Heroes.txt");
 }
 */
-void CObjectTypesHandler::init()
+
+CObjectClassesHandler::CObjectClassesHandler()
+{
+	// list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code
+	handlerConstructors["configurable"] = std::make_shared<CObjectWithRewardConstructor>;
+
+#define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared<CDefaultObjectTypeHandler<TYPENAME> >
+
+	SET_HANDLER("", CGObjectInstance);
+	SET_HANDLER("generic", CGObjectInstance);
+
+	SET_HANDLER("market", CGMarket);
+	SET_HANDLER("bank", CBank);
+	SET_HANDLER("cartographer", CCartographer);
+	SET_HANDLER("artifact", CGArtifact);
+	SET_HANDLER("blackMarket", CGBlackMarket);
+	SET_HANDLER("boat", CGBoat);
+	SET_HANDLER("bonusingObject", CGBonusingObject);
+	SET_HANDLER("borderGate", CGBorderGate);
+	SET_HANDLER("borderGuard", CGBorderGuard);
+	SET_HANDLER("monster", CGCreature);
+	SET_HANDLER("denOfThieves", CGDenOfthieves);
+	SET_HANDLER("dwelling", CGDwelling);
+	SET_HANDLER("event", CGEvent);
+	SET_HANDLER("garrison", CGGarrison);
+	SET_HANDLER("hero", CGHeroInstance);
+	SET_HANDLER("heroPlaceholder", CGHeroPlaceholder);
+	SET_HANDLER("keymaster", CGKeymasterTent);
+	SET_HANDLER("lighthouse", CGLighthouse);
+	SET_HANDLER("magi", CGMagi);
+	SET_HANDLER("magicSpring", CGMagicSpring);
+	SET_HANDLER("magicWell", CGMagicWell);
+	SET_HANDLER("market", CGMarket);
+	SET_HANDLER("mine", CGMine);
+	SET_HANDLER("obelisk", CGObelisk);
+	SET_HANDLER("observatory", CGObservatory);
+	SET_HANDLER("onceVisitable", CGOnceVisitable);
+	SET_HANDLER("pandora", CGPandoraBox);
+	SET_HANDLER("pickable", CGPickable);
+	SET_HANDLER("pyramid", CGPyramid);
+	SET_HANDLER("questGuard", CGQuestGuard);
+	SET_HANDLER("resource", CGResource);
+	SET_HANDLER("scholar", CGScholar);
+	SET_HANDLER("seerHut", CGSeerHut);
+	SET_HANDLER("shipyard", CGShipyard);
+	SET_HANDLER("shrine", CGShrine);
+	SET_HANDLER("sign", CGSignBottle);
+	SET_HANDLER("siren", CGSirens);
+	SET_HANDLER("teleport", CGTeleport);
+	SET_HANDLER("town", CGTownInstance);
+	SET_HANDLER("university", CGUniversity);
+	SET_HANDLER("oncePerHero", CGVisitableOPH);
+	SET_HANDLER("oncePerWeek", CGVisitableOPW);
+	SET_HANDLER("witch", CGWitchHut);
+
+#undef SET_HANDLER
+}
+
+static std::vector<JsonNode> readTextFile(std::string path)
+{
+	//TODO
+}
+
+std::vector<JsonNode> CObjectClassesHandler::loadLegacyData(size_t dataSize)
+{
+	std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
+
+	auto parseFile = [&](std::string filename)
+	{
+		auto entries = readTextFile(filename);
+		for (JsonNode & entry : entries)
+		{
+			si32 id = entry["basebase"].Float();
+			si32 subid = entry["base"].Float();
+
+			entry.Struct().erase("basebase");
+			entry.Struct().erase("base");
+
+			if (ret[id].Vector().size() <= subid)
+				ret[id].Vector().resize(subid+1);
+			ret[id]["legacyTypes"].Vector()[subid][entry["animation"].String()].swap(entry);
+		}
+	};
+
+	parseFile("Data/Objects.txt");
+	parseFile("Data/Heroes.txt");
+
+	CLegacyConfigParser parser("Data/ObjNames.txt");
+	for (size_t i=0; i<256; i++)
+	{
+		ret[i]["name"].String() = parser.readString();
+		parser.endLine();
+	}
+	return ret;
+}
+
+CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json)
+{
+	auto obj = new ObjectContainter();
+	obj->name = json["name"].String();
+	obj->handlerName = json["handler"].String();
+	obj->base = json["base"];
+	for (auto entry : json["types"].Struct())
+	{
+		auto handler = handlerConstructors.at(obj->handlerName)();
+		handler->init(entry.second);
+	}
+	return obj;
+}
+
+void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
+}
 
+void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
 }
 
-TObjectTypeHandler CObjectTypesHandler::getHandlerFor(si32 type, si32 subtype) const
+std::vector<bool> CObjectClassesHandler::getDefaultAllowed() const
 {
-	if (objectTypes.count(type))
+	return std::vector<bool>(); //TODO?
+}
+
+TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const
+{
+	if (objects.count(type))
 	{
-		if (objectTypes.at(type).count(subtype))
-			return objectTypes.at(type).at(subtype);
+		if (objects.at(type)->objects.count(subtype))
+			return objects.at(type)->objects.at(subtype);
 	}
 	assert(0); // FIXME: throw error?
 	return nullptr;
 }
 
-void AObjectTypeHandler::init(si32 type, si32 subtype)
+std::string CObjectClassesHandler::getObjectName(si32 type) const
+{
+	assert(objects.count(type));
+	return objects.at(type)->name;
+}
+
+void AObjectTypeHandler::setType(si32 type, si32 subtype)
 {
 	this->type = type;
 	this->subtype = subtype;
 }
 
-void AObjectTypeHandler::load(const JsonNode & input)
+void AObjectTypeHandler::init(const JsonNode & input)
 {
 	for (auto entry : input["templates"].Struct())
 	{
@@ -416,7 +542,7 @@ std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(si32 terrainType) c
 	return filtered.empty() ? ret : filtered;
 }
 
-ObjectTemplate AObjectTypeHandler::selectTemplate(si32 terrainType, CGObjectInstance * object) const
+boost::optional<ObjectTemplate> AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const
 {
 	std::vector<ObjectTemplate> ret = getTemplates(terrainType);
 	for (auto & tmpl : ret)
@@ -424,6 +550,5 @@ ObjectTemplate AObjectTypeHandler::selectTemplate(si32 terrainType, CGObjectInst
 		if (objectFilter(object, tmpl))
 			return tmpl;
 	}
-	// FIXME: no matches found. Warn? Ask for torches? Die?
-	return ret.front();
+	return boost::optional<ObjectTemplate>();
 }

+ 78 - 14
lib/CDefObjInfoHandler.h

@@ -2,6 +2,7 @@
 
 #include "GameConstants.h"
 #include "../lib/ConstTransitivePtr.h"
+#include "IHandlerBase.h"
 
 /*
  * CDefObjInfoHandler.h, part of VCMI engine
@@ -105,15 +106,17 @@ class AObjectTypeHandler
 	si32 type;
 	si32 subtype;
 
+	JsonNode base; /// describes base template
+
 	std::vector<ObjectTemplate> templates;
 protected:
-	void init(si32 type, si32 subtype);
-
-	/// loads templates from Json structure using fields "base" and "templates"
-	void load(const JsonNode & input);
+	void setType(si32 type, si32 subtype);
 
 	virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
 public:
+	/// loads templates from Json structure using fields "base" and "templates"
+	virtual void init(const JsonNode & input);
+
 	void addTemplate(const ObjectTemplate & templ);
 
 	/// returns all templates, without any filters
@@ -122,35 +125,96 @@ public:
 	/// returns all templates that can be placed on specific terrain type
 	std::vector<ObjectTemplate> getTemplates(si32 terrainType) const;
 
-	/// returns template suitable for object. If returned template is not equal to current one
-	/// it must be replaced with this one (and properly updated on all clients)
-	ObjectTemplate selectTemplate(si32 terrainType, CGObjectInstance * object) const;
-
+	/// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle)
+	/// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)
+	boost::optional<ObjectTemplate> getOverride(si32 terrainType, const CGObjectInstance * object) const;
 
+	/// Creates object and set up core properties (like ID/subID). Object is NOT initialized
+	/// to allow creating objects before game start (e.g. map loading)
 	virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0;
 
+	/// Configures object properties. Should be re-entrable, resetting state of the object if necessarily
 	virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0;
 
+	/// Returns object configuration, if available. Othervice returns NULL
 	virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & type & subtype & templates;
+	}
+};
+
+template<class ObjectType>
+class CDefaultObjectTypeHandler : public AObjectTypeHandler
+{
+	CGObjectInstance * create(ObjectTemplate tmpl) const
+	{
+		auto obj = new ObjectType();
+		obj->ID = tmpl.id;
+		obj->subID = tmpl.subid;
+		obj->appearance = tmpl;
+		return obj;
+	}
+
+	virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
+	{
+	}
+
+	virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const
+	{
+		return nullptr;
+	}
 };
 
 typedef std::shared_ptr<AObjectTypeHandler> TObjectTypeHandler;
 
-class CObjectTypesHandler
+class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 {
+	/// Small internal structure that contains information on specific group of objects
+	/// (creating separate entity is overcomplicating at least at this point)
+	struct ObjectContainter
+	{
+		si32 id;
+
+		std::string name; // human-readable name
+		std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map
+
+		JsonNode base;
+		std::map<si32, TObjectTypeHandler> objects;
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & base & objects;
+		}
+	};
+
 	/// list of object handlers, each of them handles only one type
-	std::map<si32, std::map<si32, TObjectTypeHandler> > objectTypes;
+	std::map<si32, ObjectContainter * > objects;
 
+	/// map that is filled during contruction with all known handlers. Not serializeable
+	std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
+
+	ObjectContainter * loadFromJson(const JsonNode & json);
 public:
-	void init();
+	CObjectClassesHandler();
+
+	virtual std::vector<JsonNode> loadLegacyData(size_t dataSize);
+
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data);
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index);
+
+	virtual void afterLoadFinalization(){};
+
+	virtual std::vector<bool> getDefaultAllowed() const;
 
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
 	TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
 
+	std::string getObjectName(si32 type) const;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		//h & objects;
-		if (!h.saving)
-			init(); // TODO: implement serialization
+		h & objects;
 	}
 };

+ 11 - 43
lib/CGameState.cpp

@@ -166,6 +166,10 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 	{
 		dst = VLC->arth->artifacts[ser]->EventText();
 	}
+	else if (type == OBJ_NAMES)
+	{
+		dst = VLC->objtypeh->getObjectName(ser);
+	}
 	else
 	{
 		std::vector<std::string> *vec;
@@ -177,9 +181,6 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 		case XTRAINFO_TXT:
 			vec = &VLC->generaltexth->xtrainfo;
 			break;
-		case OBJ_NAMES:
-			vec = &VLC->generaltexth->names;
-			break;
 		case RES_NAMES:
 			vec = &VLC->generaltexth->restypes;
 			break;
@@ -651,58 +652,25 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
 	std::pair<Obj,int> ran = pickObject(cur);
 	if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything
 	{
-		if(cur->ID==Obj::TOWN) //town - set def
-		{
-			const TerrainTile &tile = map->getTile(cur->visitablePos());
-			CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
-			t->town = VLC->townh->factions[t->subID]->town;
-			t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t);
-			t->updateAppearance();
-		}
+		if(cur->ID==Obj::TOWN)
+			cur->setType(cur->ID, cur->subID); // update def, if necessary
 		return;
 	}
 	else if(ran.first==Obj::HERO)//special code for hero
 	{
 		CGHeroInstance *h = dynamic_cast<CGHeroInstance *>(cur);
-        if(!h) {logGlobal->warnStream()<<"Wrong random hero at "<<cur->pos; return;}
-		cur->ID = ran.first;
-		cur->subID = ran.second;
-		h->type = VLC->heroh->heroes[ran.second];
-		h->portrait = h->type->imageIndex;
-		h->randomizeArmy(h->type->heroClass->faction);
+		cur->setType(ran.first, ran.second);
 		map->heroesOnMap.push_back(h);
-		return; //TODO: maybe we should do something with definfo?
+		return;
 	}
 	else if(ran.first==Obj::TOWN)//special code for town
 	{
-		const TerrainTile &tile = map->getTile(cur->visitablePos());
 		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;
-		t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t);
-		t->updateAppearance();
-
-		t->randomizeArmy(t->subID);
+		cur->setType(ran.first, ran.second);
 		map->towns.push_back(t);
 		return;
 	}
-	else
-	{
-		if (ran.first  != cur->appearance.id ||
-			ran.second != cur->appearance.subid)
-		{
-			const TerrainTile &tile = map->getTile(cur->visitablePos());
-			cur->appearance = VLC->objtypeh->getHandlerFor(ran.first, ran.second)->selectTemplate(tile.terType, cur);
-		}
-	}
-	//we have to replace normal random object
-	cur->ID = ran.first;
-	cur->subID = ran.second;
-	map->removeBlockVisTiles(cur, true); //recalculate blockvis tiles - picked object might have different than random placeholder
-	map->addBlockVisTiles(cur);
+	cur->setType(ran.first, ran.second);
 }
 
 int CGameState::getDate(Date::EDateType mode) const
@@ -1078,7 +1046,7 @@ void CGameState::randomizeMapObjects()
 		if(!obj) continue;
 
 		randomizeObject(obj);
-		obj->hoverName = VLC->generaltexth->names[obj->ID];
+		obj->hoverName = VLC->objtypeh->getObjectName(obj->ID);
 
 		//handle Favouring Winds - mark tiles under it
 		if(obj->ID == Obj::FAVORABLE_WINDS)

+ 0 - 1
lib/CGeneralTextHandler.cpp

@@ -287,7 +287,6 @@ CGeneralTextHandler::CGeneralTextHandler()
 	readToVector("DATA/HEROSCRN.TXT", heroscrn);
 	readToVector("DATA/TENTCOLR.TXT", tentColors);
 	readToVector("DATA/SKILLLEV.TXT", levels);
-	readToVector("DATA/OBJNAMES.TXT", names);
 
 	localizedTexts = JsonNode(ResourceID("config/translate.json", EResType::TEXT));
 

+ 0 - 1
lib/CGeneralTextHandler.h

@@ -112,7 +112,6 @@ public:
 	std::vector<std::string> victoryConditions;
 
 	//objects
-	std::vector<std::string> names; //vector of objects; i-th object in vector has subnumber i
 	std::vector<std::string> creGens; //names of creatures' generators
 	std::vector<std::string> creGens4; //names of multiple creatures' generators
 	std::vector<std::string> advobtxt;

+ 28 - 0
lib/CObjectConstructor.cpp

@@ -138,8 +138,32 @@ void CRandomRewardObjectInfo::init(const JsonNode & objectConfig)
 
 void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRandomGenerator & rng) const
 {
+	std::map<si32, si32> thrownDice;
+
 	for (const JsonNode & reward : parameters["rewards"].Vector())
 	{
+		if (!reward["appearChance"].isNull())
+		{
+			JsonNode chance = reward["appearChance"];
+			si32 diceID = chance["dice"].Float();
+
+			if (thrownDice.count(diceID) == 0)
+				thrownDice[diceID] = rng.getIntRange(1, 100)();
+
+			if (!chance["min"].isNull())
+			{
+				int min = chance["min"].Float();
+				if (min > thrownDice[diceID])
+					continue;
+			}
+			if (!chance["max"].isNull())
+			{
+				int max = chance["max"].Float();
+				if (max < thrownDice[diceID])
+					continue;
+			}
+		}
+
 		const JsonNode & limiter = reward["limiter"];
 		CVisitInfo info;
 		// load limiter
@@ -172,6 +196,9 @@ void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRando
 		info.reward.artifacts = loadArtifacts(reward["artifacts"], rng);
 		info.reward.spells = loadSpells(reward["spells"], rng);
 		info.reward.creatures = loadCreatures(reward["creatures"], rng);
+
+		info.message = loadMessage(reward["message"]);
+		info.selectChance = loadValue(reward["selectChance"], rng);
 	}
 
 	object->onSelect  = loadMessage(parameters["onSelectMessage"]);
@@ -241,6 +268,7 @@ CObjectWithRewardConstructor::CObjectWithRewardConstructor()
 
 void CObjectWithRewardConstructor::init(const JsonNode & config)
 {
+	AObjectTypeHandler::init(config);
 	objectInfo.init(config);
 }
 

+ 1 - 1
lib/CObjectConstructor.h

@@ -47,7 +47,7 @@ class CObjectWithRewardConstructor : public AObjectTypeHandler
 
 public:
 	CObjectWithRewardConstructor();
-	void init(const JsonNode & config);
+	void init(const JsonNode & config) override;
 
 	CGObjectInstance * create(ObjectTemplate tmpl) const override;
 

+ 41 - 10
lib/CObjectHandler.cpp

@@ -372,6 +372,19 @@ bool CGObjectInstance::operator<(const CGObjectInstance & cmp) const  //screen p
 	return false;
 }
 
+void CGObjectInstance::setType(si32 ID, si32 subID)
+{
+	const TerrainTile &tile = cb->gameState()->map->getTile(visitablePos());
+
+	this->ID = Obj(ID);
+	this->subID = subID;
+	this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).front();
+
+	//recalculate blockvis tiles - new appearance might have different blockmap than before
+	cb->gameState()->map->removeBlockVisTiles(this, true);
+	cb->gameState()->map->addBlockVisTiles(this);
+}
+
 void CGObjectInstance::initObj()
 {
 	switch(ID)
@@ -456,7 +469,7 @@ int3 CGObjectInstance::getVisitableOffset() const
 void CGObjectInstance::getNameVis( std::string &hname ) const
 {
 	const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
-	hname = VLC->generaltexth->names[ID];
+	hname = VLC->objtypeh->getObjectName(ID);
 	if(h)
 	{
 		const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID);
@@ -720,6 +733,15 @@ void CGHeroInstance::initHero(HeroTypeID SUBID)
 	initHero();
 }
 
+void CGHeroInstance::setType(si32 ID, si32 subID)
+{
+	assert(ID == Obj::HERO); // just in case
+	CGObjectInstance::setType(ID, subID);
+	type = VLC->heroh->heroes[subID];
+	portrait = type->imageIndex;
+	randomizeArmy(type->heroClass->faction);
+}
+
 void CGHeroInstance::initHero()
 {
 	assert(validTypes(true));
@@ -913,7 +935,7 @@ const std::string & CGHeroInstance::getHoverText() const
 		return hoverName;
 	}
 	else
-		hoverName = VLC->generaltexth->names[ID];
+		hoverName = VLC->objtypeh->getObjectName(ID);
 
 	return hoverName;
 }
@@ -2497,6 +2519,15 @@ std::vector<int> CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode
 		return IMarket::availableItemsIds(mode);
 }
 
+void CGTownInstance::setType(si32 ID, si32 subID)
+{
+	assert(ID == Obj::TOWN); // just in case
+	CGObjectInstance::setType(ID, subID);
+	town = VLC->townh->factions[subID]->town;
+	randomizeArmy(subID);
+	updateAppearance();
+}
+
 void CGTownInstance::updateAppearance()
 {
 	if (!hasFort())
@@ -4126,10 +4157,10 @@ const std::string & CGSeerHut::getHoverText() const
 			boost::algorithm::replace_first(hoverName,"%s", seerName);
 		}
 		else //just seer hut
-			hoverName = VLC->generaltexth->names[ID];
+			hoverName = VLC->objtypeh->getObjectName(ID);
 		break;
 	case Obj::QUEST_GUARD:
-		hoverName = VLC->generaltexth->names[ID];
+		hoverName = VLC->objtypeh->getObjectName(ID);
 		break;
 	default:
         logGlobal->debugStream() << "unrecognized quest object";
@@ -4448,7 +4479,7 @@ void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
 
 const std::string & CGWitchHut::getHoverText() const
 {
-	hoverName = VLC->generaltexth->names[ID];
+	hoverName = VLC->objtypeh->getObjectName(ID);
 	if(wasVisited(cb->getLocalPlayer()))
 	{
 		hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s)
@@ -4907,7 +4938,7 @@ void CGShrine::initObj()
 
 const std::string & CGShrine::getHoverText() const
 {
-	hoverName = VLC->generaltexth->names[ID];
+	hoverName = VLC->objtypeh->getObjectName(ID);
 	if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
 	{
 		hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
@@ -5509,7 +5540,7 @@ const std::string& CGKeys::getHoverText() const
 const std::string CGKeys::getName() const
 {
 	std::string name;
-	name = VLC->generaltexth->tentColors[subID] + " " + VLC->generaltexth->names[ID];
+	name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID);
 	return name;
 }
 
@@ -5545,7 +5576,7 @@ void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &comp
 void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
 {
 	if (!onHover)
-		text << VLC->generaltexth->tentColors[subID] << " " << VLC->generaltexth->names[Obj::KEYMASTER];
+		text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER);
 }
 
 bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const
@@ -5945,7 +5976,7 @@ void CGObelisk::initObj()
 const std::string & CGObelisk::getHoverText() const
 {
 	bool visited = wasVisited(cb->getLocalPlayer());
-	hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited);
+	hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited);
 	return hoverName;
 }
 
@@ -5998,7 +6029,7 @@ void CGLighthouse::initObj()
 
 const std::string & CGLighthouse::getHoverText() const
 {
-	hoverName = VLC->generaltexth->names[ID];
+	hoverName = VLC->objtypeh->getObjectName(ID);
 	//TODO: owned by %s player
 	return hoverName;
 }

+ 7 - 2
lib/CObjectHandler.h

@@ -234,6 +234,8 @@ public:
 	//CGObjectInstance& operator=(const CGObjectInstance & right);
 	virtual const std::string & getHoverText() const;
 
+	void setType(si32 ID, si32 subID);
+
 	///IObjectInterface
 	void initObj() override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
@@ -355,7 +357,7 @@ public:
 	//std::vector<const CArtifact*> artifacts; //hero's artifacts from bag
 	//std::map<ui16, const CArtifact*> artifWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
 	std::set<SpellID> spells; //known spells (spell IDs)
-	std::set<ObjectInstanceID> visitedObjects;
+	std::set<ObjectInstanceID> visitedObjects;
 
 	struct DLL_LINKAGE Patrol
 	{
@@ -422,7 +424,7 @@ public:
 		h & static_cast<CArmedInstance&>(*this);
 		h & static_cast<CArtifactSet&>(*this);
 		h & exp & level & name & biography & portrait & mana & secSkills & movement
-			& sex & inTownGarrison & spells & patrol & moveDir & skillsInfo & visitedObjects;
+			& sex & inTownGarrison & spells & patrol & moveDir & skillsInfo & visitedObjects;
 		h & visitedTown & boat;
 		h & type & specialty & commander;
 		BONUS_TREE_DESERIALIZATION_FIX
@@ -474,6 +476,8 @@ public:
 
 	//////////////////////////////////////////////////////////////////////////
 
+	void setType(si32 ID, si32 subID);
+
 	void initHero();
 	void initHero(HeroTypeID SUBID);
 
@@ -698,6 +702,7 @@ public:
 	bool allowsTrade(EMarketMode::EMarketMode mode) const;
 	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
 
+	void setType(si32 ID, si32 subID);
 	void updateAppearance();
 
 	//////////////////////////////////////////////////////////////////////////

+ 4 - 6
lib/CObjectWithReward.cpp

@@ -121,7 +121,6 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const
 				else
 					iw.text = onVisited;
 				cb->showInfoDialog(&iw);
-				onRewardGiven(h); // FIXME: dummy call to properly act on empty objects (e.g. Floatsam that must be removed after visit)
 				break;
 			}
 			case 1: // one reward. Just give it with message
@@ -141,7 +140,7 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const
 					case SELECT_FIRST: // give first available
 						grantRewardWithMessage(rewards[0]);
 						break;
-					case SELECT_RANDOM: // select one randomly
+					case SELECT_RANDOM: // select one randomly //TODO: use weights
 						grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]);
 						break;
 				}
@@ -376,7 +375,7 @@ static std::string & visitedTxt(const bool visited)
 const std::string & CObjectWithReward::getHoverText() const
 {
 	const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
-	hoverName = VLC->generaltexth->names[ID];
+	hoverName = VLC->objtypeh->getObjectName(ID);
 	if(visitMode != VISIT_UNLIMITED)
 	{
 		bool visited = wasVisited(cb->getCurrentPlayer());
@@ -975,7 +974,7 @@ const std::string & CGVisitableOPH::getHoverText() const
 	default:
 		throw std::runtime_error("Wrong CGVisitableOPH object ID!\n");
 	}
-	hoverName = VLC->generaltexth->names[ID];
+	hoverName = VLC->objtypeh->getObjectName(ID);
 	if(pom >= 0)
 		hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]);
 	const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer());
@@ -1015,7 +1014,6 @@ void CGVisitableOPW::initObj()
 		soundID = soundBase::GENIE;
 		onEmpty.addTxt(MetaString::ADVOB_TXT, 169);
 		// 3-6 of any resource but wood and gold
-		// this is UGLY. TODO: find better way to describe this
 		for (int resID = Res::MERCURY; resID < Res::GOLD; resID++)
 		{
 			for (int val = 3; val <=6; val++)
@@ -1045,7 +1043,7 @@ void CGVisitableOPW::initObj()
 
 void CGMagicSpring::initObj()
 {
-	CVisitInfo visit; // TODO: "player above max mana" limiter
+	CVisitInfo visit; // TODO: "player above max mana" limiter. Use logical expressions for limiters?
 	visit.reward.manaPercentage = 200;
 	visit.message.addTxt(MetaString::ADVOB_TXT, 74);
 	info.push_back(visit); // two rewards, one for each entrance

+ 7 - 3
lib/CObjectWithReward.h

@@ -119,6 +119,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & resources & extraComponents & removeObject;
+		h & manaPercentage & movePercentage;
 		h & gainedExp & gainedLevels & manaDiff & movePoints;
 		h & primary & secondary & bonuses;
 		h & artifacts & spells & creatures;
@@ -134,6 +135,9 @@ public:
 	/// Message that will be displayed on granting of this reward, if not empty
 	MetaString message;
 
+	/// Chance for this reward to be selected in case of random choice
+	si32 selectChance;
+
 	/// How many times this reward has been granted since last reset
 	si32 numOfGrants;
 
@@ -143,7 +147,7 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & limiter & reward & message;
+		h & limiter & reward & message & selectChance & numOfGrants;
 	}
 };
 
@@ -229,8 +233,8 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CArmedInstance&>(*this);
-		h & info & canRefuse;
-		h & onSelect & onVisited & onEmpty;
+		h & info & canRefuse & resetDuration;
+		h & onSelect & onVisited & onEmpty & visitMode;
 		h & soundID & selectMode & selectedReward;
 	}
 

+ 2 - 2
lib/NetPacksLib.cpp

@@ -623,9 +623,9 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
 	o->subID = subID;
 	o->pos = pos;
 	const TerrainTile &t = gs->map->getTile(pos);
-	o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->selectTemplate(t.terType, o);
+	o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->getTemplates(t.terType).front();
 	id = o->id = ObjectInstanceID(gs->map->objects.size());
-	o->hoverName = VLC->generaltexth->names[ID];
+	o->hoverName = VLC->objtypeh->getObjectName(ID);
 
 	gs->map->objects.push_back(o);
 	gs->map->addBlockVisTiles(o);

+ 2 - 2
lib/VCMI_Lib.h

@@ -16,7 +16,7 @@ class CCreatureHandler;
 class CSpellHandler;
 class CBuildingHandler;
 class CObjectHandler;
-class CObjectTypesHandler;
+class CObjectClassesHandler;
 class CTownHandler;
 class CGeneralTextHandler;
 class CModHandler;
@@ -42,7 +42,7 @@ public:
 	CCreatureHandler * creh;
 	CSpellHandler * spellh;
 	CObjectHandler * objh;
-	CObjectTypesHandler * objtypeh;
+	CObjectClassesHandler * objtypeh;
 	CTownHandler * townh;
 	CGeneralTextHandler * generaltexth;
 	CModHandler * modh;

+ 1 - 1
lib/rmg/CMapGenerator.cpp

@@ -166,7 +166,7 @@ void CMapGenerator::genTowns()
 		}
 		town->subID = townId;
 		town->tempOwner = owner;
-		town->appearance = VLC->objtypeh->getHandlerFor(town->ID, town->subID)->selectTemplate(map->getTile(townPos[side]).terType, town);
+		town->appearance = VLC->objtypeh->getHandlerFor(town->ID, town->subID)->getTemplates(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));