Browse Source

Refactoring of H3M loader to make HotA format support easier

- extracted low-level reader from MapFormatH3M class
- added separate structure to define version-specific values
- cleared up some H3M format edge cases
- replaced witch hut skill vector with set
- converted several fields to enum type
Ivan Savenko 2 years ago
parent
commit
3738171b21

+ 1 - 1
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -294,7 +294,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
 {
 	for(auto tdi : developmentInfos)
 	{
-		if(tdi.town->alignment == alignment && tdi.town->hasBuilt(bid))
+		if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
 			return true;
 	}
 

+ 1 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -848,7 +848,7 @@ public:
 
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 {
-	if(ai->buildAnalyzer->hasAnyBuilding(town->alignment, bi.id))
+	if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
 		return 0;
 
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

+ 3 - 0
client/CMT.cpp

@@ -117,6 +117,9 @@ void init()
 
 	logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff());
 
+	// Debug code to load all maps on start
+	//ClientCommandManager commandController;
+	//commandController.processCommand("convert txt", false);
 }
 
 static void prog_version()

+ 1 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -327,7 +327,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	if(!hero->type->battleImage.empty())
 		animationPath = hero->type->battleImage;
 	else
-	if(hero->sex)
+	if(hero->gender == EHeroGender::FEMALE)
 		animationPath = hero->type->heroClass->imageBattleFemale;
 	else
 		animationPath = hero->type->heroClass->imageBattleMale;

+ 6 - 4
client/lobby/SelectionTab.cpp

@@ -333,7 +333,7 @@ void SelectionTab::filter(int size, bool selectFirst)
 	{
 		for(auto elem : allItems)
 		{
-			if(elem->mapHeader && elem->mapHeader->version && (!size || elem->mapHeader->width == size))
+			if(elem->mapHeader && (!size || elem->mapHeader->width == size))
 				curItems.push_back(elem);
 		}
 	}
@@ -537,10 +537,12 @@ void SelectionTab::parseMaps(const std::unordered_set<ResourceID> & files)
 			auto mapInfo = std::make_shared<CMapInfo>();
 			mapInfo->mapInit(file.getName());
 
-			// ignore unsupported map versions (e.g. WoG maps without WoG)
-			// but accept VCMI maps
-			if((mapInfo->mapHeader->version >= EMapFormat::VCMI) || (mapInfo->mapHeader->version <= CGI->settings()->getInteger(EGameSettings::TEXTS_MAP_VERSION)))
+			EMapFormat maxSupported = static_cast<EMapFormat>(CGI->settings()->getInteger(EGameSettings::TEXTS_MAP_VERSION));
+
+			if(mapInfo->mapHeader->version == EMapFormat::VCMI || mapInfo->mapHeader->version <= maxSupported)
 				allItems.push_back(mapInfo);
+
+			allItems.push_back(mapInfo);
 		}
 		catch(std::exception & e)
 		{

+ 1 - 1
client/windows/CHeroWindow.cpp

@@ -335,7 +335,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 
 	formations->resetCallback();
 	//setting formations
-	formations->setSelected(curHero->formation);
+	formations->setSelected(curHero->formation == EArmyFormation::TIGHT ? 1 : 0);
 	formations->addCallback([=](int value){ LOCPLINT->cb->setFormation(curHero, value);});
 
 	morale->set(&heroWArt);

+ 4 - 0
cmake_modules/VCMI_lib.cmake

@@ -77,7 +77,9 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapping/CMapOperation.cpp
 		${MAIN_LIB_DIR}/mapping/CMapService.cpp
 		${MAIN_LIB_DIR}/mapping/MapEditUtils.cpp
+		${MAIN_LIB_DIR}/mapping/MapFeaturesH3M.cpp
 		${MAIN_LIB_DIR}/mapping/MapFormatH3M.cpp
+		${MAIN_LIB_DIR}/mapping/MapReaderH3M.cpp
 		${MAIN_LIB_DIR}/mapping/MapFormatJson.cpp
 
 		${MAIN_LIB_DIR}/registerTypes/RegisterTypes.cpp
@@ -347,7 +349,9 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapping/CMapOperation.h
 		${MAIN_LIB_DIR}/mapping/CMapService.h
 		${MAIN_LIB_DIR}/mapping/MapEditUtils.h
+		${MAIN_LIB_DIR}/mapping/MapFeaturesH3M.h
 		${MAIN_LIB_DIR}/mapping/MapFormatH3M.h
+		${MAIN_LIB_DIR}/mapping/MapReaderH3M.h
 		${MAIN_LIB_DIR}/mapping/MapFormatJson.h
 
 		${MAIN_LIB_DIR}/registerTypes/RegisterTypes.h

+ 13 - 16
lib/CCreatureSet.cpp

@@ -418,7 +418,10 @@ int CCreatureSet::stacksCount() const
 
 void CCreatureSet::setFormation(bool tight)
 {
-	formation = tight;
+	if (tight)
+		formation = EArmyFormation::TIGHT;
+	else
+		formation = EArmyFormation::WIDE;
 }
 
 void CCreatureSet::setStackCount(const SlotID & slot, TQuantity count)
@@ -694,7 +697,6 @@ void CStackInstance::init()
 	experience = 0;
 	count = 0;
 	type = nullptr;
-	idRand = -1;
 	_armyObj = nullptr;
 	setNodeType(STACK_INSTANCE);
 }
@@ -815,8 +817,7 @@ std::string CStackInstance::getQuantityTXT(bool capitalized) const
 
 bool CStackInstance::valid(bool allowUnrandomized) const
 {
-	bool isRand = (idRand != -1);
-	if(!isRand)
+	if(!randomStack)
 	{
 		return (type  &&  type == VLC->creh->objects[type->getId()]);
 	}
@@ -830,8 +831,6 @@ std::string CStackInstance::nodeName() const
 	oss << "Stack of " << count << " of ";
 	if(type)
 		oss << type->getNamePluralTextID();
-	else if(idRand >= 0)
-		oss << "[no type, idRand=" << idRand << "]";
 	else
 		oss << "[UNDEFINED TYPE]";
 
@@ -888,14 +887,13 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
 
 	if(handler.saving)
 	{
-		if(idRand > -1)
+		if(randomStack)
 		{
-			int level = idRand / 2;
-
-			boost::logic::tribool upgraded = (idRand % 2) > 0;
+			int level = randomStack->level;
+			int upgrade = randomStack->upgrade;
 
 			handler.serializeInt("level", level, 0);
-			handler.serializeBool("upgraded", upgraded);
+			handler.serializeInt("upgraded", upgrade, 0);
 		}
 	}
 	else
@@ -903,13 +901,13 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
 		//type set by CStackBasicDescriptor::serializeJson
 		if(type == nullptr)
 		{
-			int level = 0;
-			bool upgraded = false;
+			uint8_t level = 0;
+			uint8_t upgrade = 0;
 
 			handler.serializeInt("level", level, 0);
-			handler.serializeBool("upgraded", upgraded);
+			handler.serializeInt("upgrade", upgrade, 0);
 
-			idRand = level * 2 + static_cast<int>(upgraded);
+			randomStack = RandomStackInfo{ level, upgrade };
 		}
 	}
 }
@@ -946,7 +944,6 @@ void CCommanderInstance::init()
 	level = 1;
 	count = 1;
 	type = nullptr;
-	idRand = -1;
 	_armyObj = nullptr;
 	setNodeType (CBonusSystemNode::COMMANDER);
 	secondarySkills.resize (ECommander::SPELL_POWER + 1);

+ 16 - 6
lib/CCreatureSet.h

@@ -69,11 +69,13 @@ protected:
 	const CArmedInstance *_armyObj; //stack must be part of some army, army must be part of some object
 
 public:
-	// hlp variable used during loading map, when object (hero or town) have creatures that must have same alignment.
-	// idRand < 0 -> normal, non-random creature
-	// idRand / 2 -> level
-	// idRand % 2 -> upgrade number
-	int idRand;
+	struct RandomStackInfo
+	{
+		uint8_t level;
+		uint8_t upgrade;
+	};
+	// helper variable used during loading map, when object (hero or town) have creatures that must have same alignment.
+	boost::optional<RandomStackInfo> randomStack;
 
 	const CArmedInstance * const & armyObj; //stack must be part of some army, army must be part of some object
 	TExpType experience;//commander needs same amount of exp as hero
@@ -200,13 +202,21 @@ public:
 	}
 };
 
+enum class EArmyFormation : uint8_t
+{
+	WIDE,
+	TIGHT
+};
+
 class DLL_LINKAGE CCreatureSet : public IArmyDescriptor //seven combined creatures
 {
 	CCreatureSet(const CCreatureSet &) = delete;
 	CCreatureSet &operator=(const CCreatureSet&);
 public:
+
+
 	TSlots stacks; //slots[slot_id]->> pair(creature_id,creature_quantity)
-	ui8 formation = 0; //0 - wide, 1 - tight
+	EArmyFormation formation = EArmyFormation::WIDE; //0 - wide, 1 - tight
 
 	CCreatureSet() = default; //Should be here to avoid compile errors
 	virtual ~CCreatureSet();

+ 1 - 1
lib/CGameState.cpp

@@ -496,7 +496,7 @@ std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
 		return std::make_pair(Obj::RESOURCE,getRandomGenerator().nextInt(6)); //now it's OH3 style, use %8 for mithril
 	case Obj::RANDOM_TOWN:
 		{
-			PlayerColor align = PlayerColor((dynamic_cast<CGTownInstance *>(obj))->alignment);
+			PlayerColor align = (dynamic_cast<CGTownInstance *>(obj))->alignmentToPlayer;
 			si32 f; // can be negative (for random)
 			if(align >= PlayerColor::PLAYER_LIMIT) //same as owner / random
 			{

+ 1 - 1
lib/CHeroHandler.cpp

@@ -420,7 +420,7 @@ CHero * CHeroHandler::loadFromJson(const std::string & scope, const JsonNode & n
 	hero->ID = HeroTypeID(index);
 	hero->identifier = identifier;
 	hero->modScope = scope;
-	hero->sex = node["female"].Bool();
+	hero->gender = node["female"].Bool() ? EHeroGender::FEMALE : EHeroGender::MALE;
 	hero->special = node["special"].Bool();
 
 	VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"].String());

+ 9 - 2
lib/CHeroHandler.h

@@ -29,6 +29,13 @@ class CRandomGenerator;
 class JsonSerializeFormat;
 class BattleField;
 
+enum class EHeroGender : uint8_t
+{
+	MALE = 0,
+	FEMALE = 1,
+	DEFAULT = 0xff // from h3m, instance has same gender as hero type
+};
+
 class DLL_LINKAGE CHero : public HeroType
 {
 	friend class CHeroHandler;
@@ -61,7 +68,7 @@ public:
 	std::set<SpellID> spells;
 	bool haveSpellBook = false;
 	bool special = false; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes
-	ui8 sex = 0; // default sex: 0=male, 1=female
+	EHeroGender gender = EHeroGender::MALE; // default sex: 0=male, 1=female
 
 	/// Graphics
 	std::string iconSpecSmall;
@@ -104,7 +111,7 @@ public:
 		h & specialty;
 		h & spells;
 		h & haveSpellBook;
-		h & sex;
+		h & gender;
 		h & special;
 		h & iconSpecSmall;
 		h & iconSpecLarge;

+ 1 - 0
lib/GameConstants.h

@@ -1079,6 +1079,7 @@ public:
 		FIRST_AID_TENT = 6,
 		//CENTAUR_AXE = 7,
 		//BLACKSHARD_OF_THE_DEAD_KNIGHT = 8,
+		VIAL_OF_DRAGON_BLOOD = 127,
 		ARMAGEDDONS_BLADE = 128,
 		TITANS_THUNDER = 135,
 		//CORNUCOPIA = 140,

+ 4 - 3
lib/battle/BattleInfo.cpp

@@ -414,13 +414,14 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 		for(auto i = armies[side]->Slots().begin(); i != armies[side]->Slots().end(); i++, k++)
 		{
 			std::vector<int> *formationVector = nullptr;
-			if(creatureBank)
-				formationVector = &creBankFormations[side][formationNo];
-			else if(armies[side]->formation)
+			if(armies[side]->formation == EArmyFormation::TIGHT )
 				formationVector = &tightFormations[side][formationNo];
 			else
 				formationVector = &looseFormations[side][formationNo];
 
+			if(creatureBank)
+				formationVector = &creBankFormations[side][formationNo];
+
 			BattleHex pos = (k < formationVector->size() ? formationVector->at(k) : 0);
 			if(creatureBank && i->second->type->isDoubleWide())
 				pos += side ? BattleHex::LEFT : BattleHex::RIGHT;

+ 4 - 5
lib/mapObjects/CArmedInstance.cpp

@@ -23,14 +23,13 @@ void CArmedInstance::randomizeArmy(int type)
 {
 	for (auto & elem : stacks)
 	{
-		int & randID = elem.second->idRand;
-		if(randID >= 0)
+		if(elem.second->randomStack)
 		{
-			int level = randID / 2;
-			bool upgrade = randID % 2;
+			int level = elem.second->randomStack->level;
+			int upgrade = elem.second->randomStack->upgrade;
 			elem.second->setType((*VLC->townh)[type]->town->creatures[level][upgrade]);
 
-			randID = -1;
+			elem.second->randomStack = boost::none;
 		}
 		assert(elem.second->valid(false));
 		assert(elem.second->armyObj == this);

+ 7 - 5
lib/mapObjects/CGHeroInstance.cpp

@@ -232,7 +232,7 @@ CGHeroInstance::CGHeroInstance():
 	portrait(UNINITIALIZED_PORTRAIT),
 	level(1),
 	exp(UNINITIALIZED_EXPERIENCE),
-	sex(std::numeric_limits<ui8>::max()),
+	gender(EHeroGender::DEFAULT),
 	lowestCreatureSpeed(0)
 {
 	setNodeType(HERO);
@@ -296,8 +296,8 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::DEFAULT, -1)) //set secondary skills to default
 		secSkills = type->secSkillsInit;
 
-	if (sex == 0xFF)//sex is default
-		sex = type->sex;
+	if (gender == EHeroGender::DEFAULT)
+		gender = type->gender;
 
 	setFormation(false);
 	if (!stacksCount()) //standard army//initial army
@@ -1468,7 +1468,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 	}
 
 	handler.serializeString("name", nameCustom);
-	handler.serializeBool<ui8>("female", sex, 1, 0, 0xFF);
+	handler.serializeInt("gender", gender, 0);
 
 	{
 		const int legacyHeroes = VLC->settings()->getInteger(EGameSettings::TEXTS_HERO);
@@ -1619,8 +1619,10 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 			setHeroTypeName(typeName);
 	}
 
+	static const std::vector<std::string> FORMATIONS  =	{ "wide", "tight" };
+
 	CCreatureSet::serializeJson(handler, "army", 7);
-	handler.serializeBool<ui8>("tightFormation", formation, 1, 0, 0);
+	handler.serializeEnum("formation", formation, FORMATIONS);
 
 	{
 		static constexpr int NO_PATROLING = -1;

+ 3 - 2
lib/mapObjects/CGHeroInstance.h

@@ -25,6 +25,7 @@ class CGTownInstance;
 class CMap;
 struct TerrainTile;
 struct TurnInfo;
+enum class EHeroGender : uint8_t;
 
 class CGHeroPlaceholder : public CGObjectInstance
 {
@@ -69,7 +70,7 @@ public:
 	si32 mana; // remaining spell points
 	std::vector<std::pair<SecondarySkill,ui8> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
 	ui32 movement; //remaining movement points
-	ui8 sex;
+	EHeroGender gender;
 
 	std::string nameCustom;
 	std::string biographyCustom;
@@ -312,7 +313,7 @@ public:
 		h & mana;
 		h & secSkills;
 		h & movement;
-		h & sex;
+		h & gender;
 		h & inTownGarrison;
 		h & spells;
 		h & patrol;

+ 4 - 2
lib/mapObjects/CGTownInstance.cpp

@@ -634,7 +634,7 @@ CGTownInstance::CGTownInstance():
 	builded(0),
 	destroyed(0),
 	identifier(0),
-	alignment(0xff)
+	alignmentToPlayer(PlayerColor::NEUTRAL)
 {
 	this->setNodeType(CBonusSystemNode::TOWN);
 }
@@ -1494,9 +1494,11 @@ void CGTownInstance::reset()
 
 void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 {
+	static const std::vector<std::string> FORMATIONS  =	{ "wide", "tight" };
+
 	CGObjectInstance::serializeJsonOwner(handler);
 	CCreatureSet::serializeJson(handler, "army", 7);
-	handler.serializeBool<ui8>("tightFormation", formation, 1, 0, 0);
+	handler.serializeEnum("tightFormation", formation, FORMATIONS);
 	handler.serializeString("name", name);
 
 	{

+ 2 - 2
lib/mapObjects/CGTownInstance.h

@@ -214,7 +214,7 @@ public:
 	si32 destroyed; //how many buildings has been destroyed this turn
 	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
 	ui32 identifier; //special identifier from h3m (only > RoE maps)
-	si32 alignment;
+	PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player
 	std::set<BuildingID> forbiddenBuildings;
 	std::set<BuildingID> builtBuildings;
 	std::set<BuildingID> overriddenBuildings; ///buildings which bonuses are overridden and should not be applied
@@ -239,7 +239,7 @@ public:
 		h & identifier;
 		h & garrisonHero;
 		h & visitingHero;
-		h & alignment;
+		h & alignmentToPlayer;
 		h & forbiddenBuildings;
 		h & builtBuildings;
 		h & bonusValue;

+ 17 - 45
lib/mapObjects/MiscObjects.cpp

@@ -664,23 +664,13 @@ void CGMine::initObj(CRandomGenerator & rand)
 		auto * troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes);
 		putStack(SlotID(0), troglodytes);
 
-		//after map reading tempOwner placeholds bitmask for allowed resources
-		std::vector<GameResID> possibleResources;
-		for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
-			if(tempOwner.getNum() & 1<<i) //NOTE: reuse of tempOwner
-				possibleResources.push_back(GameResID(i));
-
-		assert(!possibleResources.empty());
-		producedResource = *RandomGeneratorUtil::nextItem(possibleResources, rand);
-		tempOwner = PlayerColor::NEUTRAL;
+		assert(!abandonedMineResources.empty());
+		producedResource = *RandomGeneratorUtil::nextItem(abandonedMineResources, rand);
 	}
 	else
 	{
 		producedResource = GameResID(subID);
-		if(tempOwner >= PlayerColor::PLAYER_LIMIT)
-			tempOwner = PlayerColor::NEUTRAL;
 	}
-
 	producedQuantity = defaultResProduction();
 }
 
@@ -766,14 +756,11 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 		if(handler.saving)
 		{
 			JsonNode node(JsonNode::JsonType::DATA_VECTOR);
-			for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+			for(auto const & resID : abandonedMineResources)
 			{
-				if(tempOwner.getNum() & 1<<i)
-				{
-					JsonNode one(JsonNode::JsonType::DATA_STRING);
-					one.String() = GameConstants::RESOURCE_NAMES[i];
-					node.Vector().push_back(one);
-				}
+				JsonNode one(JsonNode::JsonType::DATA_STRING);
+				one.String() = GameConstants::RESOURCE_NAMES[resID];
+				node.Vector().push_back(one);
 			}
 			handler.serializeRaw("possibleResources", node, boost::none);
 		}
@@ -781,32 +768,17 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 		{
 			auto guard = handler.enterArray("possibleResources");
 			const JsonNode & node = handler.getCurrent();
-			std::set<int> possibleResources;
-
-			if(node.getType() != JsonNode::JsonType::DATA_VECTOR || node.Vector().empty())
-			{
-				//assume all allowed
-				for(int i = static_cast<int>(EGameResID::WOOD); i < static_cast<int>(EGameResID::GOLD); i++)
-					possibleResources.insert(i);
-			}
-			else
-			{
-				auto names = node.convertTo<std::vector<std::string>>();
+			std::set<int> abandonedMineResources;
 
-				for(const std::string & s : names)
-				{
-					int raw_res = vstd::find_pos(GameConstants::RESOURCE_NAMES, s);
-					if(raw_res < 0)
-						logGlobal->error("Invalid resource name: %s", s);
-					else
-						possibleResources.insert(raw_res);
-				}
+			auto names = node.convertTo<std::vector<std::string>>();
 
-				int tmp = 0;
-
-				for(int r : possibleResources)
-					tmp |=  (1<<r);
-				tempOwner = PlayerColor(tmp);
+			for(const std::string & s : names)
+			{
+				int raw_res = vstd::find_pos(GameConstants::RESOURCE_NAMES, s);
+				if(raw_res < 0)
+					logGlobal->error("Invalid resource name: %s", s);
+				else
+					abandonedMineResources.insert(raw_res);
 			}
 		}
 	}
@@ -1427,7 +1399,7 @@ void CGWitchHut::initObj(CRandomGenerator & rand)
 		// Necromancy can't be learned on random maps
 		for(int i = 0; i < VLC->skillh->size(); i++)
 			if(VLC->skillh->getByIndex(i)->getId() != SecondarySkill::NECROMANCY)
-				allowedAbilities.push_back(i);
+				allowedAbilities.insert(i);
 	}
 	ability = *RandomGeneratorUtil::nextItem(allowedAbilities, rand);
 }
@@ -1503,7 +1475,7 @@ void CGWitchHut::serializeJsonOptions(JsonSerializeFormat & handler)
 		allowedAbilities.clear();
 		for(si32 i = 0; i < skillCount; ++i)
 			if(temp[i])
-				allowedAbilities.push_back(i);
+				allowedAbilities.insert(i);
 	}
 }
 

+ 3 - 1
lib/mapObjects/MiscObjects.h

@@ -131,7 +131,7 @@ protected:
 class DLL_LINKAGE CGWitchHut : public CTeamVisited
 {
 public:
-	std::vector<si32> allowedAbilities;
+	std::set<si32> allowedAbilities;
 	ui32 ability;
 
 	std::string getHoverText(PlayerColor player) const override;
@@ -265,6 +265,7 @@ class DLL_LINKAGE CGMine : public CArmedInstance
 public:
 	GameResID producedResource;
 	ui32 producedQuantity;
+	std::set<GameResID> abandonedMineResources;
 
 private:
 	void onHeroVisit(const CGHeroInstance * h) const override;
@@ -285,6 +286,7 @@ public:
 		h & static_cast<CArmedInstance&>(*this);
 		h & producedResource;
 		h & producedQuantity;
+		h & abandonedMineResources;
 	}
 	ui32 defaultResProduction() const;
 

+ 1 - 1
lib/mapping/CMap.cpp

@@ -35,7 +35,7 @@ SHeroName::SHeroName() : heroId(-1)
 
 PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false),
 	aiTactic(EAiTactic::RANDOM), isFactionRandom(false), hasRandomHero(false), mainCustomHeroPortrait(-1), mainCustomHeroId(-1), hasMainTown(false),
-	generateHeroAtMainTown(false), posOfMainTown(-1), team(TeamID::NO_TEAM), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1)
+	generateHeroAtMainTown(false), posOfMainTown(-1), team(TeamID::NO_TEAM)
 {
 	allowedFactions = VLC->townh->getAllowedFactions();
 }

+ 7 - 19
lib/mapping/CMap.h

@@ -86,17 +86,9 @@ struct DLL_LINKAGE PlayerInfo
 	int3 posOfMainTown;
 	TeamID team; /// The default value NO_TEAM
 
-
-	bool generateHero; /// Unused.
-	si32 p7; /// Unknown and unused.
-	/// Unused. Count of hero placeholders containing hero type.
-	/// WARNING: powerPlaceholders sometimes gives false 0 (eg. even if there is one placeholder), maybe different meaning ???
-	ui8 powerPlaceholders;
-
 	template <typename Handler>
 	void serialize(Handler & h, const int version)
 	{
-		h & p7;
 		h & hasRandomHero;
 		h & mainCustomHeroId;
 		h & canHumanPlay;
@@ -111,7 +103,6 @@ struct DLL_LINKAGE PlayerInfo
 		h & generateHeroAtMainTown;
 		h & posOfMainTown;
 		h & team;
-		h & generateHero;
 		h & mainHeroInstance;
 	}
 };
@@ -259,20 +250,17 @@ struct DLL_LINKAGE DisposedHero
 	}
 };
 
-namespace EMapFormat
-{
-enum EMapFormat: ui8
+enum class EMapFormat: uint8_t
 {
 	INVALID = 0,
 	//    HEX     DEC
-	ROE = 0x0e, // 14
-	AB  = 0x15, // 21
-	SOD = 0x1c, // 28
-// HOTA = 0x1e ... 0x20 // 28 ... 30
-	WOG = 0x33,  // 51
+	ROE  = 0x0e, // 14
+	AB   = 0x15, // 21
+	SOD  = 0x1c, // 28
+	HOTA = 0x1e, // 30
+	WOG  = 0x33, // 51
 	VCMI = 0xF0
 };
-}
 
 /// The map header holds information about loss/victory condition,map format, version, players, height, width,...
 class DLL_LINKAGE CMapHeader
@@ -293,7 +281,7 @@ public:
 
 	ui8 levels() const;
 
-	EMapFormat::EMapFormat version; /// The default value is EMapFormat::SOD.
+	EMapFormat version; /// The default value is EMapFormat::SOD.
 	si32 height; /// The default value is 72.
 	si32 width; /// The default value is 72.
 	bool twoLevel; /// The default value is true.

+ 4 - 4
lib/mapping/CMapService.cpp

@@ -120,10 +120,10 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
 			case 0x00088B1F:
 				stream = std::unique_ptr<CInputStream>(new CCompressedStream(std::move(stream), true));
 				return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
-			case EMapFormat::WOG :
-			case EMapFormat::AB  :
-			case EMapFormat::ROE :
-			case EMapFormat::SOD :
+			case static_cast<int>(EMapFormat::WOG) :
+			case static_cast<int>(EMapFormat::AB)  :
+			case static_cast<int>(EMapFormat::ROE) :
+			case static_cast<int>(EMapFormat::SOD) :
 				return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(mapName, modName, encoding, stream.get()));
 			default :
 				throw std::runtime_error("Unknown map format");

+ 116 - 0
lib/mapping/MapFeaturesH3M.cpp

@@ -0,0 +1,116 @@
+/*
+ * MapFeaturesH3M.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "MapFeaturesH3M.h"
+#include "CMap.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::find(EMapFormat format)
+{
+	switch (format)
+	{
+		case EMapFormat::ROE:  return getFeaturesROE();
+		case EMapFormat::AB:   return getFeaturesAB();
+		case EMapFormat::SOD:  return getFeaturesSOD();
+		case EMapFormat::WOG:  return getFeaturesWOG();
+		case EMapFormat::HOTA: return getFeaturesHOTA();
+		default:
+			throw std::runtime_error("Invalid map format!");
+	}
+}
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesROE()
+{
+	MapFormatFeaturesH3M result;
+	result.levelROE = true;
+
+	result.factionsBytes = 1;
+	result.heroesBytes = 16;
+	result.artifactsBytes = 16;
+	result.skillsBytes = 4;
+	result.resourcesBytes = 4;
+	result.spellsBytes = 9;
+	result.buildingsBytes = 6;
+
+	result.factionsCount = 8;
+	result.heroesCount = 128;
+	result.heroesPortraitsCount = 128;
+	result.artifactsCount = 127;
+	result.resourcesCount = 7;
+	result.creaturesCount = 118;
+	result.spellsCount = 70;
+	result.skillsCount = 28;
+	result.terrainsCount = 10;
+	result.artifactSlotsCount = 18;
+	result.buildingsCount = 40;
+
+	result.heroIdentifierInvalid = 0xff;
+	result.artifactIdentifierInvalid = 0xff;
+	result.creatureIdentifierInvalid = 0xff;
+	result.spellIdentifierInvalid = 0xff;
+
+	return result;
+}
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesAB()
+{
+	MapFormatFeaturesH3M result = getFeaturesROE();
+	result.levelAB = true;
+
+	result.factionsBytes = 2; // + Conflux
+	result.factionsCount = 9;
+
+	result.creaturesCount = 144; // + Conflux and new neutrals
+
+	result.heroesCount = 156; // + Conflux and campaign heroes
+	result.heroesPortraitsCount = 163;
+	result.heroesBytes = 20;
+
+	result.artifactsCount = 129; // + Armaggedon Blade and Vial of Dragon Blood
+	result.artifactsBytes = 17;
+
+	result.artifactIdentifierInvalid = 0xffff; // Now uses 2 bytes / object
+	result.creatureIdentifierInvalid = 0xffff; // Now uses 2 bytes / object
+
+	return result;
+}
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesSOD()
+{
+	MapFormatFeaturesH3M result = getFeaturesAB();
+	result.levelSOD = true;
+
+	result.artifactsCount = 141; // + Combined artifacts
+	result.artifactsBytes = 18;
+
+	result.artifactSlotsCount = 19; // + MISC_5 slot
+
+	return result;
+}
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesWOG()
+{
+	MapFormatFeaturesH3M result = getFeaturesSOD();
+	result.levelWOG = true;
+
+	return result;
+}
+
+MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesHOTA()
+{
+	MapFormatFeaturesH3M result = getFeaturesSOD();
+	result.levelHOTA = true;
+
+	return result;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 69 - 0
lib/mapping/MapFeaturesH3M.h

@@ -0,0 +1,69 @@
+/*
+ * MapFeaturesH3M.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+enum class EMapFormat : uint8_t;
+
+struct MapFormatFeaturesH3M
+{
+public:
+	static MapFormatFeaturesH3M find(EMapFormat format);
+	static MapFormatFeaturesH3M getFeaturesROE();
+	static MapFormatFeaturesH3M getFeaturesAB();
+	static MapFormatFeaturesH3M getFeaturesSOD();
+	static MapFormatFeaturesH3M getFeaturesWOG();
+	static MapFormatFeaturesH3M getFeaturesHOTA();
+
+	MapFormatFeaturesH3M() = default;
+
+	// number of bytes in bitmask of appropriate type
+
+	int factionsBytes;
+	int heroesBytes;
+	int artifactsBytes;
+	int resourcesBytes;
+	int skillsBytes;
+	int spellsBytes;
+	int buildingsBytes;
+
+	// total number of elements of appropriate type
+
+	int factionsCount;
+	int heroesCount;
+	int heroesPortraitsCount;
+	int artifactsCount;
+	int resourcesCount;
+	int creaturesCount;
+	int spellsCount;
+	int skillsCount;
+	int terrainsCount;
+	int artifactSlotsCount;
+	int buildingsCount;
+
+	// identifier that should be treated as "invalid", usually - '-1'
+
+	int heroIdentifierInvalid;
+	int artifactIdentifierInvalid;
+	int creatureIdentifierInvalid;
+	int spellIdentifierInvalid;
+
+	// features from which map format are available
+
+	bool levelROE = false;
+	bool levelAB = false;
+	bool levelSOD = false;
+	bool levelWOG = false;
+	bool levelHOTA = false;
+};
+
+VCMI_LIB_NAMESPACE_END

File diff suppressed because it is too large
+ 223 - 383
lib/mapping/MapFormatH3M.cpp


+ 12 - 39
lib/mapping/MapFormatH3M.h

@@ -11,17 +11,12 @@
 #pragma once
 
 #include "CMapService.h"
-#include "../GameConstants.h"
-#include "../ResourceSet.h"
-#include "../mapObjects/ObjectTemplate.h"
-
-#include "../int3.h"
-
+#include "MapFeaturesH3M.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGHeroInstance;
-class CBinaryReader;
+class MapReaderH3M;
 class CArtifactInstance;
 class CGObjectInstance;
 class CGSeerHut;
@@ -31,6 +26,11 @@ class CCreatureSet;
 class CInputStream;
 class TextIdentifier;
 
+class ObjectInstanceID;
+class BuildingID;
+class ObjectTemplate;
+class SpellID;
+class int3;
 
 class DLL_LINKAGE CMapLoaderH3M : public IMapLoader
 {
@@ -61,9 +61,6 @@ public:
 	 */
 	std::unique_ptr<CMapHeader> loadMapHeader() override;
 
-	/** true if you want to enable the map loader profiler to see how long a specific part took; default=false */
-	static const bool IS_PROFILING_ENABLED;
-
 private:
 	/**
 	 * Initializes the map object from parsing the input buffer.
@@ -209,42 +206,18 @@ private:
 	*/
 	void readMessageAndGuards(std::string & message, CCreatureSet * guards, const int3 & position);
 
-	void readSpells(std::set<SpellID> & dest);
-
-	void readResourses(TResources& resources);
-
-	template <class Indenifier>
-	void readBitmask(std::set<Indenifier> &dest, const int byteCount, const int limit, bool negate = true);
-
-	/** Reads bitmask to boolean vector
-	* @param dest destination vector, shall be filed with "true" values
-	* @param byteCount size in bytes of bimask
-	* @param limit max count of vector elements to alter
-	* @param negate if true then set bit in mask means clear flag in vertor
-	*/
-	void readBitmask(std::vector<bool> & dest, const int byteCount, const int limit, bool negate = true);
-
-	/**
-	 * Reverses the input argument.
-	 *
-	 * @param arg the input argument
-	 * @return the reversed 8-bit integer
-	 */
-	ui8 reverse(ui8 arg) const;
-
-	/**
-	* Helper to read map position
-	*/
-	int3 readInt3();
-
 	/// reads string from input stream and converts it to unicode
 	std::string readBasicString();
 
 	/// reads string from input stream, converts it to unicode and attempts to translate it
 	std::string readLocalizedString(const TextIdentifier & identifier);
 
+	void readSpells(std::set<SpellID> & dest);
+
 	void afterRead();
 
+	MapFormatFeaturesH3M features;
+
 	/** List of templates loaded from the map, used on later stage to create
 	 *  objects but not needed for fully functional CMap */
 	std::vector<std::shared_ptr<const ObjectTemplate>> templates;
@@ -257,7 +230,7 @@ private:
 	 * (when loading a map then the mapHeader ptr points to the same object)
 	 */
 	std::unique_ptr<CMapHeader> mapHeader;
-	std::unique_ptr<CBinaryReader> reader;
+	std::unique_ptr<MapReaderH3M> reader;
 	CInputStream * inputStream;
 
 	std::string mapName;

+ 244 - 0
lib/mapping/MapReaderH3M.cpp

@@ -0,0 +1,244 @@
+/*
+ * MapReaderH3M.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "MapReaderH3M.h"
+#include "CMap.h"
+#include "../filesystem/CBinaryReader.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+MapReaderH3M::MapReaderH3M(CInputStream * stream)
+	: reader(std::make_unique<CBinaryReader>(stream))
+{
+}
+
+void MapReaderH3M::setFormatLevel(EMapFormat newFormat)
+{
+	features = MapFormatFeaturesH3M::find(newFormat);
+}
+
+ArtifactID MapReaderH3M::readArtifact()
+{
+	ArtifactID result;
+
+	if (features.levelAB)
+		result = ArtifactID(reader->readUInt16());
+	else
+		result = ArtifactID(reader->readUInt8());
+
+	if (result == features.artifactIdentifierInvalid)
+		return ArtifactID::NONE;
+
+	assert(result < features.artifactsCount);
+	return result;
+}
+
+HeroTypeID MapReaderH3M::readHero()
+{
+	HeroTypeID result (reader->readUInt8());
+
+	if (result.getNum() == features.heroIdentifierInvalid)
+		return HeroTypeID(-1);
+
+	assert(result.getNum() < features.heroesPortraitsCount);
+	return result;
+}
+
+CreatureID MapReaderH3M::readCreature()
+{
+	CreatureID result;
+
+	if (features.levelAB)
+		result = CreatureID(reader->readUInt16());
+	else
+		result = CreatureID(reader->readUInt8());
+
+	if (result == features.creatureIdentifierInvalid)
+		return CreatureID::NONE;
+
+	if(result > features.creaturesCount)
+	{
+		// this may be random creature in army/town, to be randomized later
+		CreatureID randomIndex (result.getNum() - features.creatureIdentifierInvalid - 1);
+		assert(randomIndex < CreatureID::NONE);
+		assert(randomIndex > -16);
+		return randomIndex;
+	}
+
+	return result;
+
+
+}
+
+TerrainId MapReaderH3M::readTerrain()
+{
+	TerrainId result(readUInt8());
+	assert (result.getNum() < features.terrainsCount);
+	return result;
+}
+
+RoadId MapReaderH3M::readRoad()
+{
+	RoadId result(readUInt8());
+	assert (result < Road::ORIGINAL_ROAD_COUNT);
+	return result;
+}
+
+RiverId MapReaderH3M::readRiver()
+{
+	RiverId result(readUInt8());
+	assert (result < River::ORIGINAL_RIVER_COUNT);
+	return result;
+
+}
+
+SecondarySkill MapReaderH3M::readSkill()
+{
+	SecondarySkill result(readUInt8());
+	assert (result < features.skillsCount);
+	return result;
+}
+
+SpellID MapReaderH3M::readSpell()
+{
+	SpellID result(readUInt8());
+	if (result == features.spellIdentifierInvalid)
+		return SpellID::NONE;
+	if (result == features.spellIdentifierInvalid - 1)
+		return SpellID::PRESET;
+
+	assert (result < features.spellsCount);
+	return result;
+}
+
+SpellID MapReaderH3M::readSpell32()
+{
+	SpellID result(readUInt32());
+	if (result == features.spellIdentifierInvalid)
+		return SpellID::NONE;
+	assert (result < features.spellsCount);
+	return result;
+}
+
+PlayerColor MapReaderH3M::readPlayer()
+{
+	PlayerColor result(readUInt8());
+	assert (result < PlayerColor::PLAYER_LIMIT || result == PlayerColor::NEUTRAL);
+	return result;
+}
+
+PlayerColor MapReaderH3M::readPlayer32()
+{
+	PlayerColor result(readUInt32());
+
+	assert (result < PlayerColor::PLAYER_LIMIT || result == PlayerColor::NEUTRAL);
+	return result;
+}
+
+void MapReaderH3M::readBitmask(std::vector<bool> & dest, const int byteCount, const int limit, bool negate)
+{
+	for(int byte = 0; byte < byteCount; ++byte)
+	{
+		const ui8 mask = reader->readUInt8();
+		for(int bit = 0; bit < 8; ++bit)
+		{
+			if(byte * 8 + bit < limit)
+			{
+				const bool flag = mask & (1 << bit);
+				if((negate && flag) || (!negate && !flag)) // FIXME: check PR388
+					dest[byte * 8 + bit] = false;
+			}
+		}
+	}
+}
+
+int3 MapReaderH3M::readInt3()
+{
+	int3 p;
+	p.x = reader->readUInt8();
+	p.y = reader->readUInt8();
+	p.z = reader->readUInt8();
+	return p;
+}
+
+void MapReaderH3M::skipUnused(size_t amount)
+{
+	reader->skip(amount);
+}
+
+void MapReaderH3M::skipZero(size_t amount)
+{
+#ifdef NDEBUG
+	skipUnused(amount);
+#else
+	for (size_t i = 0; i < amount; ++i)
+	{
+		uint8_t value = reader->readUInt8();
+		assert(value == 0);
+	}
+#endif
+}
+
+void MapReaderH3M::readResourses(TResources& resources)
+{
+	for(int x = 0; x < features.resourcesCount; ++x)
+		resources[x] = reader->readUInt32();
+}
+
+bool MapReaderH3M::readBool()
+{
+	uint8_t result = readUInt8();
+	assert(result == 0 || result == 1);
+
+	return result != 0;
+}
+
+ui8  MapReaderH3M::readUInt8()
+{
+	return reader->readUInt8();
+}
+
+si8  MapReaderH3M::readInt8()
+{
+	return reader->readInt8();
+}
+
+ui16 MapReaderH3M::readUInt16()
+{
+	return reader->readUInt16();
+}
+
+si16 MapReaderH3M::readInt16()
+{
+	return reader->readInt16();
+}
+
+ui32 MapReaderH3M::readUInt32()
+{
+	return reader->readUInt32();
+}
+
+si32 MapReaderH3M::readInt32()
+{
+	return reader->readInt32();
+}
+
+std::string MapReaderH3M::readBaseString()
+{
+	return reader->readBaseString();
+}
+
+CBinaryReader & MapReaderH3M::getInternalReader()
+{
+	return *reader;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 92 - 0
lib/mapping/MapReaderH3M.h

@@ -0,0 +1,92 @@
+/*
+ * MapReaderH3M.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../ResourceSet.h"
+#include "../GameConstants.h"
+#include "MapFeaturesH3M.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CBinaryReader;
+class CInputStream;
+struct MapFormatFeaturesH3M;
+class int3;
+enum class EMapFormat : uint8_t;
+
+class MapReaderH3M
+{
+public:
+	explicit MapReaderH3M(CInputStream * stream);
+
+	void setFormatLevel(EMapFormat format);
+
+	ArtifactID readArtifact();
+	CreatureID readCreature();
+	HeroTypeID readHero();
+	TerrainId readTerrain();
+	RoadId readRoad();
+	RiverId readRiver();
+	SecondarySkill readSkill();
+	SpellID readSpell();
+	SpellID readSpell32();
+	PlayerColor readPlayer();
+	PlayerColor readPlayer32();
+
+	template <class Identifier>
+	void readBitmask(std::set<Identifier> &dest, const int byteCount, const int limit, bool negate = true)
+	{
+		std::vector<bool> temp;
+		temp.resize(limit,true);
+		readBitmask(temp, byteCount, limit, negate);
+
+		for(int i = 0; i< std::min(temp.size(), static_cast<size_t>(limit)); i++)
+			if(temp[i])
+				dest.insert(static_cast<Identifier>(i));
+	}
+
+	/** Reads bitmask to boolean vector
+	* @param dest destination vector, shall be filed with "true" values
+	* @param byteCount size in bytes of bimask
+	* @param limit max count of vector elements to alter
+	* @param negate if true then set bit in mask means clear flag in vertor
+	*/
+	void readBitmask(std::vector<bool> & dest, int byteCount, int limit, bool negate = true);
+
+	/**
+	* Helper to read map position
+	*/
+	int3 readInt3();
+
+	void skipUnused(size_t amount);
+	void skipZero(size_t amount);
+
+	void readResourses(TResources& resources);
+
+	bool readBool();
+
+	ui8  readUInt8();
+	si8  readInt8();
+	ui16 readUInt16();
+	si16 readInt16();
+	ui32 readUInt32();
+	si32 readInt32();
+
+	std::string readBaseString();
+
+	CBinaryReader & getInternalReader();
+private:
+	MapFormatFeaturesH3M features;
+
+	std::unique_ptr<CBinaryReader> reader;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/spells/effects/Summon.cpp

@@ -67,7 +67,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const
 
 				text.addReplacement(MetaString::CRE_PL_NAMES, elemental->creatureIndex());
 
-				if(caster->type->sex)
+				if(caster->type->gender == EHeroGender::FEMALE)
 					text.addReplacement(MetaString::GENERAL_TXT, 540);
 				else
 					text.addReplacement(MetaString::GENERAL_TXT, 539);

+ 1 - 1
mapeditor/inspector/armywidget.cpp

@@ -68,7 +68,7 @@ void ArmyWidget::obtainData()
 		}
 	}
 	
-	if(army.formation)
+	if(army.formation == EArmyFormation::TIGHT)
 		ui->formationTight->setChecked(true);
 	else
 		ui->formationWide->setChecked(true);

+ 5 - 5
mapeditor/inspector/inspector.cpp

@@ -132,7 +132,7 @@ void Initializer::initialize(CGHeroInstance * o)
 	if(!o->type)
 		o->type = VLC->heroh->objects.at(o->subID);
 	
-	o->sex = o->type->sex;
+	o->gender = o->type->gender;
 	o->portrait = o->type->imageIndex;
 	o->randomizeArmy(o->type->heroClass->faction);
 }
@@ -241,7 +241,7 @@ void Inspector::updateProperties(CGHeroInstance * o)
 	{ //Sex
 		auto * delegate = new InspectorDelegate;
 		delegate->options << "MALE" << "FEMALE";
-		addProperty<std::string>("Sex", (o->sex ? "FEMALE" : "MALE"), delegate , false);
+		addProperty<std::string>("Gender", (o->gender == EHeroGender::FEMALE ? "FEMALE" : "MALE"), delegate , false);
 	}
 	addProperty("Name", o->nameCustom, false);
 	addProperty("Biography", o->biographyCustom, new MessageDelegate, false);
@@ -549,8 +549,8 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 {
 	if(!o) return;
 	
-	if(key == "Sex")
-		o->sex = value.toString() == "MALE" ? 0 : 1;
+	if(key == "Gender")
+		o->gender = value.toString() == "MALE" ? EHeroGender::MALE : EHeroGender::FEMALE;
 	
 	if(key == "Name")
 		o->nameCustom = value.toString().toStdString();
@@ -565,7 +565,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 			if(t->getNameTranslated() == value.toString().toStdString())
 				o->type = t.get();
 		}
-		o->sex = o->type->sex;
+		o->gender = o->type->gender;
 		o->portrait = o->type->imageIndex;
 		o->randomizeArmy(o->type->heroClass->faction);
 		updateProperties(); //updating other properties after change

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