2017-07-20 06:08:49 +02:00
|
|
|
/*
|
|
|
|
* Damage.cpp, part of VCMI engine
|
|
|
|
*
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
*
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "StdInc.h"
|
|
|
|
|
|
|
|
#include "Damage.h"
|
|
|
|
#include "Registry.h"
|
2023-05-03 15:35:32 +02:00
|
|
|
#include "../CSpellHandler.h"
|
2017-07-20 06:08:49 +02:00
|
|
|
#include "../ISpellMechanics.h"
|
|
|
|
|
|
|
|
#include "../../NetPacks.h"
|
|
|
|
#include "../../CStack.h"
|
|
|
|
#include "../../battle/IBattleState.h"
|
|
|
|
#include "../../battle/CBattleInfoCallback.h"
|
|
|
|
#include "../../CGeneralTextHandler.h"
|
|
|
|
#include "../../serializer/JsonSerializeFormat.h"
|
|
|
|
|
2023-05-03 15:35:32 +02:00
|
|
|
#include <vcmi/spells/Spell.h>
|
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
2017-07-20 06:08:49 +02:00
|
|
|
namespace spells
|
|
|
|
{
|
|
|
|
namespace effects
|
|
|
|
{
|
|
|
|
|
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
|
|
|
void Damage::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
|
2017-07-20 06:08:49 +02:00
|
|
|
{
|
|
|
|
StacksInjured stacksInjured;
|
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
|
|
|
BattleLogMessage blm;
|
|
|
|
size_t targetIndex = 0;
|
|
|
|
const battle::Unit * firstTarget = nullptr;
|
|
|
|
const bool describe = server->describeChanges();
|
|
|
|
|
|
|
|
int64_t damageToDisplay = 0;
|
|
|
|
uint32_t killed = 0;
|
|
|
|
bool multiple = false;
|
|
|
|
|
2023-02-07 00:40:01 +02:00
|
|
|
for(const auto & t : target)
|
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
|
|
|
{
|
|
|
|
const battle::Unit * unit = t.unitValue;
|
|
|
|
if(unit && unit->alive())
|
|
|
|
{
|
|
|
|
BattleStackAttacked bsa;
|
|
|
|
bsa.damageAmount = damageForTarget(targetIndex, m, unit);
|
|
|
|
bsa.stackAttacked = unit->unitId();
|
|
|
|
bsa.attackerID = -1;
|
|
|
|
auto newState = unit->acquireState();
|
|
|
|
CStack::prepareAttacked(bsa, *server->getRNG(), newState);
|
|
|
|
|
|
|
|
if(describe)
|
|
|
|
{
|
|
|
|
if(!firstTarget)
|
|
|
|
firstTarget = unit;
|
|
|
|
else
|
|
|
|
multiple = true;
|
|
|
|
damageToDisplay += bsa.damageAmount;
|
|
|
|
killed += bsa.killedAmount;
|
|
|
|
}
|
|
|
|
stacksInjured.stacks.push_back(bsa);
|
|
|
|
}
|
|
|
|
targetIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(describe && firstTarget && damageToDisplay > 0)
|
|
|
|
{
|
|
|
|
describeEffect(blm.lines, m, firstTarget, killed, damageToDisplay, multiple);
|
|
|
|
}
|
|
|
|
|
2017-07-20 06:08:49 +02:00
|
|
|
if(!stacksInjured.stacks.empty())
|
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
|
|
|
server->apply(&stacksInjured);
|
|
|
|
|
|
|
|
if(!blm.lines.empty())
|
|
|
|
server->apply(&blm);
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Damage::isReceptive(const Mechanics * m, const battle::Unit * unit) const
|
|
|
|
{
|
|
|
|
if(!UnitEffect::isReceptive(m, unit))
|
|
|
|
return false;
|
|
|
|
|
2023-05-05 20:28:07 +02:00
|
|
|
bool isImmune = m->getSpell()->isMagical() && (unit->getBonusBearer()->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(ESpellSchool::ANY)) >= 100); //General spell damage immunity
|
2017-07-20 06:08:49 +02:00
|
|
|
//elemental immunity for damage
|
2023-05-03 15:35:32 +02:00
|
|
|
m->getSpell()->forEachSchool([&](const SchoolInfo & cnf, bool & stop)
|
2017-07-20 06:08:49 +02:00
|
|
|
{
|
2023-05-05 20:28:07 +02:00
|
|
|
isImmune |= (unit->getBonusBearer()->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, cnf.id) >= 100); //100% reduction is immunity
|
2023-05-03 15:35:32 +02:00
|
|
|
});
|
2017-07-20 06:08:49 +02:00
|
|
|
|
2023-05-03 15:35:32 +02:00
|
|
|
return !isImmune;
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Damage::serializeJsonUnitEffect(JsonSerializeFormat & handler)
|
|
|
|
{
|
|
|
|
handler.serializeBool("killByPercentage", killByPercentage);
|
|
|
|
handler.serializeBool("killByCount", killByCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t Damage::damageForTarget(size_t targetIndex, const Mechanics * m, const battle::Unit * target) const
|
|
|
|
{
|
|
|
|
int64_t baseDamage;
|
|
|
|
|
|
|
|
if(killByPercentage)
|
|
|
|
{
|
|
|
|
int64_t amountToKill = target->getCount() * m->getEffectValue() / 100;
|
2023-05-01 19:29:53 +02:00
|
|
|
baseDamage = amountToKill * target->getMaxHealth();
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
|
|
|
else if(killByCount)
|
|
|
|
{
|
2023-05-01 19:29:53 +02:00
|
|
|
baseDamage = m->getEffectValue() * target->getMaxHealth();
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
baseDamage = m->adjustEffectValue(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(chainLength > 1 && targetIndex > 0)
|
|
|
|
{
|
2023-02-11 17:18:53 +02:00
|
|
|
double indexedFactor = std::pow(chainFactor, static_cast<double>(targetIndex));
|
|
|
|
return static_cast<int64_t>(indexedFactor * baseDamage);
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return baseDamage;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Damage::describeEffect(std::vector<MetaString> & log, const Mechanics * m, const battle::Unit * firstTarget, uint32_t kills, int64_t damage, bool multiple) const
|
|
|
|
{
|
|
|
|
if(m->getSpellIndex() == SpellID::DEATH_STARE && !multiple)
|
|
|
|
{
|
|
|
|
MetaString line;
|
|
|
|
if(kills > 1)
|
|
|
|
{
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendLocalString(EMetaText::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s.
|
|
|
|
line.replaceNumber(kills);
|
2017-07-20 06:08:49 +02:00
|
|
|
firstTarget->addNameReplacement(line, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendLocalString(EMetaText::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s.
|
2017-07-20 06:08:49 +02:00
|
|
|
firstTarget->addNameReplacement(line, false);
|
|
|
|
}
|
|
|
|
m->caster->getCasterName(line);
|
|
|
|
log.push_back(line);
|
|
|
|
}
|
|
|
|
else if(m->getSpellIndex() == SpellID::THUNDERBOLT && !multiple)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
MetaString line;
|
2023-06-18 11:18:25 +02:00
|
|
|
firstTarget->addText(line, EMetaText::GENERAL_TXT, -367, true);
|
2017-07-20 06:08:49 +02:00
|
|
|
firstTarget->addNameReplacement(line, true);
|
|
|
|
log.push_back(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
MetaString line;
|
|
|
|
//todo: handle newlines in metastring
|
2022-12-27 23:17:41 +02:00
|
|
|
std::string text = VLC->generaltexth->allTexts[343]; //Does %d points of damage.
|
2017-07-20 06:08:49 +02:00
|
|
|
boost::algorithm::trim(text);
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendRawString(text);
|
|
|
|
line.replaceNumber(static_cast<int>(damage)); //no more text afterwards
|
2017-07-20 06:08:49 +02:00
|
|
|
log.push_back(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
MetaString line;
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendLocalString(EMetaText::GENERAL_TXT, 376); // Spell %s does %d damage
|
|
|
|
line.replaceLocalString(EMetaText::SPELL_NAME, m->getSpellIndex());
|
|
|
|
line.replaceNumber(static_cast<int>(damage));
|
2017-07-20 06:08:49 +02:00
|
|
|
|
|
|
|
log.push_back(line);
|
|
|
|
}
|
|
|
|
|
2023-01-07 19:52:57 +02:00
|
|
|
if (kills > 0)
|
2017-07-20 06:08:49 +02:00
|
|
|
{
|
|
|
|
MetaString line;
|
|
|
|
|
|
|
|
if(kills > 1)
|
2023-01-07 19:52:57 +02:00
|
|
|
{
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendLocalString(EMetaText::GENERAL_TXT, 379); // %d %s perishes
|
|
|
|
line.replaceNumber(kills);
|
2017-07-20 06:08:49 +02:00
|
|
|
|
|
|
|
if(multiple)
|
2023-06-18 11:18:25 +02:00
|
|
|
line.replaceLocalString(EMetaText::GENERAL_TXT, 43); // creatures
|
2017-07-20 06:08:49 +02:00
|
|
|
else
|
|
|
|
firstTarget->addNameReplacement(line, true);
|
|
|
|
}
|
2023-01-07 19:52:57 +02:00
|
|
|
else // single creature killed
|
2017-07-20 06:08:49 +02:00
|
|
|
{
|
2023-06-18 11:18:25 +02:00
|
|
|
line.appendLocalString(EMetaText::GENERAL_TXT, 378); // one %s perishes
|
2017-07-20 06:08:49 +02:00
|
|
|
if(multiple)
|
2023-06-18 11:18:25 +02:00
|
|
|
line.replaceLocalString(EMetaText::GENERAL_TXT, 42); // creature
|
2017-07-20 06:08:49 +02:00
|
|
|
else
|
|
|
|
firstTarget->addNameReplacement(line, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
log.push_back(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2022-07-26 15:07:42 +02:00
|
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_END
|