mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +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:
parent
7f6f125b4c
commit
9cac0af7be
@ -1687,13 +1687,13 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
|
||||
void CMageGuildScreen::Scroll::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -630,7 +630,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
|
||||
|| (!sp->combatSpell && owner->myInt->battleInt))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -810,7 +810,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -977,7 +977,7 @@ std::string CComponent::getDescription()
|
||||
case creature: return "";
|
||||
case artifact: return CGI->arth->artifacts[subtype]->Description();
|
||||
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 luck: return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
|
||||
case building: return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description();
|
||||
|
@ -1549,31 +1549,23 @@ void CBattleInterface::castThisSpell(int spellID)
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
sp = CGI->spellh->objects[spellID];
|
||||
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(sp->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE)
|
||||
{
|
||||
if(castingHero->getSpellSchoolLevel(sp) < 3)
|
||||
if(ti.smart)
|
||||
spellSelMode = selectionTypeByPositiveness(*sp);
|
||||
else
|
||||
spellSelMode = NO_LOCATION;
|
||||
}
|
||||
if(sp->getTargetType() == CSpell::OBSTACLE)
|
||||
spellSelMode = ANY_CREATURE;
|
||||
}
|
||||
else if(ti.type == CSpell::OBSTACLE)
|
||||
{
|
||||
spellSelMode = OBSTACLE;
|
||||
} //FIXME: Remove Obstacle has range X, unfortunatelly :(
|
||||
else if(sp->range[ castingHero->getSpellSchoolLevel(sp) ] == "X") //spell has no range
|
||||
{
|
||||
spellSelMode = NO_LOCATION;
|
||||
}
|
||||
|
||||
if(sp->range[ castingHero->getSpellSchoolLevel(sp) ].size() > 1) //spell has many-hex range
|
||||
{
|
||||
spellSelMode = ANY_LOCATION;
|
||||
}
|
||||
|
||||
}
|
||||
//todo: move to JSON config
|
||||
if(spellID == SpellID::FIRE_WALL || spellID == SpellID::FORCE_FIELD)
|
||||
{
|
||||
spellSelMode = FREE_LOCATION;
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
|
||||
"type":"object",
|
||||
"$schema": "http://json-schema.org/draft-04/schema",
|
||||
@ -7,235 +7,241 @@
|
||||
"description" : "Format used to define new spells in VCMI",
|
||||
|
||||
|
||||
"definitions" : {
|
||||
"flags" :{
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"type":"boolean"
|
||||
}
|
||||
},
|
||||
"levelInfo":{
|
||||
"type": "object",
|
||||
"required":["range","description","cost","power","aiValue","range"],
|
||||
"definitions" : {
|
||||
"flags" :{
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"type":"boolean"
|
||||
}
|
||||
},
|
||||
"levelInfo":{
|
||||
"type": "object",
|
||||
"required":["range","description","cost","power","aiValue","range"],
|
||||
|
||||
"additionalProperties" : false,
|
||||
"properties":{
|
||||
"description":{
|
||||
"type": "string",
|
||||
"description": "Localizable description. Use {xxx} for formatting"
|
||||
},
|
||||
"cost":{
|
||||
"type": "number",
|
||||
"description":"Cost in mana points"
|
||||
},
|
||||
"power":{
|
||||
"type": "number",
|
||||
},
|
||||
"aiValue":{
|
||||
"type": "number",
|
||||
},
|
||||
"additionalProperties" : false,
|
||||
"properties":{
|
||||
"description":{
|
||||
"type": "string",
|
||||
"description": "Localizable description. Use {xxx} for formatting"
|
||||
},
|
||||
"cost":{
|
||||
"type": "number",
|
||||
"description":"Cost in mana points"
|
||||
},
|
||||
"power":{
|
||||
"type": "number",
|
||||
},
|
||||
"aiValue":{
|
||||
"type": "number",
|
||||
},
|
||||
|
||||
"range":{
|
||||
"type": "string",
|
||||
"description": "spell range description in SRSL"
|
||||
},
|
||||
"effects":{
|
||||
"type": "object",
|
||||
"description": "Timed effects",
|
||||
"additionalProperties" : {
|
||||
"$ref" : "vcmi:bonus"
|
||||
}
|
||||
}
|
||||
"range":{
|
||||
"type": "string",
|
||||
"description": "spell range description in SRSL"
|
||||
},
|
||||
"effects":{
|
||||
"type": "object",
|
||||
"description": "Timed effects",
|
||||
"additionalProperties" : {
|
||||
"$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"],
|
||||
"additionalProperties" : false,
|
||||
"properties": {
|
||||
"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":{
|
||||
"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"
|
||||
},
|
||||
"school":{
|
||||
"type": "object",
|
||||
"description": "Spell schools",
|
||||
"additionalProperties": false,
|
||||
|
||||
},
|
||||
"school":{
|
||||
"type": "object",
|
||||
"description": "Spell schools",
|
||||
"additionalProperties": false,
|
||||
"properties":{
|
||||
|
||||
"properties":{
|
||||
"air":{"type": "boolean"},
|
||||
"fire":{"type": "boolean"},
|
||||
"earth":{"type": "boolean"},
|
||||
"water":{"type": "boolean"}
|
||||
}
|
||||
|
||||
"air":{"type": "boolean"},
|
||||
"fire":{"type": "boolean"},
|
||||
"earth":{"type": "boolean"},
|
||||
"water":{"type": "boolean"}
|
||||
}
|
||||
},
|
||||
"level":{
|
||||
"type": "number",
|
||||
"description": "Spell level",
|
||||
"minimum" : 0,
|
||||
"maximum" : 5
|
||||
},
|
||||
|
||||
},
|
||||
"level":{
|
||||
"type": "number",
|
||||
"description": "Spell level",
|
||||
"minimum" : 0,
|
||||
"maximum" : 5
|
||||
},
|
||||
"power":{
|
||||
"type": "number",
|
||||
"description": "Base power",
|
||||
},
|
||||
|
||||
"power":{
|
||||
"type": "number",
|
||||
"description": "Base power",
|
||||
},
|
||||
"defaultGainChance":{
|
||||
"type": "number",
|
||||
"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":{
|
||||
"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","CREATURE_EXPERT_MASSIVE"]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
"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":{
|
||||
"type": "object",
|
||||
"additionalProperties" : false,
|
||||
"properties":{
|
||||
"cast":{
|
||||
"type": "string",
|
||||
"description": "Resourse path of cast sound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"levels":{
|
||||
"type": "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : ["none", "basic", "advanced", "expert"],
|
||||
|
||||
"levels":{
|
||||
"type": "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : ["none", "basic", "advanced", "expert"],
|
||||
|
||||
"properties":{
|
||||
"none":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"basic":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"advanced":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"expert":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
"properties":{
|
||||
"none":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"basic":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"advanced":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
},
|
||||
"expert":{
|
||||
"$ref" : "#/definitions/levelInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1617,10 +1617,13 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1637,13 +1640,20 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
|
||||
}
|
||||
else //no target stack on this tile
|
||||
{
|
||||
if(spell->getTargetType() == CSpell::CREATURE
|
||||
|| (spell->getTargetType() == CSpell::CREATURE_EXPERT_MASSIVE
|
||||
&& mode == ECastingMode::HERO_CASTING //TODO why???
|
||||
&& caster
|
||||
&& caster->getSpellSchoolLevel(spell) < SecSkillLevel::EXPERT))
|
||||
if(spell->getTargetType() == CSpell::CREATURE)
|
||||
{
|
||||
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;
|
||||
//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();
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
@ -1726,43 +1737,38 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
|
||||
switch(spell->getTargetType())
|
||||
{
|
||||
case CSpell::CREATURE:
|
||||
case CSpell::CREATURE_EXPERT_MASSIVE:
|
||||
if(mode == ECastingMode::HERO_CASTING)
|
||||
{
|
||||
const CGHeroInstance * caster = battleGetFightingHero(side);
|
||||
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
|
||||
bool targetExists = false;
|
||||
for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
|
||||
{
|
||||
switch (spell->positiveness)
|
||||
{
|
||||
case CSpell::POSITIVE:
|
||||
if(stack->owner == caster->getOwner())
|
||||
bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
|
||||
bool casterStack = 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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CSpell::NEUTRAL:
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
{
|
||||
targetExists = true;
|
||||
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)
|
||||
{
|
||||
@ -1787,31 +1793,32 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
|
||||
switch(spell->getTargetType())
|
||||
{
|
||||
case CSpell::CREATURE:
|
||||
case CSpell::CREATURE_EXPERT_MASSIVE:
|
||||
{
|
||||
const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
|
||||
|
||||
const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
|
||||
|
||||
for(const CStack * stack : battleAliveStacks())
|
||||
{
|
||||
switch (spell->positiveness)
|
||||
{
|
||||
case CSpell::POSITIVE:
|
||||
if(stack->owner == caster->getOwner())
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
bool immune = battleIsImmune(caster, spell, mode, stack->position) != ESpellCastProblem::OK;
|
||||
bool casterStack = stack->owner == caster->getOwner();
|
||||
|
||||
if(!immune)
|
||||
switch (spell->positiveness)
|
||||
{
|
||||
case CSpell::POSITIVE:
|
||||
if(casterStack || ti.smart)
|
||||
ret.push_back(stack->position);
|
||||
break;
|
||||
break;
|
||||
|
||||
case CSpell::NEUTRAL:
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
case CSpell::NEUTRAL:
|
||||
ret.push_back(stack->position);
|
||||
break;
|
||||
break;
|
||||
|
||||
case CSpell::NEGATIVE:
|
||||
if(stack->owner != caster->getOwner())
|
||||
if(battleIsImmune(caster, spell, mode, stack->position) == ESpellCastProblem::OK)
|
||||
case CSpell::NEGATIVE:
|
||||
if(!casterStack || ti.smart)
|
||||
ret.push_back(stack->position);
|
||||
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
|
||||
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)
|
||||
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 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
|
||||
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())
|
||||
{
|
||||
if((spell->id == SpellID::DEATH_RIPPLE && !stack->getCreature()->isUndead()) //death ripple
|
||||
|| (spell->id == SpellID::DESTROY_UNDEAD && stack->getCreature()->isUndead()) //destroy undead
|
||||
|| (spell->id == SpellID::ARMAGEDDON) //Armageddon
|
||||
)
|
||||
{
|
||||
if(stack->isValidTarget())
|
||||
@ -2057,7 +2064,7 @@ std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell *
|
||||
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)
|
||||
{
|
||||
@ -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);
|
||||
if(st)
|
||||
attackedCres.insert(st);
|
||||
TStacks stacks = battleGetAllStacks();
|
||||
|
||||
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
|
||||
{
|
||||
for (auto stack : battleGetAllStacks())
|
||||
{
|
||||
/*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);
|
||||
if(const CStack * st = battleGetStackByPos(destinationTile, onlyAlive))
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
|
||||
}
|
||||
else //custom range from attackedHexes
|
||||
{
|
||||
|
@ -23,7 +23,7 @@
|
||||
namespace SpellConfig
|
||||
{
|
||||
static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
|
||||
|
||||
|
||||
}
|
||||
|
||||
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():
|
||||
id(SpellID::NONE), level(0),
|
||||
earth(false), water(false), fire(false), air(false),
|
||||
power(0),
|
||||
combatSpell(false), creatureAbility(false),
|
||||
positiveness(ESpellPositiveness::NEUTRAL),
|
||||
mainEffectAnim(-1),
|
||||
@ -143,18 +152,29 @@ CSpell::CSpell():
|
||||
isRising(false), isDamage(false), isOffensive(false),
|
||||
targetType(ETargetType::NO_TARGET)
|
||||
{
|
||||
|
||||
levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
using namespace SRSLPraserHelpers;
|
||||
|
||||
std::vector<BattleHex> ret;
|
||||
|
||||
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
|
||||
{
|
||||
@ -201,7 +221,7 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
|
||||
bool readingFirst = true;
|
||||
for(auto & elem : rng)
|
||||
{
|
||||
if(std::isdigit(elem) ) //reading numer
|
||||
if(std::isdigit(elem) ) //reading number
|
||||
{
|
||||
if(readingFirst)
|
||||
number1 += elem;
|
||||
@ -258,6 +278,20 @@ CSpell::ETargetType CSpell::getTargetType() const
|
||||
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
|
||||
{
|
||||
return combatSpell;
|
||||
@ -305,7 +339,7 @@ bool CSpell::isSpecialSpell() const
|
||||
|
||||
bool CSpell::hasEffects() const
|
||||
{
|
||||
return effects.size() && effects[0].size();
|
||||
return !levels[0].effects.empty();
|
||||
}
|
||||
|
||||
const std::string& CSpell::getIconImmune() const
|
||||
@ -322,12 +356,12 @@ const std::string& CSpell::getCastSound() const
|
||||
|
||||
si32 CSpell::getCost(const int skillLevel) const
|
||||
{
|
||||
return costs[skillLevel];
|
||||
return getLevelInfo(skillLevel).cost;
|
||||
}
|
||||
|
||||
si32 CSpell::getPower(const int skillLevel) const
|
||||
{
|
||||
return powers[skillLevel];
|
||||
return getLevelInfo(skillLevel).power;
|
||||
}
|
||||
|
||||
//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;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<Bonus> & effects = levels[level].effects;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
isOffensive = val;
|
||||
@ -576,8 +589,6 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
|
||||
else if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
|
||||
targetType = "OBSTACLE";
|
||||
|
||||
lineNode["targetType"].String() = targetType;
|
||||
|
||||
//save parsed level specific data
|
||||
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];
|
||||
}
|
||||
|
||||
if(targetType == "CREATURE_EXPERT_MASSIVE")
|
||||
{
|
||||
lineNode["targetType"].String() = "CREATURE";
|
||||
getLevel(3)["range"].String() = "X";
|
||||
}
|
||||
else
|
||||
{
|
||||
lineNode["targetType"].String() = targetType;
|
||||
}
|
||||
|
||||
legacyData.push_back(lineNode);
|
||||
|
||||
|
||||
@ -677,8 +698,6 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
spell->targetType = CSpell::CREATURE;
|
||||
else if(targetType == "OBSTACLE")
|
||||
spell->targetType = CSpell::OBSTACLE;
|
||||
else if(targetType == "CREATURE_EXPERT_MASSIVE")
|
||||
spell->targetType = CSpell::CREATURE_EXPERT_MASSIVE;
|
||||
|
||||
|
||||
spell->mainEffectAnim = json["anim"].Float();
|
||||
@ -767,17 +786,17 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
|
||||
|
||||
const JsonNode & graphicsNode = json["graphics"];
|
||||
|
||||
|
||||
spell->iconImmune = graphicsNode["iconImmune"].String();
|
||||
spell->iconBook = graphicsNode["iconBook"].String();
|
||||
spell->iconEffect = graphicsNode["iconEffect"].String();
|
||||
spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
|
||||
spell->iconScroll = graphicsNode["iconScroll"].String();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const JsonNode & soundsNode = json["sounds"];
|
||||
|
||||
|
||||
spell->castSound = soundsNode["cast"].String();
|
||||
|
||||
|
||||
@ -785,48 +804,37 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
|
||||
|
||||
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++)
|
||||
{
|
||||
const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];
|
||||
|
||||
CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
|
||||
|
||||
spell->descriptions[levelIndex] = levelNode["description"].String();
|
||||
spell->costs[levelIndex] = levelNode["cost"].Float();
|
||||
spell->powers[levelIndex] = levelNode["power"].Float();
|
||||
spell->AIVals[levelIndex] = levelNode["aiValue"].Float();
|
||||
|
||||
const JsonNode & effectsNode = levelNode["effects"];
|
||||
|
||||
if(!effectsNode.isNull())
|
||||
const si32 levelPower = levelNode["power"].Float();
|
||||
|
||||
levelObject.description = levelNode["description"].String();
|
||||
levelObject.cost = levelNode["cost"].Float();
|
||||
levelObject.power = levelPower;
|
||||
levelObject.AIValue = levelNode["aiValue"].Float();
|
||||
levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool();
|
||||
|
||||
for(const auto & elem : levelNode["effects"].Struct())
|
||||
{
|
||||
if(spell->effects.empty())
|
||||
spell->effects.resize(levelsCount);
|
||||
const JsonNode & bonusNode = elem.second;
|
||||
Bonus * b = JsonUtils::parseBonus(bonusNode);
|
||||
const bool usePowerAsValue = bonusNode["val"].isNull();
|
||||
|
||||
for(const auto & elem : effectsNode.Struct())
|
||||
{
|
||||
const JsonNode & bonusNode = elem.second;
|
||||
Bonus * b = JsonUtils::parseBonus(bonusNode);
|
||||
const bool usePowerAsValue = bonusNode["val"].isNull();
|
||||
//TODO: make this work. see CSpellHandler::afterLoadFinalization()
|
||||
//b->sid = spell->id; //for all
|
||||
|
||||
//TODO: make this work. see CSpellHandler::afterLoadFinalization()
|
||||
//b->sid = spell->id; //for all
|
||||
|
||||
b->source = Bonus::SPELL_EFFECT;//for all
|
||||
b->source = Bonus::SPELL_EFFECT;//for all
|
||||
|
||||
if(usePowerAsValue)
|
||||
{
|
||||
b->val = spell->powers[levelIndex];
|
||||
}
|
||||
if(usePowerAsValue)
|
||||
b->val = levelPower;
|
||||
|
||||
spell->effects[levelIndex].push_back(b);
|
||||
}
|
||||
levelObject.effects.push_back(*b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
for(auto spell: objects)
|
||||
for(auto & level: spell->effects)
|
||||
for(auto * bonus: level)
|
||||
bonus->sid = spell->id;
|
||||
for(auto & level: spell->levels)
|
||||
for(auto & bonus: level.effects)
|
||||
bonus.sid = spell->id;
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,26 +23,49 @@ struct BattleHex;
|
||||
class DLL_LINKAGE CSpell
|
||||
{
|
||||
public:
|
||||
// struct LevelInfo
|
||||
// {
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /** \brief Low level accessor. Don`t use it if absolutely necessary
|
||||
// *
|
||||
// * \param level. spell school level
|
||||
// * \return Spell level info structure
|
||||
// *
|
||||
// */
|
||||
// const LevelInfo& getLevelInfo(const int level);
|
||||
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
|
||||
si32 AIValue; //AI values: per skill level: 0 - none, 1 - basic, etc
|
||||
|
||||
bool smartTarget;
|
||||
std::string range;
|
||||
|
||||
std::vector<Bonus> effects;
|
||||
|
||||
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:
|
||||
enum ETargetType {NO_TARGET, CREATURE, CREATURE_EXPERT_MASSIVE, OBSTACLE};
|
||||
enum ETargetType {NO_TARGET, CREATURE, OBSTACLE};
|
||||
enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
|
||||
|
||||
struct TargetInfo
|
||||
{
|
||||
ETargetType type;
|
||||
bool smart;
|
||||
bool massive;
|
||||
};
|
||||
|
||||
SpellID id;
|
||||
std::string identifier; //???
|
||||
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;
|
||||
bool earth;
|
||||
bool water;
|
||||
@ -55,7 +78,7 @@ public:
|
||||
bool combatSpell; //is this spell combat (true) or adventure (false)
|
||||
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
|
||||
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)
|
||||
|
||||
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)
|
||||
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 isAdventureSpell() const;
|
||||
@ -102,23 +127,25 @@ public:
|
||||
* Returns resource name of icon for SPELL_IMMUNITY bonus
|
||||
*/
|
||||
const std::string& getIconImmune() const;
|
||||
|
||||
|
||||
const std::string& getCastSound() const;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & identifier & id & name & abbName & descriptions & level & earth & water & fire & air & power & costs
|
||||
& powers & probabilities & AIVals & attributes & combatSpell & creatureAbility & positiveness & range & counteredSpells & mainEffectAnim;
|
||||
h & identifier & id & name & level & earth & water & fire & air & power
|
||||
& probabilities & attributes & combatSpell & creatureAbility & positiveness & counteredSpells & mainEffectAnim;
|
||||
h & isRising & isDamage & isOffensive;
|
||||
h & targetType;
|
||||
h & effects & immunities & limiters;
|
||||
h & immunities & limiters;
|
||||
h & iconImmune;
|
||||
h & absoluteImmunities & defaultProbability;
|
||||
|
||||
h & isSpecial;
|
||||
|
||||
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll ;
|
||||
|
||||
|
||||
h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
|
||||
|
||||
h & levels;
|
||||
|
||||
}
|
||||
friend class CSpellHandler;
|
||||
friend class Graphics;
|
||||
@ -126,13 +153,9 @@ public:
|
||||
private:
|
||||
void setIsOffensive(const bool val);
|
||||
void setIsRising(const bool val);
|
||||
void setAttributes(const std::string& newValue);
|
||||
|
||||
private:
|
||||
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 isDamage;
|
||||
@ -141,11 +164,8 @@ private:
|
||||
|
||||
std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
|
||||
|
||||
|
||||
|
||||
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> absoluteImmunities; //any of these grants immunity, cant be negated
|
||||
std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
|
||||
@ -153,14 +173,16 @@ private:
|
||||
///graphics related stuff
|
||||
|
||||
std::string iconImmune;
|
||||
|
||||
|
||||
std::string iconBook;
|
||||
std::string iconEffect;
|
||||
std::string iconScenarioBonus;
|
||||
std::string iconScenarioBonus;
|
||||
std::string iconScroll;
|
||||
|
||||
///sound related stuff
|
||||
std::string castSound;
|
||||
|
||||
std::vector<LevelInfo> levels;
|
||||
};
|
||||
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
#include "mapping/CCampaignHandler.h" //for CCampaignState
|
||||
#include "rmg/CMapGenerator.h" // for CMapGenOptions
|
||||
|
||||
const ui32 version = 747;
|
||||
const ui32 version = 748;
|
||||
const ui32 minSupportedVersion = version;
|
||||
|
||||
class CConnection;
|
||||
|
@ -4082,50 +4082,50 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
|
||||
|
||||
if (spell->isOffensiveSpell())
|
||||
{
|
||||
int spellDamage = 0;
|
||||
if (stack && mode != ECastingMode::MAGIC_MIRROR)
|
||||
int spellDamage = 0;
|
||||
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());
|
||||
if (unitSpellPower)
|
||||
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;
|
||||
}
|
||||
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
|
||||
continue;
|
||||
|
||||
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;
|
||||
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 (spell->hasEffects())
|
||||
@ -4456,7 +4456,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
@ -5354,7 +5354,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
||||
void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat)
|
||||
{
|
||||
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 )
|
||||
|
Loading…
Reference in New Issue
Block a user