mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Use spell mechanics clases in GameHandler
This commit is contained in:
		| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user