| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403 | /* * CCreatureHandler.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 "CCreatureHandler.h"#include "ResourceSet.h"#include "entities/faction/CFaction.h"#include "entities/faction/CTownHandler.h"#include "filesystem/Filesystem.h"#include "VCMI_Lib.h"#include "GameSettings.h"#include "constants/StringConstants.h"#include "bonuses/Limiters.h"#include "bonuses/Updaters.h"#include "json/JsonBonus.h"#include "serializer/JsonDeserializer.h"#include "serializer/JsonUpdater.h"#include "texts/CGeneralTextHandler.h"#include "texts/CLegacyConfigParser.h"#include "mapObjectConstructors/AObjectTypeHandler.h"#include "mapObjectConstructors/CObjectClassesHandler.h"#include "modding/CModHandler.h"#include "ExceptionsCommon.h"#include <vstd/RNG.h>VCMI_LIB_NAMESPACE_BEGINconst std::map<CCreature::CreatureQuantityId, std::string> CCreature::creatureQuantityRanges ={		{CCreature::CreatureQuantityId::FEW, "1-4"},		{CCreature::CreatureQuantityId::SEVERAL, "5-9"},		{CCreature::CreatureQuantityId::PACK, "10-19"},		{CCreature::CreatureQuantityId::LOTS, "20-49"},		{CCreature::CreatureQuantityId::HORDE, "50-99"},		{CCreature::CreatureQuantityId::THRONG, "100-249"},		{CCreature::CreatureQuantityId::SWARM, "250-499"},		{CCreature::CreatureQuantityId::ZOUNDS, "500-999"},		{CCreature::CreatureQuantityId::LEGION, "1000+"}};int32_t CCreature::getIndex() const{	return idNumber.toEnum();}int32_t CCreature::getIconIndex() const{	return iconIndex;}std::string CCreature::getJsonKey() const{	return modScope + ':' + identifier;}std::string CCreature::getModScope() const{	return modScope;}void CCreature::registerIcons(const IconRegistar & cb) const{	cb(getIconIndex(), 0, "CPRSMALL", smallIconName);	cb(getIconIndex(), 0, "TWCRPORT", largeIconName);}CreatureID CCreature::getId() const{	return idNumber;}const IBonusBearer * CCreature::getBonusBearer() const{	return this;}int32_t CCreature::getAdvMapAmountMin() const{	return ammMin;}int32_t CCreature::getAdvMapAmountMax() const{	return ammMax;}int32_t CCreature::getAIValue() const{	return AIValue;}int32_t CCreature::getFightValue() const{	return fightValue;}int32_t CCreature::getLevel() const{	return level;}int32_t CCreature::getGrowth() const{	return growth;}int32_t CCreature::getHorde() const{	return hordeGrowth;}FactionID CCreature::getFaction() const{	return FactionID(faction);}int32_t CCreature::getBaseAttack() const{	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseDefense() const{	static const auto SELECTOR = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseDamageMin() const{	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseDamageMax() const{	static const auto SELECTOR = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseHitPoints() const{	static const auto SELECTOR = Selector::type()(BonusType::STACK_HEALTH).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseSpellPoints() const{	static const auto SELECTOR = Selector::type()(BonusType::CASTS).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseSpeed() const{	static const auto SELECTOR = Selector::type()(BonusType::STACKS_SPEED).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getBaseShots() const{	static const auto SELECTOR = Selector::type()(BonusType::SHOTS).And(Selector::sourceTypeSel(BonusSource::CREATURE_ABILITY));	return getExportedBonusList().valOfBonuses(SELECTOR);}int32_t CCreature::getRecruitCost(GameResID resIndex) const{	if(resIndex.getNum() >= 0 && resIndex.getNum() < cost.size())		return cost[resIndex];	else		return 0;}TResources CCreature::getFullRecruitCost() const{	return cost;}bool CCreature::hasUpgrades() const {	return !upgrades.empty();}std::string CCreature::getNameTranslated() const{	return getNameSingularTranslated();}std::string CCreature::getNamePluralTranslated() const{	return VLC->generaltexth->translate(getNamePluralTextID());}std::string CCreature::getNameSingularTranslated() const{	return VLC->generaltexth->translate(getNameSingularTextID());}std::string CCreature::getNameTextID() const{	return getNameSingularTextID();}std::string CCreature::getDescriptionTranslated() const{	return VLC->generaltexth->translate(getDescriptionTextID());}std::string CCreature::getNamePluralTextID() const{	return TextIdentifier("creatures", modScope, identifier, "name", "plural" ).get();}std::string CCreature::getNameSingularTextID() const{	return TextIdentifier("creatures", modScope, identifier, "name", "singular" ).get();}std::string CCreature::getDescriptionTextID() const{	return TextIdentifier("creatures", modScope, identifier, "description").get();}CCreature::CreatureQuantityId CCreature::getQuantityID(const int & quantity){	if (quantity<5)		return CCreature::CreatureQuantityId::FEW;	if (quantity<10)		return CCreature::CreatureQuantityId::SEVERAL;	if (quantity<20)		return CCreature::CreatureQuantityId::PACK;	if (quantity<50)		return CCreature::CreatureQuantityId::LOTS;	if (quantity<100)		return CCreature::CreatureQuantityId::HORDE;	if (quantity<250)		return CCreature::CreatureQuantityId::THRONG;	if (quantity<500)		return CCreature::CreatureQuantityId::SWARM;	if (quantity<1000)		return CCreature::CreatureQuantityId::ZOUNDS;	return CCreature::CreatureQuantityId::LEGION;}std::string CCreature::getQuantityRangeStringForId(const CCreature::CreatureQuantityId & quantityId){	if(creatureQuantityRanges.find(quantityId) != creatureQuantityRanges.end())		return creatureQuantityRanges.at(quantityId);	logGlobal->error("Wrong quantityId: %d", (int)quantityId);	assert(0);	return "[ERROR]";}int CCreature::estimateCreatureCount(ui32 countID){	static const int creature_count[] = { 0, 3, 8, 15, 35, 75, 175, 375, 750, 2500 };	if(countID > 9)	{		logGlobal->error("Wrong countID %d!", countID);		return 0;	}	else		return creature_count[countID];}bool CCreature::isDoubleWide() const{	return doubleWide;}/** * Determines if the creature is of a good alignment. * @return true if the creture is good, false otherwise. */bool CCreature::isGood () const{	return VLC->factions()->getById(faction)->getAlignment() == EAlignment::GOOD;}/** * Determines if the creature is of an evil alignment. * @return true if the creature is evil, false otherwise. */bool CCreature::isEvil () const{	return VLC->factions()->getById(faction)->getAlignment() == EAlignment::EVIL;}si32 CCreature::maxAmount(const TResources &res) const //how many creatures can be bought{	int ret = 2147483645;	int resAmnt = static_cast<int>(std::min(res.size(),cost.size()));	for(int i=0;i<resAmnt;i++)		if(cost[i])			ret = std::min(ret, (res[i] / cost[i]));	return ret;}CCreature::CCreature(){	setNodeType(CBonusSystemNode::CREATURE);	fightValue = AIValue = growth = hordeGrowth = ammMin = ammMax = 0;}void CCreature::addBonus(int val, BonusType type){	addBonus(val, type, BonusSubtypeID());}void CCreature::addBonus(int val, BonusType type, BonusSubtypeID subtype){	auto selector = Selector::typeSubtype(type, subtype).And(Selector::source(BonusSource::CREATURE_ABILITY, BonusSourceID(getId())));	BonusList & exported = getExportedBonusList();	BonusList existing;	exported.getBonuses(existing, selector, Selector::all);	if(existing.empty())	{		auto added = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::CREATURE_ABILITY, val, BonusSourceID(getId()), subtype, BonusValueType::BASE_NUMBER);		addNewBonus(added);	}	else	{		std::shared_ptr<Bonus> b = existing[0];		b->val = val;	}}bool CCreature::isMyUpgrade(const CCreature *anotherCre) const{	//TODO upgrade of upgrade?	return vstd::contains(upgrades, anotherCre->getId());}bool CCreature::valid() const{	return this == (*VLC->creh)[idNumber];}std::string CCreature::nodeName() const{	return "\"" + getNamePluralTextID() + "\"";}void CCreature::updateFrom(const JsonNode & data){	JsonUpdater handler(nullptr, data);	{		auto configScope = handler.enterStruct("config");		const JsonNode & configNode = handler.getCurrent();		serializeJson(handler);		if(!configNode["hitPoints"].isNull())			addBonus(configNode["hitPoints"].Integer(), BonusType::STACK_HEALTH);		if(!configNode["speed"].isNull())			addBonus(configNode["speed"].Integer(), BonusType::STACKS_SPEED);		if(!configNode["attack"].isNull())			addBonus(configNode["attack"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));		if(!configNode["defense"].isNull())			addBonus(configNode["defense"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));		if(!configNode["damage"]["min"].isNull())			addBonus(configNode["damage"]["min"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);		if(!configNode["damage"]["max"].isNull())			addBonus(configNode["damage"]["max"].Integer(), BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);		if(!configNode["shots"].isNull())			addBonus(configNode["shots"].Integer(), BonusType::SHOTS);		if(!configNode["spellPoints"].isNull())			addBonus(configNode["spellPoints"].Integer(), BonusType::CASTS);	}	handler.serializeBonuses("bonuses", this);}void CCreature::serializeJson(JsonSerializeFormat & handler){	handler.serializeInt("fightValue", fightValue);	handler.serializeInt("aiValue", AIValue);	handler.serializeInt("growth", growth);	handler.serializeInt("horde", hordeGrowth);// Needed at least until configurable buildings	{		auto advMapNode = handler.enterStruct("advMapAmount");		handler.serializeInt("min", ammMin);		handler.serializeInt("max", ammMax);	}	if(handler.updating)	{		cost.serializeJson(handler, "cost");		handler.serializeId("faction", faction);	}	handler.serializeInt("level", level);	handler.serializeBool("doubleWide", doubleWide);	if(!handler.saving)	{		if(ammMin > ammMax)		{			logMod->error("Invalid creature '%s' configuration, advMapAmount.min > advMapAmount.max", identifier);			std::swap(ammMin, ammMax);		}	}}CCreatureHandler::CCreatureHandler()	: expAfterUpgrade(0){	loadCommanders();}void CCreatureHandler::loadCommanders(){	auto configResource = JsonPath::builtin("config/commanders.json");	std::string modSource = VLC->modh->findResourceOrigin(configResource);	JsonNode data(configResource);	data.setModScope(modSource);	const JsonNode & config = data; // switch to const data accessors	for (auto bonus : config["bonusPerLevel"].Vector())	{		commanderLevelPremy.push_back(JsonUtils::parseBonus(bonus.Vector()));	}	int i = 0;	for (auto skill : config["skillLevels"].Vector())	{		skillLevels.emplace_back();		for (auto skillLevel : skill["levels"].Vector())		{			skillLevels[i].push_back(static_cast<ui8>(skillLevel.Float()));		}		++i;	}	for (auto ability : config["abilityRequirements"].Vector())	{		std::pair <std::shared_ptr<Bonus>, std::pair <ui8, ui8> > a;		a.first = JsonUtils::parseBonus (ability["ability"].Vector());		a.second.first =  static_cast<ui8>(ability["skills"].Vector()[0].Float());		a.second.second = static_cast<ui8>(ability["skills"].Vector()[1].Float());		skillRequirements.push_back (a);	}}void CCreatureHandler::loadBonuses(JsonNode & creature, std::string bonuses) const{	auto makeBonusNode = [&](const std::string & type, double val = 0) -> JsonNode	{		JsonNode ret;		ret["type"].String() = type;		ret["val"].Float() = val;		return ret;	};	static const std::map<std::string, JsonNode> abilityMap =	{		{"FLYING_ARMY",            makeBonusNode("FLYING")},		{"SHOOTING_ARMY",          makeBonusNode("SHOOTER")},		{"SIEGE_WEAPON",           makeBonusNode("SIEGE_WEAPON")},		{"const_free_attack",      makeBonusNode("BLOCKS_RETALIATION")},		{"IS_UNDEAD",              makeBonusNode("UNDEAD")},		{"const_no_melee_penalty", makeBonusNode("NO_MELEE_PENALTY")},		{"const_jousting",         makeBonusNode("JOUSTING", 5)},		{"KING_1",                 makeBonusNode("KING")}, // Slayer with no expertise		{"KING_2",                 makeBonusNode("KING", 2)}, // Advanced Slayer or better		{"KING_3",                 makeBonusNode("KING", 3)}, // Expert Slayer only		{"const_no_wall_penalty",  makeBonusNode("NO_WALL_PENALTY")},		{"MULTI_HEADED",           makeBonusNode("ATTACKS_ALL_ADJACENT")},		{"IMMUNE_TO_MIND_SPELLS",  makeBonusNode("MIND_IMMUNITY")},		{"HAS_EXTENDED_ATTACK",    makeBonusNode("TWO_HEX_ATTACK_BREATH")}	};	auto hasAbility = [&](const std::string & name) -> bool 	{		return boost::algorithm::find_first(bonuses, name);	};	for(const auto & a : abilityMap)	{		if(hasAbility(a.first))			creature["abilities"][a.first] = a.second;	}	if(hasAbility("DOUBLE_WIDE"))		creature["doubleWide"].Bool() = true;	if(hasAbility("const_raises_morale"))	{		JsonNode node = makeBonusNode("MORALE");		node["val"].Float() = 1;		node["propagator"].String() = "HERO";		creature["abilities"]["const_raises_morale"] = node;	}}std::vector<JsonNode> CCreatureHandler::loadLegacyData(){	size_t dataSize = VLC->settings()->getInteger(EGameSettings::TEXTS_CREATURE);	objects.resize(dataSize);	std::vector<JsonNode> h3Data;	h3Data.reserve(dataSize);	CLegacyConfigParser parser(TextPath::builtin("DATA/CRTRAITS.TXT"));	parser.endLine(); // header	// this file is a bit different in some of Russian localisations:	//ENG: Singular	Plural Wood ...	//RUS: Singular	Plural	Plural2 Wood ...	// Try to detect which version this is by header	// TODO: use 3rd name? Stand for "whose", e.g. pikemans'	size_t namesCount = 2;	{		if ( parser.readString() != "Singular" || parser.readString() != "Plural" )			throw std::runtime_error("Incorrect format of CrTraits.txt");		if (parser.readString() == "Plural2")			namesCount = 3;		parser.endLine();	}	for (size_t i=0; i<dataSize; i++)	{		//loop till non-empty line		while (parser.isNextEntryEmpty())			parser.endLine();		JsonNode data;		data["name"]["singular"].String() =  parser.readString();		if (namesCount == 3)			parser.readString();		data["name"]["plural"].String() =  parser.readString();		for(int v=0; v<7; ++v)			data["cost"][GameConstants::RESOURCE_NAMES[v]].Float() = parser.readNumber();		data["fightValue"].Float() = parser.readNumber();		data["aiValue"].Float() = parser.readNumber();		data["growth"].Float() = parser.readNumber();		data["horde"].Float() = parser.readNumber();		data["hitPoints"].Float() = parser.readNumber();		data["speed"].Float() = parser.readNumber();		data["attack"].Float() = parser.readNumber();		data["defense"].Float() = parser.readNumber();		data["damage"]["min"].Float() = parser.readNumber();		data["damage"]["max"].Float() = parser.readNumber();		if (float shots = parser.readNumber())			data["shots"].Float() = shots;		if (float spells = parser.readNumber())			data["spellPoints"].Float() = spells;		data["advMapAmount"]["min"].Float() = parser.readNumber();		data["advMapAmount"]["max"].Float() = parser.readNumber();		// unused - ability text, not used since we no longer have original creature window		parser.readString();		loadBonuses(data, parser.readString()); //Attributes		h3Data.push_back(data);	}	loadAnimationInfo(h3Data);	return h3Data;}std::shared_ptr<CCreature> CCreatureHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index){	assert(identifier.find(':') == std::string::npos);	assert(!scope.empty());	auto cre = std::make_shared<CCreature>();	if(node["hasDoubleWeek"].Bool())	{		doubledCreatures.insert(CreatureID(index));	}	cre->idNumber = CreatureID(index);	cre->iconIndex = cre->getIndex() + 2;	cre->identifier = identifier;	cre->modScope = scope;	JsonDeserializer handler(nullptr, node);	cre->serializeJson(handler);	cre->cost = ResourceSet(node["cost"]);	VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());	VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());	VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String());	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);	cre->addBonus(node["attack"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));	cre->addBonus(node["defense"].Integer(), BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));	int minDamage = node["damage"]["min"].Integer();	int maxDamage = node["damage"]["max"].Integer();	if (minDamage <= maxDamage)	{		cre->addBonus(minDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);		cre->addBonus(maxDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);	}	else	{		logMod->error("Mod %s: creature %s has minimal damage (%d) greater than maximal damage (%d)!", scope, identifier, minDamage, maxDamage);		cre->addBonus(maxDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);		cre->addBonus(minDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);	}	if(!node["shots"].isNull())		cre->addBonus(node["shots"].Integer(), BonusType::SHOTS);	loadStackExperience(cre.get(), node["stackExperience"]);	loadJsonAnimation(cre.get(), node["graphics"]);	loadCreatureJson(cre.get(), node);	for(const auto & extraName : node["extraNames"].Vector())	{		for(const auto & type_name : getTypeNames())			registerObject(scope, type_name, extraName.String(), cre->getIndex());	}	if (!cre->special &&		!CResourceHandler::get()->existsResource(cre->animDefName) &&		!CResourceHandler::get()->existsResource(cre->animDefName.toType<EResType::JSON>()) &&		!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/")) &&		!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/").toType<EResType::JSON>()))		throw ModLoadingException(scope, "creature " + cre->getJsonKey() + " has no combat animation but is not marked as special!" );	JsonNode advMapFile = node["graphics"]["map"];	JsonNode advMapMask = node["graphics"]["mapMask"];	VLC->identifiers()->requestIdentifier(scope, "object", "monster", [cre, scope, advMapFile, advMapMask](si32 monsterIndex)	{		JsonNode conf;		conf.setModScope(scope);		VLC->objtypeh->loadSubObject(cre->identifier, conf, Obj::MONSTER, cre->getId().num);		if (!advMapFile.isNull())		{			JsonNode templ;			templ["animation"] = advMapFile;			if (!advMapMask.isNull())				templ["mask"] = advMapMask;			templ.setModScope(scope);			// if creature has custom advMapFile, reset any potentially imported H3M templates and use provided file instead			VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->clearTemplates();			VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->addTemplate(templ);		}		// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)		if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->getTemplates().empty())		{			if (!cre->special)				throw ModLoadingException(scope, "creature " + cre->getJsonKey() + " has no adventure map animation but is not marked as special!" );			VLC->objtypeh->removeSubObject(Obj::MONSTER, cre->getId().num);		}	});	return cre;}const std::vector<std::string> & CCreatureHandler::getTypeNames() const{	static const std::vector<std::string> typeNames = { "creature" };	return typeNames;}void CCreatureHandler::loadCrExpMod(){	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience values	{		//Calculate rank exp values, formula appears complicated bu no parsing needed		expRanks.resize(8);		int dif = 0;		int it = 8000; //ignore name of this variable		expRanks[0].push_back(it);		for (int j = 1; j < 10; ++j) //used for tiers 8-10, and all other probably		{			expRanks[0].push_back(expRanks[0][j-1] + it + dif);			dif += it/5;		}		for (int i = 1; i < 8; ++i) //used for tiers 1-7		{			dif = 0;			it = 1000 * i;			expRanks[i].push_back(it);			for (int j = 1; j < 10; ++j)			{				expRanks[i].push_back(expRanks[i][j-1] + it + dif);				dif += it/5;			}		}		CLegacyConfigParser expBonParser(TextPath::builtin("DATA/CREXPMOD.TXT"));		expBonParser.endLine(); //header		maxExpPerBattle.resize(8);		for (int i = 1; i < 8; ++i)		{			expBonParser.readString(); //index			expBonParser.readString(); //float multiplier -> hardcoded			expBonParser.readString(); //ignore upgrade mod? ->hardcoded			expBonParser.readString(); //already calculated			maxExpPerBattle[i] = static_cast<ui32>(expBonParser.readNumber());			expRanks[i].push_back(expRanks[i].back() + static_cast<ui32>(expBonParser.readNumber()));			expBonParser.endLine();		}		//exp for tier >7, rank 11		expRanks[0].push_back(147000);		expAfterUpgrade = 75; //percent		maxExpPerBattle[0] = maxExpPerBattle[7];	}}void CCreatureHandler::loadCrExpBon(CBonusSystemNode & globalEffects){	if (VLC->settings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) 	//reading default stack experience bonuses	{		logGlobal->debug("\tLoading stack experience bonuses");		auto addBonusForAllCreatures = [&](std::shared_ptr<Bonus> b) {			auto limiter = std::make_shared<CreatureLevelLimiter>();			b->addLimiter(limiter);			globalEffects.addNewBonus(b);		};		auto addBonusForTier = [&](int tier, std::shared_ptr<Bonus> b) {			assert(vstd::iswithin(tier, 1, 7));			//bonuses from level 7 are given to high-level creatures too			auto max = tier == GameConstants::CREATURES_PER_TOWN ? std::numeric_limits<int>::max() : tier + 1;			auto limiter = std::make_shared<CreatureLevelLimiter>(tier, max);			b->addLimiter(limiter);			globalEffects.addNewBonus(b);		};		CLegacyConfigParser parser(TextPath::builtin("DATA/CREXPBON.TXT"));		Bonus b; //prototype with some default properties		b.source = BonusSource::STACK_EXPERIENCE;		b.duration = BonusDuration::PERMANENT;		b.valType = BonusValueType::ADDITIVE_VALUE;		b.effectRange = BonusLimitEffect::NO_LIMIT;		b.additionalInfo = 0;		b.turnsRemain = 0;		BonusList bl;		parser.endLine();		parser.readString(); //ignore index		loadStackExp(b, bl, parser);		for(const auto & b : bl)			addBonusForAllCreatures(b); //health bonus is common for all		parser.endLine();		for (int i = 1; i < 7; ++i)		{			for (int j = 0; j < 4; ++j) //four modifiers common for tiers			{				parser.readString(); //ignore index				bl.clear();				loadStackExp(b, bl, parser);				for(const auto & b : bl)					addBonusForTier(i, b);				parser.endLine();			}		}		for (int j = 0; j < 4; ++j) //tier 7		{			parser.readString(); //ignore index			bl.clear();			loadStackExp(b, bl, parser);			for(const auto & b : bl)				addBonusForTier(7, b);			parser.endLine();		}		do //parse everything that's left		{			CreatureID sid = parser.readNumber(); //id = this particular creature ID			b.sid = BonusSourceID(sid);			bl.clear();			loadStackExp(b, bl, parser);			for(const auto & b : bl)				objects[sid.getNum()]->addNewBonus(b); //add directly to CCreature Node		}		while (parser.endLine());	}//end of Stack Experience}void CCreatureHandler::loadAnimationInfo(std::vector<JsonNode> &h3Data) const{	CLegacyConfigParser parser(TextPath::builtin("DATA/CRANIM.TXT"));	parser.endLine(); // header	parser.endLine();	for(int dd = 0; dd < VLC->settings()->getInteger(EGameSettings::TEXTS_CREATURE); ++dd)	{		while (parser.isNextEntryEmpty() && parser.endLine()) // skip empty lines			;		loadUnitAnimInfo(h3Data[dd]["graphics"], parser);		parser.endLine();	}}void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser & parser) const{	graphics["timeBetweenFidgets"].Float() = parser.readNumber();	JsonNode & animationTime = graphics["animationTime"];	animationTime["walk"].Float() = parser.readNumber();	animationTime["attack"].Float() = parser.readNumber();	parser.readNumber(); // unused value "Flight animation time" - H3 actually uses "Walk animation time" even for flying creatures	animationTime["idle"].Float() = 10.0;	JsonNode & missile = graphics["missile"];	JsonNode & offsets = missile["offset"];	offsets["upperX"].Float() = parser.readNumber();	offsets["upperY"].Float() = parser.readNumber();	offsets["middleX"].Float() = parser.readNumber();	offsets["middleY"].Float() = parser.readNumber();	offsets["lowerX"].Float() = parser.readNumber();	offsets["lowerY"].Float() = parser.readNumber();	for(int i=0; i<12; i++)	{		JsonNode entry;		entry.Float() = parser.readNumber();		missile["frameAngles"].Vector().push_back(entry);	}	// Unused property "troopCountLocationOffset"	parser.readNumber();	missile["attackClimaxFrame"].Float() = parser.readNumber();	// assume that creature is not a shooter and should not have whole missile field	if (missile["frameAngles"].Vector()[0].Integer() == 0 &&		missile["attackClimaxFrame"].Integer() == 0)		graphics.Struct().erase("missile");}void CCreatureHandler::loadJsonAnimation(CCreature * cre, const JsonNode & graphics) const{	cre->animation.timeBetweenFidgets = graphics["timeBetweenFidgets"].Float();	const JsonNode & animationTime = graphics["animationTime"];	cre->animation.walkAnimationTime = animationTime["walk"].Float();	cre->animation.idleAnimationTime = animationTime["idle"].Float();	cre->animation.attackAnimationTime = animationTime["attack"].Float();	const JsonNode & missile = graphics["missile"];	const JsonNode & offsets = missile["offset"];	cre->animation.upperRightMissileOffsetX = static_cast<int>(offsets["upperX"].Float());	cre->animation.upperRightMissileOffsetY = static_cast<int>(offsets["upperY"].Float());	cre->animation.rightMissileOffsetX =      static_cast<int>(offsets["middleX"].Float());	cre->animation.rightMissileOffsetY =      static_cast<int>(offsets["middleY"].Float());	cre->animation.lowerRightMissileOffsetX = static_cast<int>(offsets["lowerX"].Float());	cre->animation.lowerRightMissileOffsetY = static_cast<int>(offsets["lowerY"].Float());	cre->animation.attackClimaxFrame = static_cast<int>(missile["attackClimaxFrame"].Float());	cre->animation.missileFrameAngles = missile["frameAngles"].convertTo<std::vector<double> >();	cre->smallIconName = graphics["iconSmall"].String();	cre->largeIconName = graphics["iconLarge"].String();}void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & config) const{	creature->animDefName = AnimationPath::fromJson(config["graphics"]["animation"]);	//FIXME: MOD COMPATIBILITY	if (config["abilities"].getType() == JsonNode::JsonType::DATA_STRUCT)	{		for(const auto & ability : config["abilities"].Struct())		{			if (!ability.second.isNull())			{				auto b = JsonUtils::parseBonus(ability.second);				b->source = BonusSource::CREATURE_ABILITY;				b->sid = BonusSourceID(creature->getId());				b->duration = BonusDuration::PERMANENT;				creature->addNewBonus(b);			}		}	}	else	{		for(const JsonNode &ability : config["abilities"].Vector())		{			if(ability.getType() == JsonNode::JsonType::DATA_VECTOR)			{				logMod->error("Ignored outdated creature ability format in %s", creature->getJsonKey());			}			else			{				auto b = JsonUtils::parseBonus(ability);				b->source = BonusSource::CREATURE_ABILITY;				b->sid = BonusSourceID(creature->getId());				b->duration = BonusDuration::PERMANENT;				creature->addNewBonus(b);			}		}	}	VLC->identifiers()->requestIdentifier("faction", config["faction"], [=](si32 faction)	{		creature->faction = FactionID(faction);	});	for(const JsonNode &value : config["upgrades"].Vector())	{		VLC->identifiers()->requestIdentifier("creature", value, [=](si32 identifier)		{			creature->upgrades.insert(CreatureID(identifier));		});	}	creature->animation.projectileImageName = AnimationPath::fromJson(config["graphics"]["missile"]["projectile"]);	for(const JsonNode & value : config["graphics"]["missile"]["ray"].Vector())	{		CCreature::CreatureAnimation::RayColor color;		color.start.r = value["start"].Vector()[0].Integer();		color.start.g = value["start"].Vector()[1].Integer();		color.start.b = value["start"].Vector()[2].Integer();		color.start.a = value["start"].Vector()[3].Integer();		color.end.r = value["end"].Vector()[0].Integer();		color.end.g = value["end"].Vector()[1].Integer();		color.end.b = value["end"].Vector()[2].Integer();		color.end.a = value["end"].Vector()[3].Integer();		creature->animation.projectileRay.push_back(color);	}	creature->special = config["special"].Bool() || config["disabled"].Bool();	creature->excludeFromRandomization = config["excludeFromRandomization"].Bool();	const JsonNode & sounds = config["sound"];	creature->sounds.attack = AudioPath::fromJson(sounds["attack"]);	creature->sounds.defend = AudioPath::fromJson(sounds["defend"]);	creature->sounds.killed = AudioPath::fromJson(sounds["killed"]);	creature->sounds.move = AudioPath::fromJson(sounds["move"]);	creature->sounds.shoot = AudioPath::fromJson(sounds["shoot"]);	creature->sounds.wince = AudioPath::fromJson(sounds["wince"]);	creature->sounds.startMoving = AudioPath::fromJson(sounds["startMoving"]);	creature->sounds.endMoving = AudioPath::fromJson(sounds["endMoving"]);}void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode & input) const{	for (const JsonNode &exp : input.Vector())	{		const JsonVector &values = exp["values"].Vector();		int lowerLimit = 1;//, upperLimit = 255;		if (values[0].getType() == JsonNode::JsonType::DATA_BOOL)		{			for (const JsonNode &val : values)			{				if(val.Bool())				{					// parse each bonus separately					// we can not create copies since identifiers resolution does not tracks copies					// leading to unset identifier values in copies					auto bonus = JsonUtils::parseBonus (exp["bonus"]);					bonus->source = BonusSource::STACK_EXPERIENCE;					bonus->duration = BonusDuration::PERMANENT;					bonus->limiter = std::make_shared<RankRangeLimiter>(RankRangeLimiter(lowerLimit));					creature->addNewBonus (bonus);					break; //TODO: allow bonuses to turn off?				}				++lowerLimit;			}		}		else		{			int lastVal = 0;			for (const JsonNode &val : values)			{				if (val.Integer() != lastVal)				{					JsonNode bonusInput = exp["bonus"];					bonusInput["val"].Float() = val.Integer() - lastVal;					auto bonus = JsonUtils::parseBonus (bonusInput);					bonus->source = BonusSource::STACK_EXPERIENCE;					bonus->duration = BonusDuration::PERMANENT;					bonus->limiter.reset (new RankRangeLimiter(lowerLimit));					creature->addNewBonus (bonus);				}				lastVal = static_cast<int>(val.Float());				++lowerLimit;			}		}	}}void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigParser & parser) const//help function for parsing CREXPBON.txt{	bool enable = false; //some bonuses are activated with values 2 or 1	std::string buf = parser.readString();	std::string mod = parser.readString();	switch (buf[0])	{	case 'H':		b.type = BonusType::STACK_HEALTH;		b.valType = BonusValueType::PERCENT_TO_BASE;		break;	case 'A':		b.type = BonusType::PRIMARY_SKILL;		b.subtype = BonusSubtypeID(PrimarySkill::ATTACK);		break;	case 'D':		b.type = BonusType::PRIMARY_SKILL;		b.subtype = BonusSubtypeID(PrimarySkill::DEFENSE);		break;	case 'M': //Max damage		b.type = BonusType::CREATURE_DAMAGE;		b.subtype = BonusCustomSubtype::creatureDamageMax;		break;	case 'm': //Min damage		b.type = BonusType::CREATURE_DAMAGE;		b.subtype = BonusCustomSubtype::creatureDamageMin;		break;	case 'S':		b.type = BonusType::STACKS_SPEED; break;	case 'O':		b.type = BonusType::SHOTS; break;	case 'b':		b.type = BonusType::ENEMY_DEFENCE_REDUCTION; break;	case 'C':		b.type = BonusType::CHANGES_SPELL_COST_FOR_ALLY; break;	case 'd':		b.type = BonusType::DEFENSIVE_STANCE; break;	case 'e':		b.type = BonusType::DOUBLE_DAMAGE_CHANCE;		break;	case 'E':		b.type = BonusType::DEATH_STARE;		b.subtype = BonusCustomSubtype::deathStareGorgon;		break;	case 'F':		b.type = BonusType::FEAR; break;	case 'g':		b.type = BonusType::SPELL_DAMAGE_REDUCTION;		b.subtype = BonusSubtypeID(SpellSchool::ANY);		break;	case 'P':		b.type = BonusType::CASTS; break;	case 'R':		b.type = BonusType::ADDITIONAL_RETALIATION; break;	case 'W':		b.type = BonusType::MAGIC_RESISTANCE;		break;	case 'f': //on-off skill		enable = true; //sometimes format is: 2 -> 0, 1 -> 1		switch (mod[0])		{			case 'A':				b.type = BonusType::ATTACKS_ALL_ADJACENT; break;			case 'b':				b.type = BonusType::RETURN_AFTER_STRIKE; break;			case 'B':				b.type = BonusType::TWO_HEX_ATTACK_BREATH; break;			case 'c':				b.type = BonusType::JOUSTING; 				b.val = 5;				break;			case 'D':				b.type = BonusType::ADDITIONAL_ATTACK; break;			case 'f':				b.type = BonusType::FEARLESS; break;			case 'F':				b.type = BonusType::FLYING; break;			case 'm':				b.type = BonusType::MORALE;				b.val = 1;				b.valType = BonusValueType::INDEPENDENT_MAX;				break;			case 'M':				b.type = BonusType::NO_MORALE; break;			case 'p': //Mind spells			case 'P':				b.type = BonusType::MIND_IMMUNITY; break;			case 'r':				b.type = BonusType::REBIRTH; //on/off? makes sense?				b.subtype = BonusCustomSubtype::rebirthRegular;				b.val = 20; //arbitrary value				break;			case 'R':				b.type = BonusType::BLOCKS_RETALIATION; break;			case 's':				b.type = BonusType::FREE_SHOOTING; break;			case 'u':				b.type = BonusType::SPELL_RESISTANCE_AURA; break;			case 'U':				b.type = BonusType::UNDEAD; break;			default:				logGlobal->trace("Not parsed bonus %s %s", buf, mod);				return;				break;		}		break;	case 'w': //specific spell immunities, enabled/disabled		enable = true;		switch (mod[0])		{			case 'B': //Blind				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::BLIND));				b.additionalInfo = 0;//normal immunity				break;			case 'H': //Hypnotize				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::HYPNOTIZE));				b.additionalInfo = 0;//normal immunity				break;			case 'I': //Implosion				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::IMPLOSION));				b.additionalInfo = 0;//normal immunity				break;			case 'K': //Berserk				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::BERSERK));				b.additionalInfo = 0;//normal immunity				break;			case 'M': //Meteor Shower				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::METEOR_SHOWER));				b.additionalInfo = 0;//normal immunity				break;			case 'N': //dispel beneficial spells				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::DISPEL_HELPFUL_SPELLS));				b.additionalInfo = 0;//normal immunity				break;			case 'R': //Armageddon				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::ARMAGEDDON));				b.additionalInfo = 0;//normal immunity				break;			case 'S': //Slow				b.type = BonusType::SPELL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellID(SpellID::SLOW));				b.additionalInfo = 0;//normal immunity				break;			case '6':			case '7':			case '8':			case '9':				b.type = BonusType::LEVEL_SPELL_IMMUNITY;				b.val = std::atoi(mod.c_str()) - 5;				break;			case ':':				b.type = BonusType::LEVEL_SPELL_IMMUNITY;				b.val = GameConstants::SPELL_LEVELS; //in case someone adds higher level spells?				break;			case 'F':				b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::FIRE);				break;			case 'O':				b.type = BonusType::SPELL_DAMAGE_REDUCTION;				b.subtype = BonusSubtypeID(SpellSchool::FIRE);				b.val = 100; //Full damage immunity				break;			case 'f':				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::FIRE);				break;			case 'C':				b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::WATER);				break;			case 'W':				b.type = BonusType::SPELL_DAMAGE_REDUCTION;				b.subtype = BonusSubtypeID(SpellSchool::WATER);				b.val = 100; //Full damage immunity				break;			case 'w':				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::WATER);				break;			case 'E':				b.type = BonusType::SPELL_DAMAGE_REDUCTION;				b.subtype = BonusSubtypeID(SpellSchool::EARTH);				b.val = 100; //Full damage immunity				break;			case 'e':				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::EARTH);				break;			case 'A':				b.type = BonusType::SPELL_DAMAGE_REDUCTION;				b.subtype = BonusSubtypeID(SpellSchool::AIR);				b.val = 100; //Full damage immunity				break;			case 'a':				b.type = BonusType::SPELL_SCHOOL_IMMUNITY;				b.subtype = BonusSubtypeID(SpellSchool::AIR);				break;			case 'D':				b.type = BonusType::SPELL_DAMAGE_REDUCTION;				b.subtype = BonusSubtypeID(SpellSchool::ANY);				b.val = 100; //Full damage immunity				break;			case '0':				b.type = BonusType::RECEPTIVE;				break;			case 'm':				b.type = BonusType::MIND_IMMUNITY;				break;			default:				logGlobal->trace("Not parsed bonus %s %s", buf, mod);				return;		}		break;	case 'i':		enable = true;		b.type = BonusType::NO_DISTANCE_PENALTY;		break;	case 'o':		enable = true;		b.type = BonusType::NO_WALL_PENALTY;		break;	case 'a':	case 'c':	case 'K':	case 'k':		b.type = BonusType::SPELL_AFTER_ATTACK;		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));		break;	case 'h':		b.type = BonusType::HATE;		b.subtype = BonusSubtypeID(CreatureID(stringToNumber(mod)));		break;	case 'p':	case 'J':		b.type = BonusType::SPELL_BEFORE_ATTACK;		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));		b.additionalInfo = 3; //always expert?		break;	case 'r':		b.type = BonusType::HP_REGENERATION;		b.val = stringToNumber(mod);		break;	case 's':		b.type = BonusType::ENCHANTED;		b.subtype = BonusSubtypeID(SpellID(stringToNumber(mod)));		b.valType = BonusValueType::INDEPENDENT_MAX;		break;	default:		logGlobal->trace("Not parsed bonus %s %s", buf, mod);		return;		break;	}	switch (mod[0])	{		case '+':		case '=': //should we allow percent values to stack or pick highest?			b.valType = BonusValueType::ADDITIVE_VALUE;			break;	}	//limiters, range	si32 lastVal;	si32 curVal;	si32 lastLev = 0;	if (enable) //0 and 2 means non-active, 1 - active	{		if (b.type != BonusType::REBIRTH)			b.val = 0; //on-off ability, no value specified		parser.readNumber(); // 0 level is never active		for (int i = 1; i < 11; ++i)		{			curVal = static_cast<si32>(parser.readNumber());			if (curVal == 1)			{				b.limiter.reset (new RankRangeLimiter(i));				bl.push_back(std::make_shared<Bonus>(b));				break; //never turned off it seems			}		}	}	else	{		lastVal = static_cast<si32>(parser.readNumber());		if (b.type == BonusType::HATE)			lastVal *= 10; //odd fix		//FIXME: value for zero level should be stored in our config files (independent of stack exp)		for (int i = 1; i < 11; ++i)		{			curVal = static_cast<si32>(parser.readNumber());			if (b.type == BonusType::HATE)				curVal *= 10; //odd fix			if (curVal > lastVal) //threshold, add new bonus			{				b.val = curVal - lastVal;				lastVal = curVal;				b.limiter.reset (new RankRangeLimiter(i));				bl.push_back(std::make_shared<Bonus>(b));				lastLev = i; //start new range from here, i = previous rank			}			else if (curVal < lastVal)			{				b.val = lastVal;				b.limiter.reset (new RankRangeLimiter(lastLev, i));			}		}	}}int CCreatureHandler::stringToNumber(std::string & s) const{	boost::algorithm::replace_first(s,"#",""); //drop hash character	return std::atoi(s.c_str());}CCreatureHandler::~CCreatureHandler(){	for(auto & p : skillRequirements)		p.first = nullptr;}CreatureID CCreatureHandler::pickRandomMonster(vstd::RNG & rand, int tier) const{	std::vector<CreatureID> allowed;	for(const auto & creature : objects)	{		if(creature->special)			continue;		if(creature->excludeFromRandomization)			continue;		if (creature->level == tier || tier == -1)			allowed.push_back(creature->getId());	}	if(allowed.empty())	{		logGlobal->warn("Cannot pick a random creature of tier %d!", tier);		return CreatureID::NONE;	}	return *RandomGeneratorUtil::nextItem(allowed, rand);}void CCreatureHandler::afterLoadFinalization(){}VCMI_LIB_NAMESPACE_END
 |