Explorar o código

Merge pull request #94 from vcmi/feature/spell.earthQuake

Okay, let's get this all together for 0.98.
DjWarmonger %!s(int64=10) %!d(string=hai) anos
pai
achega
6001a89632

+ 19 - 6
client/battle/CBattleInterface.cpp

@@ -1188,19 +1188,32 @@ void CBattleInterface::hexLclicked(int whichOne)
 
 
 void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 {
 {
-	for(auto it = ca.attackedParts.begin(); it != ca.attackedParts.end(); ++it)
+	if(ca.attacker != -1)
 	{
 	{
 		const CStack * stack = curInt->cb->battleGetStackByID(ca.attacker);
 		const CStack * stack = curInt->cb->battleGetStackByID(ca.attacker);
-		addNewAnim(new CShootingAnimation(this, stack, it->destinationTile, nullptr, true, it->damageDealt));
+		for(auto attackInfo : ca.attackedParts)
+		{
+			addNewAnim(new CShootingAnimation(this, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
+		}		
 	}
 	}
+	else
+	{
+		//no attacker stack, assume spell-related (earthquake) - only hit animation 
+		for(auto attackInfo : ca.attackedParts)
+		{
+			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, this) + Point(99, 120);
+	
+			addNewAnim(new CSpellEffectAnimation(this, "SGEXPL.DEF", destPos.x, destPos.y));
+		}
+	}	
 
 
 	waitForAnims();
 	waitForAnims();
 
 
-	for(auto it = ca.attackedParts.begin(); it != ca.attackedParts.end(); ++it)
+	for(auto attackInfo : ca.attackedParts)
 	{
 	{
-		SDL_FreeSurface(siegeH->walls[it->attackedPart + 2]);
-		siegeH->walls[it->attackedPart + 2] = BitmapHandler::loadBitmap(
-			siegeH->getSiegeName(it->attackedPart + 2, curInt->cb->battleGetWallState(it->attackedPart)) );
+		SDL_FreeSurface(siegeH->walls[attackInfo.attackedPart + 2]);
+		siegeH->walls[attackInfo.attackedPart + 2] = BitmapHandler::loadBitmap(
+			siegeH->getSiegeName(attackInfo.attackedPart + 2, curInt->cb->battleGetWallState(attackInfo.attackedPart)));
 	}
 	}
 }
 }
 
 

+ 1 - 0
config/spells/other.json

@@ -86,6 +86,7 @@
 		},
 		},
 		"levels" : {
 		"levels" : {
 			"base":{
 			"base":{
+				"targetModifier":{"smart":true},
 				"range" : "X"
 				"range" : "X"
 			}
 			}
 		},
 		},

+ 6 - 1
lib/CBattleCallback.cpp

@@ -1599,6 +1599,11 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	if(!spell->combatSpell)
 	if(!spell->combatSpell)
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 
 
+	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCasted(this, player);
+	
+	if(specificProblem != ESpellCastProblem::OK)
+		return specificProblem;	
+
 	if(spell->isNegative() || spell->hasEffects())
 	if(spell->isNegative() || spell->hasEffects())
 	{
 	{
 		bool allStacksImmune = true;
 		bool allStacksImmune = true;
@@ -1633,7 +1638,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	if(arpos < ARRAY_COUNT(spellIDs))
 	if(arpos < ARRAY_COUNT(spellIDs))
 	{
 	{
 		//check if there are summoned elementals of other type
 		//check if there are summoned elementals of other type
-		for( const CStack * st : battleAliveStacks())
+		for(const CStack * st : battleAliveStacks(side))
 			if(vstd::contains(st->state, EBattleStackState::SUMMONED) && st->getCreature()->idNumber != creIDs[arpos])
 			if(vstd::contains(st->state, EBattleStackState::SUMMONED) && st->getCreature()->idNumber != creIDs[arpos])
 				return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED;
 				return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED;
 	}
 	}

+ 0 - 1
lib/NetPacks.h

@@ -1555,7 +1555,6 @@ struct ObstaclesRemoved : public CPackForClient //3014
 };
 };
 
 
 struct ELF_VISIBILITY CatapultAttack : public CPackForClient //3015
 struct ELF_VISIBILITY CatapultAttack : public CPackForClient //3015
-
 {
 {
 	struct AttackInfo
 	struct AttackInfo
 	{
 	{

+ 109 - 0
lib/spells/BattleSpellMechanics.cpp

@@ -152,6 +152,115 @@ void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast *
 	}
 	}
 }
 }
 
 
+///EarthquakeMechanics
+void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	if(nullptr == parameters.cb->town)
+	{
+		env->complain("EarthquakeMechanics: not town siege");
+		return;
+	}
+
+	if(CGTownInstance::NONE == parameters.cb->town->fortLevel())
+	{
+		env->complain("EarthquakeMechanics: town has no fort");
+		return;
+	}
+
+	//start with all destructible parts
+	std::set<EWallPart::EWallPart> possibleTargets =
+	{
+		EWallPart::KEEP,
+		EWallPart::BOTTOM_TOWER,
+		EWallPart::BOTTOM_WALL,
+		EWallPart::BELOW_GATE,
+		EWallPart::OVER_GATE,
+		EWallPart::UPPER_WALL,
+		EWallPart::UPPER_TOWER,
+		EWallPart::GATE
+	};
+
+	assert(possibleTargets.size() == EWallPart::PARTS_COUNT);
+
+	const int targetsToAttack = 2 + std::max<int>(parameters.spellLvl - 1, 0);
+
+	CatapultAttack ca;
+	ca.attacker = -1;
+
+	for(int i = 0; i < targetsToAttack; i++)
+	{
+		//Any destructible part can be hit regardless of its HP. Multiple hit on same target is allowed.
+		EWallPart::EWallPart target = *RandomGeneratorUtil::nextItem(possibleTargets, env->getRandomGenerator());
+
+		auto & currentHP = parameters.cb->si.wallState;
+
+		if(currentHP.at(target) == EWallState::DESTROYED || currentHP.at(target) == EWallState::NONE)
+			continue;
+
+		CatapultAttack::AttackInfo attackInfo;
+
+		attackInfo.damageDealt = 1;
+		attackInfo.attackedPart = target;
+		attackInfo.destinationTile = parameters.cb->wallPartToBattleHex(target);
+
+		ca.attackedParts.push_back(attackInfo);
+
+		//removing creatures in turrets / keep if one is destroyed
+		BattleHex posRemove;
+
+		switch(target)
+		{
+		case EWallPart::KEEP:
+			posRemove = -2;
+			break;
+		case EWallPart::BOTTOM_TOWER:
+			posRemove = -3;
+			break;
+		case EWallPart::UPPER_TOWER:
+			posRemove = -4;
+			break;
+		}
+
+		if(posRemove != BattleHex::INVALID)
+		{
+			BattleStacksRemoved bsr;
+			for(auto & elem : parameters.cb->stacks)
+			{
+				if(elem->position == posRemove)
+				{
+					bsr.stackIDs.insert(elem->ID);
+					break;
+				}
+			}
+			if(bsr.stackIDs.size() > 0)
+				env->sendAndApply(&bsr);
+		}
+	};
+
+	env->sendAndApply(&ca);
+}
+
+ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+{
+	if(nullptr == cb->battleGetDefendedTown())
+	{
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+	if(CGTownInstance::NONE == cb->battleGetDefendedTown()->fortLevel())
+	{
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+	if(owner->getTargetInfo(0).smart) //TODO: use real spell level
+	{
+		//if spell targeting is smart, then only attacker can use it
+		if(cb->playerToSide(player) != 0)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+	return ESpellCastProblem::OK;
+}
 
 
 ///HypnotizeMechanics
 ///HypnotizeMechanics
 ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
 ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const

+ 9 - 0
lib/spells/BattleSpellMechanics.h

@@ -44,6 +44,15 @@ public:
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 };
 };
 
 
+class DLL_LINKAGE EarthquakeMechanics : public DefaultSpellMechanics
+{
+public:
+	EarthquakeMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;	
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
+};
+
 class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
 class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
 {
 {
 public:
 public:

+ 7 - 0
lib/spells/CDefaultSpellMechanics.cpp

@@ -712,6 +712,13 @@ std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargeting
 	return attackedCres;
 	return attackedCres;
 }
 }
 
 
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+{
+	//no problems by default, this method is for spell-specific problems	
+	return ESpellCastProblem::OK;
+}
+
+
 ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
 ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
 {
 {
 	//by default use general algorithm
 	//by default use general algorithm

+ 2 - 0
lib/spells/CDefaultSpellMechanics.h

@@ -33,6 +33,8 @@ public:
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
 	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
 	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
 
 
+	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
 
 
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;

+ 5 - 0
lib/spells/CSpellHandler.cpp

@@ -234,6 +234,11 @@ ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affec
 	return ret;
 	return ret;
 }
 }
 
 
+ESpellCastProblem::ESpellCastProblem CSpell::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+{
+	return mechanics->canBeCasted(cb, player);
+}
+
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
 {
 {
 	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
 	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);

+ 8 - 3
lib/spells/CSpellHandler.h

@@ -246,9 +246,6 @@ public:
 	//internal, for use only by Mechanics classes
 	//internal, for use only by Mechanics classes
 	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
 	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
 
 
-	//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
-
 	//internal, for use only by Mechanics classes. applying secondary skills
 	//internal, for use only by Mechanics classes. applying secondary skills
 	ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
 	ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
 
 
@@ -301,6 +298,14 @@ public:
 	}
 	}
 	friend class CSpellHandler;
 	friend class CSpellHandler;
 	friend class Graphics;
 	friend class Graphics;
+public:
+	///internal interface (for callbacks)
+	
+	///Checks general but spell-specific problems for all casting modes. Use only during battle.
+	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const;
+
+	///checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
 public:
 public:
 	///Server logic. Has write access to GameState via packets.
 	///Server logic. Has write access to GameState via packets.
 	///May be executed on client side by (future) non-cheat-proof scripts.
 	///May be executed on client side by (future) non-cheat-proof scripts.

+ 2 - 0
lib/spells/ISpellMechanics.cpp

@@ -42,6 +42,8 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 		return new DispellMechanics(s);
 		return new DispellMechanics(s);
 	case SpellID::DISPEL_HELPFUL_SPELLS:
 	case SpellID::DISPEL_HELPFUL_SPELLS:
 		return new DispellHelpfulMechanics(s);
 		return new DispellHelpfulMechanics(s);
+	case SpellID::EARTHQUAKE:
+		return new EarthquakeMechanics(s);
 	case SpellID::FIRE_WALL:
 	case SpellID::FIRE_WALL:
 	case SpellID::FORCE_FIELD:
 	case SpellID::FORCE_FIELD:
 		return new WallMechanics(s);
 		return new WallMechanics(s);

+ 4 - 2
lib/spells/ISpellMechanics.h

@@ -38,9 +38,11 @@ public:
 
 
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
 	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
 	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
-
+	
+	virtual ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
+	
 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
-
+	
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
 	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
 	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
 	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
 	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;