1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-04 23:17:41 +02:00

[Spells] More spell related refactoring

+ smart target modifier
- CREATURE_EXPERT_MASSIVE target type
* save format changed

spell format changes already documented in http://wiki.vcmi.eu/index.php?title=Spell_Format
This commit is contained in:
alexvins 2014-03-17 13:11:10 +00:00
parent 7f6f125b4c
commit 9cac0af7be
11 changed files with 806 additions and 551 deletions

View File

@ -1687,13 +1687,13 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState) void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState)
{ {
if(down) if(down)
LOCPLINT->showInfoDialog(spell->descriptions[0], new CComponent(CComponent::spell,spell->id)); LOCPLINT->showInfoDialog(spell->getLevelInfo(0).description, new CComponent(CComponent::spell,spell->id));
} }
void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState) void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
{ {
if(down) if(down)
CRClickPopup::createAndPush(spell->descriptions[0], new CComponent(CComponent::spell, spell->id)); CRClickPopup::createAndPush(spell->getLevelInfo(0).description, new CComponent(CComponent::spell, spell->id));
} }
void CMageGuildScreen::Scroll::hover(bool on) void CMageGuildScreen::Scroll::hover(bool on)

View File

@ -630,7 +630,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
|| (!sp->combatSpell && owner->myInt->battleInt)) || (!sp->combatSpell && owner->myInt->battleInt))
{ {
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0)); std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0));
LOCPLINT->showInfoDialog(sp->descriptions[schoolLevel], hlp); LOCPLINT->showInfoDialog(sp->getLevelInfo(schoolLevel).description, hlp);
return; return;
} }
@ -810,7 +810,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(causedDmg)); boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(causedDmg));
} }
CRClickPopup::createAndPush(CGI->spellh->objects[mySpell]->descriptions[schoolLevel] + dmgInfo, CRClickPopup::createAndPush(CGI->spellh->objects[mySpell]->getLevelInfo(schoolLevel).description + dmgInfo,
new CComponent(CComponent::spell, mySpell)); new CComponent(CComponent::spell, mySpell));
} }
} }

View File

@ -977,7 +977,7 @@ std::string CComponent::getDescription()
case creature: return ""; case creature: return "";
case artifact: return CGI->arth->artifacts[subtype]->Description(); case artifact: return CGI->arth->artifacts[subtype]->Description();
case experience: return CGI->generaltexth->allTexts[241]; case experience: return CGI->generaltexth->allTexts[241];
case spell: return CGI->spellh->objects[subtype]->descriptions[val]; case spell: return CGI->spellh->objects[subtype]->getLevelInfo(val).description;
case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)]; case morale: return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)]; case luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description(); case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description();

View File

@ -1549,31 +1549,23 @@ void CBattleInterface::castThisSpell(int spellID)
assert(castingHero); // code below assumes non-null hero assert(castingHero); // code below assumes non-null hero
sp = CGI->spellh->objects[spellID]; sp = CGI->spellh->objects[spellID];
spellSelMode = ANY_LOCATION; spellSelMode = ANY_LOCATION;
if(sp->getTargetType() == CSpell::CREATURE)
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
if(ti.massive)
spellSelMode = NO_LOCATION;
else if(ti.type == CSpell::CREATURE)
{ {
spellSelMode = selectionTypeByPositiveness(*sp); if(ti.smart)
}
if(sp->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
{
if(castingHero->getSpellSchoolLevel(sp) < 3)
spellSelMode = selectionTypeByPositiveness(*sp); spellSelMode = selectionTypeByPositiveness(*sp);
else else
spellSelMode = NO_LOCATION; spellSelMode = ANY_CREATURE;
} }
if(sp->getTargetType() == CSpell::OBSTACLE) else if(ti.type == CSpell::OBSTACLE)
{ {
spellSelMode = OBSTACLE; spellSelMode = OBSTACLE;
} //FIXME: Remove Obstacle has range X, unfortunatelly :( }
else if(sp->range[ castingHero->getSpellSchoolLevel(sp) ] == "X") //spell has no range //todo: move to JSON config
{
spellSelMode = NO_LOCATION;
}
if(sp->range[ castingHero->getSpellSchoolLevel(sp) ].size() > 1) //spell has many-hex range
{
spellSelMode = ANY_LOCATION;
}
if(spellID == SpellID::FIRE_WALL || spellID == SpellID::FORCE_FIELD) if(spellID == SpellID::FIRE_WALL || spellID == SpellID::FORCE_FIELD)
{ {
spellSelMode = FREE_LOCATION; spellSelMode = FREE_LOCATION;

View File

@ -1,4 +1,4 @@
{ {
"type":"object", "type":"object",
"$schema": "http://json-schema.org/draft-04/schema", "$schema": "http://json-schema.org/draft-04/schema",
@ -7,235 +7,241 @@
"description" : "Format used to define new spells in VCMI", "description" : "Format used to define new spells in VCMI",
"definitions" : { "definitions" : {
"flags" :{ "flags" :{
"type" : "object", "type" : "object",
"additionalProperties" : { "additionalProperties" : {
"type":"boolean" "type":"boolean"
} }
}, },
"levelInfo":{ "levelInfo":{
"type": "object", "type": "object",
"required":["range","description","cost","power","aiValue","range"], "required":["range","description","cost","power","aiValue","range"],
"additionalProperties" : false, "additionalProperties" : false,
"properties":{ "properties":{
"description":{ "description":{
"type": "string", "type": "string",
"description": "Localizable description. Use {xxx} for formatting" "description": "Localizable description. Use {xxx} for formatting"
}, },
"cost":{ "cost":{
"type": "number", "type": "number",
"description":"Cost in mana points" "description":"Cost in mana points"
}, },
"power":{ "power":{
"type": "number", "type": "number",
}, },
"aiValue":{ "aiValue":{
"type": "number", "type": "number",
}, },
"range":{ "range":{
"type": "string", "type": "string",
"description": "spell range description in SRSL" "description": "spell range description in SRSL"
}, },
"effects":{ "effects":{
"type": "object", "type": "object",
"description": "Timed effects", "description": "Timed effects",
"additionalProperties" : { "additionalProperties" : {
"$ref" : "vcmi:bonus" "$ref" : "vcmi:bonus"
} }
} },
"targetModifier":{
"type": "object",
"additionalProperties": false,
"properties":{
"smart":{
"type": "boolean",
"description": "true: friendly/hostile based on positiveness; false: all targets"
}
}
}
}
}
},
} "required" : ["type", "name", "school", "level", "power","gainChance","flags","levels"],
} "additionalProperties" : false,
},
"required" : ["type", "name", "school", "level", "power","gainChance","flags","levels"], "properties": {
"additionalProperties" : false, "index":{
"type": "number",
"description": "numeric id of spell required only for original spells, prohibited for new spells"
},
"type":{
"type": "string",
"enum": ["adventure", "combat", "ability"],
"description":"Spell type"
},
"name":{
"type": "string",
"description": "Localizable name"
"properties": { },
"index":{ "school":{
"type": "number", "type": "object",
"description": "numeric id of spell required only for original spells, prohibited for new spells" "description": "Spell schools",
}, "additionalProperties": false,
"type":{
"type": "string",
"enum": ["adventure", "combat", "ability"],
"description":"Spell type"
},
"name":{
"type": "string",
"description": "Localizable name"
}, "properties":{
"school":{
"type": "object",
"description": "Spell schools",
"additionalProperties": false,
"properties":{ "air":{"type": "boolean"},
"fire":{"type": "boolean"},
"earth":{"type": "boolean"},
"water":{"type": "boolean"}
}
"air":{"type": "boolean"}, },
"fire":{"type": "boolean"}, "level":{
"earth":{"type": "boolean"}, "type": "number",
"water":{"type": "boolean"} "description": "Spell level",
} "minimum" : 0,
"maximum" : 5
},
}, "power":{
"level":{ "type": "number",
"type": "number", "description": "Base power",
"description": "Spell level", },
"minimum" : 0,
"maximum" : 5
},
"power":{ "defaultGainChance":{
"type": "number", "type": "number",
"description": "Base power", "description": "Gain chance by default for all factions"
},
"defaultGainChance":{ },
"type": "number",
"description": "Gain chance by default for all factions"
}, "gainChance":{
"type": "object",
"description": "Chance in % to gain for faction. NOTE: this field is merged with faction config",
"additionalProperties" : {
"type": "number",
"minimum" : 0
}
},
"targetType":{
"type": "string",
"enum": ["NO_TARGET","CREATURE","OBSTACLE"]
},
"anim":{
"type": "number",
"description": "Main effect animation (AC format), -1 - none, deprecated",
"minimum": -1
},
"counters":{
"$ref" : "#/definitions/flags",
"description": "Flags structure ids of countering spells"
},
"flags":{
"type": "object",
"description": "Various flags",
"additionalProperties" : false,
"properties":{
"indifferent": {
"type":"boolean",
"description": "Spell is indifferent for target"
},
"negative": {
"type":"boolean",
"description": "Spell is negative for target"
},
"positive": {
"type":"boolean",
"description": "Spell is positive for target"
},
"damage": {
"type":"boolean",
"description": "Spell does damage (direct or indirect)"
},
"offensive": {
"type":"boolean",
"description": "Spell does direct damage (implicitly sets damage and negative)"
},
"rising":{
"type":"boolean",
"description": "Rising spell (implicitly sets positive)"
},
"special":{
"type": "boolean",
"description": "Special spell. Can be given only by Bonus::SPELL"
}
}
},
"immunity":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, any one of these bonus grants immunity"
},
"absoluteImmunity":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names. Any one of these bonus grants immunity, cant be negated"
},
"limit":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, presence of all bonuses required to be affected by"
},
"gainChance":{ "graphics":{
"type": "object", "type": "object",
"description": "Chance in % to gain for faction. NOTE: this field is merged with faction config", "additionalProperties" : false,
"additionalProperties" : { "properties":{
"type": "number", "iconImmune":{
"minimum" : 0 "type": "string",
} "description": "Resourse path of icon for SPELL_IMMUNITY bonus (relative to DATA or SPRITES)",
}, "format" : "imageFile"
"targetType":{ },
"type": "string", "iconScenarioBonus":{
"enum": ["NO_TARGET","CREATURE","OBSTACLE","CREATURE_EXPERT_MASSIVE"] "type": "string",
}, "description": "Resourse path of icon for scenario bonus" ,
"anim":{ "format" : "imageFile"
"type": "number", },
"description": "Main effect animation (AC format), -1 - none, deprecated", "iconEffect":{
"minimum": -1 "type": "string",
}, "description": "Resourse path of icon for spell effects during battle" ,
"counters":{ "format" : "imageFile"
"$ref" : "#/definitions/flags", },
"description": "Flags structure ids of countering spells" "iconBook":{
}, "type": "string",
"flags":{ "description":"Resourse path of icon for spellbook" ,
"type": "object", "format" : "imageFile"
"description": "Various flags", },
"additionalProperties" : false, "iconScroll":{
"properties":{ "type": "string",
"indifferent": { "description": "Resourse path of icon for spell scrolls",
"type":"boolean", "format": "imageFile"
"description": "Spell is indifferent for target" }
}, }
"negative": {
"type":"boolean",
"description": "Spell is negative for target"
},
"positive": {
"type":"boolean",
"description": "Spell is positive for target"
},
"damage": {
"type":"boolean",
"description": "Spell does damage (direct or indirect)"
},
"offensive": {
"type":"boolean",
"description": "Spell does direct damage (implicitly sets damage and negative)"
},
"rising":{
"type":"boolean",
"description": "Rising spell (implicitly sets positive)"
},
"special":{
"type": "boolean",
"description": "Special spell. Can be given only by Bonus::SPELL"
}
}
},
"immunity":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, any one of these bonus grants immunity"
},
"absoluteImmunity":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names. Any one of these bonus grants immunity, cant be negated"
},
"limit":{
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, presence of all bonuses required to be affected by"
},
"graphics":{ },
"type": "object",
"additionalProperties" : false,
"properties":{
"iconImmune":{
"type": "string",
"description": "Resourse path of icon for SPELL_IMMUNITY bonus (relative to DATA or SPRITES)",
"format" : "imageFile"
},
"iconScenarioBonus":{
"type": "string",
"description": "Resourse path of icon for scenario bonus" ,
"format" : "imageFile"
},
"iconEffect":{
"type": "string",
"description": "Resourse path of icon for spell effects during battle" ,
"format" : "imageFile"
},
"iconBook":{
"type": "string",
"description":"Resourse path of icon for spellbook" ,
"format" : "imageFile"
},
"iconScroll":{
"type": "string",
"description": "Resourse path of icon for spell scrolls",
"format": "imageFile"
}
}
}, "sounds":{
"type": "object",
"additionalProperties" : false,
"properties":{
"cast":{
"type": "string",
"description": "Resourse path of cast sound"
}
}
},
"sounds":{ "levels":{
"type": "object", "type": "object",
"additionalProperties" : false, "additionalProperties" : false,
"properties":{ "required" : ["none", "basic", "advanced", "expert"],
"cast":{
"type": "string",
"description": "Resourse path of cast sound"
}
}
},
"levels":{ "properties":{
"type": "object", "none":{
"additionalProperties" : false, "$ref" : "#/definitions/levelInfo"
"required" : ["none", "basic", "advanced", "expert"], },
"basic":{
"properties":{ "$ref" : "#/definitions/levelInfo"
"none":{ },
"$ref" : "#/definitions/levelInfo" "advanced":{
}, "$ref" : "#/definitions/levelInfo"
"basic":{ },
"$ref" : "#/definitions/levelInfo" "expert":{
}, "$ref" : "#/definitions/levelInfo"
"advanced":{ }
"$ref" : "#/definitions/levelInfo" }
}, }
"expert":{ }
"$ref" : "#/definitions/levelInfo" }
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1617,10 +1617,13 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
if (spell->isRisingSpell()) if (spell->isRisingSpell())
{ {
if (caster) //TODO: what with Archangels? if(subject->count >= subject->baseAmount)
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
if (caster) //FIXME: Archangels can cast immune stack
{ {
auto maxHealth = calculateHealedHP (caster, spell, subject); auto maxHealth = calculateHealedHP (caster, spell, subject);
if (subject->count >= subject->baseAmount || maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
} }
} }
@ -1637,13 +1640,20 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
} }
else //no target stack on this tile else //no target stack on this tile
{ {
if(spell->getTargetType() == CSpell::CREATURE if(spell->getTargetType() == CSpell::CREATURE)
|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE
&& mode == ECastingMode::HERO_CASTING //TODO why???
&& caster
&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
{ {
return ESpellCastProblem::WRONG_SPELL_TARGET; if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
{
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
if(!ti.massive)
return ESpellCastProblem::WRONG_SPELL_TARGET;
}
else
{
return ESpellCastProblem::WRONG_SPELL_TARGET;
}
} }
} }
@ -1688,6 +1698,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
{ {
bool allStacksImmune = true; bool allStacksImmune = true;
//we are interested only in enemy stacks when casting offensive spells //we are interested only in enemy stacks when casting offensive spells
//TODO: should hero be able to cast non-smart negative spell if all enemy stacks are immune?
auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks(); auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
for(auto stack : stacks) for(auto stack : stacks)
{ {
@ -1726,43 +1737,38 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
switch(spell->getTargetType()) switch(spell->getTargetType())
{ {
case CSpell::CREATURE: case CSpell::CREATURE:
case CSpell::CREATURE_EXPERT_MASSIVE:
if(mode == ECastingMode::HERO_CASTING) if(mode == ECastingMode::HERO_CASTING)
{ {
const CGHeroInstance * caster = battleGetFightingHero(side); const CGHeroInstance * caster = battleGetFightingHero(side);
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
bool targetExists = false; bool targetExists = false;
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
{ {
switch (spell->positiveness) bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
{ bool casterStack = stack->owner == caster->getOwner();
case CSpell::POSITIVE:
if(stack->owner == caster->getOwner()) if(!immune)
switch (spell->positiveness)
{ {
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) case CSpell::POSITIVE:
if(casterStack || !ti.smart)
{
targetExists = true;
break;
}
break;
case CSpell::NEUTRAL:
targetExists = true;
break;
break;
case CSpell::NEGATIVE:
if(!casterStack || !ti.smart)
{ {
targetExists = true; targetExists = true;
break; break;
} }
}
break;
case CSpell::NEUTRAL:
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
{
targetExists = true;
break; break;
} }
break;
case CSpell::NEGATIVE:
if(stack->owner != caster->getOwner())
{
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
{
targetExists = true;
break;
}
}
break;
}
} }
if(!targetExists) if(!targetExists)
{ {
@ -1787,31 +1793,32 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
switch(spell->getTargetType()) switch(spell->getTargetType())
{ {
case CSpell::CREATURE: case CSpell::CREATURE:
case CSpell::CREATURE_EXPERT_MASSIVE:
{ {
const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
for(const CStack * stack : battleAliveStacks()) for(const CStack * stack : battleAliveStacks())
{ {
switch (spell->positiveness) bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
{ bool casterStack = stack->owner == caster->getOwner();
case CSpell::POSITIVE:
if(stack->owner == caster->getOwner()) if(!immune)
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK) switch (spell->positiveness)
{
case CSpell::POSITIVE:
if(casterStack || ti.smart)
ret.push_back(stack->position); ret.push_back(stack->position);
break; break;
case CSpell::NEUTRAL: case CSpell::NEUTRAL:
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
ret.push_back(stack->position); ret.push_back(stack->position);
break; break;
case CSpell::NEGATIVE: case CSpell::NEGATIVE:
if(stack->owner != caster->getOwner()) if(!casterStack || ti.smart)
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
ret.push_back(stack->position); ret.push_back(stack->position);
break; break;
} }
} }
} }
break; break;
@ -1909,7 +1916,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
return ESpellCastProblem::NO_APPROPRIATE_TARGET; return ESpellCastProblem::NO_APPROPRIATE_TARGET;
} }
else if(spell->getTargetType() == CSpell::CREATURE || spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) else if(spell->getTargetType() == CSpell::CREATURE)
{ {
if(!aliveStack) if(!aliveStack)
return ESpellCastProblem::NO_APPROPRIATE_TARGET; return ESpellCastProblem::NO_APPROPRIATE_TARGET;
@ -2011,14 +2018,14 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide); const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide);
const bool onlyAlive = !spell->isRisingSpell(); //when casting resurrection or animate dead we should be allow to select dead stack const bool onlyAlive = !spell->isRisingSpell(); //when casting resurrection or animate dead we should be allow to select dead stack
const CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel);
//TODO: more generic solution for mass spells //TODO: more generic solution for mass spells
if(spell->id == SpellID::DEATH_RIPPLE || spell->id == SpellID::DESTROY_UNDEAD || spell->id == SpellID::ARMAGEDDON) if(spell->id == SpellID::DEATH_RIPPLE || spell->id == SpellID::DESTROY_UNDEAD )
{ {
for(const CStack *stack : battleGetAllStacks()) for(const CStack *stack : battleGetAllStacks())
{ {
if((spell->id == SpellID::DEATH_RIPPLE && !stack->getCreature()->isUndead()) //death ripple if((spell->id == SpellID::DEATH_RIPPLE && !stack->getCreature()->isUndead()) //death ripple
|| (spell->id == SpellID::DESTROY_UNDEAD && stack->getCreature()->isUndead()) //destroy undead || (spell->id == SpellID::DESTROY_UNDEAD && stack->getCreature()->isUndead()) //destroy undead
|| (spell->id == SpellID::ARMAGEDDON) //Armageddon
) )
{ {
if(stack->isValidTarget()) if(stack->isValidTarget())
@ -2057,7 +2064,7 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes); lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes);
} }
} }
else if (spell->range[skillLevel].size() > 1) //custom many-hex range else if (spell->getLevelInfo(skillLevel).range.size() > 1) //custom many-hex range
{ {
for(BattleHex hex : attackedHexes) for(BattleHex hex : attackedHexes)
{ {
@ -2075,33 +2082,34 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
} }
} }
} }
else if(spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE) else if(spell->getTargetType() == CSpell::CREATURE)
{ {
if(skillLevel < 3) /*not expert */ if (ti.massive)
{ {
const CStack * st = battleGetStackByPos(destinationTile, onlyAlive); TStacks stacks = battleGetAllStacks();
if(st)
attackedCres.insert(st); vstd::erase_if(stacks,[&](const CStack * stack){
return ti.smart && spell->isNegative() && stack->owner == attackerOwner);
});
vstd::erase_if(stacks,[&](const CStack * stack){
return ti.smart && spell->isPositive() && stack->owner != attackerOwner);
});
vstd::erase_if(stacks,[&](const CStack * stack){
return !stack->isValidTarget(!onlyAlive);
});
for (auto stack : stacks)
attackedCres.insert(stack);
} }
else else
{ {
for (auto stack : battleGetAllStacks()) if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive))
{ attackedCres.insert(st);
/*if it's non negative spell and our unit or non positive spell and hostile unit */ }
if((!spell->isNegative() && stack->owner == attackerOwner)
||(!spell->isPositive() && stack->owner != attackerOwner )
)
{
if(stack->isValidTarget(!onlyAlive))
attackedCres.insert(stack);
}
}
} //if(caster->getSpellSchoolLevel(s) < 3)
}
else if(spell->getTargetType() == CSpell::CREATURE)
{
if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive))
attackedCres.insert(st);
} }
else //custom range from attackedHexes else //custom range from attackedHexes
{ {

View File

@ -23,7 +23,7 @@
namespace SpellConfig namespace SpellConfig
{ {
static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"}; static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
} }
using namespace boost::assign; using namespace boost::assign;
@ -130,12 +130,21 @@ namespace SRSLPraserHelpers
} }
} }
using namespace SRSLPraserHelpers; CSpell::LevelInfo::LevelInfo()
:description(""),cost(0),power(0),AIValue(0),smartTarget(true),range("0")
{
}
CSpell::LevelInfo::~LevelInfo()
{
}
CSpell::CSpell(): CSpell::CSpell():
id(SpellID::NONE), level(0), id(SpellID::NONE), level(0),
earth(false), water(false), fire(false), air(false), earth(false), water(false), fire(false), air(false),
power(0),
combatSpell(false), creatureAbility(false), combatSpell(false), creatureAbility(false),
positiveness(ESpellPositiveness::NEUTRAL), positiveness(ESpellPositiveness::NEUTRAL),
mainEffectAnim(-1), mainEffectAnim(-1),
@ -143,18 +152,29 @@ CSpell::CSpell():
isRising(false), isDamage(false), isOffensive(false), isRising(false), isDamage(false), isOffensive(false),
targetType(ETargetType::NO_TARGET) targetType(ETargetType::NO_TARGET)
{ {
levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
} }
CSpell::~CSpell() CSpell::~CSpell()
{ {
for(auto & elem : effects)
for(size_t j=0; j<elem.size(); j++)
delete elem[j];
} }
const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
{
if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
{
logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
throw new std::runtime_error("Invalid school level");
}
return levels.at(level);
}
std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
{ {
using namespace SRSLPraserHelpers;
std::vector<BattleHex> ret; std::vector<BattleHex> ret;
if(id == SpellID::FIRE_WALL || id == SpellID::FORCE_FIELD) if(id == SpellID::FIRE_WALL || id == SpellID::FORCE_FIELD)
@ -192,7 +212,7 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
} }
std::string rng = range[schoolLvl] + ','; //copy + artificial comma for easier handling std::string rng = getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
if(rng.size() >= 1 && rng[0] != 'X') //there is at lest one hex in range if(rng.size() >= 1 && rng[0] != 'X') //there is at lest one hex in range
{ {
@ -201,7 +221,7 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
bool readingFirst = true; bool readingFirst = true;
for(auto & elem : rng) for(auto & elem : rng)
{ {
if(std::isdigit(elem) ) //reading numer if(std::isdigit(elem) ) //reading number
{ {
if(readingFirst) if(readingFirst)
number1 += elem; number1 += elem;
@ -258,6 +278,20 @@ CSpell::ETargetType CSpell::getTargetType() const
return targetType; return targetType;
} }
const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
{
TargetInfo info;
auto & levelInfo = getLevelInfo(level);
info.type = getTargetType();
info.smart = levelInfo.smartTarget;
info.massive = levelInfo.range == "X";
return info;
}
bool CSpell::isCombatSpell() const bool CSpell::isCombatSpell() const
{ {
return combatSpell; return combatSpell;
@ -305,7 +339,7 @@ bool CSpell::isSpecialSpell() const
bool CSpell::hasEffects() const bool CSpell::hasEffects() const
{ {
return effects.size() && effects[0].size(); return !levels[0].effects.empty();
} }
const std::string& CSpell::getIconImmune() const const std::string& CSpell::getIconImmune() const
@ -322,12 +356,12 @@ const std::string& CSpell::getCastSound() const
si32 CSpell::getCost(const int skillLevel) const si32 CSpell::getCost(const int skillLevel) const
{ {
return costs[skillLevel]; return getLevelInfo(skillLevel).cost;
} }
si32 CSpell::getPower(const int skillLevel) const si32 CSpell::getPower(const int skillLevel) const
{ {
return powers[skillLevel]; return getLevelInfo(skillLevel).power;
} }
//si32 CSpell::calculatePower(const int skillLevel) const //si32 CSpell::calculatePower(const int skillLevel) const
@ -352,27 +386,20 @@ void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level; logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
return; return;
} }
const std::vector<Bonus> & effects = levels[level].effects;
if(effects.empty()) if(effects.empty())
{
logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no bonus effects! " << name;
return;
}
if(effects.size() <= level)
{
logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") is missing entry for level " << level;
return;
}
if(effects[level].empty())
{ {
logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level; logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level;
return; return;
} }
lst.reserve(lst.size() + effects[level].size()); lst.reserve(lst.size() + effects.size());
for(Bonus *b : effects[level]) for(const Bonus & b : effects)
{ {
lst.push_back(Bonus(*b)); lst.push_back(Bonus(b));
} }
} }
@ -451,20 +478,6 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
return false; return false;
} }
void CSpell::setAttributes(const std::string& newValue)
{
attributes = newValue;
if(attributes.find("CREATURE_TARGET_1") != std::string::npos
|| attributes.find("CREATURE_TARGET_2") != std::string::npos)
targetType = CREATURE_EXPERT_MASSIVE;
else if(attributes.find("CREATURE_TARGET") != std::string::npos)
targetType = CREATURE;
else if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
targetType = OBSTACLE;
else
targetType = NO_TARGET;
}
void CSpell::setIsOffensive(const bool val) void CSpell::setIsOffensive(const bool val)
{ {
isOffensive = val; isOffensive = val;
@ -576,8 +589,6 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
else if(attributes.find("OBSTACLE_TARGET") != std::string::npos) else if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
targetType = "OBSTACLE"; targetType = "OBSTACLE";
lineNode["targetType"].String() = targetType;
//save parsed level specific data //save parsed level specific data
for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++) for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
{ {
@ -588,6 +599,16 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
level["aiValue"].Float() = AIVals[i]; level["aiValue"].Float() = AIVals[i];
} }
if(targetType == "CREATURE_EXPERT_MASSIVE")
{
lineNode["targetType"].String() = "CREATURE";
getLevel(3)["range"].String() = "X";
}
else
{
lineNode["targetType"].String() = targetType;
}
legacyData.push_back(lineNode); legacyData.push_back(lineNode);
@ -677,8 +698,6 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
spell->targetType = CSpell::CREATURE; spell->targetType = CSpell::CREATURE;
else if(targetType == "OBSTACLE") else if(targetType == "OBSTACLE")
spell->targetType = CSpell::OBSTACLE; spell->targetType = CSpell::OBSTACLE;
else if(targetType == "CREATURE_EXPERT_MASSIVE")
spell->targetType = CSpell::CREATURE_EXPERT_MASSIVE;
spell->mainEffectAnim = json["anim"].Float(); spell->mainEffectAnim = json["anim"].Float();
@ -767,17 +786,17 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
const JsonNode & graphicsNode = json["graphics"]; const JsonNode & graphicsNode = json["graphics"];
spell->iconImmune = graphicsNode["iconImmune"].String(); spell->iconImmune = graphicsNode["iconImmune"].String();
spell->iconBook = graphicsNode["iconBook"].String(); spell->iconBook = graphicsNode["iconBook"].String();
spell->iconEffect = graphicsNode["iconEffect"].String(); spell->iconEffect = graphicsNode["iconEffect"].String();
spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String(); spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
spell->iconScroll = graphicsNode["iconScroll"].String(); spell->iconScroll = graphicsNode["iconScroll"].String();
const JsonNode & soundsNode = json["sounds"]; const JsonNode & soundsNode = json["sounds"];
spell->castSound = soundsNode["cast"].String(); spell->castSound = soundsNode["cast"].String();
@ -785,48 +804,37 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS; const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;
spell->AIVals.resize(levelsCount);
spell->costs.resize(levelsCount);
spell->descriptions.resize(levelsCount);
spell->powers.resize(levelsCount);
spell->range.resize(levelsCount);
for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++) for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)
{ {
const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]]; const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];
CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
spell->descriptions[levelIndex] = levelNode["description"].String(); const si32 levelPower = levelNode["power"].Float();
spell->costs[levelIndex] = levelNode["cost"].Float();
spell->powers[levelIndex] = levelNode["power"].Float(); levelObject.description = levelNode["description"].String();
spell->AIVals[levelIndex] = levelNode["aiValue"].Float(); levelObject.cost = levelNode["cost"].Float();
levelObject.power = levelPower;
const JsonNode & effectsNode = levelNode["effects"]; levelObject.AIValue = levelNode["aiValue"].Float();
levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool();
if(!effectsNode.isNull())
for(const auto & elem : levelNode["effects"].Struct())
{ {
if(spell->effects.empty()) const JsonNode & bonusNode = elem.second;
spell->effects.resize(levelsCount); Bonus * b = JsonUtils::parseBonus(bonusNode);
const bool usePowerAsValue = bonusNode["val"].isNull();
for(const auto & elem : effectsNode.Struct()) //TODO: make this work. see CSpellHandler::afterLoadFinalization()
{ //b->sid = spell->id; //for all
const JsonNode & bonusNode = elem.second;
Bonus * b = JsonUtils::parseBonus(bonusNode);
const bool usePowerAsValue = bonusNode["val"].isNull();
//TODO: make this work. see CSpellHandler::afterLoadFinalization() b->source = Bonus::SPELL_EFFECT;//for all
//b->sid = spell->id; //for all
b->source = Bonus::SPELL_EFFECT;//for all
if(usePowerAsValue) if(usePowerAsValue)
{ b->val = levelPower;
b->val = spell->powers[levelIndex];
}
spell->effects[levelIndex].push_back(b); levelObject.effects.push_back(*b);
}
} }
} }
return spell; return spell;
@ -836,9 +844,9 @@ void CSpellHandler::afterLoadFinalization()
{ {
//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading //FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
for(auto spell: objects) for(auto spell: objects)
for(auto & level: spell->effects) for(auto & level: spell->levels)
for(auto * bonus: level) for(auto & bonus: level.effects)
bonus->sid = spell->id; bonus.sid = spell->id;
} }

View File

@ -23,26 +23,49 @@ struct BattleHex;
class DLL_LINKAGE CSpell class DLL_LINKAGE CSpell
{ {
public: public:
// struct LevelInfo struct LevelInfo
// { {
// std::string description; //descriptions of spell for skill level
// }; si32 cost; //per skill level: 0 - none, 1 - basic, etc
// si32 power; //per skill level: 0 - none, 1 - basic, etc
// /** \brief Low level accessor. Don`t use it if absolutely necessary si32 AIValue; //AI values: per skill level: 0 - none, 1 - basic, etc
// *
// * \param level. spell school level bool smartTarget;
// * \return Spell level info structure std::string range;
// *
// */ std::vector<Bonus> effects;
// const LevelInfo& getLevelInfo(const int level);
LevelInfo();
~LevelInfo();
template <typename Handler> void serialize(Handler &h, const int version)
{
h & description & cost & power & AIValue & smartTarget & range & effects;
}
};
/** \brief Low level accessor. Don`t use it if absolutely necessary
*
* \param level. spell school level
* \return Spell level info structure
*
*/
const CSpell::LevelInfo& getLevelInfo(const int level) const;
public: public:
enum ETargetType {NO_TARGET, CREATURE, CREATURE_EXPERT_MASSIVE, OBSTACLE}; enum ETargetType {NO_TARGET, CREATURE, OBSTACLE};
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1}; enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
struct TargetInfo
{
ETargetType type;
bool smart;
bool massive;
};
SpellID id; SpellID id;
std::string identifier; //??? std::string identifier; //???
std::string name; std::string name;
std::string abbName; //abbreviated name
std::vector<std::string> descriptions; //descriptions of spell for skill levels: 0 - none, 1 - basic, etc
si32 level; si32 level;
bool earth; bool earth;
bool water; bool water;
@ -55,7 +78,7 @@ public:
bool combatSpell; //is this spell combat (true) or adventure (false) bool combatSpell; //is this spell combat (true) or adventure (false)
bool creatureAbility; //if true, only creatures can use this spell bool creatureAbility; //if true, only creatures can use this spell
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
std::vector<std::string> range; //description of spell's range in SRSL by magic school level
std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs) std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
CSpell(); CSpell();
@ -63,7 +86,9 @@ public:
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret) std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none) si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
ETargetType getTargetType() const; ETargetType getTargetType() const; //deprecated
const CSpell::TargetInfo getTargetInfo(const int level) const;
bool isCombatSpell() const; bool isCombatSpell() const;
bool isAdventureSpell() const; bool isAdventureSpell() const;
@ -102,23 +127,25 @@ public:
* Returns resource name of icon for SPELL_IMMUNITY bonus * Returns resource name of icon for SPELL_IMMUNITY bonus
*/ */
const std::string& getIconImmune() const; const std::string& getIconImmune() const;
const std::string& getCastSound() const; const std::string& getCastSound() const;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & identifier & id & name & abbName & descriptions & level & earth & water & fire & air & power & costs h & identifier & id & name & level & earth & water & fire & air & power
& powers & probabilities & AIVals & attributes & combatSpell & creatureAbility & positiveness & range & counteredSpells & mainEffectAnim; & probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells & mainEffectAnim;
h & isRising & isDamage & isOffensive; h & isRising & isDamage & isOffensive;
h & targetType; h & targetType;
h & effects & immunities & limiters; h & immunities & limiters;
h & iconImmune; h & iconImmune;
h & absoluteImmunities & defaultProbability; h & absoluteImmunities & defaultProbability;
h & isSpecial; h & isSpecial;
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll ; h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
h & levels;
} }
friend class CSpellHandler; friend class CSpellHandler;
friend class Graphics; friend class Graphics;
@ -126,13 +153,9 @@ public:
private: private:
void setIsOffensive(const bool val); void setIsOffensive(const bool val);
void setIsRising(const bool val); void setIsRising(const bool val);
void setAttributes(const std::string& newValue);
private: private:
si32 defaultProbability; si32 defaultProbability;
std::vector<si32> costs; //per skill level: 0 - none, 1 - basic, etc
std::vector<si32> powers; //per skill level: 0 - none, 1 - basic, etc
std::vector<si32> AIVals; //AI values: per skill level: 0 - none, 1 - basic, etc
bool isRising; bool isRising;
bool isDamage; bool isDamage;
@ -141,11 +164,8 @@ private:
std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
ETargetType targetType; ETargetType targetType;
std::vector<std::vector<Bonus *> > effects; // [level 0-3][list of effects]
std::vector<Bonus::BonusType> immunities; //any of these grants immunity std::vector<Bonus::BonusType> immunities; //any of these grants immunity
std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, cant be negated std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, cant be negated
std::vector<Bonus::BonusType> limiters; //all of them are required to be affected std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
@ -153,14 +173,16 @@ private:
///graphics related stuff ///graphics related stuff
std::string iconImmune; std::string iconImmune;
std::string iconBook; std::string iconBook;
std::string iconEffect; std::string iconEffect;
std::string iconScenarioBonus; std::string iconScenarioBonus;
std::string iconScroll; std::string iconScroll;
///sound related stuff ///sound related stuff
std::string castSound; std::string castSound;
std::vector<LevelInfo> levels;
}; };

View File

@ -28,7 +28,7 @@
#include "mapping/CCampaignHandler.h" //for CCampaignState #include "mapping/CCampaignHandler.h" //for CCampaignState
#include "rmg/CMapGenerator.h" // for CMapGenOptions #include "rmg/CMapGenerator.h" // for CMapGenOptions
const ui32 version = 747; const ui32 version = 748;
const ui32 minSupportedVersion = version; const ui32 minSupportedVersion = version;
class CConnection; class CConnection;

View File

@ -4082,50 +4082,50 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
if (spell->isOffensiveSpell()) if (spell->isOffensiveSpell())
{ {
int spellDamage = 0; int spellDamage = 0;
if (stack && mode != ECastingMode::MAGIC_MIRROR) 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
{ {
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum()); usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
if (unitSpellPower) sc.dmgToDisplay = 0;
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) 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
{ {
if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell bsa.flags |= BattleStackAttacked::EFFECT;
continue; bsa.effect = spell->mainEffectAnim;
BattleStackAttacked bsa;
if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->range.at(spellLvl) == "X" || mode == ECastingMode::ENCHANTER_CASTING))
//display effect only upon primary target of area spell
//FIXME: if no stack is attacked, ther eis no animation and interface freezes
{
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim;
}
if (spellDamage)
bsa.damageAmount = spellDamage >> chainLightningModifier;
else
bsa.damageAmount = gs->curB->calculateSpellDmg(spell, 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);
si.stacks.push_back(bsa);
if (spellID == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
} }
if (spellDamage)
bsa.damageAmount = spellDamage >> chainLightningModifier;
else
bsa.damageAmount = gs->curB->calculateSpellDmg(spell, 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);
si.stacks.push_back(bsa);
if (spellID == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
} }
if (spell->hasEffects()) if (spell->hasEffects())
@ -4456,7 +4456,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
} }
//Magic Mirror effect //Magic Mirror effect
if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->range.at(0) == "0") //it is actual spell and can be reflected to single target, no recurrence 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) for(auto & attackedCre : attackedCres)
{ {
@ -5354,7 +5354,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat) void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat)
{ {
const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking); const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no detah stare / acid bretah needed? attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no death stare / acid breath needed?
} }
void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )