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:
commit
e14faea181
@ -643,7 +643,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
|
||||
auto attacker = AttackInfo.attacker;
|
||||
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 int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
|
||||
|
||||
|
@ -70,7 +70,7 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
|
||||
tp.r = pcx[it++];
|
||||
tp.g = pcx[it++];
|
||||
tp.b = pcx[it++];
|
||||
CSDL_Ext::colorSetAlpha(tp,SDL_ALPHA_OPAQUE);
|
||||
tp.a = SDL_ALPHA_OPAQUE;
|
||||
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
|
||||
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
|
||||
|
@ -67,7 +67,7 @@ void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
|
||||
palette[it].r = de.palette[it].R;
|
||||
palette[it].g = de.palette[it].G;
|
||||
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
|
||||
@ -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 * ret=nullptr;
|
||||
@ -180,13 +174,12 @@ SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Co
|
||||
|
||||
BaseOffset += sizeof(SSpriteDef);
|
||||
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;
|
||||
|
||||
// If there's a margin anywhere, just blank out the whole surface.
|
||||
|
@ -85,7 +85,9 @@ private:
|
||||
int group;
|
||||
} ;
|
||||
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:
|
||||
int width, height; //width and height
|
||||
std::string defName;
|
||||
@ -94,9 +96,7 @@ public:
|
||||
|
||||
CDefHandler(); //c-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();
|
||||
|
||||
static CDefHandler * giveDef(const std::string & defName);
|
||||
|
@ -186,7 +186,7 @@ public:
|
||||
|
||||
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) 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 insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;};
|
||||
bool eraseStack(const StackLocation &sl, bool forceRemoval = false){return false;};
|
||||
|
@ -58,7 +58,7 @@ void Graphics::loadPaletteAndColors()
|
||||
col.r = pals[startPoint++];
|
||||
col.g = pals[startPoint++];
|
||||
col.b = pals[startPoint++];
|
||||
CSDL_Ext::colorSetAlpha(col,SDL_ALPHA_OPAQUE);
|
||||
col.a = SDL_ALPHA_OPAQUE;
|
||||
startPoint++;
|
||||
playerColorPalette[i] = col;
|
||||
}
|
||||
@ -74,7 +74,7 @@ void Graphics::loadPaletteAndColors()
|
||||
neutralColorPalette[i].g = reader.readUInt8();
|
||||
neutralColorPalette[i].b = reader.readUInt8();
|
||||
reader.readUInt8(); // this is "flags" entry, not alpha
|
||||
CSDL_Ext::colorSetAlpha(neutralColorPalette[i], SDL_ALPHA_OPAQUE);
|
||||
neutralColorPalette[i].a = SDL_ALPHA_OPAQUE;
|
||||
}
|
||||
//colors initialization
|
||||
SDL_Color colors[] = {
|
||||
@ -92,8 +92,11 @@ void Graphics::loadPaletteAndColors()
|
||||
{
|
||||
playerColors[i] = colors[i];
|
||||
}
|
||||
neutralColor->r = 0x84; neutralColor->g = 0x84; neutralColor->b = 0x84; //gray
|
||||
CSDL_Ext::colorSetAlpha(*neutralColor,SDL_ALPHA_OPAQUE);
|
||||
//gray
|
||||
neutralColor->r = 0x84;
|
||||
neutralColor->g = 0x84;
|
||||
neutralColor->b = 0x84;
|
||||
neutralColor->a = SDL_ALPHA_OPAQUE;
|
||||
}
|
||||
|
||||
void Graphics::initializeBattleGraphics()
|
||||
|
@ -283,7 +283,9 @@ void CDefenceAnimation::endAnim()
|
||||
|
||||
CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames)
|
||||
: CBattleAnimation(_owner), counter(0), howMany(howManyFrames)
|
||||
{}
|
||||
{
|
||||
logAnim->debugStream() << "Created dummy animation for " << howManyFrames <<" frames";
|
||||
}
|
||||
|
||||
bool CDummyAnimation::init()
|
||||
{
|
||||
|
@ -360,7 +360,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
|
||||
{
|
||||
idToObstacle[ID] = CDefHandler::giveDef(elem->getInfo().defName);
|
||||
for(auto & _n : idToObstacle[ID]->ourImages)
|
||||
{
|
||||
{
|
||||
CSDL_Ext::setDefaultColorKey(_n.bitmap);
|
||||
}
|
||||
}
|
||||
@ -1240,15 +1240,9 @@ void CBattleInterface::displayBattleFinished()
|
||||
void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
{
|
||||
const SpellID spellID(sc->id);
|
||||
const CSpell &spell = * spellID.toSpell();
|
||||
const std::string & spellName = spell.name;
|
||||
const CSpell & spell = * spellID.toSpell();
|
||||
|
||||
const std::string& castSoundPath = spell.getCastSound();
|
||||
|
||||
std::string casterName("Something");
|
||||
|
||||
if(sc->castedByHero)
|
||||
casterName = curInt->cb->battleGetHeroInfo(sc->side).name;
|
||||
const std::string & castSoundPath = spell.getCastSound();
|
||||
|
||||
if(!castSoundPath.empty())
|
||||
CCS->soundh->playSound(castSoundPath);
|
||||
@ -1262,19 +1256,16 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
|
||||
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.y += 240;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: play custom cast animation
|
||||
{
|
||||
//todo: play custom cast animation
|
||||
displaySpellCast(spellID, BattleHex::INVALID, false);
|
||||
|
||||
}
|
||||
|
||||
//playing projectile animation
|
||||
if(sc->tile.isValid())
|
||||
{
|
||||
@ -1304,163 +1295,33 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
delete animDef;
|
||||
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
|
||||
}
|
||||
}
|
||||
}
|
||||
waitForAnims();
|
||||
|
||||
|
||||
displaySpellHit(spellID, sc->tile);
|
||||
|
||||
//queuing affect /resist animation
|
||||
for (auto & elem : sc->affectedCres)
|
||||
|
||||
//queuing affect animation
|
||||
for(auto & elem : sc->affectedCres)
|
||||
{
|
||||
BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
|
||||
|
||||
if(vstd::contains(sc->resisted,elem))
|
||||
displayEffect(78, position);
|
||||
else
|
||||
displaySpellEffect(spellID, position);
|
||||
displaySpellEffect(spellID, position);
|
||||
}
|
||||
|
||||
switch(sc->id)
|
||||
//queuing additional animation
|
||||
for(auto & elem : sc->customEffects)
|
||||
{
|
||||
case SpellID::SUMMON_FIRE_ELEMENTAL:
|
||||
case SpellID::SUMMON_EARTH_ELEMENTAL:
|
||||
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)
|
||||
BattleHex position = curInt->cb->battleGetStackByID(elem.stack, false)->position;
|
||||
displayEffect(elem.effect, position);
|
||||
}
|
||||
|
||||
//displaying message in console
|
||||
bool customSpell = false;
|
||||
if(sc->affectedCres.size() == 1)
|
||||
{
|
||||
const CStack * attackedStack = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false);
|
||||
std::vector<std::string> logLines;
|
||||
|
||||
spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
|
||||
|
||||
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();
|
||||
//mana absorption
|
||||
if(sc->manaGained > 0)
|
||||
@ -1517,14 +1378,14 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
sp = spellID.toSpell();
|
||||
spellSelMode = ANY_LOCATION;
|
||||
|
||||
|
||||
const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
|
||||
|
||||
|
||||
if(ti.massive || ti.type == CSpell::NO_TARGET)
|
||||
spellSelMode = NO_LOCATION;
|
||||
spellSelMode = NO_LOCATION;
|
||||
else if(ti.type == CSpell::LOCATION && ti.clearAffected)
|
||||
{
|
||||
spellSelMode = FREE_LOCATION;
|
||||
spellSelMode = FREE_LOCATION;
|
||||
}
|
||||
else if(ti.type == CSpell::CREATURE)
|
||||
{
|
||||
@ -1532,11 +1393,11 @@ void CBattleInterface::castThisSpell(SpellID spellID)
|
||||
spellSelMode = selectionTypeByPositiveness(*sp);
|
||||
else
|
||||
spellSelMode = ANY_CREATURE;
|
||||
}
|
||||
}
|
||||
else if(ti.type == CSpell::OBSTACLE)
|
||||
{
|
||||
spellSelMode = OBSTACLE;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
|
||||
if(spell == nullptr)
|
||||
return;
|
||||
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
|
||||
@ -1712,8 +1597,8 @@ void CBattleInterface::endCastingSpell()
|
||||
{
|
||||
assert(spellDestSelectMode);
|
||||
|
||||
delete spellToCast;
|
||||
spellToCast = nullptr;
|
||||
vstd::clear_pointer(spellToCast);
|
||||
|
||||
sp = nullptr;
|
||||
spellDestSelectMode = false;
|
||||
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
|
||||
@ -1797,7 +1682,7 @@ void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, i
|
||||
{
|
||||
if (attacker)
|
||||
formattedText.append(" ");
|
||||
|
||||
|
||||
boost::format txt;
|
||||
if(killed > 1)
|
||||
{
|
||||
@ -2218,7 +2103,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
ui8 skill = 0;
|
||||
if (creatureCasting)
|
||||
skill = sactive->valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, SpellID::TELEPORT));
|
||||
skill = sactive->getSpellSchoolLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
else
|
||||
skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]);
|
||||
//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
|
||||
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();
|
||||
else //no legal action possible
|
||||
{
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
//#include "../../lib/CCreatureSet.h"
|
||||
#include "../../lib/ConstTransitivePtr.h" //may be reundant
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
||||
#include "CBattleAnimations.h"
|
||||
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
|
||||
/*
|
||||
* 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
|
||||
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 spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
|
||||
PossibleActions spellSelMode;
|
||||
@ -319,8 +319,12 @@ public:
|
||||
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 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 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 setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
|
||||
|
@ -69,6 +69,7 @@ void CBattleConsole::showAll(SDL_Surface * to)
|
||||
|
||||
bool CBattleConsole::addText(const std::string & text)
|
||||
{
|
||||
logGlobal->traceStream() <<"CBattleConsole message: "<<text;
|
||||
if(text.size()>70)
|
||||
return false; //text too long!
|
||||
int firstInToken = 0;
|
||||
|
@ -178,7 +178,7 @@ CCreatureAnimation::CCreatureAnimation(std::string name, TSpeedController contro
|
||||
elem.r = reader.readUInt8();
|
||||
elem.g = reader.readUInt8();
|
||||
elem.b = reader.readUInt8();
|
||||
CSDL_Ext::colorSetAlpha(elem,0);
|
||||
elem.a = SDL_ALPHA_OPAQUE;
|
||||
}
|
||||
|
||||
for (int i=0; i<totalBlocks; i++)
|
||||
|
@ -158,7 +158,7 @@ CDefFile::CDefFile(std::string Name):
|
||||
palette[i].r = data[it++];
|
||||
palette[i].g = 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
|
||||
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;
|
||||
|
||||
//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);
|
||||
lineStart = position = (ui8*)image->surf->pixels;
|
||||
}
|
||||
|
@ -59,11 +59,6 @@ inline bool isShiftKeyDown()
|
||||
}
|
||||
namespace CSDL_Ext
|
||||
{
|
||||
//todo: remove
|
||||
STRONG_INLINE void colorSetAlpha(SDL_Color & color, Uint8 alpha)
|
||||
{
|
||||
color.a = alpha;
|
||||
}
|
||||
//todo: should this better be assignment operator?
|
||||
STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source)
|
||||
{
|
||||
|
@ -302,7 +302,7 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
const CArmedInstance * selectedObj = owner->armedObjs[selection->upg];
|
||||
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
|
||||
LOCPLINT->cb->swapCreatures(owner->armedObjs[upg], owner->armedObjs[selection->upg], ID, selection->ID);
|
||||
}
|
||||
|
@ -361,40 +361,35 @@ void MoraleLuckBox::set(const IBonusBearer *node)
|
||||
const int hoverTextBase[] = {7, 4};
|
||||
const Bonus::BonusType bonusType[] = {Bonus::LUCK, Bonus::MORALE};
|
||||
int (IBonusBearer::*getValue[])() const = {&IBonusBearer::LuckVal, &IBonusBearer::MoraleVal};
|
||||
|
||||
int mrlt = -9;
|
||||
TModDescr mrl;
|
||||
TBonusListPtr modifierList(new BonusList());
|
||||
|
||||
if (node)
|
||||
{
|
||||
node->getModifiersWDescr(mrl, bonusType[morale]);
|
||||
modifierList = node->getBonuses(Selector::type(bonusType[morale]));
|
||||
bonusValue = (node->*getValue[morale])();
|
||||
}
|
||||
else
|
||||
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];
|
||||
baseType = componentType[morale];
|
||||
text = CGI->generaltexth->arraytxt[textId[morale]];
|
||||
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
|
||||
{
|
||||
//it's a creature window
|
||||
if ((morale && node && node->hasBonusOfType(Bonus::UNDEAD)) ||
|
||||
node->hasBonusOfType(Bonus::BLOCK_MORALE) || node->hasBonusOfType(Bonus::NON_LIVING))
|
||||
for(const Bonus * elem : *modifierList)
|
||||
{
|
||||
text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto & elem : mrl)
|
||||
{
|
||||
if (elem.first) //no bonuses with value 0
|
||||
text += "\n" + elem.second;
|
||||
}
|
||||
if(elem->val != 0)
|
||||
//no bonuses with value 0
|
||||
text += "\n" + elem->Description();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,10 +498,10 @@ void CTextInput::setText( const std::string &nText, bool callCb )
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
void CTextInput::textInputed(const SDL_TextInputEvent & event)
|
||||
|
@ -135,7 +135,7 @@ void CQuestLog::init()
|
||||
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
|
||||
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
|
||||
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());
|
||||
@ -182,7 +182,7 @@ void CQuestLog::recreateLabelList()
|
||||
auto label = make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString());
|
||||
label->disable();
|
||||
|
||||
label->callback = boost::bind(&CQuestLog::selectQuest, this, i, currentLabel);
|
||||
label->callback = std::bind(&CQuestLog::selectQuest, this, i, currentLabel);
|
||||
labels.push_back(label);
|
||||
|
||||
// Select latest active quest
|
||||
|
@ -11,8 +11,8 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":10, "val": 1, "subtype": 5, "info": 0 }
|
||||
],
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 106 }
|
||||
],
|
||||
"army" :
|
||||
[
|
||||
{
|
||||
@ -41,7 +41,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 106 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 98 }
|
||||
]
|
||||
},
|
||||
"wystan":
|
||||
@ -56,7 +56,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 98 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 100 }
|
||||
]
|
||||
},
|
||||
"tazar":
|
||||
@ -70,7 +70,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 100 }
|
||||
{ "type":2, "val": 5, "subtype": 23, "info": 0 }
|
||||
]
|
||||
},
|
||||
"alkin":
|
||||
@ -85,7 +85,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":2, "val": 5, "subtype": 23, "info": 0 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 102 }
|
||||
]
|
||||
},
|
||||
"korbac":
|
||||
@ -100,7 +100,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 102 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 104 }
|
||||
]
|
||||
},
|
||||
"gerwulf":
|
||||
@ -115,7 +115,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 104 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
|
||||
]
|
||||
},
|
||||
"broghild":
|
||||
@ -130,7 +130,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 146 }
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 108 }
|
||||
]
|
||||
},
|
||||
"mirlanda":
|
||||
@ -145,7 +145,7 @@
|
||||
],
|
||||
"specialties":
|
||||
[
|
||||
{ "type":1, "val": 0, "subtype": 0, "info": 108 }
|
||||
{ "type":8, "val": 0, "subtype": 45, "info": 0 }
|
||||
]
|
||||
},
|
||||
"rosic":
|
||||
|
@ -12,6 +12,10 @@
|
||||
"type": "array",
|
||||
"items":{
|
||||
"anyOf":[
|
||||
{
|
||||
//dummy animation, pause, Value - frame count
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
//assumed verticalPosition: top
|
||||
"type": "string",
|
||||
|
@ -242,7 +242,9 @@
|
||||
"removeObstacle" : {
|
||||
"index" : 64,
|
||||
"targetType" : "OBSTACLE",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "REMOVEOB"
|
||||
},
|
||||
@ -258,7 +260,9 @@
|
||||
"clone" : {
|
||||
"index" : 65,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CLONE"
|
||||
},
|
||||
@ -278,7 +282,9 @@
|
||||
"fireElemental" : {
|
||||
"index" : 66,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -294,7 +300,9 @@
|
||||
"earthElemental" : {
|
||||
"index" : 67,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -310,7 +318,9 @@
|
||||
"waterElemental" : {
|
||||
"index" : 68,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
@ -326,7 +336,9 @@
|
||||
"airElemental" : {
|
||||
"index" : 69,
|
||||
"targetType" : "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"cast":[2]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SUMNELM"
|
||||
},
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"shield" : {
|
||||
"index" : 27,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SHIELD"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"generalDamageReduction" : {
|
||||
@ -32,16 +32,16 @@
|
||||
"airShield" : {
|
||||
"index" : 28,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "AIRSHELD"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"generalDamageReduction" : {
|
||||
@ -62,19 +62,19 @@
|
||||
"fireShield" : {
|
||||
"index" : 29,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FIRESHIE"
|
||||
},
|
||||
// It looks that fireshield has two separate sounds
|
||||
// "soundfile":"FIRESHLD.wav"
|
||||
//
|
||||
//
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"fireShield" : {
|
||||
@ -82,9 +82,6 @@
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -94,10 +91,10 @@
|
||||
"protectAir" : {
|
||||
"index" : 30,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTA"
|
||||
},
|
||||
@ -124,10 +121,10 @@
|
||||
"protectFire" : {
|
||||
"index" : 31,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPW0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTF"
|
||||
},
|
||||
@ -154,16 +151,16 @@
|
||||
"protectWater" : {
|
||||
"index" : 32,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C11SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTW"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
@ -184,10 +181,10 @@
|
||||
"protectEarth" : {
|
||||
"index" : 33,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PROTECTE"
|
||||
},
|
||||
@ -214,10 +211,10 @@
|
||||
"antiMagic" : {
|
||||
"index" : 34,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ANTIMAGK"
|
||||
},
|
||||
@ -227,7 +224,7 @@
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"levelSpellImmunity" : {
|
||||
"val" : 3,
|
||||
"val" : 3,
|
||||
"type" : "LEVEL_SPELL_IMMUNITY",
|
||||
"valueType" : "INDEPENDENT_MAX",
|
||||
"duration" : "N_TURNS"
|
||||
@ -257,10 +254,10 @@
|
||||
"magicMirror" : {
|
||||
"index" : 36,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BACKLASH"
|
||||
},
|
||||
@ -284,16 +281,16 @@
|
||||
"bless" : {
|
||||
"index" : 41,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPW"] //C01SPW0
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLESS"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"alwaysMaximumDamage" : {
|
||||
@ -322,16 +319,16 @@
|
||||
"curse" : {
|
||||
"index" : 42,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPW"]//C04SPW0
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CURSE"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"alwaysMinimumDamage" : {
|
||||
@ -362,20 +359,20 @@
|
||||
"bloodlust" : {
|
||||
"index" : 43,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["SP12_"] //???
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLOODLUS"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"primarySkill" : {
|
||||
"val" : 3,
|
||||
"val" : 3,
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.attack",
|
||||
"effectRange" : "ONLY_MELEE_FIGHT",
|
||||
@ -412,10 +409,10 @@
|
||||
"precision" : {
|
||||
"index" : 44,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C12SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PRECISON"
|
||||
},
|
||||
@ -459,22 +456,22 @@
|
||||
"weakness" : {
|
||||
"index" : 45,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C0ACID"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "WEAKNESS"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"primarySkill" : {
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.attack",
|
||||
"val" : -3,
|
||||
"val" : -3,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -506,22 +503,22 @@
|
||||
"stoneSkin" : {
|
||||
"index" : 46,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C16SPE"] //C16SPE0
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "TUFFSKIN"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"primarySkill" : {
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.defence",
|
||||
"val" : 3,
|
||||
"val" : 3,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -549,11 +546,11 @@
|
||||
"disruptingRay" : {
|
||||
"index" : 47,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C07SPA1"],
|
||||
"projectile":[{"defName":"C07SPA0"}]//???
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DISRUPTR"
|
||||
},
|
||||
@ -565,7 +562,7 @@
|
||||
"primarySkill" : {
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.defence",
|
||||
"val" : -3,
|
||||
"val" : -3,
|
||||
"valueType" : "ADDITIVE_VALUE",
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
@ -593,34 +590,34 @@
|
||||
"prayer" : {
|
||||
"index" : 48,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PRAYER"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"attack" : {
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.attack",
|
||||
"val" : 2,
|
||||
"val" : 2,
|
||||
"duration" : "N_TURNS"
|
||||
},
|
||||
"defence" : {
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.defence",
|
||||
"val" : 2,
|
||||
"val" : 2,
|
||||
"duration" : "N_TURNS"
|
||||
},
|
||||
"stacksSpeed" : {
|
||||
"addInfo" : 0,
|
||||
"type" : "STACKS_SPEED",
|
||||
"val" : 2,
|
||||
"val" : 2,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -660,21 +657,21 @@
|
||||
"mirth" : {
|
||||
"index" : 49,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C09SPW0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MIRTH"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"morale" : {
|
||||
"type" : "MORALE",
|
||||
"val" : 1,
|
||||
"val" : 1,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -700,7 +697,7 @@
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
"UNDEAD": true,
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
@ -713,21 +710,21 @@
|
||||
"sorrow" : {
|
||||
"index" : 50,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C14SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SORROW"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"morale" : {
|
||||
"type" : "MORALE",
|
||||
"val" : -1,
|
||||
"val" : -1,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -753,7 +750,7 @@
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
"UNDEAD": true,
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
@ -766,21 +763,21 @@
|
||||
"fortune" : {
|
||||
"index" : 51,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C09SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FORTUNE"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"luck" : {
|
||||
"type" : "LUCK",
|
||||
"val" : 1,
|
||||
"val" : 1,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -811,21 +808,21 @@
|
||||
"misfortune" : {
|
||||
"index" : 52,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C10SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MISFORT"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"luck" : {
|
||||
"type" : "LUCK",
|
||||
"val" : -1,
|
||||
"val" : -1,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -856,22 +853,22 @@
|
||||
"haste" : {
|
||||
"index" : 53,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C15SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "HASTE"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"stacksSpeed" : {
|
||||
"addInfo" : 0,
|
||||
"type" : "STACKS_SPEED",
|
||||
"val" : 3,
|
||||
"val" : 3,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -905,22 +902,22 @@
|
||||
"slow" : {
|
||||
"index" : 54,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "MUCKMIRE"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"stacksSpeed" : {
|
||||
"addInfo" : 0,
|
||||
"type" : "STACKS_SPEED",
|
||||
"val" : -25,
|
||||
"val" : -25,
|
||||
"valueType" : "PERCENT_TO_ALL",
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
@ -956,10 +953,10 @@
|
||||
"slayer" : {
|
||||
"index" : 55,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C13SPW0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SLAYER"
|
||||
},
|
||||
@ -1010,10 +1007,10 @@
|
||||
"frenzy" : {
|
||||
"index" : 56,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C08SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FRENZY"
|
||||
},
|
||||
@ -1024,8 +1021,9 @@
|
||||
"effects" : {
|
||||
"inFrenzy" : {
|
||||
"type" : "IN_FRENZY",
|
||||
"val" : 100,
|
||||
"duration" : "STACK_GETS_TURN"
|
||||
"val" : 100,
|
||||
"duration" : "N_TURNS",
|
||||
"turns" : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1055,21 +1053,21 @@
|
||||
"counterstrike" : {
|
||||
"index" : 58,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CNTRSTRK"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"additionalRetaliation" : {
|
||||
"type" : "ADDITIONAL_RETALIATION",
|
||||
"val" : 1,
|
||||
"val" : 1,
|
||||
"duration" : "N_TURNS"
|
||||
}
|
||||
}
|
||||
@ -1099,11 +1097,11 @@
|
||||
},
|
||||
"berserk" : {
|
||||
"index" : 59,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"targetType" : "LOCATION",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPF"] //C01SPF0
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BERSERK"
|
||||
},
|
||||
@ -1114,7 +1112,8 @@
|
||||
"effects" : {
|
||||
"attacksNearestCreature" : {
|
||||
"type" : "ATTACKS_NEAREST_CREATURE",
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "N_TURNS",
|
||||
"turns" : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1164,10 +1163,10 @@
|
||||
"hypnotize" : {
|
||||
"index" : 60,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C10SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "HYPNOTIZ"
|
||||
},
|
||||
@ -1225,17 +1224,17 @@
|
||||
},
|
||||
"forgetfulness" : {
|
||||
"index" : 61,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C06SPW"]//C06SPW0
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FORGET"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"forgetful" : {
|
||||
@ -1292,10 +1291,10 @@
|
||||
"blind" : {
|
||||
"index" : 62,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C02SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "BLIND"
|
||||
},
|
||||
|
@ -763,7 +763,7 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
|
||||
|
||||
CStack::CStack(const CStackInstance *Base, PlayerColor O, int I, bool AO, SlotID S)
|
||||
: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),
|
||||
counterAttacks(1)
|
||||
counterAttacksPerformed(0),counterAttacksTotalCache(0), cloneID(-1)
|
||||
{
|
||||
assert(base);
|
||||
type = base->type;
|
||||
@ -776,7 +776,8 @@ CStack::CStack()
|
||||
setNodeType(STACK_BATTLE);
|
||||
}
|
||||
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;
|
||||
count = baseAmount = stack->count;
|
||||
@ -794,7 +795,9 @@ void CStack::init()
|
||||
slot = SlotID(255);
|
||||
attackerOwned = false;
|
||||
position = BattleHex();
|
||||
counterAttacks = -1;
|
||||
counterAttacksPerformed = 0;
|
||||
counterAttacksTotalCache = 0;
|
||||
cloneID = -1;
|
||||
}
|
||||
|
||||
void CStack::postInit()
|
||||
@ -804,9 +807,11 @@ void CStack::postInit()
|
||||
|
||||
firstHPleft = MaxHealth();
|
||||
shots = getCreature()->valOfBonuses(Bonus::SHOTS);
|
||||
counterAttacks = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION);
|
||||
counterAttacksPerformed = 0;
|
||||
counterAttacksTotalCache = 0;
|
||||
casts = valOfBonuses(Bonus::CASTS);
|
||||
resurrected = 0;
|
||||
cloneID = -1;
|
||||
}
|
||||
|
||||
ui32 CStack::level() const
|
||||
@ -848,10 +853,10 @@ void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
|
||||
|
||||
for(Bonus& b : tmp)
|
||||
{
|
||||
b.turnsRemain = sse.turnsRemain;
|
||||
if(b.turnsRemain == 0)
|
||||
b.turnsRemain = sse.turnsRemain;
|
||||
sf.push_back(b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return alive()
|
||||
&& (counterAttacks > 0 || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
|
||||
&& (counterAttacksPerformed < counterAttacksTotal() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
|
||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON)
|
||||
&& !hasBonusOfType(Bonus::HYPNOTIZED)
|
||||
&& !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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
switch(phase)
|
||||
|
@ -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 "HeroBonus.h"
|
||||
@ -12,16 +21,7 @@
|
||||
#include "GameConstants.h"
|
||||
#include "CBattleCallback.h"
|
||||
#include "int3.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
|
||||
*
|
||||
*/
|
||||
#include "spells/Magic.h"
|
||||
|
||||
class CGHeroInstance;
|
||||
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
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor
|
||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor, public ISpellCaster
|
||||
{
|
||||
public:
|
||||
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)
|
||||
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
|
||||
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
|
||||
ui8 casts; //how many casts left
|
||||
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;
|
||||
//overrides
|
||||
const CCreature* getCreature() const {return type;}
|
||||
@ -191,6 +195,10 @@ public:
|
||||
std::string getName() const; //plural or singular
|
||||
bool willMove(int turn = 0) const; //if stack has remaining move this turn
|
||||
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 waited(int turn = 0) const;
|
||||
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>
|
||||
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)
|
||||
{
|
||||
assert(isIndependentNode());
|
||||
h & static_cast<CBonusSystemNode&>(*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;
|
||||
|
||||
const CArmedInstance *army = (base ? base->armyObj : nullptr);
|
||||
|
@ -28,8 +28,8 @@ DLL_LINKAGE CConsoleHandler * console = nullptr;
|
||||
#define CONSOLE_TEAL "\x1b[1;36m"
|
||||
#else
|
||||
#include <Windows.h>
|
||||
#include <dbghelp.h>
|
||||
#ifndef __MINGW32__
|
||||
#include <dbghelp.h>
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#endif
|
||||
typedef WORD TColor;
|
||||
@ -121,7 +121,6 @@ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception)
|
||||
const DWORD threadId = ::GetCurrentThreadId();
|
||||
logGlobal->errorStream() << "Thread ID: " << threadId << " [" << std::dec << std::setw(0) << threadId << "]";
|
||||
|
||||
#ifndef __MINGW32__
|
||||
//exception info to be placed in the dump
|
||||
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);
|
||||
logGlobal->errorStream() << "Crash info will be put in " << mname;
|
||||
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);
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
@ -206,12 +206,24 @@ std::string CLegacyConfigParser::extractQuotedString()
|
||||
{
|
||||
ret += extractQuotedPart();
|
||||
|
||||
// double quote - add it to string and continue unless
|
||||
// line terminated using tabulation
|
||||
if (curr < end && *curr == '\"' && *curr != '\t')
|
||||
// double quote - add it to string and continue quoted part
|
||||
if (curr < end && *curr == '\"')
|
||||
{
|
||||
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
|
||||
return ret;
|
||||
}
|
||||
|
@ -27,8 +27,8 @@
|
||||
#include "mapping/CCampaignHandler.h" //for CCampaignState
|
||||
#include "rmg/CMapGenerator.h" // for CMapGenOptions
|
||||
|
||||
const ui32 version = 753;
|
||||
const ui32 minSupportedVersion = version;
|
||||
const ui32 version = 754;
|
||||
const ui32 minSupportedVersion = 753;
|
||||
|
||||
class CISer;
|
||||
class COSer;
|
||||
|
@ -864,7 +864,10 @@ public:
|
||||
WALKING_DEAD = 58,
|
||||
WIGHTS = 60,
|
||||
LICHES = 64,
|
||||
BONE_DRAGON = 68,
|
||||
TROGLODYTES = 70,
|
||||
HYDRA = 110,
|
||||
CHAOS_HYDRA = 111,
|
||||
AIR_ELEMENTAL = 112,
|
||||
EARTH_ELEMENTAL = 113,
|
||||
FIRE_ELEMENTAL = 114,
|
||||
|
@ -211,21 +211,8 @@ Bonus * BonusList::getFirst(const CSelector &select)
|
||||
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
|
||||
{
|
||||
// for(Bonus *i : *this)
|
||||
// if(selector(i) && i->effectRange == Bonus::NO_LIMIT)
|
||||
// out.push_back(i);
|
||||
|
||||
getBonuses(out, selector, nullptr);
|
||||
}
|
||||
|
||||
@ -359,17 +346,6 @@ bool IBonusBearer::hasBonusOfType(Bonus::BonusType type, int subtype /*= -1*/) c
|
||||
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
|
||||
{
|
||||
std::stringstream cachingStr;
|
||||
@ -524,8 +500,13 @@ bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" b
|
||||
const TBonusListPtr IBonusBearer::getSpellBonuses() const
|
||||
{
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "source_" << Bonus::SPELL_EFFECT;
|
||||
return getBonuses(Selector::sourceType(Bonus::SPELL_EFFECT), Selector::anyRange(), cachingStr.str());
|
||||
cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
|
||||
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
|
||||
@ -1105,12 +1086,6 @@ bool NBonus::hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int s
|
||||
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)
|
||||
{
|
||||
if(obj)
|
||||
@ -1127,28 +1102,34 @@ const CSpell * Bonus::sourceSpell() const
|
||||
|
||||
std::string Bonus::Description() const
|
||||
{
|
||||
if(description.size())
|
||||
return description;
|
||||
|
||||
std::ostringstream str;
|
||||
str << std::showpos << val << " ";
|
||||
|
||||
switch(source)
|
||||
{
|
||||
case ARTIFACT:
|
||||
str << VLC->arth->artifacts[sid]->Name();
|
||||
break;;
|
||||
case SPELL_EFFECT:
|
||||
str << SpellID(sid).toSpell()->name;
|
||||
break;
|
||||
case CREATURE_ABILITY:
|
||||
str << VLC->creh->creatures[sid]->namePl;
|
||||
break;
|
||||
case SECONDARY_SKILL:
|
||||
str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(description.empty())
|
||||
switch(source)
|
||||
{
|
||||
case ARTIFACT:
|
||||
str << VLC->arth->artifacts[sid]->Name();
|
||||
break;;
|
||||
case SPELL_EFFECT:
|
||||
str << SpellID(sid).toSpell()->name;
|
||||
break;
|
||||
case CREATURE_ABILITY:
|
||||
str << VLC->creh->creatures[sid]->namePl;
|
||||
break;
|
||||
case SECONDARY_SKILL:
|
||||
str << VLC->generaltexth->skillName[sid]/* << " secondary skill"*/;
|
||||
break;
|
||||
default:
|
||||
//todo: handle all possible sources
|
||||
str << "Unknown";
|
||||
break;
|
||||
}
|
||||
else
|
||||
str << description;
|
||||
|
||||
if(val != 0)
|
||||
str << " " << std::showpos << val;
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ class BonusList;
|
||||
typedef shared_ptr<BonusList> TBonusListPtr;
|
||||
typedef shared_ptr<ILimiter> TLimiterPtr;
|
||||
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<const CBonusSystemNode*> TCNodes;
|
||||
typedef std::vector<CBonusSystemNode *> TNodesVector;
|
||||
@ -448,7 +447,6 @@ public:
|
||||
int totalValue() const; //subtype -> subtype of bonus, if -1 then any
|
||||
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit) const;
|
||||
void getAllBonuses(BonusList &out) const;
|
||||
void getModifiersWDescr(TModDescr &out) 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)
|
||||
//interface
|
||||
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 valOfBonuses(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;
|
||||
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;
|
||||
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;
|
||||
|
||||
//various hlp functions for non-trivial values
|
||||
@ -722,8 +718,6 @@ namespace NBonus
|
||||
//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 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);
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ public:
|
||||
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 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 eraseStack(const StackLocation &sl, bool forceRemoval = false) =0;
|
||||
virtual bool swapStacks(const StackLocation &sl1, const StackLocation &sl2) =0;
|
||||
|
@ -795,7 +795,7 @@ struct ChangeStackCount : CGarrisonOperationPack //521
|
||||
struct SetStackType : CGarrisonOperationPack //522
|
||||
{
|
||||
StackLocation sl;
|
||||
CCreature *type;
|
||||
const CCreature *type;
|
||||
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
@ -1478,6 +1478,18 @@ struct EndAction : public CPackForClient//3008
|
||||
|
||||
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;};
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
@ -1488,13 +1500,13 @@ struct BattleSpellCast : public CPackForClient//3009
|
||||
ui8 skill; //caster's skill level
|
||||
ui8 manaGained; //mana channeling ability
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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;};
|
||||
|
||||
enum BattleStackProperty {CASTS, ENCHANTER_COUNTER, UNBIND, CLONED};
|
||||
enum BattleStackProperty {CASTS, ENCHANTER_COUNTER, UNBIND, CLONED, HAS_CLONE};
|
||||
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
//void applyCl(CClient *cl){};
|
||||
|
||||
int stackID;
|
||||
BattleStackProperty which;
|
||||
|
@ -1095,7 +1095,8 @@ DLL_LINKAGE void BattleNextRound::applyGs( CGameState *gs )
|
||||
s->state -= EBattleStackState::HAD_MORALE;
|
||||
s->state -= EBattleStackState::FEAR;
|
||||
s->state -= EBattleStackState::DRAINED_MANA;
|
||||
s->counterAttacks = 1 + s->valOfBonuses(Bonus::ADDITIONAL_RETALIATION);
|
||||
s->counterAttacksPerformed = 0;
|
||||
s->counterAttacksTotalCache = 0;
|
||||
// new turn effects
|
||||
s->battleTurnPassed();
|
||||
}
|
||||
@ -1247,7 +1248,12 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
|
||||
{
|
||||
//"hide" killed creatures instead so we keep info about it
|
||||
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);
|
||||
if(counter())
|
||||
attacker->counterAttacks--;
|
||||
attacker->counterAttacksPerformed++;
|
||||
|
||||
if(shot())
|
||||
{
|
||||
@ -1603,6 +1609,11 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
|
||||
stack->state.insert(EBattleStackState::CLONED);
|
||||
break;
|
||||
}
|
||||
case HAS_CLONE:
|
||||
{
|
||||
stack->cloneID = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
<Add option="-lboost_chrono$(#boost.libsuffix)" />
|
||||
<Add option="-lboost_locale$(#boost.libsuffix)" />
|
||||
<Add option="-liconv" />
|
||||
<Add option="-ldbghelp" />
|
||||
<Add directory="$(#sdl2.lib)" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
<Add directory="$(#zlib.lib)" />
|
||||
@ -59,6 +60,7 @@
|
||||
<Add option="-lboost_chrono$(#boost.libsuffix)" />
|
||||
<Add option="-lboost_locale$(#boost.libsuffix)" />
|
||||
<Add option="-liconv" />
|
||||
<Add option="-ldbghelp" />
|
||||
<Add directory="$(#sdl2.lib)" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
<Add directory="$(#zlib.lib)" />
|
||||
@ -88,6 +90,7 @@
|
||||
<Add option="-lboost_chrono$(#boost.libsuffix)" />
|
||||
<Add option="-lboost_locale$(#boost.libsuffix)" />
|
||||
<Add option="-liconv" />
|
||||
<Add option="-ldbghelp" />
|
||||
<Add directory="$(#sdl2.lib64)" />
|
||||
<Add directory="$(#boost.lib64)" />
|
||||
<Add directory="$(#zlib64.lib)" />
|
||||
@ -311,6 +314,7 @@
|
||||
<Unit filename="spells/CreatureSpellMechanics.h" />
|
||||
<Unit filename="spells/ISpellMechanics.cpp" />
|
||||
<Unit filename="spells/ISpellMechanics.h" />
|
||||
<Unit filename="spells/Magic.h" />
|
||||
<Unit filename="spells/ViewSpellInt.cpp" />
|
||||
<Unit filename="spells/ViewSpellInt.h" />
|
||||
<Unit filename="vcmi_endian.h" />
|
||||
|
@ -86,11 +86,13 @@ void CArmedInstance::updateMoraleBonusFromArmy()
|
||||
{
|
||||
b->val = +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
|
||||
{
|
||||
b->val = 2 - factionsInArmy;
|
||||
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);
|
||||
|
||||
@ -100,7 +102,11 @@ void CArmedInstance::updateMoraleBonusFromArmy()
|
||||
if(hasUndead)
|
||||
{
|
||||
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)
|
||||
removeBonus(undeadModifier);
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "../IGameCallback.h"
|
||||
#include "../CGameState.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../BattleState.h"
|
||||
|
||||
///helpers
|
||||
static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
|
||||
@ -216,9 +217,10 @@ CGHeroInstance::CGHeroInstance()
|
||||
setNodeType(HERO);
|
||||
ID = Obj::HERO;
|
||||
tacticFormationEnabled = inTownGarrison = false;
|
||||
mana = movement = portrait = level = -1;
|
||||
mana = movement = portrait = -1;
|
||||
isStanding = true;
|
||||
moveDir = 4;
|
||||
level = 1;
|
||||
exp = 0xffffffff;
|
||||
visitedTown = nullptr;
|
||||
type = nullptr;
|
||||
@ -290,7 +292,6 @@ void CGHeroInstance::initHero()
|
||||
}
|
||||
assert(validTypes());
|
||||
|
||||
level = 1;
|
||||
if(exp == 0xffffffff)
|
||||
{
|
||||
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::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);
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "CObjectHandler.h"
|
||||
#include "CArmedInstance.h"
|
||||
#include "../spells/Magic.h"
|
||||
|
||||
#include "../CArtHandler.h" // For CArtifactSet
|
||||
#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:
|
||||
enum ECanDig
|
||||
@ -162,14 +163,13 @@ public:
|
||||
int maxMovePoints(bool onLand) 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
|
||||
double getFightingStrength() const; // takes attack / defense skill into account
|
||||
double getMagicStrength() const; // takes knowledge / spell power skill into account
|
||||
double getHeroStrength() const; // includes fighting and magic strength
|
||||
ui64 getTotalStrength() const; // includes fighting strength and army strength
|
||||
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
|
||||
CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
|
||||
void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
|
||||
@ -198,13 +198,18 @@ public:
|
||||
|
||||
CGHeroInstance();
|
||||
virtual ~CGHeroInstance();
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
||||
///ArtBearer
|
||||
ArtBearer::ArtBearer bearerType() const override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///IBonusBearer
|
||||
CBonusSystemNode *whereShouldBeAttached(CGameState *gs) 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 initObj() override;
|
||||
|
@ -436,12 +436,12 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
|
||||
//other *-of-legion-like bonuses (%d to growth cumulative with grail)
|
||||
TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level)));
|
||||
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
|
||||
TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT));
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
GrowthInfo::Entry::Entry(int _count, const std::string &fullDescription)
|
||||
: count(_count)
|
||||
{
|
||||
description = fullDescription;
|
||||
}
|
||||
|
||||
CTownAndVisitingHero::CTownAndVisitingHero()
|
||||
{
|
||||
setNodeType(TOWN_AND_VISITOR);
|
||||
|
@ -130,6 +130,7 @@ struct DLL_LINKAGE GrowthInfo
|
||||
std::string description;
|
||||
Entry(const std::string &format, int _count);
|
||||
Entry(int subID, BuildingID building, int _count);
|
||||
Entry(int _count, const std::string &fullDescription);
|
||||
};
|
||||
|
||||
std::vector<Entry> entries;
|
||||
|
@ -93,6 +93,12 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battle
|
||||
ssp.val = 0;
|
||||
ssp.absolute = 1;
|
||||
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
|
||||
@ -100,6 +106,8 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHer
|
||||
//can't clone already cloned creature
|
||||
if(vstd::contains(obj->state, EBattleStackState::CLONED))
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
if(obj->cloneID != -1)
|
||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||
//TODO: how about stacks casting Clone?
|
||||
//currently Clone casted by stack is assumed Expert level
|
||||
ui8 schoolLevel;
|
||||
@ -127,26 +135,15 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHer
|
||||
void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
DefaultSpellMechanics::applyBattle(battle, packet);
|
||||
|
||||
for(auto stackID : packet->affectedCres)
|
||||
doDispell(battle, packet, [](const Bonus * b) -> bool
|
||||
{
|
||||
if(vstd::contains(packet->resisted, stackID))
|
||||
if(b->source == Bonus::SPELL_EFFECT)
|
||||
{
|
||||
logGlobal->errorStream() << "Resistance to positive spell CURE";
|
||||
continue;
|
||||
CSpell * sp = SpellID(b->sid).toSpell();
|
||||
return sp->isNegative();
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
return false; //not a spell effect
|
||||
});
|
||||
}
|
||||
|
||||
///DispellMechanics
|
||||
@ -310,8 +307,8 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
|
||||
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
||||
ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
|
||||
//apply 'damage' bonus for hypnotize, including hero specialty
|
||||
ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
|
||||
ui64 maxHealth = caster->getSpellBonus(owner, caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
|
||||
* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), obj);
|
||||
if (subjectHealth > maxHealth)
|
||||
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
|
||||
//therefore we do not need to check caster and casting mode
|
||||
//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;
|
||||
|
||||
if(!immune && casterStack)
|
||||
@ -482,7 +480,6 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBatt
|
||||
if(targetExists && targetToSacrificeExists)
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
//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
|
||||
* owner->getPower(parameters.spellLvl)
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "../NetPacks.h"
|
||||
#include "../BattleState.h"
|
||||
|
||||
#include "../CGeneralTextHandler.h"
|
||||
|
||||
namespace SRSLPraserHelpers
|
||||
{
|
||||
static int XYToHex(int x, int y)
|
||||
@ -120,7 +122,7 @@ namespace SRSLPraserHelpers
|
||||
///DefaultSpellMechanics
|
||||
void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
|
||||
{
|
||||
if (packet->castedByHero)
|
||||
if (packet->castByHero)
|
||||
{
|
||||
if (packet->side < 2)
|
||||
{
|
||||
@ -131,9 +133,6 @@ void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCa
|
||||
//handle countering spells
|
||||
for(auto stackID : packet->affectedCres)
|
||||
{
|
||||
if(vstd::contains(packet->resisted, stackID))
|
||||
continue;
|
||||
|
||||
CStack * s = battle->getStack(stackID);
|
||||
s->popBonuses([&](const Bonus * b) -> bool
|
||||
{
|
||||
@ -230,16 +229,16 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
sc.skill = parameters.spellLvl;
|
||||
sc.tile = parameters.destination;
|
||||
sc.dmgToDisplay = 0;
|
||||
sc.castedByHero = nullptr != parameters.caster;
|
||||
sc.castByHero = nullptr != parameters.casterHero;
|
||||
sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
|
||||
sc.manaGained = 0;
|
||||
|
||||
int spellCost = 0;
|
||||
|
||||
//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
|
||||
{
|
||||
@ -259,27 +258,60 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
//must be vector, as in Chain Lightning order matters
|
||||
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));
|
||||
|
||||
for (auto cre : attackedCres)
|
||||
{
|
||||
sc.affectedCres.insert(cre->ID);
|
||||
}
|
||||
|
||||
std::vector <const CStack*> reflected;//for magic mirror
|
||||
|
||||
//checking if creatures resist
|
||||
//resistance is applied only to negative spells
|
||||
//resistance/reflection is applied only to negative spells
|
||||
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)
|
||||
{
|
||||
//magic resistance
|
||||
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
|
||||
|
||||
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;
|
||||
@ -290,12 +322,12 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
env->sendAndApply(&sc);
|
||||
|
||||
//spend mana
|
||||
if(parameters.caster)
|
||||
if(parameters.casterHero)
|
||||
{
|
||||
SetMana sm;
|
||||
sm.absolute = false;
|
||||
|
||||
sm.hid = parameters.caster->id;
|
||||
sm.hid = parameters.casterHero->id;
|
||||
sm.val = -spellCost;
|
||||
|
||||
env->sendAndApply(&sm);
|
||||
@ -328,61 +360,143 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
|
||||
}
|
||||
|
||||
//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);
|
||||
if(mirrorChance > env->getRandomGenerator().nextInt(99))
|
||||
{
|
||||
std::vector<const CStack *> mirrorTargets;
|
||||
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;
|
||||
//Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
|
||||
return battleStack->owner == parameters.casterColor;
|
||||
},
|
||||
true);//turrets included
|
||||
|
||||
BattleSpellCastParameters mirrorParameters = parameters;
|
||||
mirrorParameters.spellLvl = 0;
|
||||
mirrorParameters.casterSide = 1-parameters.casterSide;
|
||||
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;
|
||||
if(!mirrorTargets.empty())
|
||||
{
|
||||
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
|
||||
|
||||
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
|
||||
{
|
||||
if(!caster)
|
||||
if(caster == nullptr)
|
||||
{
|
||||
if (!usedSpellPower)
|
||||
return 3; //default duration of all creature spells
|
||||
else
|
||||
return usedSpellPower; //use creature spell power
|
||||
return usedSpellPower; //use creature spell power
|
||||
}
|
||||
switch(owner->id)
|
||||
{
|
||||
case SpellID::FRENZY:
|
||||
return 1;
|
||||
default: //other spells
|
||||
else
|
||||
return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
else
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
if(owner->isOffensiveSpell())
|
||||
{
|
||||
@ -427,14 +549,11 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
int chainLightningModifier = 0;
|
||||
for(auto & attackedCre : ctx.attackedCres)
|
||||
{
|
||||
if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
|
||||
continue;
|
||||
|
||||
BattleStackAttacked bsa;
|
||||
if(spellDamage)
|
||||
bsa.damageAmount = spellDamage >> chainLightningModifier;
|
||||
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;
|
||||
|
||||
@ -459,29 +578,42 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||
}
|
||||
SetStackEffect sse;
|
||||
Bonus pseudoBonus;
|
||||
pseudoBonus.sid = owner->id;
|
||||
pseudoBonus.val = parameters.spellLvl;
|
||||
pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
|
||||
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
|
||||
//get default spell duration (spell power with bonuses for heroes)
|
||||
int duration = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
|
||||
//generate actual stack bonuses
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
if(parameters.caster)
|
||||
bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
|
||||
if(parameters.casterHero)
|
||||
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
|
||||
//TODO does hero specialty should affects his stack casting spells?
|
||||
|
||||
si32 power = 0;
|
||||
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);
|
||||
|
||||
//Apply hero specials - peculiar enchants
|
||||
@ -512,17 +644,17 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
case 1: //only Coronius as yet
|
||||
{
|
||||
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;
|
||||
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
|
||||
}
|
||||
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;
|
||||
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
|
||||
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, duration);
|
||||
specialBonus.valType = Bonus::PERCENT_TO_ALL;
|
||||
specialBonus.sid = owner->id;
|
||||
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
|
||||
@ -563,13 +695,13 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
|
||||
else
|
||||
{
|
||||
//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));
|
||||
}
|
||||
}
|
||||
else
|
||||
hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
|
||||
hi.lowLevelResurrection = (parameters.spellLvl <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
|
||||
hi.healedHP = calculateHealedHP(parameters.casterHero, attackedCre, parameters.selectedStack); //Casted by hero
|
||||
hi.lowLevelResurrection = (effectLevel <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
|
||||
shr.healedStacks.push_back(hi);
|
||||
}
|
||||
if(!shr.healedStacks.empty())
|
||||
@ -734,9 +866,6 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
|
||||
{
|
||||
for(auto stackID : packet->affectedCres)
|
||||
{
|
||||
if(vstd::contains(packet->resisted, stackID))
|
||||
continue;
|
||||
|
||||
CStack *s = battle->getStack(stackID);
|
||||
s->popBonuses(selector);
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ public:
|
||||
bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
|
||||
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:
|
||||
virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../BattleState.h"
|
||||
#include "../CBattleCallback.h"
|
||||
#include "../CGameState.h"
|
||||
|
||||
#include "ISpellMechanics.h"
|
||||
|
||||
@ -71,7 +72,7 @@ namespace SpellConfig
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@ -126,38 +127,6 @@ void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParamet
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) 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 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
|
||||
{
|
||||
ui32 ret = 0; //value to return
|
||||
|
||||
@ -230,7 +177,9 @@ ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affec
|
||||
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;
|
||||
}
|
||||
|
||||
@ -551,6 +500,59 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstanc
|
||||
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)
|
||||
{
|
||||
isOffensive = val;
|
||||
@ -588,6 +590,14 @@ void CSpell::setupMechanics()
|
||||
mechanics = ISpellMechanics::createMechanics(this);
|
||||
}
|
||||
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationItem::AnimationItem()
|
||||
:resourceName(""),verticalPosition(VerticalPosition::TOP),pause(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationInfo::AnimationInfo()
|
||||
{
|
||||
@ -924,7 +934,6 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
|
||||
for(const JsonNode & item : queueNode)
|
||||
{
|
||||
CSpell::TAnimation newItem;
|
||||
newItem.verticalPosition = VerticalPosition::TOP;
|
||||
|
||||
if(item.getType() == JsonNode::DATA_STRING)
|
||||
newItem.resourceName = item.String();
|
||||
@ -936,6 +945,11 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
|
||||
if("bottom" == vPosStr)
|
||||
newItem.verticalPosition = VerticalPosition::BOTTOM;
|
||||
}
|
||||
else if(item.getType() == JsonNode::DATA_FLOAT)
|
||||
{
|
||||
newItem.pause = item.Float();
|
||||
}
|
||||
|
||||
q.push_back(newItem);
|
||||
}
|
||||
};
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Magic.h"
|
||||
#include "../IHandlerBase.h"
|
||||
#include "../ConstTransitivePtr.h"
|
||||
#include "../int3.h"
|
||||
@ -66,7 +66,7 @@ public:
|
||||
BattleHex destination;
|
||||
ui8 casterSide;
|
||||
PlayerColor casterColor;
|
||||
const CGHeroInstance * caster;
|
||||
const CGHeroInstance * casterHero; //deprecated
|
||||
const CGHeroInstance * secHero;
|
||||
int usedSpellPower;
|
||||
ECastingMode::ECastingMode mode;
|
||||
@ -104,10 +104,21 @@ public:
|
||||
{
|
||||
std::string resourceName;
|
||||
VerticalPosition verticalPosition;
|
||||
int pause;
|
||||
|
||||
AnimationItem();
|
||||
|
||||
template <typename Handler> void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & resourceName & verticalPosition;
|
||||
if(version >= 754)
|
||||
{
|
||||
h & pause;
|
||||
}
|
||||
else if(!h.saving)
|
||||
{
|
||||
pause = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -217,8 +228,6 @@ public:
|
||||
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)
|
||||
ETargetType getTargetType() const; //deprecated
|
||||
|
||||
@ -248,11 +257,8 @@ public:
|
||||
//internal, for use only by Mechanics classes
|
||||
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
|
||||
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
|
||||
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
|
||||
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:
|
||||
void setIsOffensive(const bool val);
|
||||
void setIsRising(const bool val);
|
||||
|
@ -47,6 +47,9 @@ public:
|
||||
virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & 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);
|
||||
protected:
|
||||
CSpell * owner;
|
||||
|
32
lib/spells/Magic.h
Normal file
32
lib/spells/Magic.h
Normal 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;
|
||||
};
|
@ -2789,7 +2789,7 @@ bool CGameHandler::upgradeCreature( ObjectInstanceID objid, SlotID pos, Creature
|
||||
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))
|
||||
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);
|
||||
int resCreature;//resulting creature - bone dragons or skeletons
|
||||
|
||||
if (s.hasBonusOfType(Bonus::DRAGON_NATURE))
|
||||
resCreature = 68;
|
||||
else
|
||||
resCreature = 56;
|
||||
//resulting creature - bone dragons or skeletons
|
||||
CreatureID resCreature = CreatureID::SKELETON;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -3431,7 +3432,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
//defensive stance //TODO: remove this bonus when stack becomes active
|
||||
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, 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));
|
||||
sse.stacks.push_back(ba.stackNumber);
|
||||
sendAndApply(&sse);
|
||||
@ -3880,7 +3881,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
|
||||
p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
|
||||
p.destination = destination;
|
||||
p.casterColor = stack->owner;
|
||||
p.caster = nullptr;
|
||||
p.casterHero = nullptr;
|
||||
p.usedSpellPower = 0;
|
||||
p.casterStack = stack;
|
||||
p.selectedStack = nullptr;
|
||||
@ -4080,7 +4081,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
|
||||
parameters.destination = ba.destinationTile;
|
||||
parameters.casterSide = ba.side;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = h;
|
||||
parameters.casterHero = h;
|
||||
parameters.secHero = secondHero;
|
||||
|
||||
parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
@ -4239,7 +4240,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = side;
|
||||
parameters.casterColor = st->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
|
||||
parameters.usedSpellPower = 0;
|
||||
parameters.mode = ECastingMode::ENCHANTER_CASTING;
|
||||
@ -4952,7 +4953,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
||||
parameters.destination = destination;
|
||||
parameters.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
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.casterSide = !attacker->attackerOwned;
|
||||
parameters.casterColor = attacker->owner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = nullptr;
|
||||
|
||||
parameters.usedSpellPower = power;
|
||||
@ -5297,7 +5298,7 @@ void CGameHandler::runBattle()
|
||||
parameters.destination = BattleHex::INVALID;
|
||||
parameters.casterSide = i;
|
||||
parameters.casterColor = h->tempOwner;
|
||||
parameters.caster = nullptr;
|
||||
parameters.casterHero = nullptr;
|
||||
parameters.secHero = gs->curB->battleGetFightingHero(1-i);
|
||||
|
||||
|
||||
|
@ -142,7 +142,7 @@ public:
|
||||
|
||||
void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) 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 insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override;
|
||||
bool eraseStack(const StackLocation &sl, bool forceRemoval = false) override;
|
||||
|
@ -297,7 +297,11 @@ bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const
|
||||
{
|
||||
return !vstd::contains(ourIds, dismiss->heroID);
|
||||
}
|
||||
|
||||
|
||||
if(auto upgrade = dynamic_cast<const UpgradeCreature*>(pack))
|
||||
{
|
||||
return !vstd::contains(ourIds, upgrade->id);
|
||||
}
|
||||
return CDialogQuery::blocksPack(pack);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user