1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Spells configuration version 2 (effect-based)

* Indirect spell effects loading
* Json serializer improvements
* spell->canBeCastAt do not allow useless cast for any spell
* Added proxy caster class for spell-created obstacles
* Handle damage from spell-created obstacles inside mechanics
* Experimental GameState integration/regression tests
* Ignore mod settings and load only "vcmi" mod when running tests
* fixed https://bugs.vcmi.eu/view.php?id=2765 (with tests)
* Huge improvements of BattleAI regarding spell casts
* AI can cast almost any combat spell except TELEPORT, SACRIFICE and obstacle placement spells.
* Possible fix for https://bugs.vcmi.eu/view.php?id=1811
* CStack factored out to several classes
* [Battle] Allowed RETURN_AFTER_STRIKE effect on server side to be optional
* [Battle] Allowed BattleAction have multiple destinations
* [Spells] Converted limit|immunity to target condition
* [Spells] Use partial configuration reload for backward compatibility handling
* [Tests] Started tests for CUnitState
* Partial fixes of fire shield effect
* [Battle] Do HP calculations in 64 bits
* [BattleAI] Use threading for spell cast evaluation
* [BattleAI] Made AI be able to evaluate modified turn order (on hypothetical battle state)
* Implemented https://bugs.vcmi.eu/view.php?id=2811
* plug rare freeze when hypnotized unit shots vertically
* Correctly apply ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT for unit damage, attack & defense
* [BattleAI] Try to not waste a cast if battle is actually won already
* Extended JsonSerializeFormat API
* fixed https://bugs.vcmi.eu/view.php?id=2847
* Any unit effect can be now chained (not only damage like Chain Lightning)
** only damage effect for now actually uses "chainFactor"
* Possible quick fix for https://bugs.vcmi.eu/view.php?id=2860
This commit is contained in:
AlexVinS
2017-07-20 07:08:49 +03:00
parent ff2d01a03d
commit 0b70baa95e
256 changed files with 20904 additions and 7964 deletions

View File

@@ -34,14 +34,13 @@
#include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/spells/Problem.h"
#include "../../lib/GameConstants.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner)
{
@@ -125,7 +124,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
++sitesPerOurTab[4];
spell->forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop)
{
++sitesPerOurTab[(ui8)school.id];
});
@@ -536,101 +535,46 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana));
return;
}
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
if((mySpell->isCombatSpell() && !owner->myInt->battleInt)
|| (mySpell->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt)))
//anything that is not combat spell is adventure spell
//this not an error in general to cast even creature ability with hero
const bool combatSpell = mySpell->isCombatSpell();
if(mySpell->isCombatSpell() != !mySpell->isAdventureSpell())
{
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0));
owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
logGlobal->error("Spell have invalid flags");
return;
}
//we will cast a spell
if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
const bool inCombat = owner->myInt->battleInt != nullptr;
const bool inCastle = owner->myInt->castleInt != nullptr;
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
if((combatSpell ^ inCombat) || inCastle)
{
ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero);
switch (problem)
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0));
owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
}
else if(combatSpell)
{
spells::detail::ProblemImpl problem;
if(mySpell->canBeCast(problem, owner->myInt->cb.get(), spells::Mode::HERO, owner->myHero))
{
case ESpellCastProblem::OK:
{
owner->myInt->battleInt->castThisSpell(mySpell->id);
owner->fexitb();
return;
}
break;
case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
{
std::string text = CGI->generaltexth->allTexts[538], elemental, caster;
const PlayerColor player = owner->myInt->playerID;
const TStacks stacks = owner->myInt->cb->battleGetStacksIf([player](const CStack * s)
{
return s->owner == player
&& vstd::contains(s->state, EBattleStackState::SUMMONED)
&& !s->isClone();
});
for(const CStack * s : stacks)
{
elemental = s->getCreature()->namePl;
}
if (owner->myHero->type->sex)
{ //female
caster = CGI->generaltexth->allTexts[540];
}
else
{ //male
caster = CGI->generaltexth->allTexts[539];
}
std::string summoner = owner->myHero->name;
text = boost::str(boost::format(text) % summoner % elemental % caster);
owner->myInt->showInfoDialog(text);
}
break;
case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
{
//Recanter's Cloak or similar effect. Try to retrieve bonus
const auto b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
//TODO what about other values and non-artifact sources?
if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
{
std::string artName = CGI->arth->artifacts[b->sid]->Name();
//The %s prevents %s from casting 3rd level or higher spells.
owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536])
% artName % owner->myHero->name));
}
else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND)
{
owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[537]);
}
else
{
// General message:
// %s recites the incantations but they seem to have no effect.
std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
text = boost::str(boost::format(text) % caster);
owner->myInt->showInfoDialog(text);
}
}
break;
case ESpellCastProblem::NO_APPROPRIATE_TARGET:
{
owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]);
}
break;
default:
{
// General message:
std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
text = boost::str(boost::format(text) % caster);
owner->myInt->showInfoDialog(text);
}
owner->myInt->battleInt->castThisSpell(mySpell->id);
owner->fexitb();
}
else
{
std::vector<std::string> texts;
problem.getAll(texts);
if(!texts.empty())
owner->myInt->showInfoDialog(texts.front());
else
owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available.");
}
}
else if(mySpell->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle
else //adventure spell
{
const CGHeroInstance *h = owner->myHero;
const CGHeroInstance * h = owner->myHero;
GH.popInt(owner);
auto guard = vstd::makeScopeGuard([this]()
@@ -639,9 +583,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
});
if(mySpell->getTargetType() == CSpell::LOCATION)
if(mySpell->getTargetType() == spells::AimType::LOCATION)
adventureInt->enterCastingMode(mySpell);
else if(mySpell->getTargetType() == CSpell::NO_TARGET)
else if(mySpell->getTargetType() == spells::AimType::NO_TARGET)
owner->myInt->cb->castSpell(h, mySpell->id);
else
logGlobal->error("Invalid spell target type");
@@ -654,7 +598,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
if(mySpell && down)
{
std::string dmgInfo;
int causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero);
auto causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero);
if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included
dmgInfo = "";
else