mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Split mechanics implementation
This commit is contained in:
		| @@ -150,8 +150,6 @@ | |||||||
| 		<Unit filename="CRandomGenerator.h" /> | 		<Unit filename="CRandomGenerator.h" /> | ||||||
| 		<Unit filename="CScriptingModule.h" /> | 		<Unit filename="CScriptingModule.h" /> | ||||||
| 		<Unit filename="CSoundBase.h" /> | 		<Unit filename="CSoundBase.h" /> | ||||||
| 		<Unit filename="CSpellHandler.cpp" /> |  | ||||||
| 		<Unit filename="CSpellHandler.h" /> |  | ||||||
| 		<Unit filename="CStopWatch.h" /> | 		<Unit filename="CStopWatch.h" /> | ||||||
| 		<Unit filename="CThreadHelper.cpp" /> | 		<Unit filename="CThreadHelper.cpp" /> | ||||||
| 		<Unit filename="CThreadHelper.h" /> | 		<Unit filename="CThreadHelper.h" /> | ||||||
| @@ -185,8 +183,6 @@ | |||||||
| 		<Unit filename="ResourceSet.cpp" /> | 		<Unit filename="ResourceSet.cpp" /> | ||||||
| 		<Unit filename="ResourceSet.h" /> | 		<Unit filename="ResourceSet.h" /> | ||||||
| 		<Unit filename="ScopeGuard.h" /> | 		<Unit filename="ScopeGuard.h" /> | ||||||
| 		<Unit filename="SpellMechanics.cpp" /> |  | ||||||
| 		<Unit filename="SpellMechanics.h" /> |  | ||||||
| 		<Unit filename="StartInfo.h" /> | 		<Unit filename="StartInfo.h" /> | ||||||
| 		<Unit filename="StdInc.h"> | 		<Unit filename="StdInc.h"> | ||||||
| 			<Option weight="0" /> | 			<Option weight="0" /> | ||||||
| @@ -294,6 +290,18 @@ | |||||||
| 		<Unit filename="rmg/CZoneGraphGenerator.h" /> | 		<Unit filename="rmg/CZoneGraphGenerator.h" /> | ||||||
| 		<Unit filename="rmg/CZonePlacer.cpp" /> | 		<Unit filename="rmg/CZonePlacer.cpp" /> | ||||||
| 		<Unit filename="rmg/CZonePlacer.h" /> | 		<Unit filename="rmg/CZonePlacer.h" /> | ||||||
|  | 		<Unit filename="spells/AdventureSpellMechanics.cpp" /> | ||||||
|  | 		<Unit filename="spells/AdventureSpellMechanics.h" /> | ||||||
|  | 		<Unit filename="spells/BattleSpellMechanics.cpp" /> | ||||||
|  | 		<Unit filename="spells/BattleSpellMechanics.h" /> | ||||||
|  | 		<Unit filename="spells/CDefaultSpellMechanics.cpp" /> | ||||||
|  | 		<Unit filename="spells/CDefaultSpellMechanics.h" /> | ||||||
|  | 		<Unit filename="spells/CSpellHandler.cpp" /> | ||||||
|  | 		<Unit filename="spells/CSpellHandler.h" /> | ||||||
|  | 		<Unit filename="spells/CreatureSpellMechanics.cpp" /> | ||||||
|  | 		<Unit filename="spells/CreatureSpellMechanics.h" /> | ||||||
|  | 		<Unit filename="spells/ISpellMechanics.cpp" /> | ||||||
|  | 		<Unit filename="spells/ISpellMechanics.h" /> | ||||||
| 		<Unit filename="vcmi_endian.h" /> | 		<Unit filename="vcmi_endian.h" /> | ||||||
| 		<Extensions> | 		<Extensions> | ||||||
| 			<code_completion /> | 			<code_completion /> | ||||||
|   | |||||||
| @@ -11,3 +11,245 @@ | |||||||
| #include "StdInc.h" | #include "StdInc.h" | ||||||
|  |  | ||||||
| #include "AdventureSpellMechanics.h" | #include "AdventureSpellMechanics.h" | ||||||
|  |  | ||||||
|  | #include "../CRandomGenerator.h" | ||||||
|  | #include "../mapObjects/CGHeroInstance.h" | ||||||
|  | #include "../NetPacks.h" | ||||||
|  | #include "../BattleState.h" | ||||||
|  | #include "../CGameState.h" | ||||||
|  | #include "../CGameInfoCallback.h" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///AdventureBonusingMechanics | ||||||
|  | bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||||
|  | { | ||||||
|  | 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	 | ||||||
|  | 	const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert | ||||||
|  |  | ||||||
|  | 	GiveBonus gb; | ||||||
|  | 	gb.id = parameters.caster->id.getNum(); | ||||||
|  | 	gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype); | ||||||
|  | 	env->sendAndApply(&gb);	 | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///SummonBoatMechanics | ||||||
|  | bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||||
|  | { | ||||||
|  | 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	 | ||||||
|  | 	//check if spell works at all | ||||||
|  | 	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success | ||||||
|  | 	{ | ||||||
|  | 		InfoWindow iw; | ||||||
|  | 		iw.player = parameters.caster->tempOwner; | ||||||
|  | 		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed. | ||||||
|  | 		iw.text.addReplacement(parameters.caster->name); | ||||||
|  | 		env->sendAndApply(&iw); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//try to find unoccupied boat to summon | ||||||
|  | 	const CGBoat * nearest = nullptr; | ||||||
|  | 	double dist = 0; | ||||||
|  | 	int3 summonPos = parameters.caster->bestLocation(); | ||||||
|  | 	if(summonPos.x < 0) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("There is no water tile available!"); | ||||||
|  | 		return false; | ||||||
|  | 	}	 | ||||||
|  | 	 | ||||||
|  | 	for(const CGObjectInstance * obj : env->getMap()->objects) | ||||||
|  | 	{ | ||||||
|  | 		if(obj && obj->ID == Obj::BOAT) | ||||||
|  | 		{ | ||||||
|  | 			const CGBoat *b = static_cast<const CGBoat*>(obj); | ||||||
|  | 			if(b->hero)  | ||||||
|  | 				continue; //we're looking for unoccupied boat | ||||||
|  |  | ||||||
|  | 			double nDist = b->pos.dist2d(parameters.caster->getPosition()); | ||||||
|  | 			if(!nearest || nDist < dist) //it's first boat or closer than previous | ||||||
|  | 			{ | ||||||
|  | 				nearest = b; | ||||||
|  | 				dist = nDist; | ||||||
|  | 			} | ||||||
|  | 		}						 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(nullptr != nearest) //we found boat to summon | ||||||
|  | 	{ | ||||||
|  | 		ChangeObjPos cop; | ||||||
|  | 		cop.objid = nearest->id; | ||||||
|  | 		cop.nPos = summonPos + int3(1,0,0);; | ||||||
|  | 		cop.flags = 1; | ||||||
|  | 		env->sendAndApply(&cop); | ||||||
|  | 	} | ||||||
|  | 	else if(schoolLevel < 2) //none or basic level -> cannot create boat :( | ||||||
|  | 	{ | ||||||
|  | 		InfoWindow iw; | ||||||
|  | 		iw.player = parameters.caster->tempOwner; | ||||||
|  | 		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon. | ||||||
|  | 		env->sendAndApply(&iw); | ||||||
|  | 	} | ||||||
|  | 	else //create boat | ||||||
|  | 	{ | ||||||
|  | 		NewObject no; | ||||||
|  | 		no.ID = Obj::BOAT; | ||||||
|  | 		no.subID = parameters.caster->getBoatType(); | ||||||
|  | 		no.pos = summonPos + int3(1,0,0);; | ||||||
|  | 		env->sendAndApply(&no); | ||||||
|  | 	}	 | ||||||
|  | 	return true;	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///ScuttleBoatMechanics | ||||||
|  | bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const | ||||||
|  | { | ||||||
|  | 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner); | ||||||
|  | 	//check if spell works at all | ||||||
|  | 	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success | ||||||
|  | 	{ | ||||||
|  | 		InfoWindow iw; | ||||||
|  | 		iw.player = parameters.caster->tempOwner; | ||||||
|  | 		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed | ||||||
|  | 		iw.text.addReplacement(parameters.caster->name); | ||||||
|  | 		env->sendAndApply(&iw); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 		 | ||||||
|  | 	if(!env->getMap()->isInTheMap(parameters.pos)) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Invalid dst tile for scuttle!"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//TODO: test range, visibility | ||||||
|  | 	const TerrainTile *t = &env->getMap()->getTile(parameters.pos); | ||||||
|  | 	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("There is no boat to scuttle!"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 		 | ||||||
|  |  | ||||||
|  | 	RemoveObject ro; | ||||||
|  | 	ro.id = t->visitableObjects.back()->id; | ||||||
|  | 	env->sendAndApply(&ro); | ||||||
|  | 	return true;	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///DimensionDoorMechanics | ||||||
|  | bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const | ||||||
|  | { | ||||||
|  | 	if(!env->getMap()->isInTheMap(parameters.pos)) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Destination is out of map!"); | ||||||
|  | 		return false; | ||||||
|  | 	}	 | ||||||
|  | 	 | ||||||
|  | 	const TerrainTile * dest = env->getCb()->getTile(parameters.pos); | ||||||
|  | 	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter()); | ||||||
|  |  | ||||||
|  | 	if(nullptr == dest) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Destination tile doesn't exist!"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if(nullptr == curr) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Source tile doesn't exist!"); | ||||||
|  | 		return false; | ||||||
|  | 	}	 | ||||||
|  | 		 | ||||||
|  | 	if(parameters.caster->movement <= 0) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Hero needs movement points to cast Dimension Door!"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner); | ||||||
|  | 			 | ||||||
|  | 	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn | ||||||
|  | 	{ | ||||||
|  | 		InfoWindow iw; | ||||||
|  | 		iw.player = parameters.caster->tempOwner; | ||||||
|  | 		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today. | ||||||
|  | 		iw.text.addReplacement(parameters.caster->name); | ||||||
|  | 		env->sendAndApply(&iw); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	GiveBonus gb; | ||||||
|  | 	gb.id = parameters.caster->id.getNum(); | ||||||
|  | 	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id); | ||||||
|  | 	env->sendAndApply(&gb); | ||||||
|  |  | ||||||
|  | 	if(!dest->isClear(curr)) //wrong dest tile | ||||||
|  | 	{ | ||||||
|  | 		InfoWindow iw; | ||||||
|  | 		iw.player = parameters.caster->tempOwner; | ||||||
|  | 		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed! | ||||||
|  | 		env->sendAndApply(&iw);		 | ||||||
|  | 	} | ||||||
|  | 	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true)) | ||||||
|  | 	{ | ||||||
|  | 		SetMovePoints smp; | ||||||
|  | 		smp.hid = parameters.caster->id; | ||||||
|  | 		smp.val = std::max<ui32>(0, parameters.caster->movement - 300); | ||||||
|  | 		env->sendAndApply(&smp); | ||||||
|  | 	} | ||||||
|  | 	return true; 	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///TownPortalMechanics | ||||||
|  | bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const | ||||||
|  | { | ||||||
|  | 	if (!env->getMap()->isInTheMap(parameters.pos)) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Destination tile not present!"); | ||||||
|  | 		return false; | ||||||
|  | 	}	 | ||||||
|  |  | ||||||
|  | 	TerrainTile tile = env->getMap()->getTile(parameters.pos); | ||||||
|  | 	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Town not found for Town Portal!");	 | ||||||
|  | 		return false; | ||||||
|  | 	}		 | ||||||
|  |  | ||||||
|  | 	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back()); | ||||||
|  | 	if (town->tempOwner != parameters.caster->tempOwner) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Can't teleport to another player!"); | ||||||
|  | 		return false; | ||||||
|  | 	}			 | ||||||
|  | 	 | ||||||
|  | 	if (town->visitingHero) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Can't teleport to occupied town!"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if (parameters.caster->getSpellSchoolLevel(owner) < 2) | ||||||
|  | 	{ | ||||||
|  | 		si32 dist = town->pos.dist2dSQ(parameters.caster->pos); | ||||||
|  | 		ObjectInstanceID nearest = town->id; //nearest town's ID | ||||||
|  | 		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns) | ||||||
|  | 		{ | ||||||
|  | 			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos); | ||||||
|  | 			if (currDist < dist) | ||||||
|  | 			{ | ||||||
|  | 				nearest = currTown->id; | ||||||
|  | 				dist = currDist; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if (town->id != nearest) | ||||||
|  | 		{ | ||||||
|  | 			env->complain("This hero can only teleport to nearest town!"); | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 			 | ||||||
|  | 	} | ||||||
|  | 	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);	 | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,3 +11,47 @@ | |||||||
|  #pragma once |  #pragma once | ||||||
|   |   | ||||||
|  #include "CDefaultSpellMechanics.h" |  #include "CDefaultSpellMechanics.h" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //todo: make configurable | ||||||
|  | class AdventureBonusingMechanics: public DefaultSpellMechanics  | ||||||
|  | { | ||||||
|  | public:	 | ||||||
|  | 	AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};	 | ||||||
|  | protected: | ||||||
|  | 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||||
|  | private: | ||||||
|  | 	Bonus::BonusType bonusTypeID; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SummonBoatMechanics: public DefaultSpellMechanics  | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ScuttleBoatMechanics: public DefaultSpellMechanics  | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DimensionDoorMechanics: public DefaultSpellMechanics  | ||||||
|  | { | ||||||
|  | public:	 | ||||||
|  | 	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||||
|  | protected: | ||||||
|  | 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class TownPortalMechanics: public DefaultSpellMechanics  | ||||||
|  | { | ||||||
|  | public:	 | ||||||
|  | 	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||||
|  | protected: | ||||||
|  | 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -11,3 +11,409 @@ | |||||||
| #include "StdInc.h" | #include "StdInc.h" | ||||||
|  |  | ||||||
| #include "BattleSpellMechanics.h" | #include "BattleSpellMechanics.h" | ||||||
|  |  | ||||||
|  | #include "../NetPacks.h" | ||||||
|  | #include "../BattleState.h" | ||||||
|  |  | ||||||
|  | ///ChainLightningMechanics | ||||||
|  | std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const | ||||||
|  | { | ||||||
|  | 	std::set<const CStack* > attackedCres; | ||||||
|  | 	 | ||||||
|  | 	std::set<BattleHex> possibleHexes; | ||||||
|  | 	for(auto stack : ctx.cb->battleGetAllStacks()) | ||||||
|  | 	{ | ||||||
|  | 		if(stack->isValidTarget()) | ||||||
|  | 		{ | ||||||
|  | 			for(auto hex : stack->getHexes()) | ||||||
|  | 			{ | ||||||
|  | 				possibleHexes.insert (hex); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	int targetsOnLevel[4] = {4, 4, 5, 5}; | ||||||
|  |  | ||||||
|  | 	BattleHex lightningHex = ctx.destination; | ||||||
|  | 	for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i) | ||||||
|  | 	{ | ||||||
|  | 		auto stack = ctx.cb->battleGetStackByPos(lightningHex, true); | ||||||
|  | 		if(!stack) | ||||||
|  | 			break; | ||||||
|  | 		attackedCres.insert (stack); | ||||||
|  | 		for(auto hex : stack->getHexes()) | ||||||
|  | 		{ | ||||||
|  | 			possibleHexes.erase(hex); //can't hit same place twice | ||||||
|  | 		} | ||||||
|  | 		if(possibleHexes.empty()) //not enough targets | ||||||
|  | 			break; | ||||||
|  | 		lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes); | ||||||
|  | 	}	 | ||||||
|  | 		 | ||||||
|  | 	return attackedCres; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///CloneMechanics | ||||||
|  | void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	const CStack * clonedStack = nullptr; | ||||||
|  | 	if(ctx.attackedCres.size()) | ||||||
|  | 		clonedStack = *ctx.attackedCres.begin(); | ||||||
|  | 	if(!clonedStack) | ||||||
|  | 	{ | ||||||
|  | 		env->complain ("No target stack to clone!"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	const int attacker = !(bool)parameters.casterSide;  | ||||||
|  |  | ||||||
|  | 	BattleStackAdded bsa; | ||||||
|  | 	bsa.creID = clonedStack->type->idNumber; | ||||||
|  | 	bsa.attacker = attacker; | ||||||
|  | 	bsa.summoned = true; | ||||||
|  | 	bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it | ||||||
|  | 	bsa.amount = clonedStack->count; | ||||||
|  | 	env->sendAndApply(&bsa); | ||||||
|  |  | ||||||
|  | 	BattleSetStackProperty ssp; | ||||||
|  | 	ssp.stackID = bsa.newStackID;//we know stack ID after apply | ||||||
|  | 	ssp.which = BattleSetStackProperty::CLONED; | ||||||
|  | 	ssp.val = 0; | ||||||
|  | 	ssp.absolute = 1; | ||||||
|  | 	env->sendAndApply(&ssp);	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const | ||||||
|  | { | ||||||
|  | 	//can't clone already cloned creature | ||||||
|  | 	if(vstd::contains(obj->state, EBattleStackState::CLONED)) | ||||||
|  | 		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; | ||||||
|  | 	//TODO: how about stacks casting Clone? | ||||||
|  | 	//currently Clone casted by stack is assumed Expert level | ||||||
|  | 	ui8 schoolLevel; | ||||||
|  | 	if(caster) | ||||||
|  | 	{ | ||||||
|  | 		schoolLevel = caster->getSpellSchoolLevel(owner); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		schoolLevel = 3; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(schoolLevel < 3) | ||||||
|  | 	{ | ||||||
|  | 		int maxLevel = (std::max(schoolLevel, (ui8)1) + 4); | ||||||
|  | 		int creLevel = obj->getCreature()->level; | ||||||
|  | 		if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert | ||||||
|  | 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; | ||||||
|  | 	} | ||||||
|  | 	//use default algorithm only if there is no mechanics-related problem		 | ||||||
|  | 	return DefaultSpellMechanics::isImmuneByStack(caster, obj);	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///CureMechanics | ||||||
|  | void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const | ||||||
|  | { | ||||||
|  | 	DefaultSpellMechanics::applyBattle(battle, packet); | ||||||
|  | 	 | ||||||
|  | 	for(auto stackID : packet->affectedCres) | ||||||
|  | 	{ | ||||||
|  | 		if(vstd::contains(packet->resisted, stackID)) | ||||||
|  | 		{ | ||||||
|  | 			logGlobal->errorStream() << "Resistance to positive spell CURE"; | ||||||
|  | 			continue; | ||||||
|  | 		}			 | ||||||
|  |  | ||||||
|  | 		CStack *s = battle->getStack(stackID); | ||||||
|  | 		s->popBonuses([&](const Bonus *b) -> bool | ||||||
|  | 		{ | ||||||
|  | 			if(b->source == Bonus::SPELL_EFFECT) | ||||||
|  | 			{ | ||||||
|  | 				CSpell * sp = SpellID(b->sid).toSpell(); | ||||||
|  | 				return sp->isNegative(); | ||||||
|  | 			} | ||||||
|  | 			return false; //not a spell effect | ||||||
|  | 		}); | ||||||
|  | 	}		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///DispellMechanics | ||||||
|  | void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const | ||||||
|  | { | ||||||
|  | 	DefaultSpellMechanics::applyBattle(battle, packet); | ||||||
|  | 	 | ||||||
|  | 	for(auto stackID : packet->affectedCres) | ||||||
|  | 	{ | ||||||
|  | 		if(vstd::contains(packet->resisted, stackID)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		CStack *s = battle->getStack(stackID); | ||||||
|  | 		s->popBonuses([&](const Bonus *b) -> bool | ||||||
|  | 		{ | ||||||
|  | 			return Selector::sourceType(Bonus::SPELL_EFFECT)(b); | ||||||
|  | 		}); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///HypnotizeMechanics | ||||||
|  | ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const | ||||||
|  | { | ||||||
|  | 	if(nullptr != caster) //do not resist hypnotize casted after attack, for example | ||||||
|  | 	{ | ||||||
|  | 		//TODO: what with other creatures casting hypnotize, Faerie Dragons style? | ||||||
|  | 		ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft; | ||||||
|  | 		//apply 'damage' bonus for hypnotize, including hero specialty | ||||||
|  | 		ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) | ||||||
|  | 			* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj); | ||||||
|  | 		if (subjectHealth > maxHealth) | ||||||
|  | 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; | ||||||
|  | 	}			 | ||||||
|  | 	return DefaultSpellMechanics::isImmuneByStack(caster, obj); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///ObstacleMechanics | ||||||
|  | void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	auto placeObstacle = [&, this](BattleHex pos) | ||||||
|  | 	{ | ||||||
|  | 		static int obstacleIdToGive =  parameters.cb->obstacles.size() | ||||||
|  | 									? (parameters.cb->obstacles.back()->uniqueID+1) | ||||||
|  | 									: 0; | ||||||
|  |  | ||||||
|  | 		auto obstacle = make_shared<SpellCreatedObstacle>(); | ||||||
|  | 		switch(owner->id) // :/ | ||||||
|  | 		{ | ||||||
|  | 		case SpellID::QUICKSAND: | ||||||
|  | 			obstacle->obstacleType = CObstacleInstance::QUICKSAND; | ||||||
|  | 			obstacle->turnsRemaining = -1; | ||||||
|  | 			obstacle->visibleForAnotherSide = false; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::LAND_MINE: | ||||||
|  | 			obstacle->obstacleType = CObstacleInstance::LAND_MINE; | ||||||
|  | 			obstacle->turnsRemaining = -1; | ||||||
|  | 			obstacle->visibleForAnotherSide = false; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::FIRE_WALL: | ||||||
|  | 			obstacle->obstacleType = CObstacleInstance::FIRE_WALL; | ||||||
|  | 			obstacle->turnsRemaining = 2; | ||||||
|  | 			obstacle->visibleForAnotherSide = true; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::FORCE_FIELD: | ||||||
|  | 			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD; | ||||||
|  | 			obstacle->turnsRemaining = 2; | ||||||
|  | 			obstacle->visibleForAnotherSide = true; | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			//this function cannot be used with spells that do not create obstacles | ||||||
|  | 			assert(0); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		obstacle->pos = pos; | ||||||
|  | 		obstacle->casterSide = parameters.casterSide; | ||||||
|  | 		obstacle->ID = owner->id; | ||||||
|  | 		obstacle->spellLevel = parameters.spellLvl; | ||||||
|  | 		obstacle->casterSpellPower = parameters.usedSpellPower; | ||||||
|  | 		obstacle->uniqueID = obstacleIdToGive++; | ||||||
|  |  | ||||||
|  | 		BattleObstaclePlaced bop; | ||||||
|  | 		bop.obstacle = obstacle; | ||||||
|  | 		env->sendAndApply(&bop); | ||||||
|  | 	};	 | ||||||
|  | 	 | ||||||
|  | 	switch(owner->id) | ||||||
|  | 	{ | ||||||
|  | 	case SpellID::QUICKSAND: | ||||||
|  | 	case SpellID::LAND_MINE: | ||||||
|  | 		{ | ||||||
|  | 			std::vector<BattleHex> availableTiles; | ||||||
|  | 			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1) | ||||||
|  | 			{ | ||||||
|  | 				BattleHex hex = i; | ||||||
|  | 				if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false))) | ||||||
|  | 					availableTiles.push_back(hex); | ||||||
|  | 			} | ||||||
|  | 			boost::range::random_shuffle(availableTiles); | ||||||
|  |  | ||||||
|  | 			const int patchesForSkill[] = {4, 4, 6, 8}; | ||||||
|  | 			const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size()); | ||||||
|  |  | ||||||
|  | 			//land mines or quicksand patches are handled as spell created obstacles | ||||||
|  | 			for (int i = 0; i < patchesToPut; i++) | ||||||
|  | 				placeObstacle(availableTiles.at(i)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		break; | ||||||
|  | 	case SpellID::FORCE_FIELD: | ||||||
|  | 		placeObstacle(parameters.destination); | ||||||
|  | 		break; | ||||||
|  | 	case SpellID::FIRE_WALL: | ||||||
|  | 		{ | ||||||
|  | 			//fire wall is build from multiple obstacles - one fire piece for each affected hex | ||||||
|  | 			auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide); | ||||||
|  | 			for(BattleHex hex : affectedHexes) | ||||||
|  | 				placeObstacle(hex); | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  | 	default:		 | ||||||
|  | 		assert(0); | ||||||
|  | 	}			 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///WallMechanics | ||||||
|  | std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const | ||||||
|  | { | ||||||
|  | 	std::vector<BattleHex> ret;	 | ||||||
|  | 	 | ||||||
|  | 	//Special case - shape of obstacle depends on caster's side | ||||||
|  | 	//TODO make it possible through spell config | ||||||
|  |  | ||||||
|  | 	BattleHex::EDir firstStep, secondStep; | ||||||
|  | 	if(side) | ||||||
|  | 	{ | ||||||
|  | 		firstStep = BattleHex::TOP_LEFT; | ||||||
|  | 		secondStep = BattleHex::TOP_RIGHT; | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		firstStep = BattleHex::TOP_RIGHT; | ||||||
|  | 		secondStep = BattleHex::TOP_LEFT; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given. | ||||||
|  | 	auto addIfValid = [&](BattleHex hex) | ||||||
|  | 	{ | ||||||
|  | 		if(hex.isValid()) | ||||||
|  | 			ret.push_back(hex); | ||||||
|  | 		else if(outDroppedHexes) | ||||||
|  | 			*outDroppedHexes = true; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	ret.push_back(centralHex); | ||||||
|  | 	addIfValid(centralHex.moveInDir(firstStep, false)); | ||||||
|  | 	if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes | ||||||
|  | 		addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex | ||||||
|  |  | ||||||
|  | 	return ret;	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///RemoveObstacleMechanics | ||||||
|  | void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false)) | ||||||
|  | 	{ | ||||||
|  | 		ObstaclesRemoved obr; | ||||||
|  | 		obr.obstacles.insert(obstacleToRemove->uniqueID); | ||||||
|  | 		env->sendAndApply(&obr); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		env->complain("There's no obstacle to remove!");	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///SpecialRisingSpellMechanics | ||||||
|  | void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	RisingSpellMechanics::applyBattleEffects(env, parameters, ctx); | ||||||
|  |  | ||||||
|  | 	if(parameters.selectedStack == parameters.cb->battleActiveStack()) | ||||||
|  | 	//set another active stack than the one removed, or bad things will happen | ||||||
|  | 	//TODO: make that part of BattleStacksRemoved? what about client update? | ||||||
|  | 	{ | ||||||
|  | 		//makeStackDoNothing(gs->curB->getStack (selectedStack)); | ||||||
|  |  | ||||||
|  | 		BattleSetActiveStack sas; | ||||||
|  |  | ||||||
|  | 		//std::vector<const CStack *> hlp; | ||||||
|  | 		//battleGetStackQueue(hlp, 1, selectedStack); //next after this one | ||||||
|  |  | ||||||
|  | 		//if(hlp.size()) | ||||||
|  | 		//{ | ||||||
|  | 		//	sas.stack = hlp[0]->ID; | ||||||
|  | 		//} | ||||||
|  | 		//else | ||||||
|  | 		//	complain ("No new stack to activate!"); | ||||||
|  | 		sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current? | ||||||
|  | 		env->sendAndApply(&sas); | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	BattleStacksRemoved bsr; | ||||||
|  | 	bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport? | ||||||
|  | 	env->sendAndApply(&bsr); | ||||||
|  | 		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///SpecialRisingSpellMechanics | ||||||
|  | ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const | ||||||
|  | { | ||||||
|  | 	// following does apply to resurrect and animate dead(?) only | ||||||
|  | 	// for sacrifice health calculation and health limit check don't matter | ||||||
|  |  | ||||||
|  | 	if(obj->count >= obj->baseAmount) | ||||||
|  | 		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; | ||||||
|  | 	 | ||||||
|  | 	if(caster) //FIXME: Archangels can cast immune stack | ||||||
|  | 	{ | ||||||
|  | 		auto maxHealth = calculateHealedHP(caster, obj, nullptr); | ||||||
|  | 		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature | ||||||
|  | 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; | ||||||
|  | 	}	 | ||||||
|  | 	 | ||||||
|  | 	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///SummonMechanics | ||||||
|  | void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	//todo: make configurable | ||||||
|  | 	CreatureID creID = CreatureID::NONE; | ||||||
|  | 	switch(owner->id) | ||||||
|  | 	{ | ||||||
|  | 		case SpellID::SUMMON_FIRE_ELEMENTAL: | ||||||
|  | 			creID = CreatureID::FIRE_ELEMENTAL; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::SUMMON_EARTH_ELEMENTAL: | ||||||
|  | 			creID = CreatureID::EARTH_ELEMENTAL; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::SUMMON_WATER_ELEMENTAL: | ||||||
|  | 			creID = CreatureID::WATER_ELEMENTAL; | ||||||
|  | 			break; | ||||||
|  | 		case SpellID::SUMMON_AIR_ELEMENTAL: | ||||||
|  | 			creID = CreatureID::AIR_ELEMENTAL; | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			env->complain("Unable to determine summoned creature"); | ||||||
|  | 			return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	BattleStackAdded bsa; | ||||||
|  | 	bsa.creID = creID; | ||||||
|  | 	bsa.attacker = !(bool)parameters.casterSide; | ||||||
|  | 	bsa.summoned = true; | ||||||
|  | 	bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it | ||||||
|  |  | ||||||
|  | 	//TODO stack casting -> probably power will be zero; set the proper number of creatures manually | ||||||
|  | 	int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0; | ||||||
|  |  | ||||||
|  | 	bsa.amount = parameters.usedSpellPower | ||||||
|  | 		* owner->getPower(parameters.spellLvl) | ||||||
|  | 		* (100 + percentBonus) / 100.0; //new feature - percentage bonus | ||||||
|  | 	if(bsa.amount) | ||||||
|  | 		env->sendAndApply(&bsa); | ||||||
|  | 	else | ||||||
|  | 		env->complain("Summoning didn't summon any!");	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///TeleportMechanics | ||||||
|  | void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	BattleStackMoved bsm; | ||||||
|  | 	bsm.distance = -1; | ||||||
|  | 	bsm.stack = parameters.selectedStack->ID; | ||||||
|  | 	std::vector<BattleHex> tiles; | ||||||
|  | 	tiles.push_back(parameters.destination); | ||||||
|  | 	bsm.tilesToMove = tiles; | ||||||
|  | 	bsm.teleporting = true; | ||||||
|  | 	env->sendAndApply(&bsm);	 | ||||||
|  | } | ||||||
|  | 	 | ||||||
|   | |||||||
| @@ -11,3 +11,109 @@ | |||||||
|  #pragma once |  #pragma once | ||||||
|   |   | ||||||
|  #include "CDefaultSpellMechanics.h" |  #include "CDefaultSpellMechanics.h" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ChainLightningMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||||
|  | 	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CloneMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CureMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	CureMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||||
|  | 	 | ||||||
|  | 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DispellMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | 	 | ||||||
|  | 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class HypnotizeMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||||
|  | 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	 | ||||||
|  | };  | ||||||
|  |  | ||||||
|  | class ObstacleMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};		 | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class WallMechanics: public ObstacleMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	WallMechanics(CSpell * s): ObstacleMechanics(s){};	 | ||||||
|  | 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class RemoveObstacleMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ///all rising spells | ||||||
|  | class RisingSpellMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};		 | ||||||
|  | 	 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SacrificeMechanics: public RisingSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};	 | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ///all rising spells but SACRIFICE | ||||||
|  | class SpecialRisingSpellMechanics: public RisingSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){}; | ||||||
|  | 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;						 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SummonMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	SummonMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class TeleportMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -11,3 +11,694 @@ | |||||||
| #include "StdInc.h" | #include "StdInc.h" | ||||||
|  |  | ||||||
| #include "CDefaultSpellMechanics.h" | #include "CDefaultSpellMechanics.h" | ||||||
|  |  | ||||||
|  | #include "../NetPacks.h" | ||||||
|  | #include "../BattleState.h" | ||||||
|  |  | ||||||
|  | namespace SRSLPraserHelpers | ||||||
|  | { | ||||||
|  | 	static int XYToHex(int x, int y) | ||||||
|  | 	{ | ||||||
|  | 		return x + GameConstants::BFIELD_WIDTH * y; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static int XYToHex(std::pair<int, int> xy) | ||||||
|  | 	{ | ||||||
|  | 		return XYToHex(xy.first, xy.second); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static int hexToY(int battleFieldPosition) | ||||||
|  | 	{ | ||||||
|  | 		return battleFieldPosition/GameConstants::BFIELD_WIDTH; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static int hexToX(int battleFieldPosition) | ||||||
|  | 	{ | ||||||
|  | 		int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH; | ||||||
|  | 		return pos; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static std::pair<int, int> hexToPair(int battleFieldPosition) | ||||||
|  | 	{ | ||||||
|  | 		return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//moves hex by one hex in given direction | ||||||
|  | 	//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left | ||||||
|  | 	static std::pair<int, int> gotoDir(int x, int y, int direction) | ||||||
|  | 	{ | ||||||
|  | 		switch(direction) | ||||||
|  | 		{ | ||||||
|  | 		case 0: //top left | ||||||
|  | 			return std::make_pair((y%2) ? x-1 : x, y-1); | ||||||
|  | 		case 1: //top right | ||||||
|  | 			return std::make_pair((y%2) ? x : x+1, y-1); | ||||||
|  | 		case 2:  //right | ||||||
|  | 			return std::make_pair(x+1, y); | ||||||
|  | 		case 3: //right bottom | ||||||
|  | 			return std::make_pair((y%2) ? x : x+1, y+1); | ||||||
|  | 		case 4: //left bottom | ||||||
|  | 			return std::make_pair((y%2) ? x-1 : x, y+1); | ||||||
|  | 		case 5: //left | ||||||
|  | 			return std::make_pair(x-1, y); | ||||||
|  | 		default: | ||||||
|  | 			throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction) | ||||||
|  | 	{ | ||||||
|  | 		return gotoDir(xy.first, xy.second, direction); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static bool isGoodHex(std::pair<int, int> xy) | ||||||
|  | 	{ | ||||||
|  | 		return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//helper function for rangeInHexes | ||||||
|  | 	static std::set<ui16> getInRange(unsigned int center, int low, int high) | ||||||
|  | 	{ | ||||||
|  | 		std::set<ui16> ret; | ||||||
|  | 		if(low == 0) | ||||||
|  | 		{ | ||||||
|  | 			ret.insert(center); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points | ||||||
|  | 		for(auto & elem : mainPointForLayer) | ||||||
|  | 			elem = hexToPair(center); | ||||||
|  |  | ||||||
|  | 		for(int it=1; it<=high; ++it) //it - distance to the center | ||||||
|  | 		{ | ||||||
|  | 			for(int b=0; b<6; ++b) | ||||||
|  | 				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b); | ||||||
|  |  | ||||||
|  | 			if(it>=low) | ||||||
|  | 			{ | ||||||
|  | 				std::pair<int, int> curHex; | ||||||
|  |  | ||||||
|  | 				//adding lines (A-b, B-c, C-d, etc) | ||||||
|  | 				for(int v=0; v<6; ++v) | ||||||
|  | 				{ | ||||||
|  | 					curHex = mainPointForLayer[v]; | ||||||
|  | 					for(int h=0; h<it; ++h) | ||||||
|  | 					{ | ||||||
|  | 						if(isGoodHex(curHex)) | ||||||
|  | 							ret.insert(XYToHex(curHex)); | ||||||
|  | 						curHex = gotoDir(curHex, (v+2)%6); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			} //if(it>=low) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///DefaultSpellMechanics | ||||||
|  | void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const | ||||||
|  | { | ||||||
|  | 	if (packet->castedByHero) | ||||||
|  | 	{ | ||||||
|  | 		if (packet->side < 2) | ||||||
|  | 		{ | ||||||
|  | 			battle->sides[packet->side].castSpellsCount++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	//handle countering spells | ||||||
|  | 	for(auto stackID : packet->affectedCres) | ||||||
|  | 	{ | ||||||
|  | 		if(vstd::contains(packet->resisted, stackID)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		CStack * s = battle->getStack(stackID); | ||||||
|  | 		s->popBonuses([&](const Bonus * b) -> bool | ||||||
|  | 		{ | ||||||
|  | 			//check for each bonus if it should be removed | ||||||
|  | 			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b); | ||||||
|  | 			const int spellID = isSpellEffect ? b->sid : -1; | ||||||
|  |  | ||||||
|  | 			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID); | ||||||
|  | 		}); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||||
|  | { | ||||||
|  | 	if(!owner->isAdventureSpell()) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Attempt to cast non adventure spell in adventure mode"); | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	const CGHeroInstance * caster = parameters.caster; | ||||||
|  | 	const int cost = caster->getSpellCost(owner); | ||||||
|  | 	 | ||||||
|  | 	if(!caster->canCastThisSpell(owner)) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Hero cannot cast this spell!"); | ||||||
|  | 		return false;		 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(caster->mana < cost) | ||||||
|  | 	{ | ||||||
|  | 		env->complain("Hero doesn't have enough spell points to cast this spell!"); | ||||||
|  | 		return false;		 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		AdvmapSpellCast asc; | ||||||
|  | 		asc.caster = caster; | ||||||
|  | 		asc.spellID = owner->id; | ||||||
|  | 		env->sendAndApply(&asc);		 | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if(applyAdventureEffects(env, parameters)) | ||||||
|  | 	{ | ||||||
|  | 		SetMana sm; | ||||||
|  | 		sm.hid = caster->id; | ||||||
|  | 		sm.absolute = false; | ||||||
|  | 		sm.val = -cost; | ||||||
|  | 		env->sendAndApply(&sm); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||||
|  | { | ||||||
|  | 	//There is no generic algorithm of adventure cast | ||||||
|  | 	env->complain("Unimplemented adventure spell"); | ||||||
|  | 	return false;		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const | ||||||
|  | { | ||||||
|  | 	BattleSpellCast sc; | ||||||
|  | 	sc.side = parameters.casterSide; | ||||||
|  | 	sc.id = owner->id; | ||||||
|  | 	sc.skill = parameters.spellLvl; | ||||||
|  | 	sc.tile = parameters.destination; | ||||||
|  | 	sc.dmgToDisplay = 0; | ||||||
|  | 	sc.castedByHero = nullptr != parameters.caster; | ||||||
|  | 	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1); | ||||||
|  | 	sc.manaGained = 0; | ||||||
|  | 	 | ||||||
|  | 	int spellCost = 0;	 | ||||||
|  | 	 | ||||||
|  | 	//calculate spell cost | ||||||
|  | 	if(parameters.caster)  | ||||||
|  | 	{ | ||||||
|  | 		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster); | ||||||
|  |  | ||||||
|  | 		if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel | ||||||
|  | 		{ | ||||||
|  | 			int manaChannel = 0; | ||||||
|  | 			for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow? | ||||||
|  | 			{ | ||||||
|  | 				if(stack->owner == parameters.secHero->tempOwner) | ||||||
|  | 				{ | ||||||
|  | 					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			sc.manaGained = (manaChannel * spellCost) / 100; | ||||||
|  | 		} | ||||||
|  | 	}	 | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  | 	//calculating affected creatures for all spells | ||||||
|  | 	//must be vector, as in Chain Lightning order matters | ||||||
|  | 	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector | ||||||
|  |  | ||||||
|  | 	auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster); | ||||||
|  | 	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres)); | ||||||
|  | 	 | ||||||
|  | 	for (auto cre : attackedCres) | ||||||
|  | 	{ | ||||||
|  | 		sc.affectedCres.insert(cre->ID); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	//checking if creatures resist | ||||||
|  | 	//resistance is applied only to negative spells | ||||||
|  | 	if(owner->isNegative()) | ||||||
|  | 	{ | ||||||
|  | 		for(auto s : attackedCres) | ||||||
|  | 		{ | ||||||
|  | 			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in % | ||||||
|  | 			 | ||||||
|  | 			if(env->getRandomGenerator().nextInt(99) < prob) | ||||||
|  | 			{ | ||||||
|  | 				sc.resisted.push_back(s->ID); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	StacksInjured si;	 | ||||||
|  | 	SpellCastContext ctx(attackedCres, sc, si); | ||||||
|  | 	 | ||||||
|  | 	applyBattleEffects(env, parameters, ctx); | ||||||
|  | 	 | ||||||
|  | 	env->sendAndApply(&sc); | ||||||
|  | 	 | ||||||
|  |  | ||||||
|  | 	//spend mana | ||||||
|  | 	if(parameters.caster)  | ||||||
|  | 	{ | ||||||
|  | 		SetMana sm; | ||||||
|  | 		sm.absolute = false; | ||||||
|  | 		 | ||||||
|  | 		sm.hid = parameters.caster->id; | ||||||
|  | 		sm.val = -spellCost; | ||||||
|  | 		 | ||||||
|  | 		env->sendAndApply(&sm); | ||||||
|  | 		 | ||||||
|  | 		if(sc.manaGained > 0) | ||||||
|  | 		{ | ||||||
|  | 			assert(parameters.secHero); | ||||||
|  | 			 | ||||||
|  | 			sm.hid = parameters.secHero->id; | ||||||
|  | 			sm.val = sc.manaGained; | ||||||
|  | 			env->sendAndApply(&sm); | ||||||
|  | 		}		 | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if(!si.stacks.empty()) //after spellcast info shows | ||||||
|  | 		env->sendAndApply(&si); | ||||||
|  | 	 | ||||||
|  | 	//reduce number of casts remaining | ||||||
|  | 	//TODO: this should be part of BattleSpellCast apply | ||||||
|  | 	if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)  | ||||||
|  | 	{ | ||||||
|  | 		assert(parameters.casterStack); | ||||||
|  | 		 | ||||||
|  | 		BattleSetStackProperty ssp; | ||||||
|  | 		ssp.stackID = parameters.casterStack->ID; | ||||||
|  | 		ssp.which = BattleSetStackProperty::CASTS; | ||||||
|  | 		ssp.val = -1; | ||||||
|  | 		ssp.absolute = false; | ||||||
|  | 		env->sendAndApply(&ssp); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Magic Mirror effect | ||||||
|  | 	if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence | ||||||
|  | 	{ | ||||||
|  | 		for(auto & attackedCre : attackedCres) | ||||||
|  | 		{ | ||||||
|  | 			int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR); | ||||||
|  | 			if(mirrorChance > env->getRandomGenerator().nextInt(99)) | ||||||
|  | 			{ | ||||||
|  | 				std::vector<const CStack *> mirrorTargets; | ||||||
|  | 				auto battleStacks = parameters.cb->battleGetAllStacks(true); | ||||||
|  | 				for(auto & battleStack : battleStacks) | ||||||
|  | 				{ | ||||||
|  | 					if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell | ||||||
|  | 					{ | ||||||
|  | 						if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack)) | ||||||
|  | 							mirrorTargets.push_back(battleStack); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if(!mirrorTargets.empty()) | ||||||
|  | 				{ | ||||||
|  | 					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position; | ||||||
|  | 					 | ||||||
|  | 					BattleSpellCastParameters mirrorParameters = parameters; | ||||||
|  | 					mirrorParameters.spellLvl = 0; | ||||||
|  | 					mirrorParameters.casterSide = 1-parameters.casterSide; | ||||||
|  | 					mirrorParameters.casterColor = (attackedCre)->owner; | ||||||
|  | 					mirrorParameters.caster = nullptr; | ||||||
|  | 					mirrorParameters.destination = targetHex; | ||||||
|  | 					mirrorParameters.secHero = parameters.caster; | ||||||
|  | 					mirrorParameters.mode = ECastingMode::MAGIC_MIRROR; | ||||||
|  | 					mirrorParameters.casterStack = (attackedCre); | ||||||
|  | 					mirrorParameters.selectedStack = nullptr; | ||||||
|  | 					 | ||||||
|  | 					battleCast(env, mirrorParameters);					 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const | ||||||
|  | { | ||||||
|  | 	if(!caster) | ||||||
|  | 	{ | ||||||
|  | 		if (!usedSpellPower) | ||||||
|  | 			return 3; //default duration of all creature spells | ||||||
|  | 		else | ||||||
|  | 			return usedSpellPower; //use creature spell power | ||||||
|  | 	} | ||||||
|  | 	switch(owner->id) | ||||||
|  | 	{ | ||||||
|  | 	case SpellID::FRENZY: | ||||||
|  | 		return 1; | ||||||
|  | 	default: //other spells | ||||||
|  | 		return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const | ||||||
|  | { | ||||||
|  | 	int healedHealth; | ||||||
|  | 	 | ||||||
|  | 	if(!owner->isHealingSpell()) | ||||||
|  | 	{ | ||||||
|  | 		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name; | ||||||
|  | 		return 0; | ||||||
|  | 	}		 | ||||||
|  | 	 | ||||||
|  | 	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER); | ||||||
|  | 	const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner)); | ||||||
|  | 	 | ||||||
|  | 	if (owner->id == SpellID::SACRIFICE && sacrificedStack) | ||||||
|  | 		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count; | ||||||
|  | 	else | ||||||
|  | 		healedHealth = spellPowerSkill * owner->power + levelPower; //??? | ||||||
|  | 	healedHealth = owner->calculateBonus(healedHealth, caster, stack); | ||||||
|  | 	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	//applying effects | ||||||
|  | 	if(owner->isOffensiveSpell()) | ||||||
|  | 	{ | ||||||
|  | 		int spellDamage = 0; | ||||||
|  | 		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR) | ||||||
|  | 		{ | ||||||
|  | 			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum()); | ||||||
|  | 			if(unitSpellPower) | ||||||
|  | 				ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities | ||||||
|  | 			else //Faerie Dragon | ||||||
|  | 			{ | ||||||
|  | 				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100; | ||||||
|  | 				ctx.sc.dmgToDisplay = 0; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		int chainLightningModifier = 0; | ||||||
|  | 		for(auto & attackedCre : ctx.attackedCres) | ||||||
|  | 		{ | ||||||
|  | 			if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell | ||||||
|  | 				continue; | ||||||
|  |  | ||||||
|  | 			BattleStackAttacked bsa; | ||||||
|  | 			if(spellDamage) | ||||||
|  | 				bsa.damageAmount = spellDamage >> chainLightningModifier; | ||||||
|  | 			else | ||||||
|  | 				bsa.damageAmount =  owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier; | ||||||
|  |  | ||||||
|  | 			ctx.sc.dmgToDisplay += bsa.damageAmount; | ||||||
|  |  | ||||||
|  | 			bsa.stackAttacked = (attackedCre)->ID; | ||||||
|  | 			if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast | ||||||
|  | 				bsa.attackerID = parameters.casterStack->ID; | ||||||
|  | 			else | ||||||
|  | 				bsa.attackerID = -1; | ||||||
|  | 			(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator()); | ||||||
|  | 			ctx.si.stacks.push_back(bsa); | ||||||
|  |  | ||||||
|  | 			if(owner->id == SpellID::CHAIN_LIGHTNING) | ||||||
|  | 				++chainLightningModifier; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if(owner->hasEffects()) | ||||||
|  | 	{ | ||||||
|  | 		int stackSpellPower = 0; | ||||||
|  | 		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR) | ||||||
|  | 		{ | ||||||
|  | 			stackSpellPower =  parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER); | ||||||
|  | 		} | ||||||
|  | 		SetStackEffect sse; | ||||||
|  | 		Bonus pseudoBonus; | ||||||
|  | 		pseudoBonus.sid = owner->id; | ||||||
|  | 		pseudoBonus.val = parameters.spellLvl; | ||||||
|  | 		pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower); | ||||||
|  | 		CStack::stackEffectToFeature(sse.effect, pseudoBonus); | ||||||
|  | 		if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD) | ||||||
|  | 		{ | ||||||
|  | 			sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction | ||||||
|  | 		} | ||||||
|  | 		if(owner->id == SpellID::BIND &&  parameters.casterStack)//bind | ||||||
|  | 		{ | ||||||
|  | 			sse.effect.back().additionalInfo =  parameters.casterStack->ID; //we need to know who casted Bind | ||||||
|  | 		} | ||||||
|  | 		const Bonus * bonus = nullptr; | ||||||
|  | 		if(parameters.caster) | ||||||
|  | 			bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id)); | ||||||
|  | 		//TODO does hero specialty should affects his stack casting spells? | ||||||
|  |  | ||||||
|  | 		si32 power = 0; | ||||||
|  | 		for(const CStack * affected : ctx.attackedCres) | ||||||
|  | 		{ | ||||||
|  | 			if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell | ||||||
|  | 				continue; | ||||||
|  | 			sse.stacks.push_back(affected->ID); | ||||||
|  |  | ||||||
|  | 			//Apply hero specials - peculiar enchants | ||||||
|  | 			const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines) | ||||||
|  | 			if(bonus) | ||||||
|  | 			{ | ||||||
|  | 				switch(bonus->additionalInfo) | ||||||
|  | 				{ | ||||||
|  | 					case 0: //normal | ||||||
|  | 					{ | ||||||
|  | 						switch(tier) | ||||||
|  | 						{ | ||||||
|  | 							case 1: case 2: | ||||||
|  | 								power = 3; | ||||||
|  | 							break; | ||||||
|  | 							case 3: case 4: | ||||||
|  | 								power = 2; | ||||||
|  | 							break; | ||||||
|  | 							case 5: case 6: | ||||||
|  | 								power = 1; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 						Bonus specialBonus(sse.effect.back()); | ||||||
|  | 						specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely | ||||||
|  | 						sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect | ||||||
|  | 					} | ||||||
|  | 					break; | ||||||
|  | 					case 1: //only Coronius as yet | ||||||
|  | 					{ | ||||||
|  | 						power = std::max(5 - tier, 0); | ||||||
|  | 						Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain); | ||||||
|  | 						specialBonus.sid = owner->id; | ||||||
|  | 						sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect | ||||||
|  | 					} | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages | ||||||
|  | 			{ | ||||||
|  | 				int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier; | ||||||
|  | 				Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain); | ||||||
|  | 				specialBonus.valType = Bonus::PERCENT_TO_ALL; | ||||||
|  | 				specialBonus.sid = owner->id; | ||||||
|  | 				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(!sse.stacks.empty()) | ||||||
|  | 			env->sendAndApply(&sse); | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if(owner->isHealingSpell()) | ||||||
|  | 	{ | ||||||
|  | 		int hpGained = 0; | ||||||
|  | 		if(parameters.casterStack) | ||||||
|  | 		{ | ||||||
|  | 			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum()); | ||||||
|  | 			if(unitSpellPower) | ||||||
|  | 				hpGained = parameters.casterStack->count * unitSpellPower; //Archangel | ||||||
|  | 			else //Faerie Dragon-like effect - unused so far | ||||||
|  | 				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100; | ||||||
|  | 		} | ||||||
|  | 		StacksHealedOrResurrected shr; | ||||||
|  | 		shr.lifeDrain = false; | ||||||
|  | 		shr.tentHealing = false; | ||||||
|  | 		for(auto & attackedCre : ctx.attackedCres) | ||||||
|  | 		{ | ||||||
|  | 			StacksHealedOrResurrected::HealInfo hi; | ||||||
|  | 			hi.stackID = (attackedCre)->ID; | ||||||
|  | 			if (parameters.casterStack) //casted by creature | ||||||
|  | 			{ | ||||||
|  | 				const bool resurrect = owner->isRisingSpell(); | ||||||
|  | 				if (hpGained) | ||||||
|  | 				{ | ||||||
|  | 					//archangel | ||||||
|  | 					hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0)); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					//any typical spell (commander's cure or animate dead) | ||||||
|  | 					int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl); | ||||||
|  | 					hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0)); | ||||||
|  | 				}					 | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero | ||||||
|  | 			hi.lowLevelResurrection = parameters.spellLvl <= 1; | ||||||
|  | 			shr.healedStacks.push_back(hi); | ||||||
|  | 		} | ||||||
|  | 		if(!shr.healedStacks.empty()) | ||||||
|  | 			env->sendAndApply(&shr); | ||||||
|  | 	}		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const | ||||||
|  | { | ||||||
|  | 	using namespace SRSLPraserHelpers; | ||||||
|  | 	 | ||||||
|  | 	std::vector<BattleHex> ret; | ||||||
|  | 	std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling | ||||||
|  |  | ||||||
|  | 	if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma) | ||||||
|  | 	{ | ||||||
|  | 		std::string number1, number2; | ||||||
|  | 		int beg, end; | ||||||
|  | 		bool readingFirst = true; | ||||||
|  | 		for(auto & elem : rng) | ||||||
|  | 		{ | ||||||
|  | 			if(std::isdigit(elem) ) //reading number | ||||||
|  | 			{ | ||||||
|  | 				if(readingFirst) | ||||||
|  | 					number1 += elem; | ||||||
|  | 				else | ||||||
|  | 					number2 += elem; | ||||||
|  | 			} | ||||||
|  | 			else if(elem == ',') //comma | ||||||
|  | 			{ | ||||||
|  | 				//calculating variables | ||||||
|  | 				if(readingFirst) | ||||||
|  | 				{ | ||||||
|  | 					beg = atoi(number1.c_str()); | ||||||
|  | 					number1 = ""; | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					end = atoi(number2.c_str()); | ||||||
|  | 					number2 = ""; | ||||||
|  | 				} | ||||||
|  | 				//obtaining new hexes | ||||||
|  | 				std::set<ui16> curLayer; | ||||||
|  | 				if(readingFirst) | ||||||
|  | 				{ | ||||||
|  | 					curLayer = getInRange(centralHex, beg, beg); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					curLayer = getInRange(centralHex, beg, end); | ||||||
|  | 					readingFirst = true; | ||||||
|  | 				} | ||||||
|  | 				//adding abtained hexes | ||||||
|  | 				for(auto & curLayer_it : curLayer) | ||||||
|  | 				{ | ||||||
|  | 					ret.push_back(curLayer_it); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			} | ||||||
|  | 			else if(elem == '-') //dash | ||||||
|  | 			{ | ||||||
|  | 				beg = atoi(number1.c_str()); | ||||||
|  | 				number1 = ""; | ||||||
|  | 				readingFirst = false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//remove duplicates (TODO check if actually needed) | ||||||
|  | 	range::unique(ret); | ||||||
|  | 	return ret;		 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const | ||||||
|  | { | ||||||
|  | 	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures | ||||||
|  | 	 | ||||||
|  | 	const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1; | ||||||
|  | 	const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide); | ||||||
|  |  | ||||||
|  | 	const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode); | ||||||
|  | 	 | ||||||
|  | 	//TODO: more generic solution for mass spells | ||||||
|  | 	if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range | ||||||
|  | 	{ | ||||||
|  | 		for(BattleHex hex : attackedHexes) | ||||||
|  | 		{ | ||||||
|  | 			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive)) | ||||||
|  | 			{ | ||||||
|  | 				attackedCres.insert(st); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else if(ti.type == CSpell::CREATURE) | ||||||
|  | 	{ | ||||||
|  | 		auto predicate = [=](const CStack * s){ | ||||||
|  | 			const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor; | ||||||
|  | 			const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor; | ||||||
|  | 			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class | ||||||
|  | 	 | ||||||
|  | 			//for single target spells select stacks covering destination tile | ||||||
|  | 			const bool rangeCovers = ti.massive || s->coversPos(ctx.destination); | ||||||
|  | 			//handle smart targeting | ||||||
|  | 			const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy; | ||||||
|  | 			 | ||||||
|  | 			return rangeCovers && positivenessFlag && validTarget;		 | ||||||
|  | 		}; | ||||||
|  | 		 | ||||||
|  | 		TStacks stacks = ctx.cb->battleGetStacksIf(predicate); | ||||||
|  | 		 | ||||||
|  | 		if(ti.massive) | ||||||
|  | 		{ | ||||||
|  | 			//for massive spells add all targets | ||||||
|  | 			for (auto stack : stacks) | ||||||
|  | 				attackedCres.insert(stack); | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			//for single target spells we must select one target. Alive stack is preferred (issue #1763) | ||||||
|  | 			for(auto stack : stacks) | ||||||
|  | 			{ | ||||||
|  | 				if(stack->alive()) | ||||||
|  | 				{ | ||||||
|  | 					attackedCres.insert(stack); | ||||||
|  | 					break; | ||||||
|  | 				}				 | ||||||
|  | 			}	 | ||||||
|  | 			 | ||||||
|  | 			if(attackedCres.empty() && !stacks.empty()) | ||||||
|  | 			{ | ||||||
|  | 				attackedCres.insert(stacks.front()); | ||||||
|  | 			}						 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else //custom range from attackedHexes | ||||||
|  | 	{ | ||||||
|  | 		for(BattleHex hex : attackedHexes) | ||||||
|  | 		{ | ||||||
|  | 			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive)) | ||||||
|  | 				attackedCres.insert(st); | ||||||
|  | 		} | ||||||
|  | 	}	 | ||||||
|  | 	 | ||||||
|  | 	return attackedCres; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const | ||||||
|  | { | ||||||
|  | 	//by default use general algorithm | ||||||
|  | 	return owner->isImmuneBy(obj); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -10,4 +10,44 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  |  | ||||||
| #include "ISpellMechanics.h" | #include "ISpellMechanics.h" | ||||||
|  |  | ||||||
|  | class BattleSpellCast; | ||||||
|  | class StacksInjured; | ||||||
|  |  | ||||||
|  | struct SpellCastContext | ||||||
|  | { | ||||||
|  | 	SpellCastContext(std::vector<const CStack*> & attackedCres, BattleSpellCast & sc, StacksInjured & si): | ||||||
|  | 		attackedCres(attackedCres), sc(sc), si(si){};  | ||||||
|  | 	std::vector<const CStack*> & attackedCres; | ||||||
|  | 	BattleSpellCast & sc; | ||||||
|  | 	StacksInjured & si; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DefaultSpellMechanics: public ISpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){}; | ||||||
|  | 	 | ||||||
|  | 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override; | ||||||
|  | 	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override; | ||||||
|  | 	 | ||||||
|  | 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; | ||||||
|  | 	 | ||||||
|  | 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;  | ||||||
|  | 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override; | ||||||
|  | 	 | ||||||
|  | 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override; | ||||||
|  | protected: | ||||||
|  | 	 | ||||||
|  | 	virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const; | ||||||
|  | 	 | ||||||
|  | 	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const; | ||||||
|  | 	 | ||||||
|  | 	///calculate healed HP for all spells casted by hero | ||||||
|  | 	ui32 calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const; | ||||||
|  | 	 | ||||||
|  | 	///actual adventure cast implementation | ||||||
|  | 	virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const; | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -11,3 +11,86 @@ | |||||||
| #include "StdInc.h" | #include "StdInc.h" | ||||||
|  |  | ||||||
| #include "CreatureSpellMechanics.h" | #include "CreatureSpellMechanics.h" | ||||||
|  |  | ||||||
|  | #include "../NetPacks.h" | ||||||
|  | #include "../BattleState.h" | ||||||
|  |  | ||||||
|  | ///AcidBreathDamageMechanics | ||||||
|  | void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	//calculating dmg to display | ||||||
|  | 	ctx.sc.dmgToDisplay = parameters.usedSpellPower; | ||||||
|  | 	 | ||||||
|  | 	for(auto & attackedCre : ctx.attackedCres) //no immunities | ||||||
|  | 	{ | ||||||
|  | 		BattleStackAttacked bsa; | ||||||
|  | 		bsa.flags |= BattleStackAttacked::SPELL_EFFECT; | ||||||
|  | 		bsa.spellID = owner->id; | ||||||
|  | 		bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers | ||||||
|  | 		bsa.stackAttacked = (attackedCre)->ID; | ||||||
|  | 		bsa.attackerID = -1; | ||||||
|  | 		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator()); | ||||||
|  | 		ctx.si.stacks.push_back(bsa); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ///DeathStareMechanics | ||||||
|  | void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||||
|  | { | ||||||
|  | 	//calculating dmg to display | ||||||
|  | 	ctx.sc.dmgToDisplay = parameters.usedSpellPower; | ||||||
|  | 	if(!ctx.attackedCres.empty()) | ||||||
|  | 		vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack	 | ||||||
|  | 	 | ||||||
|  | 	for(auto & attackedCre : ctx.attackedCres) | ||||||
|  | 	{ | ||||||
|  | 		BattleStackAttacked bsa; | ||||||
|  | 		bsa.flags |= BattleStackAttacked::SPELL_EFFECT; | ||||||
|  | 		bsa.spellID = owner->id; | ||||||
|  | 		bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH); | ||||||
|  | 		bsa.stackAttacked = (attackedCre)->ID; | ||||||
|  | 		bsa.attackerID = -1; | ||||||
|  | 		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator()); | ||||||
|  | 		ctx.si.stacks.push_back(bsa); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ///DispellHelpfulMechanics | ||||||
|  | void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const | ||||||
|  | { | ||||||
|  | 	DefaultSpellMechanics::applyBattle(battle, packet); | ||||||
|  | 	 | ||||||
|  | 	for(auto stackID : packet->affectedCres) | ||||||
|  | 	{ | ||||||
|  | 		if(vstd::contains(packet->resisted, stackID)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		CStack *s = battle->getStack(stackID); | ||||||
|  | 		s->popBonuses([&](const Bonus *b) -> bool | ||||||
|  | 		{ | ||||||
|  | 			return Selector::positiveSpellEffects(b); | ||||||
|  | 		}); | ||||||
|  | 	}	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster,  const CStack * obj) const | ||||||
|  | { | ||||||
|  | 	TBonusListPtr spellBon = obj->getSpellBonuses(); | ||||||
|  | 	bool hasPositiveSpell = false; | ||||||
|  | 	for(const Bonus * b : *spellBon) | ||||||
|  | 	{ | ||||||
|  | 		if(SpellID(b->sid).toSpell()->isPositive()) | ||||||
|  | 		{ | ||||||
|  | 			hasPositiveSpell = true; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if(!hasPositiveSpell) | ||||||
|  | 	{ | ||||||
|  | 		return ESpellCastProblem::NO_SPELLS_TO_DISPEL; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	//use default algorithm only if there is no mechanics-related problem		 | ||||||
|  | 	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	 | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,3 +11,29 @@ | |||||||
|  #pragma once |  #pragma once | ||||||
|   |   | ||||||
|  #include "CDefaultSpellMechanics.h" |  #include "CDefaultSpellMechanics.h" | ||||||
|  |  | ||||||
|  | class AcidBreathDamageMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DeathStareMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | protected: | ||||||
|  | 	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DispellHelpfulMechanics: public DefaultSpellMechanics | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||||
|  | 	 | ||||||
|  | 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override; | ||||||
|  | 	 | ||||||
|  | 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	 | ||||||
|  | }; | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * SpellMechanics.h, part of VCMI engine |  * ISpellMechanics.h, part of VCMI engine | ||||||
|  * |  * | ||||||
|  * Authors: listed in file AUTHORS in main folder |  * Authors: listed in file AUTHORS in main folder | ||||||
|  * |  * | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user