1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Merge branch 'develop' of https://github.com/vcmi/vcmi into mutexRelax

This commit is contained in:
DjWarmonger 2015-09-20 09:46:52 +02:00
commit e14faea181
48 changed files with 890 additions and 664 deletions

View File

@ -643,7 +643,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
auto attacker = AttackInfo.attacker; auto attacker = AttackInfo.attacker;
auto enemy = AttackInfo.defender; auto enemy = AttackInfo.defender;
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks); const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining());
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();

View File

@ -70,7 +70,7 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
tp.r = pcx[it++]; tp.r = pcx[it++];
tp.g = pcx[it++]; tp.g = pcx[it++];
tp.b = pcx[it++]; tp.b = pcx[it++];
CSDL_Ext::colorSetAlpha(tp,SDL_ALPHA_OPAQUE); tp.a = SDL_ALPHA_OPAQUE;
ret->format->palette->colors[i] = tp; ret->format->palette->colors[i] = tp;
} }
} }
@ -142,7 +142,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
{ {
//set correct value for alpha\unused channel //set correct value for alpha\unused channel
for (int i=0; i < ret->format->palette->ncolors; i++) for (int i=0; i < ret->format->palette->ncolors; i++)
CSDL_Ext::colorSetAlpha(ret->format->palette->colors[i],SDL_ALPHA_OPAQUE); ret->format->palette->colors[i].a = SDL_ALPHA_OPAQUE;
} }
} }
else else

View File

@ -67,7 +67,7 @@ void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
palette[it].r = de.palette[it].R; palette[it].r = de.palette[it].R;
palette[it].g = de.palette[it].G; palette[it].g = de.palette[it].G;
palette[it].b = de.palette[it].B; palette[it].b = de.palette[it].B;
CSDL_Ext::colorSetAlpha(palette[it],SDL_ALPHA_OPAQUE); palette[it].a = SDL_ALPHA_OPAQUE;
} }
// The SDefEntryBlock starts just after the SDefEntry // The SDefEntryBlock starts just after the SDefEntry
@ -122,12 +122,6 @@ void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
} }
} }
void CDefHandler::expand(ui8 N,ui8 & BL, ui8 & BR)
{
BL = (N & 0xE0) >> 5;
BR = N & 0x1F;
}
SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const
{ {
SDL_Surface * ret=nullptr; SDL_Surface * ret=nullptr;
@ -180,13 +174,12 @@ SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Co
BaseOffset += sizeof(SSpriteDef); BaseOffset += sizeof(SSpriteDef);
int BaseOffsetor = BaseOffset; int BaseOffsetor = BaseOffset;
if(SDL_SetPaletteColors(ret->format->palette,palette,0,256) != 0)
{
logGlobal->errorStream() << __FUNCTION__ <<": Unable to set palette";
logGlobal->errorStream() << SDL_GetError();
}
SDL_Palette * p = SDL_AllocPalette(256);
SDL_SetPaletteColors(p, palette, 0, 256);
SDL_SetSurfacePalette(ret, p);
SDL_FreePalette(p);
int ftcp=0; int ftcp=0;
// If there's a margin anywhere, just blank out the whole surface. // If there's a margin anywhere, just blank out the whole surface.

View File

@ -85,7 +85,9 @@ private:
int group; int group;
} ; } ;
std::vector<SEntry> SEntries ; std::vector<SEntry> SEntries ;
void openFromMemory(ui8 * table, const std::string & name);
SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const;
public: public:
int width, height; //width and height int width, height; //width and height
std::string defName; std::string defName;
@ -94,9 +96,7 @@ public:
CDefHandler(); //c-tor CDefHandler(); //c-tor
~CDefHandler(); //d-tor ~CDefHandler(); //d-tor
SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const; //saves picture with given number to "testtt.bmp"
static void expand(ui8 N,ui8 & BL, ui8 & BR);
void openFromMemory(ui8 * table, const std::string & name);
CDefEssential * essentialize(); CDefEssential * essentialize();
static CDefHandler * giveDef(const std::string & defName); static CDefHandler * giveDef(const std::string & defName);

View File

@ -186,7 +186,7 @@ public:
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {}; void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override {}; void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override {};
bool changeStackType(const StackLocation &sl, CCreature *c) override {return false;}; bool changeStackType(const StackLocation &sl, const CCreature *c) override {return false;};
bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override {return false;}; bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override {return false;};
bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;}; bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;};
bool eraseStack(const StackLocation &sl, bool forceRemoval = false){return false;}; bool eraseStack(const StackLocation &sl, bool forceRemoval = false){return false;};

View File

@ -58,7 +58,7 @@ void Graphics::loadPaletteAndColors()
col.r = pals[startPoint++]; col.r = pals[startPoint++];
col.g = pals[startPoint++]; col.g = pals[startPoint++];
col.b = pals[startPoint++]; col.b = pals[startPoint++];
CSDL_Ext::colorSetAlpha(col,SDL_ALPHA_OPAQUE); col.a = SDL_ALPHA_OPAQUE;
startPoint++; startPoint++;
playerColorPalette[i] = col; playerColorPalette[i] = col;
} }
@ -74,7 +74,7 @@ void Graphics::loadPaletteAndColors()
neutralColorPalette[i].g = reader.readUInt8(); neutralColorPalette[i].g = reader.readUInt8();
neutralColorPalette[i].b = reader.readUInt8(); neutralColorPalette[i].b = reader.readUInt8();
reader.readUInt8(); // this is "flags" entry, not alpha reader.readUInt8(); // this is "flags" entry, not alpha
CSDL_Ext::colorSetAlpha(neutralColorPalette[i], SDL_ALPHA_OPAQUE); neutralColorPalette[i].a = SDL_ALPHA_OPAQUE;
} }
//colors initialization //colors initialization
SDL_Color colors[] = { SDL_Color colors[] = {
@ -92,8 +92,11 @@ void Graphics::loadPaletteAndColors()
{ {
playerColors[i] = colors[i]; playerColors[i] = colors[i];
} }
neutralColor->r = 0x84; neutralColor->g = 0x84; neutralColor->b = 0x84; //gray //gray
CSDL_Ext::colorSetAlpha(*neutralColor,SDL_ALPHA_OPAQUE); neutralColor->r = 0x84;
neutralColor->g = 0x84;
neutralColor->b = 0x84;
neutralColor->a = SDL_ALPHA_OPAQUE;
} }
void Graphics::initializeBattleGraphics() void Graphics::initializeBattleGraphics()

View File

@ -283,7 +283,9 @@ void CDefenceAnimation::endAnim()
CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames) CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames)
: CBattleAnimation(_owner), counter(0), howMany(howManyFrames) : CBattleAnimation(_owner), counter(0), howMany(howManyFrames)
{} {
logAnim->debugStream() << "Created dummy animation for " << howManyFrames <<" frames";
}
bool CDummyAnimation::init() bool CDummyAnimation::init()
{ {

View File

@ -360,7 +360,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
{ {
idToObstacle[ID] = CDefHandler::giveDef(elem->getInfo().defName); idToObstacle[ID] = CDefHandler::giveDef(elem->getInfo().defName);
for(auto & _n : idToObstacle[ID]->ourImages) for(auto & _n : idToObstacle[ID]->ourImages)
{ {
CSDL_Ext::setDefaultColorKey(_n.bitmap); CSDL_Ext::setDefaultColorKey(_n.bitmap);
} }
} }
@ -1240,15 +1240,9 @@ void CBattleInterface::displayBattleFinished()
void CBattleInterface::spellCast( const BattleSpellCast * sc ) void CBattleInterface::spellCast( const BattleSpellCast * sc )
{ {
const SpellID spellID(sc->id); const SpellID spellID(sc->id);
const CSpell &spell = * spellID.toSpell(); const CSpell & spell = * spellID.toSpell();
const std::string & spellName = spell.name;
const std::string& castSoundPath = spell.getCastSound(); const std::string & castSoundPath = spell.getCastSound();
std::string casterName("Something");
if(sc->castedByHero)
casterName = curInt->cb->battleGetHeroInfo(sc->side).name;
if(!castSoundPath.empty()) if(!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath); CCS->soundh->playSound(castSoundPath);
@ -1262,19 +1256,16 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID); const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
if(casterStack != nullptr) if(casterStack != nullptr)
{ {
casterName = casterStack->type->namePl; srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this);
srccoord.x += 250; srccoord.x += 250;
srccoord.y += 240; srccoord.y += 240;
} }
} }
} }
//TODO: play custom cast animation //todo: play custom cast animation
{ displaySpellCast(spellID, BattleHex::INVALID, false);
}
//playing projectile animation //playing projectile animation
if(sc->tile.isValid()) if(sc->tile.isValid())
{ {
@ -1304,163 +1295,33 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
delete animDef; delete animDef;
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip)); addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
} }
} }
waitForAnims(); waitForAnims();
displaySpellHit(spellID, sc->tile); displaySpellHit(spellID, sc->tile);
//queuing affect /resist animation //queuing affect animation
for (auto & elem : sc->affectedCres) for(auto & elem : sc->affectedCres)
{ {
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position; BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
displaySpellEffect(spellID, position);
if(vstd::contains(sc->resisted,elem))
displayEffect(78, position);
else
displaySpellEffect(spellID, position);
} }
switch(sc->id) //queuing additional animation
for(auto & elem : sc->customEffects)
{ {
case SpellID::SUMMON_FIRE_ELEMENTAL: BattleHex position = curInt->cb->battleGetStackByID(elem.stack, false)->position;
case SpellID::SUMMON_EARTH_ELEMENTAL: displayEffect(elem.effect, position);
case SpellID::SUMMON_WATER_ELEMENTAL: }
case SpellID::SUMMON_AIR_ELEMENTAL:
case SpellID::CLONE:
case SpellID::REMOVE_OBSTACLE:
addNewAnim(new CDummyAnimation(this, 2)); //interface won't return until animation is played. TODO: make it smarter?
break;
} //switch(sc->id)
//displaying message in console //displaying message in console
bool customSpell = false; std::vector<std::string> logLines;
if(sc->affectedCres.size() == 1)
{ spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
const CStack * attackedStack = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false);
for(auto line : logLines)
console->addText(line);
const std::string attackedName = attackedStack->getName();
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
std::string text = CGI->generaltexth->allTexts[195];
if(sc->castedByHero)
{
boost::algorithm::replace_first(text, "%s", casterName);
boost::algorithm::replace_first(text, "%s", spellName);
boost::algorithm::replace_first(text, "%s", attackedNamePl); //target
}
else
{
auto getPluralText = [attackedStack](const int baseTextID) -> std::string
{
return CGI->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID+1 : baseTextID)];
};
bool plural = false; //add singular / plural form of creature text if this is true
int textID = 0;
switch(sc->id)
{
case SpellID::STONE_GAZE:
customSpell = true;
plural = true;
textID = 558;
break;
case SpellID::POISON:
customSpell = true;
plural = true;
textID = 561;
break;
case SpellID::BIND:
customSpell = true;
text = CGI->generaltexth->allTexts[560];
boost::algorithm::replace_first(text, "%s", attackedNamePl);
break;//Roots and vines bind the %s to the ground!
case SpellID::DISEASE:
customSpell = true;
plural = true;
textID = 553;
break;
case SpellID::PARALYZE:
customSpell = true;
plural = true;
textID = 563;
break;
case SpellID::AGE:
{
customSpell = true;
text = getPluralText(551);
boost::algorithm::replace_first(text, "%s", attackedName);
//The %s shrivel with age, and lose %d hit points."
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
}
break;
case SpellID::THUNDERBOLT:
text = CGI->generaltexth->allTexts[367];
boost::algorithm::replace_first(text, "%s", attackedNamePl);
console->addText(text);
text = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay)); //no more text afterwards
console->addText(text);
customSpell = true;
text = ""; //yeah, it's a terrible mess
break;
case SpellID::DISPEL_HELPFUL_SPELLS:
text = CGI->generaltexth->allTexts[555];
boost::algorithm::replace_first(text, "%s", attackedNamePl);
customSpell = true;
break;
case SpellID::DEATH_STARE:
customSpell = true;
if (sc->dmgToDisplay)
{
if (sc->dmgToDisplay > 1)
{
text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
boost::algorithm::replace_first(text, "%s", attackedNamePl);
}
else
{
text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%s", attackedNameSing);
}
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
}
else
text = "";
break;
default:
text = CGI->generaltexth->allTexts[565]; //The %s casts %s
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
}
if (plural)
{
text = getPluralText(textID);
boost::algorithm::replace_first(text, "%s", attackedName);
}
}
if (!customSpell && !sc->dmgToDisplay)
boost::algorithm::replace_first(text, "%s", spellName); //simple spell name
if (text.size())
console->addText(text);
}
else
{
std::string text = CGI->generaltexth->allTexts[196];
boost::algorithm::replace_first(text, "%s", casterName);
boost::algorithm::replace_first(text, "%s", spellName);
console->addText(text);
}
if(sc->dmgToDisplay && !customSpell)
{
std::string dmgInfo = CGI->generaltexth->allTexts[376];
boost::algorithm::replace_first(dmgInfo, "%s", spellName); //simple spell name
boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
console->addText(dmgInfo); //todo: casualties (?)
}
waitForAnims(); waitForAnims();
//mana absorption //mana absorption
if(sc->manaGained > 0) if(sc->manaGained > 0)
@ -1517,14 +1378,14 @@ void CBattleInterface::castThisSpell(SpellID spellID)
assert(castingHero); // code below assumes non-null hero assert(castingHero); // code below assumes non-null hero
sp = spellID.toSpell(); sp = spellID.toSpell();
spellSelMode = ANY_LOCATION; spellSelMode = ANY_LOCATION;
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp)); const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
if(ti.massive || ti.type == CSpell::NO_TARGET) if(ti.massive || ti.type == CSpell::NO_TARGET)
spellSelMode = NO_LOCATION; spellSelMode = NO_LOCATION;
else if(ti.type == CSpell::LOCATION && ti.clearAffected) else if(ti.type == CSpell::LOCATION && ti.clearAffected)
{ {
spellSelMode = FREE_LOCATION; spellSelMode = FREE_LOCATION;
} }
else if(ti.type == CSpell::CREATURE) else if(ti.type == CSpell::CREATURE)
{ {
@ -1532,11 +1393,11 @@ void CBattleInterface::castThisSpell(SpellID spellID)
spellSelMode = selectionTypeByPositiveness(*sp); spellSelMode = selectionTypeByPositiveness(*sp);
else else
spellSelMode = ANY_CREATURE; spellSelMode = ANY_CREATURE;
} }
else if(ti.type == CSpell::OBSTACLE) else if(ti.type == CSpell::OBSTACLE)
{ {
spellSelMode = OBSTACLE; spellSelMode = OBSTACLE;
} }
if (spellSelMode == NO_LOCATION) //user does not have to select location if (spellSelMode == NO_LOCATION) //user does not have to select location
{ {
@ -1558,33 +1419,57 @@ void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false)); addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
} }
void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect)
{
if(animation.pause > 0)
{
addNewAnim(new CDummyAnimation(this, animation.pause));
}
else
{
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
}
}
void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect)
{
const CSpell * spell = spellID.toSpell();
if(spell == nullptr)
return;
for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
{
displaySpellAnimation(animation, destinationTile, areaEffect);
}
}
void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect) void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
{ {
const CSpell * spell = spellID.toSpell(); const CSpell * spell = spellID.toSpell();
if(spell == nullptr) if(spell == nullptr)
return; return;
for(const CSpell::TAnimation & animation : spell->animationInfo.affect) for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
{ {
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM)); displaySpellAnimation(animation, destinationTile, areaEffect);
} }
} }
void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect) void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
{ {
const CSpell * spell = spellID.toSpell(); const CSpell * spell = spellID.toSpell();
if(spell == nullptr) if(spell == nullptr)
return; return;
for(const CSpell::TAnimation & animation : spell->animationInfo.hit) for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
{ {
addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM)); displaySpellAnimation(animation, destinationTile, areaEffect);
} }
} }
void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte) void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
{ {
const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID); const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
@ -1712,8 +1597,8 @@ void CBattleInterface::endCastingSpell()
{ {
assert(spellDestSelectMode); assert(spellDestSelectMode);
delete spellToCast; vstd::clear_pointer(spellToCast);
spellToCast = nullptr;
sp = nullptr; sp = nullptr;
spellDestSelectMode = false; spellDestSelectMode = false;
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
@ -1797,7 +1682,7 @@ void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, i
{ {
if (attacker) if (attacker)
formattedText.append(" "); formattedText.append(" ");
boost::format txt; boost::format txt;
if(killed > 1) if(killed > 1)
{ {
@ -2218,7 +2103,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
{ {
ui8 skill = 0; ui8 skill = 0;
if (creatureCasting) if (creatureCasting)
skill = sactive->valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, SpellID::TELEPORT)); skill = sactive->getSpellSchoolLevel(SpellID(SpellID::TELEPORT).toSpell());
else else
skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]); skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]);
//TODO: explicitely save power, skill //TODO: explicitely save power, skill
@ -2283,7 +2168,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
currentAction = selectedAction; currentAction = selectedAction;
else if (localActions.size()) //if not possible, select first available action 9they are sorted by suggested priority) else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
currentAction = localActions.front(); currentAction = localActions.front();
else //no legal action possible else //no legal action possible
{ {

View File

@ -1,12 +1,12 @@
#pragma once #pragma once
//#include "../../lib/CCreatureSet.h"
#include "../../lib/ConstTransitivePtr.h" //may be reundant #include "../../lib/ConstTransitivePtr.h" //may be reundant
#include "../../lib/GameConstants.h" #include "../../lib/GameConstants.h"
#include "CBattleAnimations.h" #include "CBattleAnimations.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
/* /*
* CBattleInterface.h, part of VCMI engine * CBattleInterface.h, part of VCMI engine
* *
@ -155,7 +155,7 @@ private:
shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
bool tacticsMode; bool tacticsMode;
bool stackCanCastSpell; //if true, active stack could possibly cats some target spell bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
bool creatureCasting; //if true, stack currently aims to cats a spell bool creatureCasting; //if true, stack currently aims to cats a spell
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
PossibleActions spellSelMode; PossibleActions spellSelMode;
@ -319,8 +319,12 @@ public:
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
void displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect = true);
void battleTriggerEffect(const BattleTriggerEffect & bte); void battleTriggerEffect(const BattleTriggerEffect & bte);
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex

View File

@ -69,6 +69,7 @@ void CBattleConsole::showAll(SDL_Surface * to)
bool CBattleConsole::addText(const std::string & text) bool CBattleConsole::addText(const std::string & text)
{ {
logGlobal->traceStream() <<"CBattleConsole message: "<<text;
if(text.size()>70) if(text.size()>70)
return false; //text too long! return false; //text too long!
int firstInToken = 0; int firstInToken = 0;

View File

@ -178,7 +178,7 @@ CCreatureAnimation::CCreatureAnimation(std::string name, TSpeedController contro
elem.r = reader.readUInt8(); elem.r = reader.readUInt8();
elem.g = reader.readUInt8(); elem.g = reader.readUInt8();
elem.b = reader.readUInt8(); elem.b = reader.readUInt8();
CSDL_Ext::colorSetAlpha(elem,0); elem.a = SDL_ALPHA_OPAQUE;
} }
for (int i=0; i<totalBlocks; i++) for (int i=0; i<totalBlocks; i++)

View File

@ -158,7 +158,7 @@ CDefFile::CDefFile(std::string Name):
palette[i].r = data[it++]; palette[i].r = data[it++];
palette[i].g = data[it++]; palette[i].g = data[it++];
palette[i].b = data[it++]; palette[i].b = data[it++];
CSDL_Ext::colorSetAlpha(palette[i],255); palette[i].a = SDL_ALPHA_OPAQUE;
} }
if (type == 71 || type == 64)//Buttons/buildings don't have shadows\semi-transparency if (type == 71 || type == 64)//Buttons/buildings don't have shadows\semi-transparency
memset(palette, 0, sizeof(SDL_Color)*2); memset(palette, 0, sizeof(SDL_Color)*2);
@ -355,7 +355,11 @@ void SDLImageLoader::init(Point SpriteSize, Point Margins, Point FullSize, SDL_C
image->fullSize = FullSize; image->fullSize = FullSize;
//Prepare surface //Prepare surface
SDL_SetColors(image->surf, pal, 0, 256); SDL_Palette * p = SDL_AllocPalette(256);
SDL_SetPaletteColors(p, pal, 0, 256);
SDL_SetSurfacePalette(image->surf, p);
SDL_FreePalette(p);
SDL_LockSurface(image->surf); SDL_LockSurface(image->surf);
lineStart = position = (ui8*)image->surf->pixels; lineStart = position = (ui8*)image->surf->pixels;
} }

View File

@ -59,11 +59,6 @@ inline bool isShiftKeyDown()
} }
namespace CSDL_Ext namespace CSDL_Ext
{ {
//todo: remove
STRONG_INLINE void colorSetAlpha(SDL_Color & color, Uint8 alpha)
{
color.a = alpha;
}
//todo: should this better be assignment operator? //todo: should this better be assignment operator?
STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source) STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source)
{ {

View File

@ -302,7 +302,7 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
{ {
const CArmedInstance * selectedObj = owner->armedObjs[selection->upg]; const CArmedInstance * selectedObj = owner->armedObjs[selection->upg];
if (!creature && selectedObj->stacksCount() == 1) if (!creature && selectedObj->stacksCount() == 1)
LOCPLINT->cb->splitStack(selectedObj, owner->armedObjs[upg], selection->ID, ID, myStack->count - 1); LOCPLINT->cb->splitStack(selectedObj, owner->armedObjs[upg], selection->ID, ID, selection->myStack->count - 1);
else else
LOCPLINT->cb->swapCreatures(owner->armedObjs[upg], owner->armedObjs[selection->upg], ID, selection->ID); LOCPLINT->cb->swapCreatures(owner->armedObjs[upg], owner->armedObjs[selection->upg], ID, selection->ID);
} }

View File

@ -361,40 +361,35 @@ void MoraleLuckBox::set(const IBonusBearer *node)
const int hoverTextBase[] = {7, 4}; const int hoverTextBase[] = {7, 4};
const Bonus::BonusType bonusType[] = {Bonus::LUCK, Bonus::MORALE}; const Bonus::BonusType bonusType[] = {Bonus::LUCK, Bonus::MORALE};
int (IBonusBearer::*getValue[])() const = {&IBonusBearer::LuckVal, &IBonusBearer::MoraleVal}; int (IBonusBearer::*getValue[])() const = {&IBonusBearer::LuckVal, &IBonusBearer::MoraleVal};
TBonusListPtr modifierList(new BonusList());
int mrlt = -9;
TModDescr mrl;
if (node) if (node)
{ {
node->getModifiersWDescr(mrl, bonusType[morale]); modifierList = node->getBonuses(Selector::type(bonusType[morale]));
bonusValue = (node->*getValue[morale])(); bonusValue = (node->*getValue[morale])();
} }
else else
bonusValue = 0; bonusValue = 0;
mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good int mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt]; hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt];
baseType = componentType[morale]; baseType = componentType[morale];
text = CGI->generaltexth->arraytxt[textId[morale]]; text = CGI->generaltexth->arraytxt[textId[morale]];
boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]); boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
if (!mrl.size())
text += CGI->generaltexth->arraytxt[noneTxtId]; if (morale && node && (node->hasBonusOfType(Bonus::UNDEAD)
|| node->hasBonusOfType(Bonus::BLOCK_MORALE)
|| node->hasBonusOfType(Bonus::NON_LIVING)))
text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
else if(modifierList->empty())
text += CGI->generaltexth->arraytxt[noneTxtId];//no modifiers
else else
{ {
//it's a creature window for(const Bonus * elem : *modifierList)
if ((morale && node && node->hasBonusOfType(Bonus::UNDEAD)) ||
node->hasBonusOfType(Bonus::BLOCK_MORALE) || node->hasBonusOfType(Bonus::NON_LIVING))
{ {
text += CGI->generaltexth->arraytxt[113]; //unaffected by morale if(elem->val != 0)
} //no bonuses with value 0
else text += "\n" + elem->Description();
{
for(auto & elem : mrl)
{
if (elem.first) //no bonuses with value 0
text += "\n" + elem.second;
}
} }
} }

View File

@ -498,10 +498,10 @@ void CTextInput::setText( const std::string &nText, bool callCb )
bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key) bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
{ {
if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER) if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER || key.keysym.sym == SDLK_ESCAPE)
return false; return false;
return false; return true;
} }
void CTextInput::textInputed(const SDL_TextInputEvent & event) void CTextInput::textInputed(const SDL_TextInputEvent & event)

View File

@ -135,7 +135,7 @@ void CQuestLog::init()
minimap = new CQuestMinimap (Rect (12, 12, 169, 169)); minimap = new CQuestMinimap (Rect (12, 12, 169, 169));
// TextBox have it's own 4 pixel padding from top at least for English. To achieve 10px from both left and top only add 6px margin // TextBox have it's own 4 pixel padding from top at least for English. To achieve 10px from both left and top only add 6px margin
description = new CTextBox ("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, TOPLEFT, Colors::WHITE); description = new CTextBox ("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, TOPLEFT, Colors::WHITE);
ok = new CButton(Point(539, 398), "IOKAY.DEF", CGI->generaltexth->zelp[445], boost::bind(&CQuestLog::close,this), SDLK_RETURN); ok = new CButton(Point(539, 398), "IOKAY.DEF", CGI->generaltexth->zelp[445], std::bind(&CQuestLog::close,this), SDLK_RETURN);
// Both button and lable are shifted to -2px by x and y to not make them actually look like they're on same line with quests list and ok button // Both button and lable are shifted to -2px by x and y to not make them actually look like they're on same line with quests list and ok button
hideCompleteButton = new CToggleButton(Point(10, 396), "sysopchk.def", CButton::tooltip(texts["hideComplete"]), std::bind(&CQuestLog::toggleComplete, this, _1)); hideCompleteButton = new CToggleButton(Point(10, 396), "sysopchk.def", CButton::tooltip(texts["hideComplete"]), std::bind(&CQuestLog::toggleComplete, this, _1));
hideCompleteLabel = new CLabel(46, 398, FONT_MEDIUM, TOPLEFT, Colors::WHITE, texts["hideComplete"]["label"].String()); hideCompleteLabel = new CLabel(46, 398, FONT_MEDIUM, TOPLEFT, Colors::WHITE, texts["hideComplete"]["label"].String());
@ -182,7 +182,7 @@ void CQuestLog::recreateLabelList()
auto label = make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString()); auto label = make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString());
label->disable(); label->disable();
label->callback = boost::bind(&CQuestLog::selectQuest, this, i, currentLabel); label->callback = std::bind(&CQuestLog::selectQuest, this, i, currentLabel);
labels.push_back(label); labels.push_back(label);
// Select latest active quest // Select latest active quest

View File

@ -11,8 +11,8 @@
], ],
"specialties": "specialties":
[ [
{ "type":10, "val": 1, "subtype": 5, "info": 0 } { "type":1, "val": 0, "subtype": 0, "info": 106 }
], ],
"army" : "army" :
[ [
{ {
@ -41,7 +41,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 106 } { "type":1, "val": 0, "subtype": 0, "info": 98 }
] ]
}, },
"wystan": "wystan":
@ -56,7 +56,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 98 } { "type":1, "val": 0, "subtype": 0, "info": 100 }
] ]
}, },
"tazar": "tazar":
@ -70,7 +70,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 100 } { "type":2, "val": 5, "subtype": 23, "info": 0 }
] ]
}, },
"alkin": "alkin":
@ -85,7 +85,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":2, "val": 5, "subtype": 23, "info": 0 } { "type":1, "val": 0, "subtype": 0, "info": 102 }
] ]
}, },
"korbac": "korbac":
@ -100,7 +100,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 102 } { "type":1, "val": 0, "subtype": 0, "info": 104 }
] ]
}, },
"gerwulf": "gerwulf":
@ -115,7 +115,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 104 } { "type":1, "val": 0, "subtype": 0, "info": 146 }
] ]
}, },
"broghild": "broghild":
@ -130,7 +130,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 146 } { "type":1, "val": 0, "subtype": 0, "info": 108 }
] ]
}, },
"mirlanda": "mirlanda":
@ -145,7 +145,7 @@
], ],
"specialties": "specialties":
[ [
{ "type":1, "val": 0, "subtype": 0, "info": 108 } { "type":8, "val": 0, "subtype": 45, "info": 0 }
] ]
}, },
"rosic": "rosic":

View File

@ -12,6 +12,10 @@
"type": "array", "type": "array",
"items":{ "items":{
"anyOf":[ "anyOf":[
{
//dummy animation, pause, Value - frame count
"type": "number"
},
{ {
//assumed verticalPosition: top //assumed verticalPosition: top
"type": "string", "type": "string",

View File

@ -242,7 +242,9 @@
"removeObstacle" : { "removeObstacle" : {
"index" : 64, "index" : 64,
"targetType" : "OBSTACLE", "targetType" : "OBSTACLE",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "REMOVEOB" "cast": "REMOVEOB"
}, },
@ -258,7 +260,9 @@
"clone" : { "clone" : {
"index" : 65, "index" : 65,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "CLONE" "cast": "CLONE"
}, },
@ -278,7 +282,9 @@
"fireElemental" : { "fireElemental" : {
"index" : 66, "index" : 66,
"targetType" : "NO_TARGET", "targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "SUMNELM" "cast": "SUMNELM"
}, },
@ -294,7 +300,9 @@
"earthElemental" : { "earthElemental" : {
"index" : 67, "index" : 67,
"targetType" : "NO_TARGET", "targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "SUMNELM" "cast": "SUMNELM"
}, },
@ -310,7 +318,9 @@
"waterElemental" : { "waterElemental" : {
"index" : 68, "index" : 68,
"targetType" : "NO_TARGET", "targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "SUMNELM" "cast": "SUMNELM"
}, },
@ -326,7 +336,9 @@
"airElemental" : { "airElemental" : {
"index" : 69, "index" : 69,
"targetType" : "NO_TARGET", "targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": { "sounds": {
"cast": "SUMNELM" "cast": "SUMNELM"
}, },

View File

@ -1,17 +1,17 @@
{ {
"shield" : { "shield" : {
"index" : 27, "index" : 27,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C13SPE0"] "affect":["C13SPE0"]
}, },
"sounds": { "sounds": {
"cast": "SHIELD" "cast": "SHIELD"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"generalDamageReduction" : { "generalDamageReduction" : {
@ -32,16 +32,16 @@
"airShield" : { "airShield" : {
"index" : 28, "index" : 28,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C01SPA0"] "affect":["C01SPA0"]
}, },
"sounds": { "sounds": {
"cast": "AIRSHELD" "cast": "AIRSHELD"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"generalDamageReduction" : { "generalDamageReduction" : {
@ -62,19 +62,19 @@
"fireShield" : { "fireShield" : {
"index" : 29, "index" : 29,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C05SPF0"] "affect":["C05SPF0"]
}, },
"sounds": { "sounds": {
"cast": "FIRESHIE" "cast": "FIRESHIE"
}, },
// It looks that fireshield has two separate sounds // It looks that fireshield has two separate sounds
// "soundfile":"FIRESHLD.wav" // "soundfile":"FIRESHLD.wav"
// //
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"fireShield" : { "fireShield" : {
@ -82,9 +82,6 @@
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
},
"expert":{
"range" : "X"
} }
}, },
"flags" : { "flags" : {
@ -94,10 +91,10 @@
"protectAir" : { "protectAir" : {
"index" : 30, "index" : 30,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C11SPE0"] "affect":["C11SPE0"]
}, },
"sounds": { "sounds": {
"cast": "PROTECTA" "cast": "PROTECTA"
}, },
@ -124,10 +121,10 @@
"protectFire" : { "protectFire" : {
"index" : 31, "index" : 31,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C11SPW0"] "affect":["C11SPW0"]
}, },
"sounds": { "sounds": {
"cast": "PROTECTF" "cast": "PROTECTF"
}, },
@ -154,16 +151,16 @@
"protectWater" : { "protectWater" : {
"index" : 32, "index" : 32,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C11SPF0"] "affect":["C11SPF0"]
}, },
"sounds": { "sounds": {
"cast": "PROTECTW" "cast": "PROTECTW"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"spellDamageReduction" : { "spellDamageReduction" : {
@ -184,10 +181,10 @@
"protectEarth" : { "protectEarth" : {
"index" : 33, "index" : 33,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C13SPA0"] "affect":["C13SPA0"]
}, },
"sounds": { "sounds": {
"cast": "PROTECTE" "cast": "PROTECTE"
}, },
@ -214,10 +211,10 @@
"antiMagic" : { "antiMagic" : {
"index" : 34, "index" : 34,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C02SPE0"] "affect":["C02SPE0"]
}, },
"sounds": { "sounds": {
"cast": "ANTIMAGK" "cast": "ANTIMAGK"
}, },
@ -227,7 +224,7 @@
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"levelSpellImmunity" : { "levelSpellImmunity" : {
"val" : 3, "val" : 3,
"type" : "LEVEL_SPELL_IMMUNITY", "type" : "LEVEL_SPELL_IMMUNITY",
"valueType" : "INDEPENDENT_MAX", "valueType" : "INDEPENDENT_MAX",
"duration" : "N_TURNS" "duration" : "N_TURNS"
@ -257,10 +254,10 @@
"magicMirror" : { "magicMirror" : {
"index" : 36, "index" : 36,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C02SPA0"] "affect":["C02SPA0"]
}, },
"sounds": { "sounds": {
"cast": "BACKLASH" "cast": "BACKLASH"
}, },
@ -284,16 +281,16 @@
"bless" : { "bless" : {
"index" : 41, "index" : 41,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C01SPW"] //C01SPW0 "affect":["C01SPW"] //C01SPW0
}, },
"sounds": { "sounds": {
"cast": "BLESS" "cast": "BLESS"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"alwaysMaximumDamage" : { "alwaysMaximumDamage" : {
@ -322,16 +319,16 @@
"curse" : { "curse" : {
"index" : 42, "index" : 42,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C04SPW"]//C04SPW0 "affect":["C04SPW"]//C04SPW0
}, },
"sounds": { "sounds": {
"cast": "CURSE" "cast": "CURSE"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"alwaysMinimumDamage" : { "alwaysMinimumDamage" : {
@ -362,20 +359,20 @@
"bloodlust" : { "bloodlust" : {
"index" : 43, "index" : 43,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["SP12_"] //??? "affect":["SP12_"] //???
}, },
"sounds": { "sounds": {
"cast": "BLOODLUS" "cast": "BLOODLUS"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"primarySkill" : { "primarySkill" : {
"val" : 3, "val" : 3,
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.attack", "subtype" : "primSkill.attack",
"effectRange" : "ONLY_MELEE_FIGHT", "effectRange" : "ONLY_MELEE_FIGHT",
@ -412,10 +409,10 @@
"precision" : { "precision" : {
"index" : 44, "index" : 44,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C12SPA0"] "affect":["C12SPA0"]
}, },
"sounds": { "sounds": {
"cast": "PRECISON" "cast": "PRECISON"
}, },
@ -459,22 +456,22 @@
"weakness" : { "weakness" : {
"index" : 45, "index" : 45,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C0ACID"] "affect":["C0ACID"]
}, },
"sounds": { "sounds": {
"cast": "WEAKNESS" "cast": "WEAKNESS"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"primarySkill" : { "primarySkill" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.attack", "subtype" : "primSkill.attack",
"val" : -3, "val" : -3,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -506,22 +503,22 @@
"stoneSkin" : { "stoneSkin" : {
"index" : 46, "index" : 46,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C16SPE"] //C16SPE0 "affect":["C16SPE"] //C16SPE0
}, },
"sounds": { "sounds": {
"cast": "TUFFSKIN" "cast": "TUFFSKIN"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"primarySkill" : { "primarySkill" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence", "subtype" : "primSkill.defence",
"val" : 3, "val" : 3,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -549,11 +546,11 @@
"disruptingRay" : { "disruptingRay" : {
"index" : 47, "index" : 47,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C07SPA1"], "affect":["C07SPA1"],
"projectile":[{"defName":"C07SPA0"}]//??? "projectile":[{"defName":"C07SPA0"}]//???
}, },
"sounds": { "sounds": {
"cast": "DISRUPTR" "cast": "DISRUPTR"
}, },
@ -565,7 +562,7 @@
"primarySkill" : { "primarySkill" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence", "subtype" : "primSkill.defence",
"val" : -3, "val" : -3,
"valueType" : "ADDITIVE_VALUE", "valueType" : "ADDITIVE_VALUE",
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
@ -593,34 +590,34 @@
"prayer" : { "prayer" : {
"index" : 48, "index" : 48,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}] "affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
}, },
"sounds": { "sounds": {
"cast": "PRAYER" "cast": "PRAYER"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"attack" : { "attack" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.attack", "subtype" : "primSkill.attack",
"val" : 2, "val" : 2,
"duration" : "N_TURNS" "duration" : "N_TURNS"
}, },
"defence" : { "defence" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence", "subtype" : "primSkill.defence",
"val" : 2, "val" : 2,
"duration" : "N_TURNS" "duration" : "N_TURNS"
}, },
"stacksSpeed" : { "stacksSpeed" : {
"addInfo" : 0, "addInfo" : 0,
"type" : "STACKS_SPEED", "type" : "STACKS_SPEED",
"val" : 2, "val" : 2,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -660,21 +657,21 @@
"mirth" : { "mirth" : {
"index" : 49, "index" : 49,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C09SPW0"] "affect":["C09SPW0"]
}, },
"sounds": { "sounds": {
"cast": "MIRTH" "cast": "MIRTH"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"morale" : { "morale" : {
"type" : "MORALE", "type" : "MORALE",
"val" : 1, "val" : 1,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -700,7 +697,7 @@
}, },
"absoluteImmunity":{ "absoluteImmunity":{
"SIEGE_WEAPON": true, "SIEGE_WEAPON": true,
"UNDEAD": true, "UNDEAD": true,
}, },
"immunity" : { "immunity" : {
"MIND_IMMUNITY": true, "MIND_IMMUNITY": true,
@ -713,21 +710,21 @@
"sorrow" : { "sorrow" : {
"index" : 50, "index" : 50,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C14SPE0"] "affect":["C14SPE0"]
}, },
"sounds": { "sounds": {
"cast": "SORROW" "cast": "SORROW"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"morale" : { "morale" : {
"type" : "MORALE", "type" : "MORALE",
"val" : -1, "val" : -1,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -753,7 +750,7 @@
}, },
"absoluteImmunity":{ "absoluteImmunity":{
"SIEGE_WEAPON": true, "SIEGE_WEAPON": true,
"UNDEAD": true, "UNDEAD": true,
}, },
"immunity" : { "immunity" : {
"MIND_IMMUNITY": true, "MIND_IMMUNITY": true,
@ -766,21 +763,21 @@
"fortune" : { "fortune" : {
"index" : 51, "index" : 51,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C09SPA0"] "affect":["C09SPA0"]
}, },
"sounds": { "sounds": {
"cast": "FORTUNE" "cast": "FORTUNE"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"luck" : { "luck" : {
"type" : "LUCK", "type" : "LUCK",
"val" : 1, "val" : 1,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -811,21 +808,21 @@
"misfortune" : { "misfortune" : {
"index" : 52, "index" : 52,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C10SPF0"] "affect":["C10SPF0"]
}, },
"sounds": { "sounds": {
"cast": "MISFORT" "cast": "MISFORT"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"luck" : { "luck" : {
"type" : "LUCK", "type" : "LUCK",
"val" : -1, "val" : -1,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -856,22 +853,22 @@
"haste" : { "haste" : {
"index" : 53, "index" : 53,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C15SPA0"] "affect":["C15SPA0"]
}, },
"sounds": { "sounds": {
"cast": "HASTE" "cast": "HASTE"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"stacksSpeed" : { "stacksSpeed" : {
"addInfo" : 0, "addInfo" : 0,
"type" : "STACKS_SPEED", "type" : "STACKS_SPEED",
"val" : 3, "val" : 3,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -905,22 +902,22 @@
"slow" : { "slow" : {
"index" : 54, "index" : 54,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}] "affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}]
}, },
"sounds": { "sounds": {
"cast": "MUCKMIRE" "cast": "MUCKMIRE"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"stacksSpeed" : { "stacksSpeed" : {
"addInfo" : 0, "addInfo" : 0,
"type" : "STACKS_SPEED", "type" : "STACKS_SPEED",
"val" : -25, "val" : -25,
"valueType" : "PERCENT_TO_ALL", "valueType" : "PERCENT_TO_ALL",
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
@ -956,10 +953,10 @@
"slayer" : { "slayer" : {
"index" : 55, "index" : 55,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C13SPW0"] "affect":["C13SPW0"]
}, },
"sounds": { "sounds": {
"cast": "SLAYER" "cast": "SLAYER"
}, },
@ -1010,10 +1007,10 @@
"frenzy" : { "frenzy" : {
"index" : 56, "index" : 56,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C08SPF0"] "affect":["C08SPF0"]
}, },
"sounds": { "sounds": {
"cast": "FRENZY" "cast": "FRENZY"
}, },
@ -1024,8 +1021,9 @@
"effects" : { "effects" : {
"inFrenzy" : { "inFrenzy" : {
"type" : "IN_FRENZY", "type" : "IN_FRENZY",
"val" : 100, "val" : 100,
"duration" : "STACK_GETS_TURN" "duration" : "N_TURNS",
"turns" : 1
} }
} }
}, },
@ -1055,21 +1053,21 @@
"counterstrike" : { "counterstrike" : {
"index" : 58, "index" : 58,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C04SPA0"] "affect":["C04SPA0"]
}, },
"sounds": { "sounds": {
"cast": "CNTRSTRK" "cast": "CNTRSTRK"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"additionalRetaliation" : { "additionalRetaliation" : {
"type" : "ADDITIONAL_RETALIATION", "type" : "ADDITIONAL_RETALIATION",
"val" : 1, "val" : 1,
"duration" : "N_TURNS" "duration" : "N_TURNS"
} }
} }
@ -1099,11 +1097,11 @@
}, },
"berserk" : { "berserk" : {
"index" : 59, "index" : 59,
"targetType" : "CREATURE", "targetType" : "LOCATION",
"animation":{ "animation":{
"affect":["C01SPF"] //C01SPF0 "affect":["C01SPF"] //C01SPF0
}, },
"sounds": { "sounds": {
"cast": "BERSERK" "cast": "BERSERK"
}, },
@ -1114,7 +1112,8 @@
"effects" : { "effects" : {
"attacksNearestCreature" : { "attacksNearestCreature" : {
"type" : "ATTACKS_NEAREST_CREATURE", "type" : "ATTACKS_NEAREST_CREATURE",
"duration" : "N_TURNS" "duration" : "N_TURNS",
"turns" : 1
} }
} }
}, },
@ -1164,10 +1163,10 @@
"hypnotize" : { "hypnotize" : {
"index" : 60, "index" : 60,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C10SPA0"] "affect":["C10SPA0"]
}, },
"sounds": { "sounds": {
"cast": "HYPNOTIZ" "cast": "HYPNOTIZ"
}, },
@ -1225,17 +1224,17 @@
}, },
"forgetfulness" : { "forgetfulness" : {
"index" : 61, "index" : 61,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C06SPW"]//C06SPW0 "affect":["C06SPW"]//C06SPW0
}, },
"sounds": { "sounds": {
"cast": "FORGET" "cast": "FORGET"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "effects" : {
"forgetful" : { "forgetful" : {
@ -1292,10 +1291,10 @@
"blind" : { "blind" : {
"index" : 62, "index" : 62,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C02SPF0"] "affect":["C02SPF0"]
}, },
"sounds": { "sounds": {
"cast": "BLIND" "cast": "BLIND"
}, },

View File

@ -763,7 +763,7 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
CStack::CStack(const CStackInstance *Base, PlayerColor O, int I, bool AO, SlotID S) CStack::CStack(const CStackInstance *Base, PlayerColor O, int I, bool AO, SlotID S)
: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO), : base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),
counterAttacks(1) counterAttacksPerformed(0),counterAttacksTotalCache(0), cloneID(-1)
{ {
assert(base); assert(base);
type = base->type; type = base->type;
@ -776,7 +776,8 @@ CStack::CStack()
setNodeType(STACK_BATTLE); setNodeType(STACK_BATTLE);
} }
CStack::CStack(const CStackBasicDescriptor *stack, PlayerColor O, int I, bool AO, SlotID S) CStack::CStack(const CStackBasicDescriptor *stack, PlayerColor O, int I, bool AO, SlotID S)
: base(nullptr), ID(I), owner(O), slot(S), attackerOwned(AO), counterAttacks(1) : base(nullptr), ID(I), owner(O), slot(S), attackerOwned(AO), counterAttacksPerformed(0),
cloneID(-1)
{ {
type = stack->type; type = stack->type;
count = baseAmount = stack->count; count = baseAmount = stack->count;
@ -794,7 +795,9 @@ void CStack::init()
slot = SlotID(255); slot = SlotID(255);
attackerOwned = false; attackerOwned = false;
position = BattleHex(); position = BattleHex();
counterAttacks = -1; counterAttacksPerformed = 0;
counterAttacksTotalCache = 0;
cloneID = -1;
} }
void CStack::postInit() void CStack::postInit()
@ -804,9 +807,11 @@ void CStack::postInit()
firstHPleft = MaxHealth(); firstHPleft = MaxHealth();
shots = getCreature()->valOfBonuses(Bonus::SHOTS); shots = getCreature()->valOfBonuses(Bonus::SHOTS);
counterAttacks = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION); counterAttacksPerformed = 0;
counterAttacksTotalCache = 0;
casts = valOfBonuses(Bonus::CASTS); casts = valOfBonuses(Bonus::CASTS);
resurrected = 0; resurrected = 0;
cloneID = -1;
} }
ui32 CStack::level() const ui32 CStack::level() const
@ -848,10 +853,10 @@ void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
for(Bonus& b : tmp) for(Bonus& b : tmp)
{ {
b.turnsRemain = sse.turnsRemain; if(b.turnsRemain == 0)
b.turnsRemain = sse.turnsRemain;
sf.push_back(b); sf.push_back(b);
} }
} }
bool CStack::willMove(int turn /*= 0*/) const bool CStack::willMove(int turn /*= 0*/) const
@ -1129,12 +1134,25 @@ bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defen
bool CStack::ableToRetaliate() const //FIXME: crash after clone is killed bool CStack::ableToRetaliate() const //FIXME: crash after clone is killed
{ {
return alive() return alive()
&& (counterAttacks > 0 || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS)) && (counterAttacksPerformed < counterAttacksTotal() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
&& !hasBonusOfType(Bonus::SIEGE_WEAPON) && !hasBonusOfType(Bonus::SIEGE_WEAPON)
&& !hasBonusOfType(Bonus::HYPNOTIZED) && !hasBonusOfType(Bonus::HYPNOTIZED)
&& !hasBonusOfType(Bonus::NO_RETALIATION); && !hasBonusOfType(Bonus::NO_RETALIATION);
} }
ui8 CStack::counterAttacksTotal() const
{
//after dispell bonus should remain during current round
ui8 val = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION);
vstd::amax(counterAttacksTotalCache, val);
return counterAttacksTotalCache;
}
si8 CStack::counterAttacksRemaining() const
{
return counterAttacksTotal() - counterAttacksPerformed;
}
std::string CStack::getName() const std::string CStack::getName() const
{ {
return (count > 1) ? type->namePl : type->nameSing; //War machines can't use base return (count > 1) ? type->namePl : type->nameSing; //War machines can't use base
@ -1152,6 +1170,21 @@ bool CStack::canBeHealed() const
&& !hasBonusOfType(Bonus::SIEGE_WEAPON); && !hasBonusOfType(Bonus::SIEGE_WEAPON);
} }
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
{
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
vstd::abetween(skill, 0, 3);
return skill;
}
ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
{
//stacks does not have spellpower etc. (yet?)
return base;
}
bool CMP_stack::operator()( const CStack* a, const CStack* b ) bool CMP_stack::operator()( const CStack* a, const CStack* b )
{ {
switch(phase) switch(phase)

View File

@ -1,5 +1,14 @@
#pragma once /*
* BattleState.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "BattleHex.h" #include "BattleHex.h"
#include "HeroBonus.h" #include "HeroBonus.h"
@ -12,16 +21,7 @@
#include "GameConstants.h" #include "GameConstants.h"
#include "CBattleCallback.h" #include "CBattleCallback.h"
#include "int3.h" #include "int3.h"
#include "spells/Magic.h"
/*
* BattleState.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
class CGHeroInstance; class CGHeroInstance;
class CStack; class CStack;
@ -159,7 +159,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
static int battlefieldTypeToTerrain(int bfieldType); //converts above to ERM BI format static int battlefieldTypeToTerrain(int bfieldType); //converts above to ERM BI format
}; };
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor, public ISpellCaster
{ {
public: public:
const CStackInstance *base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) const CStackInstance *base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
@ -171,11 +171,15 @@ public:
SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures) SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures)
bool attackerOwned; //if true, this stack is owned by attakcer (this one from left hand side of battle) bool attackerOwned; //if true, this stack is owned by attakcer (this one from left hand side of battle)
BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
ui8 counterAttacks; //how many counter attacks can be performed more in this turn (by default set at the beginning of the round to 1) ///how many times this stack has been counterattacked this round
ui8 counterAttacksPerformed;
///cached total count of counterattacks; should be cleared each round;do not serialize
mutable ui8 counterAttacksTotalCache;
si16 shots; //how many shots left si16 shots; //how many shots left
ui8 casts; //how many casts left ui8 casts; //how many casts left
TQuantity resurrected; // these units will be taken back after battle is over TQuantity resurrected; // these units will be taken back after battle is over
///id of alive clone of this stack clone if any
si32 cloneID;
std::set<EBattleStackState::EBattleStackState> state; std::set<EBattleStackState::EBattleStackState> state;
//overrides //overrides
const CCreature* getCreature() const {return type;} const CCreature* getCreature() const {return type;}
@ -191,6 +195,10 @@ public:
std::string getName() const; //plural or singular std::string getName() const; //plural or singular
bool willMove(int turn = 0) const; //if stack has remaining move this turn bool willMove(int turn = 0) const; //if stack has remaining move this turn
bool ableToRetaliate() const; //if stack can retaliate after attacked bool ableToRetaliate() const; //if stack can retaliate after attacked
///how many times this stack can counterattack in one round
ui8 counterAttacksTotal() const;
///how many times this stack can counterattack in one round more
si8 counterAttacksRemaining() const;
bool moved(int turn = 0) const; //if stack was already moved this turn bool moved(int turn = 0) const; //if stack was already moved this turn
bool waited(int turn = 0) const; bool waited(int turn = 0) const;
bool canMove(int turn = 0) const; //if stack can move bool canMove(int turn = 0) const; //if stack can move
@ -222,12 +230,16 @@ public:
std::pair<int,int> countKilledByAttack(int damageReceived) const; //returns pair<killed count, new left HP> std::pair<int,int> countKilledByAttack(int damageReceived) const; //returns pair<killed count, new left HP>
void prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand, boost::optional<int> customCount = boost::none) const; //requires bsa.damageAmout filled void prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand, boost::optional<int> customCount = boost::none) const; //requires bsa.damageAmout filled
///ISpellCaster
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
assert(isIndependentNode()); assert(isIndependentNode());
h & static_cast<CBonusSystemNode&>(*this); h & static_cast<CBonusSystemNode&>(*this);
h & static_cast<CStackBasicDescriptor&>(*this); h & static_cast<CStackBasicDescriptor&>(*this);
h & ID & baseAmount & firstHPleft & owner & slot & attackerOwned & position & state & counterAttacks h & ID & baseAmount & firstHPleft & owner & slot & attackerOwned & position & state & counterAttacksPerformed
& shots & casts & count & resurrected; & shots & casts & count & resurrected;
const CArmedInstance *army = (base ? base->armyObj : nullptr); const CArmedInstance *army = (base ? base->armyObj : nullptr);

View File

@ -28,8 +28,8 @@ DLL_LINKAGE CConsoleHandler * console = nullptr;
#define CONSOLE_TEAL "\x1b[1;36m" #define CONSOLE_TEAL "\x1b[1;36m"
#else #else
#include <Windows.h> #include <Windows.h>
#include <dbghelp.h>
#ifndef __MINGW32__ #ifndef __MINGW32__
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib") #pragma comment(lib, "dbghelp.lib")
#endif #endif
typedef WORD TColor; typedef WORD TColor;
@ -121,7 +121,6 @@ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception)
const DWORD threadId = ::GetCurrentThreadId(); const DWORD threadId = ::GetCurrentThreadId();
logGlobal->errorStream() << "Thread ID: " << threadId << " [" << std::dec << std::setw(0) << threadId << "]"; logGlobal->errorStream() << "Thread ID: " << threadId << " [" << std::dec << std::setw(0) << threadId << "]";
#ifndef __MINGW32__
//exception info to be placed in the dump //exception info to be placed in the dump
MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE}; MINIDUMP_EXCEPTION_INFORMATION meinfo = {threadId, exception, TRUE};
@ -140,7 +139,6 @@ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception)
HANDLE dfile = CreateFileA(mname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); HANDLE dfile = CreateFileA(mname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
logGlobal->errorStream() << "Crash info will be put in " << mname; logGlobal->errorStream() << "Crash info will be put in " << mname;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dfile, MiniDumpWithDataSegs, &meinfo, 0, 0); MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dfile, MiniDumpWithDataSegs, &meinfo, 0, 0);
#endif
MessageBoxA(0, "VCMI has crashed. We are sorry. File with information about encountered problem has been created.", "VCMI Crashhandler", MB_OK | MB_ICONERROR); MessageBoxA(0, "VCMI has crashed. We are sorry. File with information about encountered problem has been created.", "VCMI Crashhandler", MB_OK | MB_ICONERROR);
return EXCEPTION_EXECUTE_HANDLER; return EXCEPTION_EXECUTE_HANDLER;
} }

View File

@ -206,12 +206,24 @@ std::string CLegacyConfigParser::extractQuotedString()
{ {
ret += extractQuotedPart(); ret += extractQuotedPart();
// double quote - add it to string and continue unless // double quote - add it to string and continue quoted part
// line terminated using tabulation if (curr < end && *curr == '\"')
if (curr < end && *curr == '\"' && *curr != '\t')
{ {
ret += '\"'; ret += '\"';
} }
//extract normal part
else if(curr < end && *curr != '\t' && *curr != '\r')
{
char * begin = curr;
while (curr < end && *curr != '\t' && *curr != '\r' && *curr != '\"')//find end of string or next quoted part start
curr++;
ret += std::string(begin, curr);
if(curr>=end || *curr != '\"')
return ret;
}
else // end of string else // end of string
return ret; return ret;
} }

View File

@ -27,8 +27,8 @@
#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 = 753; const ui32 version = 754;
const ui32 minSupportedVersion = version; const ui32 minSupportedVersion = 753;
class CISer; class CISer;
class COSer; class COSer;

View File

@ -864,7 +864,10 @@ public:
WALKING_DEAD = 58, WALKING_DEAD = 58,
WIGHTS = 60, WIGHTS = 60,
LICHES = 64, LICHES = 64,
BONE_DRAGON = 68,
TROGLODYTES = 70, TROGLODYTES = 70,
HYDRA = 110,
CHAOS_HYDRA = 111,
AIR_ELEMENTAL = 112, AIR_ELEMENTAL = 112,
EARTH_ELEMENTAL = 113, EARTH_ELEMENTAL = 113,
FIRE_ELEMENTAL = 114, FIRE_ELEMENTAL = 114,

View File

@ -211,21 +211,8 @@ Bonus * BonusList::getFirst(const CSelector &select)
return nullptr; return nullptr;
} }
void BonusList::getModifiersWDescr(TModDescr &out) const
{
for (auto & elem : bonuses)
{
Bonus *b = elem;
out.push_back(std::make_pair(b->val, b->Description()));
}
}
void BonusList::getBonuses(BonusList & out, const CSelector &selector) const void BonusList::getBonuses(BonusList & out, const CSelector &selector) const
{ {
// for(Bonus *i : *this)
// if(selector(i) && i->effectRange == Bonus::NO_LIMIT)
// out.push_back(i);
getBonuses(out, selector, nullptr); getBonuses(out, selector, nullptr);
} }
@ -359,17 +346,6 @@ bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype /*= -1*/) c
return hasBonus(s, cachingStr.str()); return hasBonus(s, cachingStr.str());
} }
void IBonusBearer::getModifiersWDescr(TModDescr &out, Bonus::BonusType type, int subtype /*= -1 */) const
{
std::stringstream cachingStr;
cachingStr << "type_" << type << "s_" << subtype;
getModifiersWDescr(out, subtype != -1 ? Selector::typeSubtype(type, subtype) : Selector::type(type), cachingStr.str());
}
void IBonusBearer::getModifiersWDescr(TModDescr &out, const CSelector &selector, const std::string &cachingStr /* =""*/) const
{
getBonuses(selector, cachingStr)->getModifiersWDescr(out);
}
int IBonusBearer::getBonusesCount(Bonus::BonusSource from, int id) const int IBonusBearer::getBonusesCount(Bonus::BonusSource from, int id) const
{ {
std::stringstream cachingStr; std::stringstream cachingStr;
@ -524,8 +500,13 @@ bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" b
const TBonusListPtr IBonusBearer::getSpellBonuses() const const TBonusListPtr IBonusBearer::getSpellBonuses() const
{ {
std::stringstream cachingStr; std::stringstream cachingStr;
cachingStr << "source_" << Bonus::SPELL_EFFECT; cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
return getBonuses(Selector::sourceType(Bonus::SPELL_EFFECT), Selector::anyRange(), cachingStr.str()); CSelector selector = Selector::sourceType(Bonus::SPELL_EFFECT)
.And(CSelector([](const Bonus * b)->bool
{
return !b->type == Bonus::NONE;
}));
return getBonuses(selector, Selector::anyRange(), cachingStr.str());
} }
const Bonus * IBonusBearer::getEffect(ui16 id, int turn /*= 0*/) const const Bonus * IBonusBearer::getEffect(ui16 id, int turn /*= 0*/) const
@ -1105,12 +1086,6 @@ bool NBonus::hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int s
return false; return false;
} }
void NBonus::getModifiersWDescr(const CBonusSystemNode *obj, TModDescr &out, Bonus::BonusType type, int subtype /*= -1 */)
{
if(obj)
return obj->getModifiersWDescr(out, type, subtype);
}
int NBonus::getCount(const CBonusSystemNode *obj, Bonus::BonusSource from, int id) int NBonus::getCount(const CBonusSystemNode *obj, Bonus::BonusSource from, int id)
{ {
if(obj) if(obj)
@ -1127,28 +1102,34 @@ const CSpell * Bonus::sourceSpell() const
std::string Bonus::Description() const std::string Bonus::Description() const
{ {
if(description.size())
return description;
std::ostringstream str; std::ostringstream str;
str << std::showpos << val << " ";
if(description.empty())
switch(source) switch(source)
{ {
case ARTIFACT: case ARTIFACT:
str << VLC->arth->artifacts[sid]->Name(); str << VLC->arth->artifacts[sid]->Name();
break;; break;;
case SPELL_EFFECT: case SPELL_EFFECT:
str << SpellID(sid).toSpell()->name; str << SpellID(sid).toSpell()->name;
break; break;
case CREATURE_ABILITY: case CREATURE_ABILITY:
str << VLC->creh->creatures[sid]->namePl; str << VLC->creh->creatures[sid]->namePl;
break; break;
case SECONDARY_SKILL: case SECONDARY_SKILL:
str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/; str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/;
break; break;
} default:
//todo: handle all possible sources
str << "Unknown";
break;
}
else
str << description;
if(val != 0)
str << " " << std::showpos << val;
return str.str(); return str.str();
} }

View File

@ -23,7 +23,6 @@ class BonusList;
typedef shared_ptr<BonusList> TBonusListPtr; typedef shared_ptr<BonusList> TBonusListPtr;
typedef shared_ptr<ILimiter> TLimiterPtr; typedef shared_ptr<ILimiter> TLimiterPtr;
typedef shared_ptr<IPropagator> TPropagatorPtr; typedef shared_ptr<IPropagator> TPropagatorPtr;
typedef std::vector<std::pair<int,std::string> > TModDescr; //modifiers values and their descriptions
typedef std::set<CBonusSystemNode*> TNodes; typedef std::set<CBonusSystemNode*> TNodes;
typedef std::set<const CBonusSystemNode*> TCNodes; typedef std::set<const CBonusSystemNode*> TCNodes;
typedef std::vector<CBonusSystemNode *> TNodesVector; typedef std::vector<CBonusSystemNode *> TNodesVector;
@ -448,7 +447,6 @@ public:
int totalValue() const; //subtype -> subtype of bonus, if -1 then any int totalValue() const; //subtype -> subtype of bonus, if -1 then any
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit) const; void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit) const;
void getAllBonuses(BonusList &out) const; void getAllBonuses(BonusList &out) const;
void getModifiersWDescr(TModDescr &out) const;
void getBonuses(BonusList & out, const CSelector &selector) const; void getBonuses(BonusList & out, const CSelector &selector) const;
@ -579,7 +577,6 @@ public:
// * root is node on which call was made (nullptr will be replaced with this) // * root is node on which call was made (nullptr will be replaced with this)
//interface //interface
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0; virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
void getModifiersWDescr(TModDescr &out, const CSelector &selector, const std::string &cachingStr = "") const; //out: pairs<modifier value, modifier description>
int getBonusesCount(const CSelector &selector, const std::string &cachingStr = "") const; int getBonusesCount(const CSelector &selector, const std::string &cachingStr = "") const;
int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const; int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const; bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const;
@ -594,7 +591,6 @@ public:
int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt; int valOfBonuses(Bonus::BonusType type, int subtype = -1) const; //subtype -> subtype of bonus, if -1 then anyt;
bool hasBonusOfType(Bonus::BonusType type, int subtype = -1) const;//determines if hero has a bonus of given type (and optionally subtype) bool hasBonusOfType(Bonus::BonusType type, int subtype = -1) const;//determines if hero has a bonus of given type (and optionally subtype)
bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const; bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
void getModifiersWDescr( TModDescr &out, Bonus::BonusType type, int subtype = -1 ) const; //out: pairs<modifier value, modifier description>
int getBonusesCount(Bonus::BonusSource from, int id) const; int getBonusesCount(Bonus::BonusSource from, int id) const;
//various hlp functions for non-trivial values //various hlp functions for non-trivial values
@ -722,8 +718,6 @@ namespace NBonus
//set of methods that may be safely called with nullptr objs //set of methods that may be safely called with nullptr objs
DLL_LINKAGE int valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype = -1); //subtype -> subtype of bonus, if -1 then any DLL_LINKAGE int valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype = -1); //subtype -> subtype of bonus, if -1 then any
DLL_LINKAGE bool hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype = -1);//determines if hero has a bonus of given type (and optionally subtype) DLL_LINKAGE bool hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype = -1);//determines if hero has a bonus of given type (and optionally subtype)
//DLL_LINKAGE const HeroBonus * get(const CBonusSystemNode *obj, int from, int id );
DLL_LINKAGE void getModifiersWDescr(const CBonusSystemNode *obj, TModDescr &out, Bonus::BonusType type, int subtype = -1 ); //out: pairs<modifier value, modifier description>
DLL_LINKAGE int getCount(const CBonusSystemNode *obj, Bonus::BonusSource from, int id); DLL_LINKAGE int getCount(const CBonusSystemNode *obj, Bonus::BonusSource from, int id);
} }

View File

@ -61,7 +61,7 @@ public:
virtual void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) =0; virtual void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) =0;
virtual void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) =0; virtual void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) =0;
virtual bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) =0; virtual bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) =0;
virtual bool changeStackType(const StackLocation &sl, CCreature *c) =0; virtual bool changeStackType(const StackLocation &sl, const CCreature *c) =0;
virtual bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count = -1) =0; //count -1 => moves whole stack virtual bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count = -1) =0; //count -1 => moves whole stack
virtual bool eraseStack(const StackLocation &sl, bool forceRemoval = false) =0; virtual bool eraseStack(const StackLocation &sl, bool forceRemoval = false) =0;
virtual bool swapStacks(const StackLocation &sl1, const StackLocation &sl2) =0; virtual bool swapStacks(const StackLocation &sl1, const StackLocation &sl2) =0;

View File

@ -795,7 +795,7 @@ struct ChangeStackCount : CGarrisonOperationPack //521
struct SetStackType : CGarrisonOperationPack //522 struct SetStackType : CGarrisonOperationPack //522
{ {
StackLocation sl; StackLocation sl;
CCreature *type; const CCreature *type;
void applyCl(CClient *cl); void applyCl(CClient *cl);
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
@ -1478,6 +1478,18 @@ struct EndAction : public CPackForClient//3008
struct BattleSpellCast : public CPackForClient//3009 struct BattleSpellCast : public CPackForClient//3009
{ {
///custom effect (resistance, reflection, etc)
struct CustomEffect
{
/// WoG AC format
ui32 effect;
ui32 stack;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & effect & stack;
}
};
BattleSpellCast(){type = 3009; casterStack = -1;}; BattleSpellCast(){type = 3009; casterStack = -1;};
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
void applyCl(CClient *cl); void applyCl(CClient *cl);
@ -1488,13 +1500,13 @@ struct BattleSpellCast : public CPackForClient//3009
ui8 skill; //caster's skill level ui8 skill; //caster's skill level
ui8 manaGained; //mana channeling ability ui8 manaGained; //mana channeling ability
BattleHex tile; //destination tile (may not be set in some global/mass spells BattleHex tile; //destination tile (may not be set in some global/mass spells
std::vector<ui32> resisted; //ids of creatures that resisted this spell std::vector<CustomEffect> customEffects;
std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure) std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
bool castedByHero; //if true - spell has been casted by hero, otherwise by a creature bool castByHero; //if true - spell has been casted by hero, otherwise by a creature
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & dmgToDisplay & side & id & skill & manaGained & tile & resisted & affectedCres & casterStack & castedByHero; h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
} }
}; };
@ -1629,10 +1641,9 @@ struct BattleSetStackProperty : public CPackForClient //3018
{ {
BattleSetStackProperty(){type = 3018;}; BattleSetStackProperty(){type = 3018;};
enum BattleStackProperty {CASTS, ENCHANTER_COUNTER, UNBIND, CLONED}; enum BattleStackProperty {CASTS, ENCHANTER_COUNTER, UNBIND, CLONED, HAS_CLONE};
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
//void applyCl(CClient *cl){};
int stackID; int stackID;
BattleStackProperty which; BattleStackProperty which;

View File

@ -1095,7 +1095,8 @@ DLL_LINKAGE void BattleNextRound::applyGs( CGameState *gs )
s->state -= EBattleStackState::HAD_MORALE; s->state -= EBattleStackState::HAD_MORALE;
s->state -= EBattleStackState::FEAR; s->state -= EBattleStackState::FEAR;
s->state -= EBattleStackState::DRAINED_MANA; s->state -= EBattleStackState::DRAINED_MANA;
s->counterAttacks = 1 + s->valOfBonuses(Bonus::ADDITIONAL_RETALIATION); s->counterAttacksPerformed = 0;
s->counterAttacksTotalCache = 0;
// new turn effects // new turn effects
s->battleTurnPassed(); s->battleTurnPassed();
} }
@ -1247,7 +1248,12 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
{ {
//"hide" killed creatures instead so we keep info about it //"hide" killed creatures instead so we keep info about it
at->state.insert(EBattleStackState::DEAD_CLONE); at->state.insert(EBattleStackState::DEAD_CLONE);
for(CStack * s : gs->curB->stacks)
{
if(s->cloneID == at->ID)
s->cloneID = -1;
}
} }
} }
@ -1255,7 +1261,7 @@ DLL_LINKAGE void BattleAttack::applyGs( CGameState *gs )
{ {
CStack *attacker = gs->curB->getStack(stackAttacking); CStack *attacker = gs->curB->getStack(stackAttacking);
if(counter()) if(counter())
attacker->counterAttacks--; attacker->counterAttacksPerformed++;
if(shot()) if(shot())
{ {
@ -1603,6 +1609,11 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
stack->state.insert(EBattleStackState::CLONED); stack->state.insert(EBattleStackState::CLONED);
break; break;
} }
case HAS_CLONE:
{
stack->cloneID = val;
break;
}
} }
} }

View File

@ -31,6 +31,7 @@
<Add option="-lboost_chrono$(#boost.libsuffix)" /> <Add option="-lboost_chrono$(#boost.libsuffix)" />
<Add option="-lboost_locale$(#boost.libsuffix)" /> <Add option="-lboost_locale$(#boost.libsuffix)" />
<Add option="-liconv" /> <Add option="-liconv" />
<Add option="-ldbghelp" />
<Add directory="$(#sdl2.lib)" /> <Add directory="$(#sdl2.lib)" />
<Add directory="$(#boost.lib32)" /> <Add directory="$(#boost.lib32)" />
<Add directory="$(#zlib.lib)" /> <Add directory="$(#zlib.lib)" />
@ -59,6 +60,7 @@
<Add option="-lboost_chrono$(#boost.libsuffix)" /> <Add option="-lboost_chrono$(#boost.libsuffix)" />
<Add option="-lboost_locale$(#boost.libsuffix)" /> <Add option="-lboost_locale$(#boost.libsuffix)" />
<Add option="-liconv" /> <Add option="-liconv" />
<Add option="-ldbghelp" />
<Add directory="$(#sdl2.lib)" /> <Add directory="$(#sdl2.lib)" />
<Add directory="$(#boost.lib32)" /> <Add directory="$(#boost.lib32)" />
<Add directory="$(#zlib.lib)" /> <Add directory="$(#zlib.lib)" />
@ -88,6 +90,7 @@
<Add option="-lboost_chrono$(#boost.libsuffix)" /> <Add option="-lboost_chrono$(#boost.libsuffix)" />
<Add option="-lboost_locale$(#boost.libsuffix)" /> <Add option="-lboost_locale$(#boost.libsuffix)" />
<Add option="-liconv" /> <Add option="-liconv" />
<Add option="-ldbghelp" />
<Add directory="$(#sdl2.lib64)" /> <Add directory="$(#sdl2.lib64)" />
<Add directory="$(#boost.lib64)" /> <Add directory="$(#boost.lib64)" />
<Add directory="$(#zlib64.lib)" /> <Add directory="$(#zlib64.lib)" />
@ -311,6 +314,7 @@
<Unit filename="spells/CreatureSpellMechanics.h" /> <Unit filename="spells/CreatureSpellMechanics.h" />
<Unit filename="spells/ISpellMechanics.cpp" /> <Unit filename="spells/ISpellMechanics.cpp" />
<Unit filename="spells/ISpellMechanics.h" /> <Unit filename="spells/ISpellMechanics.h" />
<Unit filename="spells/Magic.h" />
<Unit filename="spells/ViewSpellInt.cpp" /> <Unit filename="spells/ViewSpellInt.cpp" />
<Unit filename="spells/ViewSpellInt.h" /> <Unit filename="spells/ViewSpellInt.h" />
<Unit filename="vcmi_endian.h" /> <Unit filename="vcmi_endian.h" />

View File

@ -86,11 +86,13 @@ void CArmedInstance::updateMoraleBonusFromArmy()
{ {
b->val = +1; b->val = +1;
b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
b->description = b->description.substr(0, b->description.size()-3);//trim "+1"
} }
else if (!factions.empty()) // no bonus from empty garrison else if (!factions.empty()) // no bonus from empty garrison
{ {
b->val = 2 - factionsInArmy; b->val = 2 - factionsInArmy;
b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
b->description = b->description.substr(0, b->description.size()-2);//trim value
} }
boost::algorithm::trim(b->description); boost::algorithm::trim(b->description);
@ -100,7 +102,11 @@ void CArmedInstance::updateMoraleBonusFromArmy()
if(hasUndead) if(hasUndead)
{ {
if(!undeadModifier) if(!undeadModifier)
addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); {
undeadModifier = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]);
undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value
addNewBonus(undeadModifier);
}
} }
else if(undeadModifier) else if(undeadModifier)
removeBonus(undeadModifier); removeBonus(undeadModifier);

View File

@ -21,6 +21,7 @@
#include "../IGameCallback.h" #include "../IGameCallback.h"
#include "../CGameState.h" #include "../CGameState.h"
#include "../CCreatureHandler.h" #include "../CCreatureHandler.h"
#include "../BattleState.h"
///helpers ///helpers
static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
@ -216,9 +217,10 @@ CGHeroInstance::CGHeroInstance()
setNodeType(HERO); setNodeType(HERO);
ID = Obj::HERO; ID = Obj::HERO;
tacticFormationEnabled = inTownGarrison = false; tacticFormationEnabled = inTownGarrison = false;
mana = movement = portrait = level = -1; mana = movement = portrait = -1;
isStanding = true; isStanding = true;
moveDir = 4; moveDir = 4;
level = 1;
exp = 0xffffffff; exp = 0xffffffff;
visitedTown = nullptr; visitedTown = nullptr;
type = nullptr; type = nullptr;
@ -290,7 +292,6 @@ void CGHeroInstance::initHero()
} }
assert(validTypes()); assert(validTypes());
level = 1;
if(exp == 0xffffffff) if(exp == 0xffffffff)
{ {
initExp(); initExp();
@ -877,15 +878,73 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia)
skill = 3;
assert(skill >= 0 && skill <= 3); assert(skill >= 0 && skill <= 3);
return skill; return skill;
} }
ui32 CGHeroInstance::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
{
//applying sorcery secondary skill
base *= (100.0 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
base *= (100.0 + valOfBonuses(Bonus::SPELL_DAMAGE) + valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spell->id.toEnum())) / 100.0;
spell->forEachSchool([&base, this](const SpellSchoolInfo & cnf, bool & stop)
{
base *= (100.0 + valOfBonuses(cnf.damagePremyBonus)) / 100.0;
stop = true; //only bonus from one school is used
});
if (affectedStack && affectedStack->getCreature()->level) //Hero specials like Solmyr, Deemer
base *= (100. + ((valOfBonuses(Bonus::SPECIAL_SPELL_LEV, spell->id.toEnum()) * level) / affectedStack->getCreature()->level)) / 100.0;
return base;
}
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
{ {
return spell->isCastableBy(this, nullptr !=getArt(ArtifactPosition::SPELLBOOK), spells); if(nullptr == getArt(ArtifactPosition::SPELLBOOK))
return false;
const bool isAllowed = IObjectInterface::cb->isAllowed(0, spell->id);
const bool inSpellBook = vstd::contains(spells, spell->id);
const bool specificBonus = hasBonusOfType(Bonus::SPELL, spell->id);
bool schoolBonus = false;
spell->forEachSchool([this, &schoolBonus](const SpellSchoolInfo & cnf, bool & stop)
{
if(hasBonusOfType(cnf.knoledgeBonus))
{
schoolBonus = stop = true;
}
});
const bool levelBonus = hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level);
if (spell->isSpecialSpell())
{
if (inSpellBook)
{//hero has this spell in spellbook
logGlobal->errorStream() << "Special spell " << spell->name << "in spellbook.";
}
return specificBonus;
}
else if(!isAllowed)
{
if (inSpellBook)
{//hero has this spell in spellbook
logGlobal->errorStream() << "Banned spell " << spell->name << " in spellbook.";
}
return specificBonus;
}
else
{
return inSpellBook || schoolBonus || specificBonus || levelBonus;
}
} }
/** /**

View File

@ -2,6 +2,7 @@
#include "CObjectHandler.h" #include "CObjectHandler.h"
#include "CArmedInstance.h" #include "CArmedInstance.h"
#include "../spells/Magic.h"
#include "../CArtHandler.h" // For CArtifactSet #include "../CArtHandler.h" // For CArtifactSet
#include "../CRandomGenerator.h" #include "../CRandomGenerator.h"
@ -35,7 +36,7 @@ public:
}; };
class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public ISpellCaster
{ {
public: public:
enum ECanDig enum ECanDig
@ -162,14 +163,13 @@ public:
int maxMovePoints(bool onLand) const; int maxMovePoints(bool onLand) const;
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
//int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error
static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
double getFightingStrength() const; // takes attack / defense skill into account double getFightingStrength() const; // takes attack / defense skill into account
double getMagicStrength() const; // takes knowledge / spell power skill into account double getMagicStrength() const; // takes knowledge / spell power skill into account
double getHeroStrength() const; // includes fighting and magic strength double getHeroStrength() const; // includes fighting and magic strength
ui64 getTotalStrength() const; // includes fighting strength and army strength ui64 getTotalStrength() const; // includes fighting strength and army strength
TExpType calculateXp(TExpType exp) const; //apply learning skill TExpType calculateXp(TExpType exp) const; //apply learning skill
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
@ -198,13 +198,18 @@ public:
CGHeroInstance(); CGHeroInstance();
virtual ~CGHeroInstance(); virtual ~CGHeroInstance();
//////////////////////////////////////////////////////////////////////////
// ///ArtBearer
ArtBearer::ArtBearer bearerType() const override; ArtBearer::ArtBearer bearerType() const override;
//////////////////////////////////////////////////////////////////////////
///IBonusBearer
CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override; CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override;
std::string nodeName() const override; std::string nodeName() const override;
///ISpellCaster
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
void deserializationFix(); void deserializationFix();
void initObj() override; void initObj() override;

View File

@ -436,12 +436,12 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
//other *-of-legion-like bonuses (%d to growth cumulative with grail) //other *-of-legion-like bonuses (%d to growth cumulative with grail)
TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level))); TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level)));
for(const Bonus *b : *bonuses) for(const Bonus *b : *bonuses)
ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val)); ret.entries.push_back(GrowthInfo::Entry(b->val, b->Description()));
//statue-of-legion-like bonus: % to base+castle //statue-of-legion-like bonus: % to base+castle
TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT)); TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT));
for(const Bonus *b : *bonuses2) for(const Bonus *b : *bonuses2)
ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100)); ret.entries.push_back(GrowthInfo::Entry(b->val * (base + castleBonus) / 100, b->Description()));
if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2)); ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2));
@ -1244,6 +1244,12 @@ GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count)
description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count); description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count);
} }
GrowthInfo::Entry::Entry(int _count, const std::string &fullDescription)
: count(_count)
{
description = fullDescription;
}
CTownAndVisitingHero::CTownAndVisitingHero() CTownAndVisitingHero::CTownAndVisitingHero()
{ {
setNodeType(TOWN_AND_VISITOR); setNodeType(TOWN_AND_VISITOR);

View File

@ -130,6 +130,7 @@ struct DLL_LINKAGE GrowthInfo
std::string description; std::string description;
Entry(const std::string &format, int _count); Entry(const std::string &format, int _count);
Entry(int subID, BuildingID building, int _count); Entry(int subID, BuildingID building, int _count);
Entry(int _count, const std::string &fullDescription);
}; };
std::vector<Entry> entries; std::vector<Entry> entries;

View File

@ -93,6 +93,12 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battle
ssp.val = 0; ssp.val = 0;
ssp.absolute = 1; ssp.absolute = 1;
env->sendAndApply(&ssp); env->sendAndApply(&ssp);
ssp.stackID = clonedStack->ID;
ssp.which = BattleSetStackProperty::HAS_CLONE;
ssp.val = bsa.newStackID;
ssp.absolute = 1;
env->sendAndApply(&ssp);
} }
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
@ -100,6 +106,8 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHer
//can't clone already cloned creature //can't clone already cloned creature
if(vstd::contains(obj->state, EBattleStackState::CLONED)) if(vstd::contains(obj->state, EBattleStackState::CLONED))
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
if(obj->cloneID != -1)
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
//TODO: how about stacks casting Clone? //TODO: how about stacks casting Clone?
//currently Clone casted by stack is assumed Expert level //currently Clone casted by stack is assumed Expert level
ui8 schoolLevel; ui8 schoolLevel;
@ -127,26 +135,15 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHer
void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{ {
DefaultSpellMechanics::applyBattle(battle, packet); DefaultSpellMechanics::applyBattle(battle, packet);
doDispell(battle, packet, [](const Bonus * b) -> bool
for(auto stackID : packet->affectedCres)
{ {
if(vstd::contains(packet->resisted, stackID)) if(b->source == Bonus::SPELL_EFFECT)
{ {
logGlobal->errorStream() << "Resistance to positive spell CURE"; CSpell * sp = SpellID(b->sid).toSpell();
continue; return sp->isNegative();
} }
return false; //not a spell effect
CStack *s = battle->getStack(stackID); });
s->popBonuses([&](const Bonus *b) -> bool
{
if(b->source == Bonus::SPELL_EFFECT)
{
CSpell * sp = SpellID(b->sid).toSpell();
return sp->isNegative();
}
return false; //not a spell effect
});
}
} }
///DispellMechanics ///DispellMechanics
@ -310,8 +307,8 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
//TODO: what with other creatures casting hypnotize, Faerie Dragons style? //TODO: what with other creatures casting hypnotize, Faerie Dragons style?
ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft; ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
//apply 'damage' bonus for hypnotize, including hero specialty //apply 'damage' bonus for hypnotize, including hero specialty
ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) ui64 maxHealth = caster->getSpellBonus(owner, caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj); * owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), obj);
if (subjectHealth > maxHealth) if (subjectHealth > maxHealth)
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
} }
@ -470,7 +467,8 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBatt
//using isImmuneBy directly as this mechanics does not have overridden immunity check //using isImmuneBy directly as this mechanics does not have overridden immunity check
//therefore we do not need to check caster and casting mode //therefore we do not need to check caster and casting mode
//TODO: check that we really should check immunity for both stacks //TODO: check that we really should check immunity for both stacks
const bool immune = ESpellCastProblem::OK != owner->isImmuneBy(stack); ESpellCastProblem::ESpellCastProblem res = owner->isImmuneBy(stack);
const bool immune = ESpellCastProblem::OK != res && ESpellCastProblem::NOT_DECIDED != res;
const bool casterStack = stack->owner == player; const bool casterStack = stack->owner == player;
if(!immune && casterStack) if(!immune && casterStack)
@ -482,7 +480,6 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBatt
if(targetExists && targetToSacrificeExists) if(targetExists && targetToSacrificeExists)
break; break;
} }
} }
if(targetExists && targetToSacrificeExists) if(targetExists && targetToSacrificeExists)
@ -571,7 +568,7 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
bsa.pos = parameters.cb->getAvaliableHex(creatureToSummon, !(bool)parameters.casterSide); //TODO: unify it bsa.pos = parameters.cb->getAvaliableHex(creatureToSummon, !(bool)parameters.casterSide); //TODO: unify it
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually //TODO stack casting -> probably power will be zero; set the proper number of creatures manually
int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0; int percentBonus = parameters.casterHero ? parameters.casterHero->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
bsa.amount = parameters.usedSpellPower bsa.amount = parameters.usedSpellPower
* owner->getPower(parameters.spellLvl) * owner->getPower(parameters.spellLvl)

View File

@ -15,6 +15,8 @@
#include "../NetPacks.h" #include "../NetPacks.h"
#include "../BattleState.h" #include "../BattleState.h"
#include "../CGeneralTextHandler.h"
namespace SRSLPraserHelpers namespace SRSLPraserHelpers
{ {
static int XYToHex(int x, int y) static int XYToHex(int x, int y)
@ -120,7 +122,7 @@ namespace SRSLPraserHelpers
///DefaultSpellMechanics ///DefaultSpellMechanics
void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
{ {
if (packet->castedByHero) if (packet->castByHero)
{ {
if (packet->side < 2) if (packet->side < 2)
{ {
@ -131,9 +133,6 @@ void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCa
//handle countering spells //handle countering spells
for(auto stackID : packet->affectedCres) for(auto stackID : packet->affectedCres)
{ {
if(vstd::contains(packet->resisted, stackID))
continue;
CStack * s = battle->getStack(stackID); CStack * s = battle->getStack(stackID);
s->popBonuses([&](const Bonus * b) -> bool s->popBonuses([&](const Bonus * b) -> bool
{ {
@ -230,16 +229,16 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
sc.skill = parameters.spellLvl; sc.skill = parameters.spellLvl;
sc.tile = parameters.destination; sc.tile = parameters.destination;
sc.dmgToDisplay = 0; sc.dmgToDisplay = 0;
sc.castedByHero = nullptr != parameters.caster; sc.castByHero = nullptr != parameters.casterHero;
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1); sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
sc.manaGained = 0; sc.manaGained = 0;
int spellCost = 0; int spellCost = 0;
//calculate spell cost //calculate spell cost
if(parameters.caster) if(parameters.casterHero)
{ {
spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster); spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
{ {
@ -259,27 +258,60 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
//must be vector, as in Chain Lightning order matters //must be vector, as in Chain Lightning order matters
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster); auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.casterHero);
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres)); std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
for (auto cre : attackedCres) std::vector <const CStack*> reflected;//for magic mirror
{
sc.affectedCres.insert(cre->ID);
}
//checking if creatures resist //checking if creatures resist
//resistance is applied only to negative spells //resistance/reflection is applied only to negative spells
if(owner->isNegative()) if(owner->isNegative())
{ {
//it is actual spell and can be reflected to single target, no recurrence
const bool tryMagicMirror = parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0";
std::vector <const CStack*> resisted;
for(auto s : attackedCres) for(auto s : attackedCres)
{ {
//magic resistance
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in % const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
if(env->getRandomGenerator().nextInt(99) < prob) if(env->getRandomGenerator().nextInt(99) < prob)
{ {
sc.resisted.push_back(s->ID); resisted.push_back(s);
}
//magic mirror
if(tryMagicMirror)
{
const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(env->getRandomGenerator().nextInt(99) < mirrorChance)
reflected.push_back(s);
} }
} }
vstd::erase_if(attackedCres, [&resisted, reflected](const CStack * s)
{
return vstd::contains(resisted, s) || vstd::contains(reflected, s);
});
for(auto s : resisted)
{
BattleSpellCast::CustomEffect effect;
effect.effect = 78;
effect.stack = s->ID;
sc.customEffects.push_back(effect);
}
for(auto s : reflected)
{
BattleSpellCast::CustomEffect effect;
effect.effect = 3;
effect.stack = s->ID;
sc.customEffects.push_back(effect);
}
}
for(auto cre : attackedCres)
{
sc.affectedCres.insert(cre->ID);
} }
StacksInjured si; StacksInjured si;
@ -290,12 +322,12 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
env->sendAndApply(&sc); env->sendAndApply(&sc);
//spend mana //spend mana
if(parameters.caster) if(parameters.casterHero)
{ {
SetMana sm; SetMana sm;
sm.absolute = false; sm.absolute = false;
sm.hid = parameters.caster->id; sm.hid = parameters.casterHero->id;
sm.val = -spellCost; sm.val = -spellCost;
env->sendAndApply(&sm); env->sendAndApply(&sm);
@ -328,61 +360,143 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
} }
//Magic Mirror effect //Magic Mirror effect
if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence for(auto & attackedCre : reflected)
{ {
for(auto & attackedCre : attackedCres) TStacks mirrorTargets = parameters.cb->battleGetStacksIf([this, parameters](const CStack * battleStack)
{ {
int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR); //Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
if(mirrorChance > env->getRandomGenerator().nextInt(99)) return battleStack->owner == parameters.casterColor;
{ },
std::vector<const CStack *> mirrorTargets; true);//turrets included
auto battleStacks = parameters.cb->battleGetAllStacks(true);
for(auto & battleStack : battleStacks)
{
if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
{
if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
mirrorTargets.push_back(battleStack);
}
}
if(!mirrorTargets.empty())
{
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
BattleSpellCastParameters mirrorParameters = parameters; if(!mirrorTargets.empty())
mirrorParameters.spellLvl = 0; {
mirrorParameters.casterSide = 1-parameters.casterSide; int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
mirrorParameters.casterColor = (attackedCre)->owner;
mirrorParameters.caster = nullptr;
mirrorParameters.destination = targetHex;
mirrorParameters.secHero = parameters.caster;
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
mirrorParameters.casterStack = (attackedCre);
mirrorParameters.selectedStack = nullptr;
battleCast(env, mirrorParameters); BattleSpellCastParameters mirrorParameters = parameters;
} mirrorParameters.spellLvl = 0;
} mirrorParameters.casterSide = 1-parameters.casterSide;
mirrorParameters.casterColor = (attackedCre)->owner;
mirrorParameters.casterHero = nullptr;
mirrorParameters.destination = targetHex;
mirrorParameters.secHero = parameters.casterHero;
mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
mirrorParameters.casterStack = (attackedCre);
mirrorParameters.selectedStack = nullptr;
battleCast(env, mirrorParameters);
} }
} }
} }
void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
{
const std::string attackedName = attackedStack->getName();
const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
{
return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
};
auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID)
{
boost::format fmt = getPluralFormat(baseTextID);
fmt % attackedName;
logLines.push_back(fmt.str());
};
auto logPlural = [&logLines, attackedNamePl](const int baseTextID)
{
boost::format fmt(VLC->generaltexth->allTexts[baseTextID]);
fmt % attackedNamePl;
logLines.push_back(fmt.str());
};
displayDamage = false; //in most following cases damage info text is custom
switch(owner->id)
{
case SpellID::STONE_GAZE:
logSimple(558);
break;
case SpellID::POISON:
logSimple(561);
break;
case SpellID::BIND:
logPlural(560);//Roots and vines bind the %s to the ground!
break;
case SpellID::DISEASE:
logSimple(553);
break;
case SpellID::PARALYZE:
logSimple(563);
break;
case SpellID::AGE:
{
boost::format text = getPluralFormat(551);
text % attackedName;
//The %s shrivel with age, and lose %d hit points."
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
const int fullHP = bl->totalValue();
bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
text % (fullHP - bl->totalValue());
logLines.push_back(text.str());
}
break;
case SpellID::THUNDERBOLT:
{
logPlural(367);
std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay)); //no more text afterwards
logLines.push_back(text);
}
break;
case SpellID::DISPEL_HELPFUL_SPELLS:
logPlural(555);
break;
case SpellID::DEATH_STARE:
if (packet->dmgToDisplay > 0)
{
std::string text;
if (packet->dmgToDisplay > 1)
{
text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay));
boost::algorithm::replace_first(text, "%s", attackedNamePl);
}
else
{
text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
boost::algorithm::replace_first(text, "%s", attackedNameSing);
}
boost::algorithm::replace_first(text, "%s", casterName); //casting stack
logLines.push_back(text);
}
break;
default:
{
boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s
text % casterName % owner->name;
displayDamage = true;
logLines.push_back(text.str());
}
break;
}
}
int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
{ {
if(!caster) if(caster == nullptr)
{ {
if (!usedSpellPower) if (!usedSpellPower)
return 3; //default duration of all creature spells return 3; //default duration of all creature spells
else else
return usedSpellPower; //use creature spell power return usedSpellPower; //use creature spell power
} }
switch(owner->id) else
{
case SpellID::FRENZY:
return 1;
default: //other spells
return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION); return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
}
} }
ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
@ -402,13 +516,21 @@ ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, cons
healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count; healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
else else
healedHealth = spellPowerSkill * owner->power + levelPower; //??? healedHealth = spellPowerSkill * owner->power + levelPower; //???
healedHealth = owner->calculateBonus(healedHealth, caster, stack); healedHealth = caster->getSpellBonus(owner, healedHealth, stack);
return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0)); return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
} }
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{ {
int effectLevel = parameters.spellLvl;
{
//MAXED_SPELL bonus.
if(parameters.casterHero != nullptr)
if(parameters.casterHero->hasBonusOfType(Bonus::MAXED_SPELL, owner->id))
effectLevel = 3;
}
//applying effects //applying effects
if(owner->isOffensiveSpell()) if(owner->isOffensiveSpell())
{ {
@ -427,14 +549,11 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
int chainLightningModifier = 0; int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres) for(auto & attackedCre : ctx.attackedCres)
{ {
if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
continue;
BattleStackAttacked bsa; BattleStackAttacked bsa;
if(spellDamage) if(spellDamage)
bsa.damageAmount = spellDamage >> chainLightningModifier; bsa.damageAmount = spellDamage >> chainLightningModifier;
else else
bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier; bsa.damageAmount = owner->calculateDamage(parameters.casterHero, attackedCre, effectLevel, parameters.usedSpellPower) >> chainLightningModifier;
ctx.sc.dmgToDisplay += bsa.damageAmount; ctx.sc.dmgToDisplay += bsa.damageAmount;
@ -459,29 +578,42 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER); stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
} }
SetStackEffect sse; SetStackEffect sse;
Bonus pseudoBonus; //get default spell duration (spell power with bonuses for heroes)
pseudoBonus.sid = owner->id; int duration = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
pseudoBonus.val = parameters.spellLvl; //generate actual stack bonuses
pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower); {
CStack::stackEffectToFeature(sse.effect, pseudoBonus); int maxDuration = 0;
std::vector<Bonus> tmp;
owner->getEffects(tmp, effectLevel);
for(Bonus& b : tmp)
{
//use configured duration if present
if(b.turnsRemain == 0)
b.turnsRemain = duration;
vstd::amax(maxDuration, b.turnsRemain);
sse.effect.push_back(b);
}
//if all spell effects have special duration, use it
duration = maxDuration;
}
//fix to original config: shield should display damage reduction
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD) if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{ {
sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction sse.effect.back().val = (100 - sse.effect.back().val);
} }
if(owner->id == SpellID::BIND && parameters.casterStack)//bind //we need to know who cast Bind
if(owner->id == SpellID::BIND && parameters.casterStack)
{ {
sse.effect.back().additionalInfo = parameters.casterStack->ID; //we need to know who casted Bind sse.effect.back().additionalInfo = parameters.casterStack->ID;
} }
const Bonus * bonus = nullptr; const Bonus * bonus = nullptr;
if(parameters.caster) if(parameters.casterHero)
bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id)); bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells? //TODO does hero specialty should affects his stack casting spells?
si32 power = 0; si32 power = 0;
for(const CStack * affected : ctx.attackedCres) for(const CStack * affected : ctx.attackedCres)
{ {
if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
continue;
sse.stacks.push_back(affected->ID); sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants //Apply hero specials - peculiar enchants
@ -512,17 +644,17 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
case 1: //only Coronius as yet case 1: //only Coronius as yet
{ {
power = std::max(5 - tier, 0); power = std::max(5 - tier, 0);
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain); Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, duration);
specialBonus.sid = owner->id; specialBonus.sid = owner->id;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
} }
break; break;
} }
} }
if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages if (parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{ {
int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier; int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain); Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, duration);
specialBonus.valType = Bonus::PERCENT_TO_ALL; specialBonus.valType = Bonus::PERCENT_TO_ALL;
specialBonus.sid = owner->id; specialBonus.sid = owner->id;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
@ -563,13 +695,13 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
else else
{ {
//any typical spell (commander's cure or animate dead) //any typical spell (commander's cure or animate dead)
int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl); int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(effectLevel);
hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0)); hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
} }
} }
else else
hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero hi.healedHP = calculateHealedHP(parameters.casterHero, attackedCre, parameters.selectedStack); //Casted by hero
hi.lowLevelResurrection = (parameters.spellLvl <= 1) && (owner->id != SpellID::ANIMATE_DEAD); hi.lowLevelResurrection = (effectLevel <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
shr.healedStacks.push_back(hi); shr.healedStacks.push_back(hi);
} }
if(!shr.healedStacks.empty()) if(!shr.healedStacks.empty())
@ -734,9 +866,6 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
{ {
for(auto stackID : packet->affectedCres) for(auto stackID : packet->affectedCres)
{ {
if(vstd::contains(packet->resisted, stackID))
continue;
CStack *s = battle->getStack(stackID); CStack *s = battle->getStack(stackID);
s->popBonuses(selector); s->popBonuses(selector);
} }

View File

@ -48,6 +48,8 @@ public:
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final; bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override; void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
protected: protected:
virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const; virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;

View File

@ -26,6 +26,7 @@
#include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGHeroInstance.h"
#include "../BattleState.h" #include "../BattleState.h"
#include "../CBattleCallback.h" #include "../CBattleCallback.h"
#include "../CGameState.h"
#include "ISpellMechanics.h" #include "ISpellMechanics.h"
@ -71,7 +72,7 @@ namespace SpellConfig
} }
BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb) BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr), : spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),casterHero(nullptr), secHero(nullptr),
usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb) usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
{ {
@ -126,38 +127,6 @@ void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParamet
mechanics->battleCast(env, parameters); mechanics->battleCast(env, parameters);
} }
bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
{
if(!hasSpellBook)
return false;
const bool inSpellBook = vstd::contains(spellBook, id);
const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
bool inTome = false;
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
{
if(caster->hasBonusOfType(cnf.knoledgeBonus))
{
inTome = stop = true;
}
});
if (isSpecialSpell())
{
if (inSpellBook)
{//hero has this spell in spellbook
logGlobal->errorStream() << "Special spell in spellbook "<<name;
}
return isBonus;
}
else
{
return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
}
}
const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
{ {
if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS) if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
@ -169,29 +138,7 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
return levels.at(level); return levels.at(level);
} }
ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
{
ui32 ret = baseDamage;
//applying sorcery secondary skill
if(caster)
{
ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
{
ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
stop = true; //only bonus from one school is used
});
if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
}
return ret;
}
ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
{ {
ui32 ret = 0; //value to return ui32 ret = 0; //value to return
@ -230,7 +177,9 @@ ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affec
ret /= 100; ret /= 100;
} }
} }
ret = calculateBonus(ret, caster, affectedCreature);
if(nullptr != caster) //todo: make sure that caster always present
ret = caster->getSpellBonus(this, ret, affectedCreature);
return ret; return ret;
} }
@ -551,6 +500,59 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstanc
return ESpellCastProblem::OK; return ESpellCastProblem::OK;
} }
void CSpell::prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const
{
bool displayDamage = true;
std::string casterName("Something"); //todo: localize
if(packet->castByHero)
casterName = cb->battleGetHeroInfo(packet->side).name;
{
const auto casterStackID = packet->casterStack;
if(casterStackID > 0)
{
const CStack * casterStack = cb->battleGetStackByID(casterStackID);
if(casterStack != nullptr)
casterName = casterStack->type->namePl;
}
}
if(packet->affectedCres.size() == 1)
{
const CStack * attackedStack = cb->battleGetStackByID(*packet->affectedCres.begin(), false);
const std::string attackedNamePl = attackedStack->getCreature()->namePl;
if(packet->castByHero)
{
const std::string fmt = VLC->generaltexth->allTexts[195];
logLines.push_back(boost::to_string(boost::format(fmt) % casterName % this->name % attackedNamePl));
}
else
{
mechanics->battleLogSingleTarget(logLines, packet, casterName, attackedStack, displayDamage);
}
}
else
{
boost::format text(VLC->generaltexth->allTexts[196]);
text % casterName % this->name;
logLines.push_back(text.str());
}
if(packet->dmgToDisplay > 0 && displayDamage)
{
boost::format dmgInfo(VLC->generaltexth->allTexts[376]);
dmgInfo % this->name % packet->dmgToDisplay;
logLines.push_back(dmgInfo.str());
}
}
void CSpell::setIsOffensive(const bool val) void CSpell::setIsOffensive(const bool val)
{ {
isOffensive = val; isOffensive = val;
@ -588,6 +590,14 @@ void CSpell::setupMechanics()
mechanics = ISpellMechanics::createMechanics(this); mechanics = ISpellMechanics::createMechanics(this);
} }
///CSpell::AnimationInfo
CSpell::AnimationItem::AnimationItem()
:resourceName(""),verticalPosition(VerticalPosition::TOP),pause(0)
{
}
///CSpell::AnimationInfo ///CSpell::AnimationInfo
CSpell::AnimationInfo::AnimationInfo() CSpell::AnimationInfo::AnimationInfo()
{ {
@ -924,7 +934,6 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
for(const JsonNode & item : queueNode) for(const JsonNode & item : queueNode)
{ {
CSpell::TAnimation newItem; CSpell::TAnimation newItem;
newItem.verticalPosition = VerticalPosition::TOP;
if(item.getType() == JsonNode::DATA_STRING) if(item.getType() == JsonNode::DATA_STRING)
newItem.resourceName = item.String(); newItem.resourceName = item.String();
@ -936,6 +945,11 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
if("bottom" == vPosStr) if("bottom" == vPosStr)
newItem.verticalPosition = VerticalPosition::BOTTOM; newItem.verticalPosition = VerticalPosition::BOTTOM;
} }
else if(item.getType() == JsonNode::DATA_FLOAT)
{
newItem.pause = item.Float();
}
q.push_back(newItem); q.push_back(newItem);
} }
}; };

View File

@ -9,7 +9,7 @@
*/ */
#pragma once #pragma once
#include "Magic.h"
#include "../IHandlerBase.h" #include "../IHandlerBase.h"
#include "../ConstTransitivePtr.h" #include "../ConstTransitivePtr.h"
#include "../int3.h" #include "../int3.h"
@ -66,7 +66,7 @@ public:
BattleHex destination; BattleHex destination;
ui8 casterSide; ui8 casterSide;
PlayerColor casterColor; PlayerColor casterColor;
const CGHeroInstance * caster; const CGHeroInstance * casterHero; //deprecated
const CGHeroInstance * secHero; const CGHeroInstance * secHero;
int usedSpellPower; int usedSpellPower;
ECastingMode::ECastingMode mode; ECastingMode::ECastingMode mode;
@ -104,10 +104,21 @@ public:
{ {
std::string resourceName; std::string resourceName;
VerticalPosition verticalPosition; VerticalPosition verticalPosition;
int pause;
AnimationItem();
template <typename Handler> void serialize(Handler & h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & resourceName & verticalPosition; h & resourceName & verticalPosition;
if(version >= 754)
{
h & pause;
}
else if(!h.saving)
{
pause = 0;
}
} }
}; };
@ -217,8 +228,6 @@ public:
CSpell(); CSpell();
~CSpell(); ~CSpell();
bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
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)
ETargetType getTargetType() const; //deprecated ETargetType getTargetType() const; //deprecated
@ -248,11 +257,8 @@ public:
//internal, for use only by Mechanics classes //internal, for use only by Mechanics classes
ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
//internal, for use only by Mechanics classes. applying secondary skills
ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account ///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
///selects from allStacks actually affected stacks ///selects from allStacks actually affected stacks
std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const; std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
@ -322,6 +328,11 @@ public:
///implementation of BattleSpellCast applying ///implementation of BattleSpellCast applying
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const; void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
public:
///Client logic.
void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const;
private: private:
void setIsOffensive(const bool val); void setIsOffensive(const bool val);
void setIsRising(const bool val); void setIsRising(const bool val);

View File

@ -47,6 +47,9 @@ public:
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0; virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0; virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0;
static ISpellMechanics * createMechanics(CSpell * s); static ISpellMechanics * createMechanics(CSpell * s);
protected: protected:
CSpell * owner; CSpell * owner;

32
lib/spells/Magic.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Magic.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
/**
* High-level interface for spells subsystem
*/
class CSpell;
class CStack;
class DLL_LINKAGE ISpellCaster
{
public:
virtual ~ISpellCaster(){};
/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
/// caster may not know this spell at all
/// optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic
virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;
virtual ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const = 0;
};

View File

@ -2789,7 +2789,7 @@ bool CGameHandler::upgradeCreature( ObjectInstanceID objid, SlotID pos, Creature
return true; return true;
} }
bool CGameHandler::changeStackType(const StackLocation &sl, CCreature *c) bool CGameHandler::changeStackType(const StackLocation &sl, const CCreature *c)
{ {
if(!sl.army->hasStackAtSlot(sl.slot)) if(!sl.army->hasStackAtSlot(sl.slot))
COMPLAIN_RET("Cannot find a stack to change type"); COMPLAIN_RET("Cannot find a stack to change type");
@ -3208,14 +3208,15 @@ bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance
const CStackInstance &s = army->getStack(slot); const CStackInstance &s = army->getStack(slot);
int resCreature;//resulting creature - bone dragons or skeletons
if (s.hasBonusOfType(Bonus::DRAGON_NATURE)) //resulting creature - bone dragons or skeletons
resCreature = 68; CreatureID resCreature = CreatureID::SKELETON;
else
resCreature = 56;
changeStackType(StackLocation(army, slot), VLC->creh->creatures.at(resCreature)); if(s.hasBonusOfType(Bonus::DRAGON_NATURE)
|| (s.getCreatureID() == CreatureID::HYDRA)
|| (s.getCreatureID() == CreatureID::CHAOS_HYDRA))
resCreature = CreatureID::BONE_DRAGON;
changeStackType(StackLocation(army, slot), resCreature.toCreature());
return true; return true;
} }
@ -3431,7 +3432,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
//defensive stance //TODO: remove this bonus when stack becomes active //defensive stance //TODO: remove this bonus when stack becomes active
SetStackEffect sse; SetStackEffect sse;
sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 20, -1, PrimarySkill::DEFENSE, Bonus::PERCENT_TO_ALL) ); sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 20, -1, PrimarySkill::DEFENSE, Bonus::PERCENT_TO_ALL) );
sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, gs->curB->stacks.at(ba.stackNumber)->valOfBonuses(Bonus::DEFENSIVE_STANCE), sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, gs->curB->battleGetStackByID(ba.stackNumber)->valOfBonuses(Bonus::DEFENSIVE_STANCE),
-1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE)); -1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE));
sse.stacks.push_back(ba.stackNumber); sse.stacks.push_back(ba.stackNumber);
sendAndApply(&sse); sendAndApply(&sse);
@ -3880,7 +3881,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
p.mode = ECastingMode::CREATURE_ACTIVE_CASTING; p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
p.destination = destination; p.destination = destination;
p.casterColor = stack->owner; p.casterColor = stack->owner;
p.caster = nullptr; p.casterHero = nullptr;
p.usedSpellPower = 0; p.usedSpellPower = 0;
p.casterStack = stack; p.casterStack = stack;
p.selectedStack = nullptr; p.selectedStack = nullptr;
@ -4080,7 +4081,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
parameters.destination = ba.destinationTile; parameters.destination = ba.destinationTile;
parameters.casterSide = ba.side; parameters.casterSide = ba.side;
parameters.casterColor = h->tempOwner; parameters.casterColor = h->tempOwner;
parameters.caster = h; parameters.casterHero = h;
parameters.secHero = secondHero; parameters.secHero = secondHero;
parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER); parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
@ -4239,7 +4240,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
parameters.destination = BattleHex::INVALID; parameters.destination = BattleHex::INVALID;
parameters.casterSide = side; parameters.casterSide = side;
parameters.casterColor = st->owner; parameters.casterColor = st->owner;
parameters.caster = nullptr; parameters.casterHero = nullptr;
parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner)); parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
parameters.usedSpellPower = 0; parameters.usedSpellPower = 0;
parameters.mode = ECastingMode::ENCHANTER_CASTING; parameters.mode = ECastingMode::ENCHANTER_CASTING;
@ -4952,7 +4953,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
parameters.destination = destination; parameters.destination = destination;
parameters.casterSide = !attacker->attackerOwned; parameters.casterSide = !attacker->attackerOwned;
parameters.casterColor = attacker->owner; parameters.casterColor = attacker->owner;
parameters.caster = nullptr; parameters.casterHero = nullptr;
parameters.secHero = nullptr; parameters.secHero = nullptr;
parameters.usedSpellPower = 0; parameters.usedSpellPower = 0;
@ -4987,7 +4988,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position; parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
parameters.casterSide = !attacker->attackerOwned; parameters.casterSide = !attacker->attackerOwned;
parameters.casterColor = attacker->owner; parameters.casterColor = attacker->owner;
parameters.caster = nullptr; parameters.casterHero = nullptr;
parameters.secHero = nullptr; parameters.secHero = nullptr;
parameters.usedSpellPower = power; parameters.usedSpellPower = power;
@ -5297,7 +5298,7 @@ void CGameHandler::runBattle()
parameters.destination = BattleHex::INVALID; parameters.destination = BattleHex::INVALID;
parameters.casterSide = i; parameters.casterSide = i;
parameters.casterColor = h->tempOwner; parameters.casterColor = h->tempOwner;
parameters.caster = nullptr; parameters.casterHero = nullptr;
parameters.secHero = gs->curB->battleGetFightingHero(1-i); parameters.secHero = gs->curB->battleGetFightingHero(1-i);

View File

@ -142,7 +142,7 @@ public:
void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override; void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override;
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override; void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override;
bool changeStackType(const StackLocation &sl, CCreature *c) override; bool changeStackType(const StackLocation &sl, const CCreature *c) override;
bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override; bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override;
bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override; bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override;
bool eraseStack(const StackLocation &sl, bool forceRemoval = false) override; bool eraseStack(const StackLocation &sl, bool forceRemoval = false) override;

View File

@ -297,7 +297,11 @@ bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const
{ {
return !vstd::contains(ourIds, dismiss->heroID); return !vstd::contains(ourIds, dismiss->heroID);
} }
if(auto upgrade = dynamic_cast<const UpgradeCreature*>(pack))
{
return !vstd::contains(ourIds, upgrade->id);
}
return CDialogQuery::blocksPack(pack); return CDialogQuery::blocksPack(pack);
} }