diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 4a84ac176..c4e93477c 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1323,12 +1323,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) } //displaying message in console - std::vector logLines; - - spell.prepareBattleLog(curInt->cb.get(), sc, logLines); - - for(auto line : logLines) - console->addText(line); + for(const auto & line : sc->battleLog) + if(!console->addText(line.toString())) + logGlobal->warn("Too long battle log line"); waitForAnims(); //mana absorption diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 7a8371312..1af318ea7 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -1243,6 +1243,11 @@ const PlayerColor CStack::getOwner() const return owner; } +void CStack::getCasterName(MetaString & text) const +{ + //always plural name in case of spell cast. + text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num); +} bool CMP_stack::operator()( const CStack* a, const CStack* b ) { diff --git a/lib/BattleState.h b/lib/BattleState.h index 31db8f997..26b70b7d5 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -249,6 +249,8 @@ public: const PlayerColor getOwner() const override; + void getCasterName(MetaString & text) const override; + ///stack will be ghost in next battle state update void makeGhost(); diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 58d9b34bb..ef0e8ca7e 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1492,7 +1492,6 @@ struct BattleSpellCast : public CPackForClient//3009 DLL_LINKAGE void applyGs(CGameState *gs); void applyCl(CClient *cl); - si32 dmgToDisplay; //this amount will be displayed as amount of damage dealt by spell ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender ui32 id; //id of spell ui8 skill; //caster's skill level @@ -1502,9 +1501,12 @@ struct BattleSpellCast : public CPackForClient//3009 std::set affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure) si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID bool castByHero; //if true - spell has been cast by hero, otherwise by a creature + + std::vector battleLog; template void serialize(Handler &h, const int version) { - h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero; + h & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero; + h & battleLog; } }; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index e66a62677..2f366740e 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -953,6 +953,13 @@ const PlayerColor CGHeroInstance::getOwner() const return tempOwner; } +void CGHeroInstance::getCasterName(MetaString & text) const +{ + //FIXME: use local name, MetaString need access to gamestate as hero name is part of map object + + text.addReplacement(name); +} + bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const { if(nullptr == getArt(ArtifactPosition::SPELLBOOK)) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index fb8c1256c..9922ffcbd 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -246,6 +246,8 @@ public: const PlayerColor getOwner() const override; + void getCasterName(MetaString & text) const override; + void deserializationFix(); void initObj() override; diff --git a/lib/spells/CDefaultSpellMechanics.cpp b/lib/spells/CDefaultSpellMechanics.cpp index 088430a51..aa8c703ef 100644 --- a/lib/spells/CDefaultSpellMechanics.cpp +++ b/lib/spells/CDefaultSpellMechanics.cpp @@ -119,13 +119,12 @@ namespace SRSLPraserHelpers } SpellCastContext::SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_): - mechanics(mechanics_), env(env_), attackedCres(), sc(), si(), parameters(parameters_), otherHero(nullptr), spellCost(0) + mechanics(mechanics_), env(env_), attackedCres(), sc(), si(), parameters(parameters_), otherHero(nullptr), spellCost(0), damageToDisplay(0) { sc.side = parameters.casterSide; sc.id = mechanics->owner->id; sc.skill = parameters.spellLvl; sc.tile = parameters.getFirstDestinationHex(); - sc.dmgToDisplay = 0; sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING; sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1); sc.manaGained = 0; @@ -146,22 +145,63 @@ SpellCastContext::~SpellCastContext() void SpellCastContext::addDamageToDisplay(const si32 value) { - sc.dmgToDisplay += value; + damageToDisplay += value; } void SpellCastContext::setDamageToDisplay(const si32 value) { - sc.dmgToDisplay = value; + damageToDisplay = value; } -void SpellCastContext::sendCastPacket() +void SpellCastContext::prepareBattleLog() { - for(auto sta : attackedCres) + //todo: prepare battle log + bool displayDamage = true; + + if(attackedCres.size() == 1) { - sc.affectedCres.insert(sta->ID); + const CStack * attackedStack = *attackedCres.begin(); + + switch(parameters.mode) + { + case ECastingMode::HERO_CASTING: + { + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, 195); + parameters.caster->getCasterName(line); + line.addReplacement(MetaString::SPELL_NAME, mechanics->owner->id.toEnum()); + line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num); + } + break; + + default: + { + mechanics->battleLogSingleTarget(sc.battleLog, parameters, attackedStack, damageToDisplay, displayDamage); + } + break; + } + } + else + { + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, 196); + parameters.caster->getCasterName(line); + line.addReplacement(MetaString::SPELL_NAME, mechanics->owner->id.toEnum()); + sc.battleLog.push_back(line); } - env->sendAndApply(&sc); + displayDamage = displayDamage && damageToDisplay > 0; + + if(displayDamage) + { + MetaString line; + + line.addTxt(MetaString::GENERAL_TXT, 376); + line.addReplacement(MetaString::SPELL_NAME, mechanics->owner->id.toEnum()); + line.addReplacement(damageToDisplay); + + sc.battleLog.push_back(line); + } } void SpellCastContext::beforeCast() @@ -190,7 +230,14 @@ void SpellCastContext::beforeCast() void SpellCastContext::afterCast() { - sendCastPacket(); + for(auto sta : attackedCres) + { + sc.affectedCres.insert(sta->ID); + } + + prepareBattleLog(); + + env->sendAndApply(&sc); if(parameters.mode == ECastingMode::HERO_CASTING) { @@ -312,33 +359,32 @@ void DefaultSpellMechanics::cast(const SpellCastEnvironment * env, const BattleS ctx.afterCast(); } -void DefaultSpellMechanics::battleLogSingleTarget(std::vector & logLines, const BattleSpellCast * packet, - const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const +void DefaultSpellMechanics::battleLogSingleTarget(std::vector& logLines, const BattleSpellCastParameters & parameters, + const CStack * attackedStack, const si32 damageToDisplay, bool & displayDamage) const { - const std::string attackedName = attackedStack->getName(); - const std::string attackedNameSing = attackedStack->getCreature()->nameSing; - const std::string attackedNamePl = attackedStack->getCreature()->namePl; - - auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format + auto getPluralFormat = [attackedStack](const int baseTextID) -> si32 { - return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]); + return attackedStack->count > 1 ? baseTextID + 1 : baseTextID; }; - auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID) + auto logSimple = [attackedStack, &logLines, getPluralFormat](const int baseTextID) { - boost::format fmt = getPluralFormat(baseTextID); - fmt % attackedName; - logLines.push_back(fmt.str()); + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(baseTextID)); + line.addReplacement(*attackedStack); + logLines.push_back(line); }; - auto logPlural = [&logLines, attackedNamePl](const int baseTextID) + auto logPlural = [attackedStack, &logLines, getPluralFormat](const int baseTextID) { - boost::format fmt(VLC->generaltexth->allTexts[baseTextID]); - fmt % attackedNamePl; - logLines.push_back(fmt.str()); + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, baseTextID); + line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num); + logLines.push_back(line); }; displayDamage = false; //in most following cases damage info text is custom + switch(owner->id) { case SpellID::STONE_GAZE: @@ -358,52 +404,61 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector & log break; case SpellID::AGE: { - boost::format text = getPluralFormat(551); - text % attackedName; //The %s shrivel with age, and lose %d hit points." + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(551)); + line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num); + + //todo: display effect from only this cast TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH)); const int fullHP = bl->totalValue(); bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE)); - text % (fullHP - bl->totalValue()); - logLines.push_back(text.str()); + line.addReplacement(fullHP - bl->totalValue()); + logLines.push_back(line); } break; case SpellID::THUNDERBOLT: { logPlural(367); + MetaString line; + //todo: handle newlines in metastring std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage. - boost::algorithm::replace_first(text, "%d", boost::lexical_cast(packet->dmgToDisplay)); //no more text afterwards - logLines.push_back(text); + line << text; + line.addReplacement(damageToDisplay); //no more text afterwards + logLines.push_back(line); } break; case SpellID::DISPEL_HELPFUL_SPELLS: logPlural(555); break; case SpellID::DEATH_STARE: - if (packet->dmgToDisplay > 0) + if (damageToDisplay > 0) { - std::string text; - if (packet->dmgToDisplay > 1) + MetaString line; + if (damageToDisplay > 1) { - text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s. - boost::algorithm::replace_first(text, "%d", boost::lexical_cast(packet->dmgToDisplay)); - boost::algorithm::replace_first(text, "%s", attackedNamePl); + line.addTxt(MetaString::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s. + line.addReplacement(damageToDisplay); + line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num); } else { - text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s. - boost::algorithm::replace_first(text, "%s", attackedNameSing); + line.addTxt(MetaString::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s. + line.addReplacement(MetaString::CRE_SING_NAMES, attackedStack->getCreature()->idNumber.num); } - boost::algorithm::replace_first(text, "%s", casterName); //casting stack - logLines.push_back(text); + parameters.caster->getCasterName(line); + logLines.push_back(line); } break; default: { - boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s - text % casterName % owner->name; + MetaString line; + line.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s + //todo: use text 566 for single creature + parameters.caster->getCasterName(line); + line.addReplacement(MetaString::SPELL_NAME, owner->id.toEnum()); displayDamage = true; - logLines.push_back(text.str()); + logLines.push_back(line); } break; } diff --git a/lib/spells/CDefaultSpellMechanics.h b/lib/spells/CDefaultSpellMechanics.h index 3ec864f94..1103c531b 100644 --- a/lib/spells/CDefaultSpellMechanics.h +++ b/lib/spells/CDefaultSpellMechanics.h @@ -36,8 +36,9 @@ public: private: const CGHeroInstance * otherHero; int spellCost; + si32 damageToDisplay; - void sendCastPacket(); + void prepareBattleLog(); }; ///all combat spells @@ -58,8 +59,8 @@ public: void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const override final; - void battleLogSingleTarget(std::vector & logLines, const BattleSpellCast * packet, - const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override; + void battleLogSingleTarget(std::vector & logLines, const BattleSpellCastParameters & parameters, + const CStack * attackedStack, const si32 damageToDisplay, bool & displayDamage) const; bool requiresCreatureTarget() const override; protected: diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 0c9767771..f85594c79 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -586,59 +586,6 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster return ESpellCastProblem::OK; } -void CSpell::prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector & logLines) const -{ - bool displayDamage = true; - - std::string casterName("Something"); //todo: localize - - if(packet->castByHero) - casterName = cb->battleGetHeroInfo(packet->side).name; - - { - const auto casterStackID = packet->casterStack; - - if(casterStackID > 0) - { - const CStack * casterStack = cb->battleGetStackByID(casterStackID); - if(casterStack != nullptr) - casterName = casterStack->type->namePl; - } - } - - if(packet->affectedCres.size() == 1) - { - const CStack * attackedStack = cb->battleGetStackByID(*packet->affectedCres.begin(), false); - - const std::string attackedNamePl = attackedStack->getCreature()->namePl; - - if(packet->castByHero) - { - const std::string fmt = VLC->generaltexth->allTexts[195]; - logLines.push_back(boost::to_string(boost::format(fmt) % casterName % this->name % attackedNamePl)); - } - else - { - mechanics->battleLogSingleTarget(logLines, packet, casterName, attackedStack, displayDamage); - } - } - else - { - boost::format text(VLC->generaltexth->allTexts[196]); - text % casterName % this->name; - logLines.push_back(text.str()); - } - - - if(packet->dmgToDisplay > 0 && displayDamage) - { - boost::format dmgInfo(VLC->generaltexth->allTexts[376]); - dmgInfo % this->name % packet->dmgToDisplay; - logLines.push_back(dmgInfo.str()); - } -} - - void CSpell::setIsOffensive(const bool val) { isOffensive = val; diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 30463b8ef..aeb7c2c1a 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -287,10 +287,6 @@ public: ///implementation of BattleSpellCast applying void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const; - -public://Client logic. - void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector & logLines) const; - public://internal, for use only by Mechanics classes ///applies caster`s secondary skills and affectedCreature`s to raw damage int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const; diff --git a/lib/spells/CreatureSpellMechanics.cpp b/lib/spells/CreatureSpellMechanics.cpp index eba0ba447..7c545c7be 100644 --- a/lib/spells/CreatureSpellMechanics.cpp +++ b/lib/spells/CreatureSpellMechanics.cpp @@ -58,12 +58,12 @@ ESpellCastProblem::ESpellCastProblem AcidBreathDamageMechanics::isImmuneByStack( void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const { //calculating dmg to display - si32 dmgToDisplay = parameters.effectPower; + si32 damageToDisplay = parameters.effectPower; if(!ctx.attackedCres.empty()) - vstd::amin(dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack + vstd::amin(damageToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack - ctx.setDamageToDisplay(dmgToDisplay); + ctx.setDamageToDisplay(damageToDisplay); for(auto & attackedCre : ctx.attackedCres) { diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 811e8c740..6636c836e 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -114,9 +114,6 @@ public: virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0; virtual void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const = 0; - virtual void battleLogSingleTarget(std::vector & logLines, const BattleSpellCast * packet, - const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0; - //if true use generic algorithm for target existence check, see CSpell::canBeCast virtual bool requiresCreatureTarget() const = 0; diff --git a/lib/spells/Magic.h b/lib/spells/Magic.h index 5055fef35..0d93047db 100644 --- a/lib/spells/Magic.h +++ b/lib/spells/Magic.h @@ -18,6 +18,7 @@ class CSpell; class CStack; class PlayerColor; +struct MetaString; class DLL_LINKAGE ISpellCaster { @@ -45,4 +46,6 @@ public: virtual int getEffectValue(const CSpell * spell) const = 0; virtual const PlayerColor getOwner() const = 0; + + virtual void getCasterName(MetaString & text) const = 0; };