|  | @@ -956,18 +956,18 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 | 
											
												
													
														|  |  			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
 |  |  			bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -	int64_t drainedLife = 0;
 |  | 
 | 
											
												
													
														|  | 
 |  | +	battle::HealInfo healInfo;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	// only primary target
 |  |  	// only primary target
 | 
											
												
													
														|  |  	if(defender->alive())
 |  |  	if(defender->alive())
 | 
											
												
													
														|  | -		drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, defender, distance, false);
 |  | 
 | 
											
												
													
														|  | 
 |  | +		applyBattleEffects(battle, bat, attackerState, fireShield, defender, &healInfo, distance, false);
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	//multiple-hex normal attack
 |  |  	//multiple-hex normal attack
 | 
											
												
													
														|  |  	std::set<const CStack*> attackedCreatures = battle.getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target
 |  |  	std::set<const CStack*> attackedCreatures = battle.getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target
 | 
											
												
													
														|  |  	for(const CStack * stack : attackedCreatures)
 |  |  	for(const CStack * stack : attackedCreatures)
 | 
											
												
													
														|  |  	{
 |  |  	{
 | 
											
												
													
														|  |  		if(stack != defender && stack->alive()) //do not hit same stack twice
 |  |  		if(stack != defender && stack->alive()) //do not hit same stack twice
 | 
											
												
													
														|  | -			drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, stack, distance, true);
 |  | 
 | 
											
												
													
														|  | 
 |  | +			applyBattleEffects(battle, bat, attackerState, fireShield, stack, &healInfo, distance, true);
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	std::shared_ptr<const Bonus> bonus = attacker->getFirstBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
 |  |  	std::shared_ptr<const Bonus> bonus = attacker->getFirstBonus(Selector::type()(BonusType::SPELL_LIKE_ATTACK));
 | 
											
										
											
												
													
														|  | @@ -995,7 +995,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 | 
											
												
													
														|  |  		{
 |  |  		{
 | 
											
												
													
														|  |  			if(stack != defender && stack->alive()) //do not hit same stack twice
 |  |  			if(stack != defender && stack->alive()) //do not hit same stack twice
 | 
											
												
													
														|  |  			{
 |  |  			{
 | 
											
												
													
														|  | -				drainedLife += applyBattleEffects(battle, bat, attackerState, fireShield, stack, distance, true);
 |  | 
 | 
											
												
													
														|  | 
 |  | +				applyBattleEffects(battle, bat, attackerState, fireShield, stack, &healInfo, distance, true);
 | 
											
												
													
														|  |  			}
 |  |  			}
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -1019,7 +1019,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 | 
											
												
													
														|  |  		bat.attackerChanges.changedStacks.push_back(info);
 |  |  		bat.attackerChanges.changedStacks.push_back(info);
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -	if (drainedLife > 0)
 |  | 
 | 
											
												
													
														|  | 
 |  | +	if (healInfo.healedHealthPoints > 0)
 | 
											
												
													
														|  |  		bat.flags |= BattleAttack::LIFE_DRAIN;
 |  |  		bat.flags |= BattleAttack::LIFE_DRAIN;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	for (BattleStackAttacked & bsa : bat.bsa)
 |  |  	for (BattleStackAttacked & bsa : bat.bsa)
 | 
											
										
											
												
													
														|  | @@ -1039,26 +1039,16 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 | 
											
												
													
														|  |  			totalKills += bsa.killedAmount;
 |  |  			totalKills += bsa.killedAmount;
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -		{
 |  | 
 | 
											
												
													
														|  | -			MetaString text;
 |  | 
 | 
											
												
													
														|  | -			attacker->addText(text, EMetaText::GENERAL_TXT, 376);
 |  | 
 | 
											
												
													
														|  | -			attacker->addNameReplacement(text);
 |  | 
 | 
											
												
													
														|  | -			text.replaceNumber(totalDamage);
 |  | 
 | 
											
												
													
														|  | -			blm.lines.push_back(text);
 |  | 
 | 
											
												
													
														|  | -		}
 |  | 
 | 
											
												
													
														|  | 
 |  | +		addGenericDamageLog(blm, attackerState, totalDamage);
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  		addGenericKilledLog(blm, defender, totalKills, multipleTargets);
 |  |  		addGenericKilledLog(blm, defender, totalKills, multipleTargets);
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	// drain life effect (as well as log entry) must be applied after the attack
 |  |  	// drain life effect (as well as log entry) must be applied after the attack
 | 
											
												
													
														|  | -	if(drainedLife > 0)
 |  | 
 | 
											
												
													
														|  | 
 |  | +	if(healInfo.healedHealthPoints > 0)
 | 
											
												
													
														|  |  	{
 |  |  	{
 | 
											
												
													
														|  | -		MetaString text;
 |  | 
 | 
											
												
													
														|  | -		attackerState->addText(text, EMetaText::GENERAL_TXT, 361);
 |  | 
 | 
											
												
													
														|  | -		attackerState->addNameReplacement(text, false);
 |  | 
 | 
											
												
													
														|  | -		text.replaceNumber(drainedLife);
 |  | 
 | 
											
												
													
														|  | -		defender->addNameReplacement(text, true);
 |  | 
 | 
											
												
													
														|  | -		blm.lines.push_back(std::move(text));
 |  | 
 | 
											
												
													
														|  | 
 |  | +		addGenericDrainedLifeLog(blm, attackerState, defender, healInfo.healedHealthPoints);
 | 
											
												
													
														|  | 
 |  | +		addGenericResurrectedLog(blm, attackerState, defender, healInfo.resurrectedCount);
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	if(!fireShield.empty())
 |  |  	if(!fireShield.empty())
 | 
											
										
											
												
													
														|  | @@ -1435,7 +1425,7 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -int64_t BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & battle, BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary)
 |  | 
 | 
											
												
													
														|  | 
 |  | +void BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & battle, BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, battle::HealInfo * healInfo, int distance, bool secondary) const
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  |  	BattleStackAttacked bsa;
 |  |  	BattleStackAttacked bsa;
 | 
											
												
													
														|  |  	if(secondary)
 |  |  	if(secondary)
 | 
											
										
											
												
													
														|  | @@ -1456,14 +1446,13 @@ int64_t BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & ba
 | 
											
												
													
														|  |  		CStack::prepareAttacked(bsa, gameHandler->getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
 |  |  		CStack::prepareAttacked(bsa, gameHandler->getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -	int64_t drainedLife = 0;
 |  | 
 | 
											
												
													
														|  | 
 |  | +	battle::HealInfo tmpHealInfo;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	//life drain handling
 |  |  	//life drain handling
 | 
											
												
													
														|  |  	if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving())
 |  |  	if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving())
 | 
											
												
													
														|  |  	{
 |  |  	{
 | 
											
												
													
														|  |  		int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
 |  |  		int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
 | 
											
												
													
														|  | -		attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
 |  | 
 | 
											
												
													
														|  | -		drainedLife += toHeal;
 |  | 
 | 
											
												
													
														|  | 
 |  | +		tmpHealInfo += attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	//soul steal handling
 |  |  	//soul steal handling
 | 
											
										
											
												
													
														|  | @@ -1477,12 +1466,12 @@ int64_t BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & ba
 | 
											
												
													
														|  |  			{
 |  |  			{
 | 
											
												
													
														|  |  				int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(BonusType::SOUL_STEAL, subtype) * attackerState->getMaxHealth();
 |  |  				int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(BonusType::SOUL_STEAL, subtype) * attackerState->getMaxHealth();
 | 
											
												
													
														|  |  				bool permanent = subtype == BonusCustomSubtype::soulStealPermanent;
 |  |  				bool permanent = subtype == BonusCustomSubtype::soulStealPermanent;
 | 
											
												
													
														|  | -				attackerState->heal(toHeal, EHealLevel::OVERHEAL, (permanent ? EHealPower::PERMANENT : EHealPower::ONE_BATTLE));
 |  | 
 | 
											
												
													
														|  | -				drainedLife += toHeal;
 |  | 
 | 
											
												
													
														|  | 
 |  | +				tmpHealInfo += attackerState->heal(toHeal, EHealLevel::OVERHEAL, (permanent ? EHealPower::PERMANENT : EHealPower::ONE_BATTLE));
 | 
											
												
													
														|  |  				break;
 |  |  				break;
 | 
											
												
													
														|  |  			}
 |  |  			}
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | 
 |  | +	*healInfo += tmpHealInfo;
 | 
											
												
													
														|  |  	bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
 |  |  	bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	//fire shield handling
 |  |  	//fire shield handling
 | 
											
										
											
												
													
														|  | @@ -1499,8 +1488,6 @@ int64_t BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & ba
 | 
											
												
													
														|  |  		auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(BonusType::FIRE_SHIELD)) / 100;
 |  |  		auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(BonusType::FIRE_SHIELD)) / 100;
 | 
											
												
													
														|  |  		fireShield.push_back(std::make_pair(def, fireShieldDamage));
 |  |  		fireShield.push_back(std::make_pair(def, fireShieldDamage));
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -	return drainedLife;
 |  | 
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  void BattleActionProcessor::sendGenericKilledLog(const CBattleInfoCallback & battle, const CStack * defender, int32_t killed, bool multiple)
 |  |  void BattleActionProcessor::sendGenericKilledLog(const CBattleInfoCallback & battle, const CStack * defender, int32_t killed, bool multiple)
 | 
											
										
											
												
													
														|  | @@ -1514,7 +1501,7 @@ void BattleActionProcessor::sendGenericKilledLog(const CBattleInfoCallback & bat
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -void BattleActionProcessor::addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple)
 |  | 
 | 
											
												
													
														|  | 
 |  | +void BattleActionProcessor::addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple) const
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  |  	if(killed > 0)
 |  |  	if(killed > 0)
 | 
											
												
													
														|  |  	{
 |  |  	{
 | 
											
										
											
												
													
														|  | @@ -1542,6 +1529,46 @@ void BattleActionProcessor::addGenericKilledLog(BattleLogMessage & blm, const CS
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +void BattleActionProcessor::addGenericDamageLog(BattleLogMessage& blm, const std::shared_ptr<battle::CUnitState> &attackerState, int64_t damageDealt) const
 | 
											
												
													
														|  | 
 |  | +{
 | 
											
												
													
														|  | 
 |  | +	MetaString text;
 | 
											
												
													
														|  | 
 |  | +	attackerState->addText(text, EMetaText::GENERAL_TXT, 376);
 | 
											
												
													
														|  | 
 |  | +	attackerState->addNameReplacement(text);
 | 
											
												
													
														|  | 
 |  | +	text.replaceNumber(damageDealt);
 | 
											
												
													
														|  | 
 |  | +	blm.lines.push_back(std::move(text));
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +void BattleActionProcessor::addGenericDrainedLifeLog(BattleLogMessage& blm, const std::shared_ptr<battle::CUnitState>& attackerState, const CStack* defender, int64_t drainedLife) const
 | 
											
												
													
														|  | 
 |  | +{
 | 
											
												
													
														|  | 
 |  | +	MetaString text;
 | 
											
												
													
														|  | 
 |  | +	attackerState->addText(text, EMetaText::GENERAL_TXT, 361);
 | 
											
												
													
														|  | 
 |  | +	attackerState->addNameReplacement(text);
 | 
											
												
													
														|  | 
 |  | +	text.replaceNumber(drainedLife);
 | 
											
												
													
														|  | 
 |  | +	defender->addNameReplacement(text);
 | 
											
												
													
														|  | 
 |  | +	blm.lines.push_back(std::move(text));
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +void BattleActionProcessor::addGenericResurrectedLog(BattleLogMessage& blm, const std::shared_ptr<battle::CUnitState>& attackerState, const CStack* defender, int64_t resurrected) const
 | 
											
												
													
														|  | 
 |  | +{
 | 
											
												
													
														|  | 
 |  | +	if (resurrected > 0)
 | 
											
												
													
														|  | 
 |  | +	{
 | 
											
												
													
														|  | 
 |  | +		auto text = blm.lines.back().toString();
 | 
											
												
													
														|  | 
 |  | +		text.pop_back();	// erase '.' at the end of line with life drain info
 | 
											
												
													
														|  | 
 |  | +		MetaString ms = MetaString::createFromRawString(text);
 | 
											
												
													
														|  | 
 |  | +		if (resurrected == 1)
 | 
											
												
													
														|  | 
 |  | +		{
 | 
											
												
													
														|  | 
 |  | +			ms.appendLocalString(EMetaText::GENERAL_TXT, 363);		// "\n and one rises from the dead."
 | 
											
												
													
														|  | 
 |  | +		}
 | 
											
												
													
														|  | 
 |  | +		else
 | 
											
												
													
														|  | 
 |  | +		{
 | 
											
												
													
														|  | 
 |  | +			ms.appendLocalString(EMetaText::GENERAL_TXT, 364);		// "\n and %d rise from the dead."
 | 
											
												
													
														|  | 
 |  | +			ms.replaceNumber(resurrected);
 | 
											
												
													
														|  | 
 |  | +		}
 | 
											
												
													
														|  | 
 |  | +		blm.lines[blm.lines.size() - 1] = std::move(ms);
 | 
											
												
													
														|  | 
 |  | +	}	
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |  bool BattleActionProcessor::makeAutomaticBattleAction(const CBattleInfoCallback & battle, const BattleAction & ba)
 |  |  bool BattleActionProcessor::makeAutomaticBattleAction(const CBattleInfoCallback & battle, const BattleAction & ba)
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  |  	return makeBattleActionImpl(battle, ba);
 |  |  	return makeBattleActionImpl(battle, ba);
 |