| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069 | /* * CSpellHandler.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 <cctype>#include "CSpellHandler.h"#include "../CGeneralTextHandler.h"#include "../filesystem/Filesystem.h"#include "../JsonNode.h"#include "../CModHandler.h"#include "../StringConstants.h"#include "../BattleState.h"#include "../CBattleCallback.h"#include "../CGameState.h" //todo: remove#include "../NetPacks.h" //todo: remove#include "ISpellMechanics.h"namespace SpellConfig{	static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};	static const SpellSchoolInfo SCHOOL[4] =	{		{			ESpellSchool::AIR,			Bonus::AIR_SPELL_DMG_PREMY,			Bonus::AIR_IMMUNITY,			"air",			SecondarySkill::AIR_MAGIC,			Bonus::AIR_SPELLS		},		{			ESpellSchool::FIRE,			Bonus::FIRE_SPELL_DMG_PREMY,			Bonus::FIRE_IMMUNITY,			"fire",			SecondarySkill::FIRE_MAGIC,			Bonus::FIRE_SPELLS		},		{			ESpellSchool::WATER,			Bonus::WATER_SPELL_DMG_PREMY,			Bonus::WATER_IMMUNITY,			"water",			SecondarySkill::WATER_MAGIC,			Bonus::WATER_SPELLS		},		{			ESpellSchool::EARTH,			Bonus::EARTH_SPELL_DMG_PREMY,			Bonus::EARTH_IMMUNITY,			"earth",			SecondarySkill::EARTH_MAGIC,			Bonus::EARTH_SPELLS		}	};	//order as described in http://bugs.vcmi.eu/view.php?id=91	static const ESpellSchool SCHOOL_ORDER[4] =	{		ESpellSchool::AIR,  //=0		ESpellSchool::FIRE, //=1		ESpellSchool::EARTH,//=3(!)		ESpellSchool::WATER //=2(!)	};}///CSpell::LevelInfoCSpell::LevelInfo::LevelInfo()	:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0"){}CSpell::LevelInfo::~LevelInfo(){}///CSpellCSpell::CSpell():	id(SpellID::NONE), level(0),	power(0),	combatSpell(false), creatureAbility(false),	positiveness(ESpellPositiveness::NEUTRAL),	defaultProbability(0),	isRising(false), isDamage(false), isOffensive(false), isSpecial(true),	targetType(ETargetType::NO_TARGET),	mechanics(),	adventureMechanics(){	levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);}CSpell::~CSpell(){}void CSpell::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const{	mechanics->applyBattle(battle, packet);}bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const{	assert(env);	if(!adventureMechanics.get())	{		env->complain("Invalid adventure spell cast attempt!");		return false;	}	return adventureMechanics->adventureCast(env, parameters);}void CSpell::battleCast(const SpellCastEnvironment * env,  const BattleSpellCastParameters & parameters) const{	assert(env);	if(parameters.destinations.size()<1)	{		env->complain("Spell must have at least one destination");		return;	}	mechanics->battleCast(env, parameters);}const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const{	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)	{		logGlobal->error("CSpell::getLevelInfo invalid school level %d", level);		throw new std::runtime_error("Invalid school level");	}	return levels.at(level);}ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const{	//check if spell really does damage - if not, return 0	if(!isDamageSpell())		return 0;	return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower));}ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const{	const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);	if(generalProblem != ESpellCastProblem::OK)		return generalProblem;	//check for creature target existence	//allow to cast spell if there is at least one smart target	if(mechanics->requiresCreatureTarget())	{		switch(mode)		{		case ECastingMode::HERO_CASTING:		case ECastingMode::CREATURE_ACTIVE_CASTING:		case ECastingMode::ENCHANTER_CASTING:		case ECastingMode::PASSIVE_CASTING:			{				TargetInfo tinfo(this, caster->getSpellSchoolLevel(this), mode);				bool targetExists = false;				for(const CStack * stack : cb->battleGetAllStacks())				{					bool immune = !(stack->isValidTarget(!tinfo.onlyAlive) && ESpellCastProblem::OK == isImmuneByStack(caster, stack));					bool casterStack = stack->owner == caster->getOwner();					if(!immune)					{						switch (positiveness)						{						case CSpell::POSITIVE:							if(casterStack)								targetExists = true;							break;						case CSpell::NEUTRAL:								targetExists = true;								break;						case CSpell::NEGATIVE:							if(!casterStack)								targetExists = true;							break;						}					}					if(targetExists)						break;				}				if(!targetExists)				{					return ESpellCastProblem::NO_APPROPRIATE_TARGET;				}			}			break;		}	}	return ESpellCastProblem::OK;}std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const{	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);}std::vector<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster, int spellLvl, BattleHex destination) const{	SpellTargetingContext ctx(this, mode, caster, spellLvl, destination);	return mechanics->getAffectedStacks(cb, ctx);}CSpell::ETargetType CSpell::getTargetType() const{	return targetType;}void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const{	bool stop = false;	for(ESpellSchool iter : SpellConfig::SCHOOL_ORDER)	{		const SpellSchoolInfo & cnf = SpellConfig::SCHOOL[(ui8)iter];		if(school.at(cnf.id))		{			cb(cnf, stop);			if(stop)				break;		}	}}bool CSpell::isCombatSpell() const{	return combatSpell;}bool CSpell::isAdventureSpell() const{	return !combatSpell;}bool CSpell::isCreatureAbility() const{	return creatureAbility;}bool CSpell::isPositive() const{	return positiveness == POSITIVE;}bool CSpell::isNegative() const{	return positiveness == NEGATIVE;}bool CSpell::isNeutral() const{	return positiveness == NEUTRAL;}boost::logic::tribool CSpell::getPositiveness() const{	switch (positiveness)	{	case CSpell::POSITIVE:		return true;	case CSpell::NEGATIVE:		return false;	default:		return boost::logic::indeterminate;	}}bool CSpell::isRisingSpell() const{	return isRising;}bool CSpell::isDamageSpell() const{	return isDamage;}bool CSpell::isOffensiveSpell() const{	return isOffensive;}bool CSpell::isSpecialSpell() const{	return isSpecial;}bool CSpell::hasEffects() const{	return !levels[0].effects.empty();}const std::string & CSpell::getIconImmune() const{	return iconImmune;}const std::string & CSpell::getCastSound() const{	return castSound;}si32 CSpell::getCost(const int skillLevel) const{	return getLevelInfo(skillLevel).cost;}si32 CSpell::getPower(const int skillLevel) const{	return getLevelInfo(skillLevel).power;}si32 CSpell::getProbability(const TFaction factionId) const{	if(!vstd::contains(probabilities,factionId))	{		return defaultProbability;	}	return probabilities.at(factionId);}void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const{	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)	{		logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;		return;	}	const std::vector<Bonus> & effects = levels[level].effects;	if(effects.empty())	{		logGlobal->errorStream() << __FUNCTION__ << " This spell ("  + name + ") has no effects for level " << level;		return;	}	lst.reserve(lst.size() + effects.size());	for(const Bonus & b : effects)	{		lst.push_back(Bonus(b));	}}ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const{	SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);	return mechanics->canBeCast(cb, ctx);}int CSpell::adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const{	int ret = rawDamage;	//affected creature-specific part	if(nullptr != affectedCreature)	{		//applying protections - when spell has more then one elements, only one protection should be applied (I think)		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)		{			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))			{				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);				ret /= 100;				stop = true;//only bonus from one school is used			}		});		//general spell dmg reduction		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))		{			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);			ret /= 100;		}		//dmg increasing		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))		{			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());			ret /= 100;		}	}	if(caster != nullptr)		ret = caster->getSpellBonus(this, ret, affectedCreature);	return ret;}int CSpell::calculateRawEffectValue(int effectLevel, int effectPower) const{	return effectPower * power + getPower(effectLevel);}ESpellCastProblem::ESpellCastProblem CSpell::internalIsImmune(const ISpellCaster * caster, const CStack *obj) const{	//todo: use new bonus API	//1. Check absolute limiters	for(auto b : absoluteLimiters)	{		if (!obj->hasBonusOfType(b))			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	//2. Check absolute immunities	for(auto b : absoluteImmunities)	{		if (obj->hasBonusOfType(b))			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	{		//spell-based spell immunity (only ANTIMAGIC in OH3) is treated as absolute		std::stringstream cachingStr;		cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "source_" << Bonus::SPELL_EFFECT;		TBonusListPtr levelImmunitiesFromSpell = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::sourceType(Bonus::SPELL_EFFECT)), cachingStr.str());		if(levelImmunitiesFromSpell->size() > 0  &&  levelImmunitiesFromSpell->totalValue() >= level  &&  level)		{			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;		}	}	{		//SPELL_IMMUNITY absolute case		std::stringstream cachingStr;		cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << id.toEnum() << "addInfo_1";		if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, id.toEnum(), 1), cachingStr.str()))			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	//check receptivity	if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells		return ESpellCastProblem::OK;	//3. Check negation	//Orb of vulnerability	//FIXME: Orb of vulnerability mechanics is not such trivial (issue 1791)	const bool battleWideNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 0);	const bool heroNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 1);	//anyone can cast on artifact holder`s stacks	if(heroNegation)		return ESpellCastProblem::NOT_DECIDED;	//this stack is from other player	//todo: check that caster is always present (not trivial is this case)	//todo: NEGATE_ALL_NATURAL_IMMUNITIES special cases: dispell, chain lightning	else if(battleWideNegation && caster)	{		if(obj->owner != caster->getOwner())			return ESpellCastProblem::NOT_DECIDED;	}	//4. Check negatable limit	for(auto b : limiters)	{		if (!obj->hasBonusOfType(b))			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	//5. Check negatable immunities	for(auto b : immunities)	{		if (obj->hasBonusOfType(b))			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	//6. Check elemental immunities	ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)	{		auto element = cnf.immunityBonus;		if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether		{			tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;			stop = true;		}		else if(!isPositive()) //negative or indifferent		{			if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))			{				tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;				stop = true;			}		}	});	if(tmp != ESpellCastProblem::NOT_DECIDED)		return tmp;	TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));	if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)		|| ( levelImmunities->size() > 0  &&  levelImmunities->totalValue() >= level  &&  level))	{		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;	}	return ESpellCastProblem::NOT_DECIDED;}ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const{	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);	if (ESpellCastProblem::NOT_DECIDED != immuneResult)		return immuneResult;	return ESpellCastProblem::OK;}void CSpell::setIsOffensive(const bool val){	isOffensive = val;	if(val)	{		positiveness = CSpell::NEGATIVE;		isDamage = true;	}}void CSpell::setIsRising(const bool val){	isRising = val;	if(val)	{		positiveness = CSpell::POSITIVE;	}}void CSpell::setup(){	setupMechanics();}void CSpell::setupMechanics(){	mechanics = ISpellMechanics::createMechanics(this);	adventureMechanics = IAdventureSpellMechanics::createMechanics(this);}///CSpell::AnimationInfoCSpell::AnimationItem::AnimationItem()	:resourceName(""),verticalPosition(VerticalPosition::TOP),pause(0){}///CSpell::AnimationInfoCSpell::AnimationInfo::AnimationInfo(){}CSpell::AnimationInfo::~AnimationInfo(){}std::string CSpell::AnimationInfo::selectProjectile(const double angle) const{	std::string res;	double maximum = 0.0;	for(const auto & info : projectile)	{		if(info.minimumAngle < angle && info.minimumAngle > maximum)		{			maximum = info.minimumAngle;			res = info.resourceName;		}	}	return res;}///CSpell::TargetInfoCSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level){	init(spell, level);}CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode){	init(spell, level);	if(mode == ECastingMode::ENCHANTER_CASTING)	{		smart = true; //FIXME: not sure about that, this makes all spells smart in this mode		massive = true;	}	else if(mode == ECastingMode::SPELL_LIKE_ATTACK)	{		alwaysHitDirectly = true;	}	else if(mode == ECastingMode::CREATURE_ACTIVE_CASTING)	{		massive = false;//FIXME: find better solution for Commander spells	}}void CSpell::TargetInfo::init(const CSpell * spell, const int level){	auto & levelInfo = spell->getLevelInfo(level);	type = spell->getTargetType();	smart = levelInfo.smartTarget;	massive = levelInfo.range == "X";	onlyAlive = !spell->isRisingSpell();	alwaysHitDirectly = false;	clearAffected = levelInfo.clearAffected;	clearTarget = levelInfo.clearTarget;}bool DLL_LINKAGE isInScreenRange(const int3 & center, const int3 & pos){	int3 diff = pos - center;	if(diff.x >= -9  &&  diff.x <= 9  &&  diff.y >= -8  &&  diff.y <= 8)		return true;	else		return false;}///CSpellHandlerCSpellHandler::CSpellHandler(){}std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize){	using namespace SpellConfig;	std::vector<JsonNode> legacyData;	CLegacyConfigParser parser("DATA/SPTRAITS.TXT");	auto readSchool = [&](JsonMap & schools, const std::string & name)	{		if (parser.readString() == "x")		{			schools[name].Bool() = true;		}	};	auto read = [&,this](bool combat, bool ability)	{		do		{			JsonNode lineNode(JsonNode::DATA_STRUCT);			const si32 id = legacyData.size();			lineNode["index"].Float() = id;			lineNode["type"].String() = ability ? "ability" : (combat ? "combat" : "adventure");			lineNode["name"].String() = parser.readString();			parser.readString(); //ignored unused abbreviated name			lineNode["level"].Float()      = parser.readNumber();			auto& schools = lineNode["school"].Struct();			readSchool(schools, "earth");			readSchool(schools, "water");			readSchool(schools, "fire");			readSchool(schools, "air");			auto& levels = lineNode["levels"].Struct();			auto getLevel = [&](const size_t idx)->JsonMap&			{				assert(idx < GameConstants::SPELL_SCHOOL_LEVELS);				return levels[LEVEL_NAMES[idx]].Struct();			};			auto costs = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);			lineNode["power"].Float() = parser.readNumber();			auto powers = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);			auto& chances = lineNode["gainChance"].Struct();			for(size_t i = 0; i < GameConstants::F_NUMBER; i++){				chances[ETownType::names[i]].Float() = parser.readNumber();			}			auto AIVals = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);			std::vector<std::string> descriptions;			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)				descriptions.push_back(parser.readString());			parser.readString(); //ignore attributes. All data present in JSON			//save parsed level specific data			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)			{				auto& level = getLevel(i);				level["description"].String() = descriptions[i];				level["cost"].Float() = costs[i];				level["power"].Float() = powers[i];				level["aiValue"].Float() = AIVals[i];			}			legacyData.push_back(lineNode);		}		while (parser.endLine() && !parser.isNextEntryEmpty());	};	auto skip = [&](int cnt)	{		for(int i=0; i<cnt; i++)			parser.endLine();	};	skip(5);// header	read(false,false); //read adventure map spells	skip(3);	read(true,false); //read battle spells	skip(3);	read(true,true);//read creature abilities    //TODO: maybe move to config	//clone Acid Breath attributes for Acid Breath damage effect	JsonNode temp = legacyData[SpellID::ACID_BREATH_DEFENSE];	temp["index"].Float() = SpellID::ACID_BREATH_DAMAGE;	legacyData.push_back(temp);	objects.resize(legacyData.size());	return legacyData;}const std::string CSpellHandler::getTypeName() const{	return "spell";}CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string & identifier){	using namespace SpellConfig;	CSpell * spell = new CSpell();	spell->identifier = identifier;	const auto type = json["type"].String();	if(type == "ability")	{		spell->creatureAbility = true;		spell->combatSpell = true;	}	else	{		spell->creatureAbility = false;		spell->combatSpell = type == "combat";	}	spell->name = json["name"].String();	logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;	const auto schoolNames = json["school"];	for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)	{		spell->school[info.id] = schoolNames[info.jsonName].Bool();	}	spell->level = json["level"].Float();	spell->power = json["power"].Float();	spell->defaultProbability = json["defaultGainChance"].Float();	for(const auto & node : json["gainChance"].Struct())	{		const int chance = node.second.Float();		VLC->modh->identifiers.requestIdentifier(node.second.meta, "faction",node.first, [=](si32 factionID)		{			spell->probabilities[factionID] = chance;		});	}	auto targetType = json["targetType"].String();	if(targetType == "NO_TARGET")		spell->targetType = CSpell::NO_TARGET;	else if(targetType == "CREATURE")		spell->targetType = CSpell::CREATURE;	else if(targetType == "OBSTACLE")		spell->targetType = CSpell::OBSTACLE;	else if(targetType == "LOCATION")		spell->targetType = CSpell::LOCATION;	else		logGlobal->warnStream() << "Spell " << spell->name << ": target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ", assumed NO_TARGET.";	for(const auto & counteredSpell: json["counters"].Struct())		if (counteredSpell.second.Bool())		{			VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)			{				spell->counteredSpells.push_back(SpellID(id));			});		}	//TODO: more error checking - f.e. conflicting flags	const auto flags = json["flags"];	//by default all flags are set to false in constructor	spell->isDamage = flags["damage"].Bool(); //do this before "offensive"	if(flags["offensive"].Bool())	{		spell->setIsOffensive(true);	}	if(flags["rising"].Bool())	{		spell->setIsRising(true);	}	const bool implicitPositiveness = spell->isOffensive || spell->isRising; //(!) "damage" does not mean NEGATIVE  --AVS	if(flags["indifferent"].Bool())	{		spell->positiveness = CSpell::NEUTRAL;	}	else if(flags["negative"].Bool())	{		spell->positiveness = CSpell::NEGATIVE;	}	else if(flags["positive"].Bool())	{		spell->positiveness = CSpell::POSITIVE;	}	else if(!implicitPositiveness)	{		spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case		logGlobal->errorStream() << "Spell " << spell->name << ": no positiveness specified, assumed NEUTRAL.";	}	spell->isSpecial = flags["special"].Bool();	auto findBonus = [&](std::string name, std::vector<Bonus::BonusType> & vec)	{		auto it = bonusNameMap.find(name);		if(it == bonusNameMap.end())		{			logGlobal->errorStream() << "Spell " << spell->name << ": invalid bonus name " << name;		}		else		{			vec.push_back((Bonus::BonusType)it->second);		}	};	auto readBonusStruct = [&](std::string name, std::vector<Bonus::BonusType> & vec)	{		for(auto bonusData: json[name].Struct())		{			const std::string bonusId = bonusData.first;			const bool flag = bonusData.second.Bool();			if(flag)				findBonus(bonusId, vec);		}	};	readBonusStruct("immunity", spell->immunities);	readBonusStruct("absoluteImmunity", spell->absoluteImmunities);	readBonusStruct("limit", spell->limiters);	readBonusStruct("absoluteLimit", spell->absoluteLimiters);	const JsonNode & graphicsNode = json["graphics"];	spell->iconImmune = graphicsNode["iconImmune"].String();	spell->iconBook = graphicsNode["iconBook"].String();	spell->iconEffect = graphicsNode["iconEffect"].String();	spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();	spell->iconScroll = graphicsNode["iconScroll"].String();	const JsonNode & animationNode = json["animation"];	auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)	{		auto queueNode = animationNode[jsonName].Vector();		for(const JsonNode & item : queueNode)		{			CSpell::TAnimation newItem;			if(item.getType() == JsonNode::DATA_STRING)				newItem.resourceName = item.String();			else if(item.getType() == JsonNode::DATA_STRUCT)			{				newItem.resourceName = item["defName"].String();				auto vPosStr = item["verticalPosition"].String();				if("bottom" == vPosStr)					newItem.verticalPosition = VerticalPosition::BOTTOM;			}			else if(item.getType() == JsonNode::DATA_FLOAT)			{				newItem.pause = item.Float();			}			q.push_back(newItem);		}	};	loadAnimationQueue("affect", spell->animationInfo.affect);	loadAnimationQueue("cast", spell->animationInfo.cast);	loadAnimationQueue("hit", spell->animationInfo.hit);	const JsonVector & projectile = animationNode["projectile"].Vector();	for(const JsonNode & item : projectile)	{		CSpell::ProjectileInfo info;		info.resourceName = item["defName"].String();		info.minimumAngle = item["minimumAngle"].Float();		spell->animationInfo.projectile.push_back(info);	}	const JsonNode & soundsNode = json["sounds"];	spell->castSound = soundsNode["cast"].String();	//load level attributes	const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;	for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)	{		const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];		CSpell::LevelInfo & levelObject = spell->levels[levelIndex];		const si32 levelPower     = levelObject.power = levelNode["power"].Float();		levelObject.description   = levelNode["description"].String();		levelObject.cost          = levelNode["cost"].Float();		levelObject.AIValue       = levelNode["aiValue"].Float();		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();		levelObject.clearTarget   = levelNode["targetModifier"]["clearTarget"].Bool();		levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();		levelObject.range         = levelNode["range"].String();		for(const auto & elem : levelNode["effects"].Struct())		{			const JsonNode & bonusNode = elem.second;			auto b = JsonUtils::parseBonus(bonusNode);			const bool usePowerAsValue = bonusNode["val"].isNull();			//TODO: make this work. see CSpellHandler::afterLoadFinalization()			//b->sid = spell->id; //for all			b->source = Bonus::SPELL_EFFECT;//for all			if(usePowerAsValue)				b->val = levelPower;			levelObject.effectsTmp.push_back(b);		}	}	return spell;}void CSpellHandler::afterLoadFinalization(){	//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading	for(auto spell: objects)	{		for(auto & level: spell->levels)		{			for(auto bonus : level.effectsTmp)			{				level.effects.push_back(*bonus);			}			level.effectsTmp.clear();			for(auto & bonus: level.effects)				bonus.sid = spell->id;		}		spell->setup();	}}void CSpellHandler::beforeValidate(JsonNode & object){	//handle "base" level info	JsonNode & levels = object["levels"];	JsonNode & base = levels["base"];	auto inheritNode = [&](const std::string & name){		JsonUtils::inherit(levels[name],base);	};	inheritNode("none");	inheritNode("basic");	inheritNode("advanced");	inheritNode("expert");}CSpellHandler::~CSpellHandler(){}std::vector<bool> CSpellHandler::getDefaultAllowed() const{	std::vector<bool> allowedSpells;	allowedSpells.reserve(objects.size());	for(const CSpell * s : objects)	{		allowedSpells.push_back( !(s->isSpecialSpell() || s->isCreatureAbility()));	}	return allowedSpells;}si32 CSpellHandler::decodeSpell(const std::string& identifier){	auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);	if(rawId)		return rawId.get();	else		return -1;}std::string CSpellHandler::encodeSpell(const si32 index){	return VLC->spellh->objects[index]->identifier;}
 |