瀏覽代碼

Use spell mechanics clases in GameHandler

AlexVinS 11 年之前
父節點
當前提交
c67887ae58
共有 2 個文件被更改,包括 49 次插入505 次删除
  1. 48 505
      server/CGameHandler.cpp
  2. 1 0
      server/CGameHandler.h

+ 48 - 505
server/CGameHandler.cpp

@@ -59,6 +59,18 @@ extern bool end2;
 		bnr.round = gs->curB->round + 1;\
 		sendAndApply(&bnr);
 
+class ServerSpellCastEnvironment: public SpellCastEnvironment
+{
+public:
+	ServerSpellCastEnvironment(CGameHandler * gh);
+	~ServerSpellCastEnvironment(){};
+	void sendAndApply(CPackForClient * info) const override;	
+	CRandomGenerator & getRandomGenerator() const override;
+	void complain(const std::string & problem) const override;
+private:
+	CGameHandler * gh;	
+};
+
 CondSh<bool> battleMadeAction;
 CondSh<BattleResult *> battleResult(nullptr);
 template <typename T> class CApplyOnGH;
@@ -3932,518 +3944,28 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 }
 
-void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
+void CGameHandler::handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
 	int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
 {
 	const CSpell * spell = SpellID(spellID).toSpell();
-
-
-	//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
-	//It creates, sends and applies needed package.
-	auto placeObstacle = [&](BattleHex pos)
-	{
-		static int obstacleIdToGive = gs->curB->obstacles.size()
-									? (gs->curB->obstacles.back()->uniqueID+1)
-									: 0;
-
-		auto obstacle = make_shared<SpellCreatedObstacle>();
-		switch(spellID.toEnum()) // :/
-		{
-		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 = casterSide;
-		obstacle->ID = spellID;
-		obstacle->spellLevel = spellLvl;
-		obstacle->casterSpellPower = usedSpellPower;
-		obstacle->uniqueID = obstacleIdToGive++;
-
-		BattleObstaclePlaced bop;
-		bop.obstacle = obstacle;
-		sendAndApply(&bop);
-	};
-
-	BattleSpellCast sc;
-	sc.side = casterSide;
-	sc.id = spellID;
-	sc.skill = spellLvl;
-	sc.tile = destination;
-	sc.dmgToDisplay = 0;
-	sc.castedByHero = (bool)caster;
-	sc.attackerType = (stack ? stack->type->idNumber : CreatureID(CreatureID::NONE));
-	sc.manaGained = 0;
-	sc.spellCost = 0;
-
-	if (caster) //calculate spell cost
-	{
-		sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
-
-		if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
-		{
-			int manaChannel = 0;
-			for(CStack * stack : gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
-			{
-				if (stack->owner == secHero->tempOwner)
-				{
-					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
-				}
-			}
-			sc.manaGained = (manaChannel * sc.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 = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster);
-	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
 	
-	for (auto cre : attackedCres)
-	{
-		sc.affectedCres.insert (cre->ID);
-	}
+	ServerSpellCastEnvironment spellEnvironment(this);
 	
-	//checking if creatures resist
-	//resistance is applied only to negative spells
-	if(spell->isNegative())
-	{
-		for(auto s : attackedCres)
-		{
-			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
-			
-			if(gs->getRandomGenerator().nextInt(99) < prob)
-			{
-				sc.resisted.push_back(s->ID);
-			}
-		}
-	}
-
-	//calculating dmg to display
-	if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
-	{
-		sc.dmgToDisplay = usedSpellPower;
-		if (spellID == SpellID::DEATH_STARE)
-			vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
-	}
-	StacksInjured si;
-
-	//applying effects
-
-	if (spell->isOffensiveSpell())
-	{
-		int spellDamage = 0;
-		if (stack && mode != ECastingMode::MAGIC_MIRROR)
-		{
-			int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
-			if (unitSpellPower)
-				sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
-			else //Faerie Dragon
-			{
-				usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
-				sc.dmgToDisplay = 0;
-			}
-		}
-		int chainLightningModifier = 0;
-		for(auto & attackedCre : attackedCres)
-		{
-			if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
-				continue;
-
-			BattleStackAttacked bsa;
-			if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->getLevelInfo(spellLvl).range == "X" || mode == ECastingMode::ENCHANTER_CASTING))
-				//display effect only upon primary target of area spell
-				//FIXME: if no stack is attacked, there is no animation and interface freezes
-			{
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim;
-			}
-			if (spellDamage)
-				bsa.damageAmount = spellDamage >> chainLightningModifier;
-			else
-				bsa.damageAmount =  spell->calculateDamage(caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier;
-
-			sc.dmgToDisplay += bsa.damageAmount;
-
-			bsa.stackAttacked = (attackedCre)->ID;
-			if (mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
-				bsa.attackerID = stack->ID;
-			else
-				bsa.attackerID = -1;
-			(attackedCre)->prepareAttacked(bsa, gs->getRandomGenerator());
-			si.stacks.push_back(bsa);
-
-			if (spellID == SpellID::CHAIN_LIGHTNING)
-				++chainLightningModifier;
-		}
-	}
+	BattleSpellCastParameters parameters;
+	parameters.spellLvl = spellLvl;
+	parameters.destination = destination;
+	parameters.casterSide = casterSide;
+	parameters.casterColor = casterColor;	
+	parameters.caster = caster;
+	parameters.secHero = secHero;
 	
-	if (spell->hasEffects())
-	{
-			int stackSpellPower = 0;
-			if (stack && mode != ECastingMode::MAGIC_MIRROR)
-			{
-				stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
-			}
-			SetStackEffect sse;
-			Bonus pseudoBonus;
-			pseudoBonus.sid = spellID;
-			pseudoBonus.val = spellLvl;
-			pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
-			CStack::stackEffectToFeature(sse.effect, pseudoBonus);
-			if (spellID == SpellID::SHIELD || spellID == SpellID::AIR_SHIELD)
-			{
-				sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
-			}
-			if (spellID == SpellID::BIND && stack)//bind
-			{
-				sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
-			}
-			const Bonus * bonus = nullptr;
-			if (caster)
-				bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
-			//TODO does hero specialty should affects his stack casting spells?
-
-			si32 power = 0;
-			for(const CStack *affected : attackedCres)
-			{
-				if(vstd::contains(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 = spellID;
-				 	 		sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
-						}
- 	 					break;
- 	 				}
- 				}
-				if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
- 	 			{
- 	 				int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID.toEnum()) / tier;
-					Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
-					specialBonus.valType = Bonus::PERCENT_TO_ALL;
-					specialBonus.sid = spellID;
- 	 				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
- 	 			}
-			}
-
-			if(!sse.stacks.empty())
-				sendAndApply(&sse);
-
-	}
+	parameters.usedSpellPower = usedSpellPower;	
+	parameters.mode = mode;
+	parameters.casterStack = stack;	
+	parameters.selectedStack = gs->curB->battleGetStackByID(selectedStack, false);
 	
-	if(spell->isHealingSpell())
-	{
-			int hpGained = 0;
-			if (stack)
-			{
-				int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
-				if (unitSpellPower)
-					hpGained = stack->count * unitSpellPower; //Archangel
-				else //Faerie Dragon-like effect - unused so far
-					usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
-			}
-			StacksHealedOrResurrected shr;
-			shr.lifeDrain = false;
-			shr.tentHealing = false;
-			for(auto & attackedCre : attackedCres)
-			{
-				StacksHealedOrResurrected::HealInfo hi;
-				hi.stackID = (attackedCre)->ID;
-				if (stack) //casted by creature
-				{
-					if (hpGained)
-					{
-						hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, attackedCre); //archangel
-					}
-					else
-						hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead)
-				}
-				else
-					hi.healedHP = spell->calculateHealedHP(caster, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero
-				hi.lowLevelResurrection = spellLvl <= 1;
-				shr.healedStacks.push_back(hi);
-			}
-			if(!shr.healedStacks.empty())
-				sendAndApply(&shr);
-			if (spellID == SpellID::SACRIFICE) //remove victim
-			{
-				if (selectedStack == gs->curB->activeStack)
-				//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 = gs->curB->getNextStack()->ID; //why the hell next stack has same ID as current?
-					sendAndApply(&sas);
-
-				}
-				BattleStacksRemoved bsr;
-				bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
-				sendAndApply(&bsr);
-			}
-	}
-
-	switch (spellID)
-	{
-	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 && !battleGetStackByPos(hex, false) && !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[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(destination);
-		break;
-	case SpellID::FIRE_WALL:
-		{
-			//fire wall is build from multiple obstacles - one fire piece for each affected hex
-			auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
-			for(BattleHex hex : affectedHexes)
-				placeObstacle(hex);
-		}
-		break;
-	case SpellID::TELEPORT:
-		{
-			BattleStackMoved bsm;
-			bsm.distance = -1;
-			bsm.stack = selectedStack;
-			std::vector<BattleHex> tiles;
-			tiles.push_back(destination);
-			bsm.tilesToMove = tiles;
-			bsm.teleporting = true;
-			sendAndApply(&bsm);
-			break;
-		}
-	case SpellID::SUMMON_FIRE_ELEMENTAL:
-	case SpellID::SUMMON_EARTH_ELEMENTAL:
-	case SpellID::SUMMON_WATER_ELEMENTAL:
-	case SpellID::SUMMON_AIR_ELEMENTAL:
-		{ //elemental summoning
-			CreatureID creID;
-			switch(spellID)
-			{
-				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;
-			}
-
-			BattleStackAdded bsa;
-			bsa.creID = creID;
-			bsa.attacker = !(bool)casterSide;
-			bsa.summoned = true;
-			bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
-
-			//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
-			int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID.toEnum()) : 0;
-
-			bsa.amount = usedSpellPower
-				* SpellID(spellID).toSpell()->getPower(spellLvl)
-				* (100 + percentBonus) / 100.0; //new feature - percentage bonus
-			if(bsa.amount)
-				sendAndApply(&bsa);
-			else
-				complain("Summoning elementals didn't summon any!");
-		}
-		break;
-	case SpellID::CLONE:
-		{
-			const CStack * clonedStack = nullptr;
-			if (attackedCres.size())
-				clonedStack = *attackedCres.begin();
-			if (!clonedStack)
-			{
-				complain ("No target stack to clone!");
-				break;
-			}
-
-			BattleStackAdded bsa;
-			bsa.creID = clonedStack->type->idNumber;
-			bsa.attacker = !(bool)casterSide;
-			bsa.summoned = true;
-			bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
-			bsa.amount = clonedStack->count;
-			sendAndApply (&bsa);
-
-			BattleSetStackProperty ssp;
-			ssp.stackID = bsa.newStackID;
-			ssp.which = BattleSetStackProperty::CLONED; //using enum values
-			ssp.val = 0;
-			ssp.absolute = 1;
-			sendAndApply(&ssp);
-		}
-		break;
-	case SpellID::REMOVE_OBSTACLE:
-		{
-			if(auto obstacleToRemove = battleGetObstacleOnPos(destination, false))
-			{
-				ObstaclesRemoved obr;
-				obr.obstacles.insert(obstacleToRemove->uniqueID);
-				sendAndApply(&obr);
-			}
-			else
-				complain("There's no obstacle to remove!");
-		}
-		break;
-	case SpellID::DEATH_STARE: //handled in a bit different way
-		{
-			for(auto & attackedCre : attackedCres)
-			{
-				BattleStackAttacked bsa;
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
-				bsa.damageAmount = usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
-				bsa.stackAttacked = (attackedCre)->ID;
-				bsa.attackerID = -1;
-				(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
-				si.stacks.push_back(bsa);
-			}
-		}
-		break;
-	case SpellID::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
-		{
-			for(auto & attackedCre : attackedCres) //no immunities
-			{
-				BattleStackAttacked bsa;
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim;
-				bsa.damageAmount = usedSpellPower; //damage times the number of attackers
-				bsa.stackAttacked = (attackedCre)->ID;
-				bsa.attackerID = -1;
-				(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
-				si.stacks.push_back(bsa);
-			}
-		}
-		break;
-	}
-
-	sendAndApply(&sc);
-	if(!si.stacks.empty()) //after spellcast info shows
-		sendAndApply(&si);
-
-	if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
-	{
-		BattleSetStackProperty ssp;
-		ssp.stackID = stack->ID;
-		ssp.which = BattleSetStackProperty::CASTS;
-		ssp.val = -1;
-		ssp.absolute = false;
-		sendAndApply(&ssp);
-	}
-
-	//Magic Mirror effect
-	if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->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 > gs->getRandomGenerator().nextInt(99))
-			{
-				std::vector<CStack *> mirrorTargets;
-				std::vector<CStack *> & battleStacks = gs->curB->stacks;
-				for (auto & battleStack : battleStacks)
-				{
-					if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
-					{
-						if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, battleStack))
-							mirrorTargets.push_back(battleStack);
-					}
-				}
-				if (!mirrorTargets.empty())
-				{
-					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position;
-					handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre));
-				}
-			}
-		}
-	}
+	
+	spell->battleCast(&spellEnvironment, parameters);
 }
 
 bool CGameHandler::makeCustomAction( BattleAction &ba )
@@ -6401,3 +5923,24 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 {
 	winnerHero = loserHero = nullptr;
 }
+
+
+ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
+{
+	
+}
+
+void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
+{
+	gh->sendAndApply(info);
+}
+
+CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
+{
+	return gh->gameState()->getRandomGenerator();
+}
+
+void ServerSpellCastEnvironment::complain(const std::string& problem) const
+{
+	gh->complain(problem);
+}

+ 1 - 0
server/CGameHandler.h

@@ -288,6 +288,7 @@ public:
 	friend class CVCMIServer;
 
 private:
+
 	std::list<PlayerColor> generatePlayerTurnOrder() const;
 	void makeStackDoNothing(const CStack * next);
 	void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;