Browse Source

- support for new heroes and hero classes
- moved hero-specific data from text handler to CHero
- moved hero classes-specific data into heroClasses.json

Ivan Savenko 13 years ago
parent
commit
e36bc50504

+ 14 - 8
client/BattleInterface/CBattleInterface.cpp

@@ -239,10 +239,13 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	//loading hero animations
 	if(hero1) // attacking hero
 	{
-		int type = hero1->type->heroClass->id;
-		if ( type % 2 )   type--;
-		if ( hero1->sex ) type++;
-		attackingHero = new CBattleHero(graphics->battleHeroes[type], false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : NULL, this);
+		std::string battleImage;
+		if ( hero1->sex )
+			battleImage = hero1->type->heroClass->imageBattleFemale;
+		else
+			battleImage = hero1->type->heroClass->imageBattleMale;
+
+		attackingHero = new CBattleHero(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : NULL, this);
 		attackingHero->pos = genRect(attackingHero->dh->ourImages[0].bitmap->h, attackingHero->dh->ourImages[0].bitmap->w, pos.x - 43, pos.y - 19);
 	}
 	else
@@ -251,10 +254,13 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	}
 	if(hero2) // defending hero
 	{
-		int type = hero2->type->heroClass->id;
-		if ( type % 2 )   type--;
-		if ( hero2->sex ) type++;
-		defendingHero = new CBattleHero(graphics->battleHeroes[type ], true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : NULL, this);
+		std::string battleImage;
+		if ( hero2->sex )
+			battleImage = hero1->type->heroClass->imageBattleFemale;
+		else
+			battleImage = hero1->type->heroClass->imageBattleMale;
+
+		defendingHero = new CBattleHero(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : NULL, this);
 		defendingHero->pos = genRect(defendingHero->dh->ourImages[0].bitmap->h, defendingHero->dh->ourImages[0].bitmap->w, pos.x + 693, pos.y - 19);
 	}
 	else

+ 3 - 3
client/CHeroWindow.cpp

@@ -175,8 +175,8 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 
 	assert(hero == curHero);
 
-	specArea->text = CGI->generaltexth->hTxts[curHero->subID].longBonus;
-	specImage->setFrame(curHero->subID);
+	specArea->text = curHero->type->specDescr;
+	specImage->setFrame(curHero->type->imageIndex);
 
 	tacticsButton->callback.clear();
 	tacticsButton->callback2.clear();
@@ -378,7 +378,7 @@ void CHeroWindow::showAll(SDL_Surface * to)
 	 
 	//printing special ability
 	printAtLoc(CGI->generaltexth->jktexts[5].substr(1, CGI->generaltexth->jktexts[5].size()-2), 69, 183, FONT_SMALL, Colors::YELLOW, to);
-	printAtLoc(CGI->generaltexth->hTxts[curHero->subID].bonusName, 69, 205, FONT_SMALL, Colors::WHITE, to);
+	printAtLoc(curHero->type->specName, 69, 205, FONT_SMALL, Colors::WHITE, to);
 	 
 	//printing necessery texts
 	printAtLoc(CGI->generaltexth->jktexts[6].substr(1, CGI->generaltexth->jktexts[6].size()-2), 69, 232, FONT_SMALL, Colors::YELLOW, to);

+ 4 - 3
client/CKingdomInterface.cpp

@@ -171,7 +171,7 @@ std::string InfoBoxAbstractHeroData::getNameText()
 			return text.substr(begin, end-begin);
 		}
 	case HERO_SPECIAL:
-		return CGI->generaltexth->hTxts[getSubID()].bonusName;
+		return CGI->heroh->heroes[getSubID()]->specName;
 	case HERO_SECONDARY_SKILL:
 		if (getValue())
 			return CGI->generaltexth->skillName[getSubID()];
@@ -237,6 +237,7 @@ size_t InfoBoxAbstractHeroData::getImageIndex()
 	switch (type)
 	{
 	case HERO_SPECIAL:
+		return VLC->heroh->heroes[getSubID()]->imageIndex;
 	case HERO_PRIMARY_SKILL:
 		return getSubID();
 	case HERO_MANA:
@@ -262,7 +263,7 @@ bool InfoBoxAbstractHeroData::prepareMessage(std::string &text, CComponent **com
 	switch (type)
 	{
 	case HERO_SPECIAL:
-		text = CGI->generaltexth->hTxts[getSubID()].longBonus;
+		text = CGI->heroh->heroes[getSubID()]->specDescr;
 		*comp = NULL;
 		return true;
 	case HERO_PRIMARY_SKILL:
@@ -918,7 +919,7 @@ CHeroItem::CHeroItem(const CGHeroInstance* Hero, CArtifactsOfHero::SCommonPart *
 
 	garr = new CGarrisonInt(6, 78, 4, Point(), NULL, Point(), hero, NULL, true, true);
 
-	portrait = new CAnimImage("PortraitsLarge", hero->subID, 0, 5, 6);
+	portrait = new CAnimImage("PortraitsLarge", hero->portrait, 0, 5, 6);
 	heroArea = new CHeroArea(5, 6, hero);
 
 	name = new CLabel(73, 7, FONT_SMALL, TOPLEFT, Colors::WHITE, hero->name);

+ 3 - 3
client/CPreGame.cpp

@@ -2640,7 +2640,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 			{
 				if(settings.heroPortrait >= 0)
 					return settings.heroPortrait;
-				return settings.hero;
+				return CGI->heroh->heroes[settings.hero]->imageIndex;
 			}
 		}
 
@@ -2870,10 +2870,10 @@ void OptionsTab::CPregameTooltipBox::genHeroWindow()
 	genHeader();
 
 	// speciality
-	new CAnimImage("UN44", settings.hero, 0, pos.w / 2 - 22, 134);
+	new CAnimImage("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134);
 
 	new CLabel(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER,  Colors::YELLOW, CGI->generaltexth->allTexts[78]);
-	new CLabel(pos.w / 2,     188, FONT_SMALL,  CENTER, Colors::WHITE, CGI->generaltexth->hTxts[settings.hero].bonusName);
+	new CLabel(pos.w / 2,     188, FONT_SMALL,  CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName);
 }
 
 void OptionsTab::CPregameTooltipBox::genBonusWindow()

+ 4 - 4
client/GUIClasses.cpp

@@ -943,7 +943,7 @@ size_t CComponent::getIndex()
 	case morale:     return val+3;
 	case luck:       return val+3;
 	case building:   return val;
-	case hero:       return subtype;
+	case hero:       return CGI->heroh->heroes[subtype]->imageIndex;
 	case flag:       return subtype;
 	}
 	assert(0);
@@ -3706,7 +3706,7 @@ CTavernWindow::HeroPortrait::HeroPortrait(int &sel, int id, int x, int y, const
 				  h->name.c_str(), h->level, h->type->heroClass->name.c_str(), artifs);
 		descr[sizeof(descr)-1] = '\0';
 
-		new CAnimImage("portraitsLarge", h->subID);
+		new CAnimImage("portraitsLarge", h->portrait);
 	}
 }
 
@@ -4965,7 +4965,7 @@ void CExchangeWindow::prepareBackground()
 		}
 
 		//hero's specialty
-		new CAnimImage("UN32", heroInst[b]->subID, 0, 67 + 490*b, 45);
+		new CAnimImage("UN32", heroInst[b]->type->imageIndex, 0, 67 + 490*b, 45);
 
 		//experience
 		new CAnimImage("PSKIL32", 4, 0, 103 + 490*b, 45);
@@ -5042,7 +5042,7 @@ CExchangeWindow::CExchangeWindow(si32 hero1, si32 hero2):
 		speciality[b] = new LRClickableAreaWText();
 		speciality[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + 45);
 		speciality[b]->hoverText = CGI->generaltexth->heroscrn[27];
-		speciality[b]->text = CGI->generaltexth->hTxts[heroInst[b]->subID].longBonus;
+		speciality[b]->text = heroInst[b]->type->specDescr;
 
 		experience[b] = new LRClickableAreaWText();
 		experience[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + 45);

+ 15 - 19
client/Graphics.cpp

@@ -9,6 +9,7 @@
 #include "CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
+#include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CObjectHandler.h"
 #include "../lib/CGeneralTextHandler.h"
@@ -103,16 +104,6 @@ void Graphics::initializeBattleGraphics()
 		idx++;
 	}
 
-	//initializing battle hero animation
-	idx = config["heroes"].Vector().size();
-	battleHeroes.resize(idx);
-
-	idx = 0;
-	BOOST_FOREACH(const JsonNode &h, config["heroes"].Vector()) {
-		battleHeroes[idx] = h.String();
-		idx ++;
-	}
-
 	//initialization of AC->def name mapping
 	BOOST_FOREACH(const JsonNode &ac, config["ac_mapping"].Vector()) {
 		int ACid = ac["id"].Float();
@@ -169,22 +160,26 @@ void Graphics::loadHeroAnims()
 	std::vector<std::pair<int,int> > rotations; //first - group number to be rotated1, second - group number after rotation1
 	rotations += std::make_pair(6,10), std::make_pair(7,11), std::make_pair(8,12), std::make_pair(1,13),
 		std::make_pair(2,14), std::make_pair(3,15);
-	for(size_t i=0; i<GameConstants::F_NUMBER * 2; ++i)
+
+	for(size_t i=0; i<CGI->heroh->classes.heroClasses.size(); ++i)
 	{
-		std::ostringstream nm;
-		nm << "AH" << std::setw(2) << std::setfill('0') << i << "_.DEF";
-		loadHeroAnim(nm.str(), rotations, &Graphics::heroAnims);
+		const CHeroClass * hc = CGI->heroh->classes.heroClasses[i];
+
+		if (!vstd::contains(heroAnims, hc->imageMapFemale))
+			heroAnims[hc->imageMapFemale] = loadHeroAnim(hc->imageMapFemale, rotations);
+
+		if (!vstd::contains(heroAnims, hc->imageMapMale))
+			heroAnims[hc->imageMapMale] = loadHeroAnim(hc->imageMapMale, rotations);
 	}
 
-	loadHeroAnim("AB01_.DEF", rotations, &Graphics::boatAnims);
-	loadHeroAnim("AB02_.DEF", rotations, &Graphics::boatAnims);
-	loadHeroAnim("AB03_.DEF", rotations, &Graphics::boatAnims);
+	boatAnims.push_back(loadHeroAnim("AB01_.DEF", rotations));
+	boatAnims.push_back(loadHeroAnim("AB02_.DEF", rotations));
+	boatAnims.push_back(loadHeroAnim("AB03_.DEF", rotations));
 }
 
-void Graphics::loadHeroAnim( const std::string &name, const std::vector<std::pair<int,int> > &rotations, std::vector<CDefEssential *> Graphics::*dst )
+CDefEssential * Graphics::loadHeroAnim( const std::string &name, const std::vector<std::pair<int,int> > &rotations)
 {
 	CDefEssential *anim = CDefHandler::giveDefEss(name);
-	(this->*dst).push_back(anim);
 	int pom = 0; //how many groups has been rotated
 	for(int o=7; pom<6; ++o)
 	{
@@ -216,6 +211,7 @@ void Graphics::loadHeroAnim( const std::string &name, const std::vector<std::pai
 	{
 		CSDL_Ext::alphaTransform(anim->ourImages[ff].bitmap);
 	}
+	return anim;
 }
 
 void Graphics::loadHeroFlags(std::pair<std::vector<CDefEssential *> Graphics::*, std::vector<const char *> > &pr, bool mode)

+ 2 - 3
client/Graphics.h

@@ -49,7 +49,7 @@ public:
 	CDefEssential * resources32; //resources 32x32
 	CDefEssential * flags;
 	CDefEssential * heroMoveArrows;
-	std::vector<CDefEssential *> heroAnims; // [class id: 0 - 17]  //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
+	std::map<std::string, CDefEssential *> heroAnims; // [hero class def name]  //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
 	std::vector<CDefEssential *> boatAnims; // [boat type: 0 - 3]  //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
 	CDefHandler * FoWfullHide; //for Fog of War
 	CDefHandler * FoWpartialHide; //for For of War
@@ -63,7 +63,6 @@ public:
 	std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
 	//for battles
 	std::vector< std::vector< std::string > > battleBacks; //battleBacks[terType] - vector of possible names for certain terrain type
-	std::vector< std::string > battleHeroes; //battleHeroes[hero type] - name of def that has hero animation for battle
 	std::map< int, std::vector < std::string > > battleACToDef; //maps AC format to vector of appropriate def names
 	CDefEssential * spellEffectsPics; //bitmaps representing spells affecting a stack in battle
 	//spells
@@ -75,7 +74,7 @@ public:
 	void loadHeroFlags();
 	void loadHeroFlags(std::pair<std::vector<CDefEssential *> Graphics::*, std::vector<const char *> > &pr, bool mode);
 	void loadHeroAnims();
-	void loadHeroAnim(const std::string &name, const std::vector<std::pair<int,int> > &rotations, std::vector<CDefEssential *> Graphics::*dst);
+	CDefEssential *  loadHeroAnim(const std::string &name, const std::vector<std::pair<int,int> > &rotations);
 	void loadErmuToPicture();
 	void blueToPlayersAdv(SDL_Surface * sur, int player); //replaces blue interface colour with a color of player
 	void loadTrueType();

+ 6 - 3
client/mapHandler.cpp

@@ -566,9 +566,12 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 						dir = themp->moveDir;
 
 						//pick graphics of hero (or boat if hero is sailing)
-						iv = (themp->boat) 
-							? &graphics->boatAnims[themp->boat->subID]->ourImages
-							: &graphics->heroAnims[themp->type->heroClass->id]->ourImages;
+						if (themp->boat)
+							iv = &graphics->boatAnims[themp->boat->subID]->ourImages;
+						else if (themp->sex)
+							iv = &graphics->heroAnims[themp->type->heroClass->imageMapFemale]->ourImages;
+						else
+							iv = &graphics->heroAnims[themp->type->heroClass->imageMapMale]->ourImages;
 
 						//pick appropriate flag set
 						if(themp->boat)

+ 0 - 23
config/battles_graphics.json

@@ -28,29 +28,6 @@
 		"CMBKDECK.BMP"
 	],
 
-	// Hero animation used in battles.
-	// Each 2 def represent male and female heroes for each race
-	"heroes": [
-		"CH00.DEF",
-		"CH01.DEF",
-		"CH02.DEF",
-		"CH03.DEF",
-		"CH05.DEF",
-		"CH04.DEF",
-		"CH06.DEF",
-		"CH07.DEF",
-		"CH08.DEF",
-		"CH09.DEF",
-		"CH010.DEF",
-		"CH11.DEF",
-		"CH013.DEF",
-		"CH012.DEF",
-		"CH014.DEF",
-		"CH015.DEF",
-		"CH16.DEF",
-		"CH17.DEF"
-	],
-
 	// WoG_Ac_format_to_def_names_mapping
 	"ac_mapping": [
 		{ "id": 0, "defnames": [ "C10SPW.DEF" ] },

+ 38 - 0
config/heroClasses.json

@@ -0,0 +1,38 @@
+{
+	// battle animations for heroes, ordered by faction
+	"heroBattleAnim" :
+	[
+		{ "male" : "CH00.DEF",  "female" : "CH01.DEF" },
+		{ "male" : "CH02.DEF",  "female" : "CH03.DEF" },
+		{ "male" : "CH05.DEF",  "female" : "CH04.DEF" },
+		{ "male" : "CH06.DEF",  "female" : "CH07.DEF" },
+		{ "male" : "CH08.DEF",  "female" : "CH09.DEF" },
+		{ "male" : "CH010.DEF", "female" : "CH11.DEF" },
+		{ "male" : "CH013.DEF", "female" : "CH012.DEF" },
+		{ "male" : "CH014.DEF", "female" : "CH015.DEF" },
+		{ "male" : "CH16.DEF",  "female" : "CH17.DEF" }
+	],
+
+	// map animations for heroes, ordered by hero class
+	"heroMapAnim" :
+	[
+		"AH00_.def",
+		"AH01_.def",
+		"AH02_.def",
+		"AH03_.def",
+		"AH04_.def",
+		"AH05_.def",
+		"AH06_.def",
+		"AH07_.def",
+		"AH08_.def",
+		"AH09_.def",
+		"AH10_.def",
+		"AH11_.def",
+		"AH12_.def",
+		"AH13_.def",
+		"AH14_.def",
+		"AH15_.def",
+		"AH16_.def",
+		"AH17_.def"
+	]
+}

File diff suppressed because it is too large
+ 161 - 161
config/heroes.json


+ 0 - 19
lib/CGeneralTextHandler.cpp

@@ -189,25 +189,6 @@ void CGeneralTextHandler::load()
 		}
 		while (parser.endLine());
 	}
-	{
-		CLegacyConfigParser parser("DATA/HEROSPEC.TXT");
-		CLegacyConfigParser bioParser("DATA/HEROBIOS.TXT");
-
-		//skip header
-		parser.endLine();
-		parser.endLine();
-
-		do
-		{
-			HeroTexts texts;
-			texts.bonusName  = parser.readString();
-			texts.shortBonus = parser.readString();
-			texts.longBonus  = parser.readString();
-			texts.biography  = bioParser.readString();
-			hTxts.push_back(texts);
-		}
-		while (parser.endLine() && bioParser.endLine());
-	}
 	{
 		CLegacyConfigParser nameParser("DATA/MINENAME.TXT");
 		CLegacyConfigParser eventParser("DATA/MINEEVNT.TXT");

+ 0 - 8
lib/CGeneralTextHandler.h

@@ -58,14 +58,6 @@ public:
 class DLL_LINKAGE CGeneralTextHandler //Handles general texts
 {
 public:
-	class HeroTexts
-	{
-	public:
-		std::string bonusName, shortBonus, longBonus; //for special abilities
-		std::string biography; //biography, of course
-	};
-
-	std::vector<HeroTexts> hTxts;
 	std::vector<std::string> allTexts;
 
 	std::vector<std::string> arraytxt;

+ 176 - 57
lib/CHeroHandler.cpp

@@ -20,12 +20,6 @@
  *
  */
 
-CHeroClass::CHeroClass()
-{
-}
-CHeroClass::~CHeroClass()
-{
-}
 int CHeroClass::chooseSecSkill(const std::set<int> & possibles) const //picks secondary skill out from given possibilities
 {
 	if(possibles.size()==1)
@@ -119,17 +113,81 @@ void CHeroClassHandler::load()
 		VLC->modh->identifiers.registerObject("heroClass." + GameConstants::HERO_CLASSES_NAMES[hc->id], hc->id);
 	}
 	while (parser.endLine() && !parser.isNextEntryEmpty());
+
+	const JsonNode & heroGraphics = JsonNode(ResourceID("config/heroClasses.json"));
+
+	for (size_t i=0; i<heroClasses.size(); i++)
+	{
+		const JsonNode & battle = heroGraphics["heroBattleAnim"].Vector()[i/2];
+
+		heroClasses[i]->imageBattleFemale = battle["female"].String();
+		heroClasses[i]->imageBattleMale = battle["male"].String();
+
+		const JsonNode & map = heroGraphics["heroMapAnim"].Vector()[i];
+
+		heroClasses[i]->imageMapMale = map.String();
+		heroClasses[i]->imageMapFemale = map.String();
+	}
 }
 
 void CHeroClassHandler::load(const JsonNode & classes)
 {
-	//TODO
+	BOOST_FOREACH(auto & entry, classes.Struct())
+	{
+		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
+		{
+			CHeroClass * heroClass = loadClass(entry.second);
+			heroClass->identifier = entry.first;
+			heroClass->id = heroClasses.size();
+
+			heroClasses.push_back(heroClass);
+			tlog3 << "Added hero class: " << entry.first << "\n";
+			VLC->modh->identifiers.registerObject("heroClass." + heroClass->identifier, heroClass->id);
+		}
+	}
 }
 
-CHeroClass *CHeroClassHandler::loadClass(const JsonNode & heroClass)
+CHeroClass *CHeroClassHandler::loadClass(const JsonNode & node)
 {
-	//TODO
-	return new CHeroClass;
+	CHeroClass * heroClass = new CHeroClass;
+
+	heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String();
+	heroClass->imageBattleMale   = node["animation"]["battle"]["male"].String();
+	heroClass->imageMapFemale    = node["animation"]["map"]["female"].String();
+	heroClass->imageMapMale      = node["animation"]["map"]["male"].String();
+
+	heroClass->name = node["name"].String();
+
+	BOOST_FOREACH(const std::string & pSkill, PrimarySkill::names)
+	{
+		heroClass->primarySkillInitial.push_back(node["primarySkills"][pSkill].Float());
+		heroClass->primarySkillLowLevel.push_back(node["lowLevelChance"][pSkill].Float());
+		heroClass->primarySkillHighLevel.push_back(node["highLevelChance"][pSkill].Float());
+	}
+
+	BOOST_FOREACH(const std::string & secSkill, SecondarySkill::names)
+	{
+		heroClass->secSkillProbability.push_back(node["secondarySkills"][secSkill].Float());
+	}
+
+	BOOST_FOREACH(auto & tavern, node["tavern"].Struct())
+	{
+		int value = tavern.second.Float();
+
+		VLC->modh->identifiers.requestIdentifier("faction." + tavern.first,
+		[=](si32 factionID)
+		{
+			heroClass->selectionProbability[factionID] = value;
+		});
+	}
+
+	VLC->modh->identifiers.requestIdentifier("faction." + node["faction"].String(),
+	[=](si32 factionID)
+	{
+		heroClass->faction = factionID;
+	});
+
+	return heroClass;
 }
 
 CHeroClassHandler::~CHeroClassHandler()
@@ -149,21 +207,102 @@ CHeroHandler::~CHeroHandler()
 CHeroHandler::CHeroHandler()
 {}
 
-void CHeroHandler::load(const JsonNode & heroes)
+void CHeroHandler::load(const JsonNode & input)
 {
-	//TODO
+	BOOST_FOREACH(auto & entry, input.Struct())
+	{
+		if (!entry.second.isNull()) // may happens if mod removed creature by setting json entry to null
+		{
+			CHero * hero = loadHero(entry.second);
+			hero->ID = heroes.size();
+
+			heroes.push_back(hero);
+			tlog3 << "Added hero : " << entry.first << "\n";
+			VLC->modh->identifiers.registerObject("hero." + entry.first, hero->ID);
+		}
+	}
 }
 
-CHero * CHeroHandler::loadHero(const JsonNode & hero)
+CHero * CHeroHandler::loadHero(const JsonNode & node)
 {
-	//TODO
-	return new CHero;
+	CHero * hero = new CHero;
+
+	hero->name        = node["texts"]["name"].String();
+	hero->biography   = node["texts"]["biography"].String();
+	hero->specName    = node["texts"]["specialty"]["name"].String();
+	hero->specTooltip = node["texts"]["specialty"]["tooltip"].String();
+	hero->specDescr   = node["texts"]["specialty"]["description"].String();
+
+	hero->imageIndex = node["images"]["index"].Float();
+	hero->iconSpecSmall = node["images"]["specialtySmall"].String();
+	hero->iconSpecLarge = node["images"]["specialtyLarge"].String();
+	hero->portraitSmall = node["images"]["small"].String();
+	hero->portraitLarge = node["images"]["large"].String();
+
+	assert(node["army"].Vector().size() <= 3); // anything bigger is useless - army initialization uses up to 3 slots
+	hero->initialArmy.resize(node["army"].Vector().size());
+
+	for (size_t i=0; i< hero->initialArmy.size(); i++)
+	{
+		const JsonNode & source = node["army"].Vector()[i];
+
+		hero->initialArmy[i].minAmount = source["min"].Float();
+		hero->initialArmy[i].maxAmount = source["max"].Float();
+
+		assert(hero->initialArmy[i].minAmount <= hero->initialArmy[i].maxAmount);
+
+		VLC->modh->identifiers.requestIdentifier(std::string("creature.") + source["creature"].String(), [=](si32 creature)
+		{
+			hero->initialArmy[i].creature = creature;
+		});
+	}
+
+	loadHeroJson(hero, node);
+	return hero;
+}
+
+void CHeroHandler::loadHeroJson(CHero * hero, const JsonNode & node)
+{
+	// sex: 0=male, 1=female
+	hero->sex = !!node["female"].Bool();
+
+	BOOST_FOREACH(const JsonNode &set, node["skills"].Vector())
+	{
+		int skillID    = boost::range::find(SecondarySkill::names,  set["skill"].String()) - boost::begin(SecondarySkill::names);
+		int skillLevel = boost::range::find(SecondarySkill::levels, set["level"].String()) - boost::begin(SecondarySkill::levels);
+
+		hero->secSkillsInit.push_back(std::make_pair(skillID, skillLevel));
+	}
+
+	BOOST_FOREACH(const JsonNode & spell, node["spellbook"].Vector())
+	{
+		hero->spells.insert(spell.Float());
+	}
+
+	BOOST_FOREACH(const JsonNode &specialty, node["specialties"].Vector())
+	{
+		SSpecialtyInfo spec;
+
+		spec.type = specialty["type"].Float();
+		spec.val = specialty["val"].Float();
+		spec.subtype = specialty["subtype"].Float();
+		spec.additionalinfo = specialty["info"].Float();
+
+		hero->spec.push_back(spec); //put a copy of dummy
+	}
+
+	VLC->modh->identifiers.requestIdentifier("heroClass." + node["class"].String(),
+	[=](si32 classID)
+	{
+		hero->heroClass = classes.heroClasses[classID];
+	});
 }
 
 void CHeroHandler::load()
 {
 	classes.load();
 	loadHeroes();
+	loadHeroTexts();
 	loadObstacles();
 	loadTerrains();
 	loadBallistics();
@@ -233,6 +372,7 @@ void CHeroHandler::loadHeroes()
 		CHero * hero = new CHero;
 		hero->name = parser.readString();
 
+		hero->initialArmy.resize(3);
 		for(int x=0;x<3;x++)
 		{
 			hero->initialArmy[x].minAmount = parser.readNumber();
@@ -248,6 +388,7 @@ void CHeroHandler::loadHeroes()
 		parser.endLine();
 
 		hero->ID = heroes.size();
+		hero->imageIndex = hero->ID;
 		heroes.push_back(hero);
 	}
 
@@ -255,40 +396,29 @@ void CHeroHandler::loadHeroes()
 	const JsonNode config(ResourceID("config/heroes.json"));
 	BOOST_FOREACH(const JsonNode &hero, config["heroes"].Vector())
 	{
-		CHero * currentHero = heroes[hero["id"].Float()];
-
-		// sex: 0=male, 1=female
-		currentHero->sex = !!hero["female"].Bool();
-
-		BOOST_FOREACH(const JsonNode &set, hero["skill_set"].Vector())
-		{
-			int skillID    = boost::range::find(SecondarySkill::names,  set["skill"].String()) - boost::begin(SecondarySkill::names);
-			int skillLevel = boost::range::find(SecondarySkill::levels, set["level"].String()) - boost::begin(SecondarySkill::levels);
-			currentHero->secSkillsInit.push_back(std::make_pair(skillID, skillLevel));
-		}
-
-		if (!hero["spell"].isNull()) {
-			currentHero->startingSpell = hero["spell"].Float();
-		}
-
-		BOOST_FOREACH(const JsonNode &specialty, hero["specialties"].Vector())
-		{
-			SSpecialtyInfo dummy;
+		loadHeroJson(heroes[hero["id"].Float()], hero);
+	}
+}
 
-			dummy.type = specialty["type"].Float();
-			dummy.val = specialty["val"].Float();
-			dummy.subtype = specialty["subtype"].Float();
-			dummy.additionalinfo = specialty["info"].Float();
+void CHeroHandler::loadHeroTexts()
+{
+	CLegacyConfigParser parser("DATA/HEROSPEC.TXT");
+	CLegacyConfigParser bioParser("DATA/HEROBIOS.TXT");
 
-			currentHero->spec.push_back(dummy); //put a copy of dummy
-		}
+	//skip header
+	parser.endLine();
+	parser.endLine();
 
-		VLC->modh->identifiers.requestIdentifier("heroClass." + hero["class"].String(),
-		[=](si32 classID)
-		{
-			currentHero->heroClass = classes.heroClasses[classID];
-		});
+	int i=0;
+	do
+	{
+		CHero * hero = heroes[i++];
+		hero->specName    = parser.readString();
+		hero->specTooltip = parser.readString();
+		hero->specDescr   = parser.readString();
+		hero->biography   = bioParser.readString();
 	}
+	while (parser.endLine() && bioParser.endLine() && heroes.size() < i);
 }
 
 void CHeroHandler::loadBallistics()
@@ -360,15 +490,4 @@ std::vector<ui8> CHeroHandler::getDefaultAllowedHeroes() const
 	allowedHeroes[4] = 0;
 	allowedHeroes[25] = 0;
 	return allowedHeroes;
-}
-
-CHero::CHero()
-{
-	startingSpell = -1;
-	sex = 0xff;
-}
-
-CHero::~CHero()
-{
-
-}
+}

+ 30 - 11
lib/CHeroHandler.h

@@ -1,6 +1,5 @@
 #pragma once
 
-
 #include "../lib/ConstTransitivePtr.h"
 #include "GameConstants.h"
 
@@ -13,6 +12,7 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
+
 class CHeroClass;
 class CDefHandler;
 class CGameInfo;
@@ -46,23 +46,35 @@ public:
 		}
 	};
 
-	std::string name; //name of hero
 	si32 ID;
+	si32 imageIndex;
 
-	InitialArmyStack initialArmy[3];
+	std::vector<InitialArmyStack> initialArmy;
 
 	CHeroClass * heroClass;
 	std::vector<std::pair<ui8,ui8> > secSkillsInit; //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
 	std::vector<SSpecialtyInfo> spec;
-	si32 startingSpell; //-1 if none
+	std::set<si32> spells;
 	ui8 sex; // default sex: 0=male, 1=female
 
-	CHero();
-	~CHero();
+	/// Localized texts
+	std::string name; //name of hero
+	std::string biography;
+	std::string specName;
+	std::string specDescr;
+	std::string specTooltip;
+
+	/// Graphics
+	std::string iconSpecSmall;
+	std::string iconSpecLarge;
+	std::string portraitSmall;
+	std::string portraitLarge;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & name & ID & initialArmy & heroClass & secSkillsInit & spec & startingSpell & sex;
+		h & ID & imageIndex & initialArmy & heroClass & secSkillsInit & spec & spells & sex;
+		h & name & biography & specName & specDescr & specTooltip;
+		h & iconSpecSmall & iconSpecLarge & portraitSmall & portraitLarge;
 	}
 };
 
@@ -83,9 +95,12 @@ public:
 
 	std::map<TFaction, int> selectionProbability; //probability of selection in towns
 
+	std::string imageBattleMale;
+	std::string imageBattleFemale;
+	std::string imageMapMale;
+	std::string imageMapFemale;
+
 	int chooseSecSkill(const std::set<int> & possibles) const; //picks secondary skill out from given possibilities
-	CHeroClass(); //c-tor
-	~CHeroClass(); //d-tor
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -93,6 +108,7 @@ public:
 		h & primarySkillInitial   & primarySkillLowLevel;
 		h & primarySkillHighLevel & secSkillProbability;
 		h & selectionProbability;
+		h & imageBattleMale & imageBattleFemale & imageMapMale & imageMapFemale;
 	}
 	EAlignment::EAlignment getAlignment() const;
 };
@@ -130,7 +146,7 @@ public:
 	void load(const JsonNode & classes);
 
 	/// load one class from json
-	CHeroClass * loadClass(const JsonNode & heroClass);
+	CHeroClass * loadClass(const JsonNode & node);
 
 	~CHeroClassHandler();
 
@@ -146,6 +162,8 @@ class DLL_LINKAGE CHeroHandler
 	/// consists of 201 values. Any higher levels require experience larger that ui64 can hold
 	std::vector<ui64> expPerLevel;
 
+	/// common function for loading heroes from mods and from H3
+	void loadHeroJson(CHero * hero, const JsonNode & node);
 public:
 	CHeroClassHandler classes;
 
@@ -177,12 +195,13 @@ public:
 	void load(const JsonNode & heroes);
 
 	/// Load single hero from json
-	CHero * loadHero(const JsonNode & hero);
+	CHero * loadHero(const JsonNode & node);
 
 	/// Load everything (calls functions below + classes.load())
 	void load();
 
 	void loadHeroes();
+	void loadHeroTexts();
 	void loadExperience();
 	void loadBallistics();
 	void loadTerrains();

+ 11 - 6
lib/CObjectHandler.cpp

@@ -708,19 +708,23 @@ void CGHeroInstance::initHero()
 		initHeroDefInfo();
 	if(!type)
 		type = VLC->heroh->heroes[subID];
-	if(!vstd::contains(spells, 0xffffffff) && type->startingSpell >= 0) //hero starts with a spell
-		spells.insert(type->startingSpell);
+
+	if(!vstd::contains(spells, 0xffffffff)) //hero starts with a spell
+	{
+		BOOST_FOREACH(auto spellID, type->spells)
+			spells.insert(spellID);
+	}
 	else //remove placeholder
 		spells -= 0xffffffff;
 
-	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->startingSpell >= 0) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
+	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && !type->spells.empty()) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
 		putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0));
 
 	if(!getArt(ArtifactPosition::MACH4))
 		putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult
 
 	if(portrait < 0 || portrait == 255)
-		portrait = subID;
+		portrait = type->imageIndex;
 	if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL)))
 	{
 		for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
@@ -781,6 +785,8 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= NULL*/)
 	else
 		howManyStacks = 3;
 
+	vstd::amin(howManyStacks, type->initialArmy.size());
+
 	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
 	{
 		auto & stack = type->initialArmy[stackNo];
@@ -892,8 +898,7 @@ const std::string & CGHeroInstance::getBiography() const
 {
 	if (biography.length())
 		return biography;
-	else
-		return VLC->generaltexth->hTxts[subID].biography;
+	return type->biography;
 }
 void CGHeroInstance::initObj()
 {

+ 7 - 1
lib/CTownHandler.cpp

@@ -472,7 +472,13 @@ void CTownHandler::load(const JsonNode &source)
 {
 	BOOST_FOREACH(auto & node, source.Struct())
 	{
-		int id = node.second["index"].Float();
+		int id;
+
+		if (node.second["index"].isNull())
+			id = factions.rbegin()->first + 1;
+		else
+			id = node.second["index"].Float();
+
 		CFaction & faction = factions[id];
 
 		faction.factionID = id;

+ 6 - 6
lib/JsonNode.cpp

@@ -416,16 +416,16 @@ bool JsonParser::extractWhitespace(bool verbose)
 
 bool JsonParser::extractEscaping(std::string &str)
 {
-	switch(input[pos++])
+	switch(input[pos])
 	{
 		break; case '\"': str += '\"';
 		break; case '\\': str += '\\';
 		break; case  '/': str += '/';
-		break; case '\b': str += '\b';
-		break; case '\f': str += '\f';
-		break; case '\n': str += '\n';
-		break; case '\r': str += '\r';
-		break; case '\t': str += '\t';
+		break; case 'b': str += '\b';
+		break; case 'f': str += '\f';
+		break; case 'n': str += '\n';
+		break; case 'r': str += '\r';
+		break; case 't': str += '\t';
 		break; default: return error("Unknown escape sequence!", true);
 	};
 	return true;

Some files were not shown because too many files changed in this diff