Explorar el Código

Copying of handleSpellCasting finished

AlexVinS hace 11 años
padre
commit
6d4cd1c91b
Se han modificado 2 ficheros con 313 adiciones y 122 borrados
  1. 3 2
      lib/CSpellHandler.h
  2. 310 120
      lib/SpellMechanics.cpp

+ 3 - 2
lib/CSpellHandler.h

@@ -46,9 +46,10 @@ struct SpellSchoolInfo
 class DLL_LINKAGE SpellCastEnvironment
 {
 public:
-	virtual void sendAndApply(CPackForClient * info) const  = 0;
+	virtual void sendAndApply(CPackForClient * info) const = 0;
 	
-	virtual CRandomGenerator & getRandomGenerator() const  = 0;
+	virtual CRandomGenerator & getRandomGenerator() const = 0;
+	virtual void complain(const std::string & problem) const = 0;
 };
 
 ///helper struct

+ 310 - 120
lib/SpellMechanics.cpp

@@ -146,23 +146,15 @@ protected:
 	virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
 	
 	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
+	
 };
 
-class ObstacleMechanics: public DefaultSpellMechanics
+class AcidBreathDamageMechnics: public DefaultSpellMechanics
 {
 public:
-	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};		
-
+	AcidBreathDamageMechnics(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;	
-	
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
 };
 
 class ChainLightningMechanics: public DefaultSpellMechanics
@@ -177,6 +169,16 @@ 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 DeathStareMechnics: public DefaultSpellMechanics
+{
+public:
+	DeathStareMechnics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
 };
 
 class DispellHelpfulMechanics: public DefaultSpellMechanics
@@ -193,6 +195,30 @@ public:
 	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
 {
@@ -201,6 +227,12 @@ public:
 	
 };
 
+class SacrificeMechanics: public RisingSpellMechanics
+{
+public:
+	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};		
+};
+
 ///all rising spells but SACRIFICE
 class SpecialRisingSpellMechanics: public RisingSpellMechanics
 {
@@ -209,10 +241,20 @@ public:
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;						
 };
 
-class SacrificeMechanics: public RisingSpellMechanics
+class SummonMechanics: public DefaultSpellMechanics
 {
 public:
-	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};		
+	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;		
 };
 
 ///ISpellMechanics
@@ -240,6 +282,19 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell* s)
 	case SpellID::LAND_MINE:
 	case SpellID::QUICKSAND:
 		return new ObstacleMechanics(s);
+	case SpellID::TELEPORT:
+		return new TeleportMechanics(s);
+	case SpellID::SUMMON_FIRE_ELEMENTAL:
+	case SpellID::SUMMON_EARTH_ELEMENTAL:
+	case SpellID::SUMMON_WATER_ELEMENTAL:
+	case SpellID::SUMMON_AIR_ELEMENTAL:
+		return new SummonMechanics(s);
+	case SpellID::REMOVE_OBSTACLE:
+		return new RemoveObstacleMechanics(s);
+	case SpellID::DEATH_STARE:
+		return new DeathStareMechnics(s);
+	case SpellID::ACID_BREATH_DAMAGE:
+		return new AcidBreathDamageMechnics(s);
 	default:		
 		if(s->isRisingSpell())
 			return new SpecialRisingSpellMechanics(s);
@@ -316,6 +371,9 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		}
 	}
 	
+	StacksInjured si;	
+	SpellCastContext ctx(attackedCres, sc, si);
+
 	//TODO: extract dmg to display calculation	
 	//calculating dmg to display
 	if (owner->id == SpellID::DEATH_STARE || owner->id == SpellID::ACID_BREATH_DAMAGE)
@@ -323,11 +381,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		sc.dmgToDisplay = parameters.usedSpellPower;
 		if (owner->id == SpellID::DEATH_STARE)
 			vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
-	}	
-		
-	StacksInjured si;
-	
-	SpellCastContext ctx(attackedCres, sc, si);
+	}
 	
 	applyBattleEffects(env, parameters, ctx);
 	
@@ -757,6 +811,178 @@ ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(cons
 	return owner->isImmuneBy(obj);
 }
 
+///AcidBreathDamageMechnics
+void AcidBreathDamageMechnics::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::EFFECT;
+		bsa.effect = owner->mainEffectAnim;
+		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);
+	}	
+}
+
+///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);	
+}
+
+///DeathStareMechnics
+void DeathStareMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	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::EFFECT;
+		bsa.effect = owner->mainEffectAnim; //from config\spell-Info.txt
+		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
+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);	
+}
+
+///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
 {
@@ -885,113 +1111,22 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
 	return ret;	
 }
 
-///ChainLightningMechanics
-std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+///RemoveObstacleMechanics
+void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	std::set<const CStack* > attackedCres;
-	
-	std::set<BattleHex> possibleHexes;
-	for(auto stack : ctx.cb->battleGetAllStacks())
+	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
 	{
-		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
-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);
+		ObstaclesRemoved obr;
+		obr.obstacles.insert(obstacleToRemove->uniqueID);
+		env->sendAndApply(&obr);
 	}
 	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);	
-}
-
-///DispellHelpfulMechanics
-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);	
-}
-
-///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);
+		env->complain("There's no obstacle to remove!");	
 }
 
 
 ///SpecialRisingSpellMechanics
-ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
+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
@@ -999,9 +1134,9 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 	if(obj->count >= obj->baseAmount)
 		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	
-	if (caster) //FIXME: Archangels can cast immune stack
+	if(caster) //FIXME: Archangels can cast immune stack
 	{
-		auto maxHealth = owner->calculateHealedHP (caster, obj);
+		auto maxHealth = owner->calculateHealedHP(caster, obj);
 		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}	
@@ -1009,5 +1144,60 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 	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);	
+}