mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-24 03:47:18 +02:00
Implemented Dragon Breath, Attack All Around & Three-Headed Attack.
Dragon Breath does not yet work correctly for all double-wide stacks, though.
This commit is contained in:
parent
aac1c0b4e7
commit
e95ae22061
1
global.h
1
global.h
@ -300,7 +300,6 @@ struct THex
|
||||
{
|
||||
h & hex;
|
||||
}
|
||||
private:
|
||||
static void checkAndPush(int tile, std::vector<THex> & ret)
|
||||
{
|
||||
if( tile>=0 && tile<BFIELD_SIZE && (tile%BFIELD_WIDTH != (BFIELD_WIDTH - 1)) && (tile%BFIELD_WIDTH != 0) )
|
||||
|
@ -713,7 +713,7 @@ std::set<CStack*> BattleInfo::getAttackedCreatures( const CSpell * s, int skillL
|
||||
{
|
||||
if (s->id == 76) //Death Cloud //TODO: fireball and fire immunity
|
||||
{
|
||||
if (st->isLiving() || st->position == destinationTile) //directly hit or alive
|
||||
if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive
|
||||
{
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
@ -763,6 +763,79 @@ std::set<CStack*> BattleInfo::getAttackedCreatures( const CSpell * s, int skillL
|
||||
}
|
||||
return attackedCres;
|
||||
}
|
||||
std::set<CStack*> BattleInfo::getAttackedCreatures(const CStack* attacker, THex destinationTile)
|
||||
{ //TODO: caching?
|
||||
std::set<CStack*> attackedCres;
|
||||
const int WN = BFIELD_WIDTH;
|
||||
if (attacker->hasBonusOfType(Bonus::ATTACKS_ALL_ADJACENT))
|
||||
{
|
||||
std::vector<THex> hexes = attacker->getSurroundingHexes();
|
||||
BOOST_FOREACH (THex tile, hexes)
|
||||
{
|
||||
CStack * st = getStackT(tile);
|
||||
if(st && st->owner != attacker->owner) //only hostile stacks - does it work well with Berserk?
|
||||
{
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui16 hex = attacker->position.hex;
|
||||
if (attacker->hasBonusOfType(Bonus::THREE_HEADED_ATTACK))
|
||||
{
|
||||
std::vector<THex> hexes;
|
||||
if (attacker->attackerOwned)
|
||||
{
|
||||
THex::checkAndPush(hex - ( (hex/WN)%2 ? WN+1 : WN ), hexes);
|
||||
THex::checkAndPush(hex + 1, hexes);
|
||||
THex::checkAndPush(hex + ( (hex/WN)%2 ? WN : WN+1 ), hexes);
|
||||
}
|
||||
else
|
||||
{
|
||||
THex::checkAndPush(hex - ( (hex/WN)%2 ? WN : WN-1 ), hexes);
|
||||
THex::checkAndPush(hex - 1, hexes);
|
||||
THex::checkAndPush(hex - ( (hex/WN)%2 ? WN-1 : WN ), hexes);
|
||||
}
|
||||
BOOST_FOREACH (THex tile, hexes)
|
||||
{
|
||||
CStack * st = getStackT(tile);
|
||||
if(st && st->owner != attacker->owner)
|
||||
{
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attacker->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH))
|
||||
{
|
||||
std::vector<THex> hexes; //only one, in fact
|
||||
int pseudoVector = destinationTile.hex - hex;
|
||||
switch (pseudoVector)
|
||||
{
|
||||
case 1:
|
||||
case -1:
|
||||
THex::checkAndPush(destinationTile.hex + pseudoVector, hexes);
|
||||
break;
|
||||
case WN: //17
|
||||
case WN + 1: //18
|
||||
case -WN: //-17
|
||||
case -WN + 1: //-16
|
||||
THex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 1 : -1 ), hexes);
|
||||
break;
|
||||
case WN-1: //16
|
||||
case -WN-1: //-18
|
||||
THex::checkAndPush(destinationTile.hex + pseudoVector + ((hex/WN)%2 ? 0 : -1), hexes);
|
||||
break;
|
||||
}
|
||||
BOOST_FOREACH (THex tile, hexes)
|
||||
{
|
||||
CStack * st = getStackT(tile);
|
||||
if(st) //friendly stacks can also be damaged by Dragon Breath
|
||||
{
|
||||
attackedCres.insert(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attackedCres;
|
||||
}
|
||||
|
||||
int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower )
|
||||
{
|
||||
@ -2292,6 +2365,42 @@ bool CStack::coversPos(THex pos) const
|
||||
return vstd::contains(getHexes(), pos);
|
||||
}
|
||||
|
||||
std::vector<THex> CStack::getSurroundingHexes() const
|
||||
{
|
||||
std::vector<THex> hexes;
|
||||
if (doubleWide())
|
||||
{
|
||||
const int WN = BFIELD_WIDTH;
|
||||
if(attackerOwned)
|
||||
{ //position is equal to front hex
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN+1 : WN ), hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN : WN-1 ), hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN-1 : WN-2 ), hexes);
|
||||
THex::checkAndPush(position.hex - 2, hexes);
|
||||
THex::checkAndPush(position.hex + 1, hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN-2 : WN-1 ), hexes);
|
||||
THex::checkAndPush(position.hex + ( (position.hex/WN)%2 ? WN-1 : WN ), hexes);
|
||||
THex::checkAndPush(position.hex + ( (position.hex/WN)%2 ? WN : WN+1 ), hexes);
|
||||
}
|
||||
else
|
||||
{
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN+2 : WN+1 ), hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN+1 : WN ), hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN : WN-1 ), hexes);
|
||||
THex::checkAndPush(position.hex + 2, hexes);
|
||||
THex::checkAndPush(position.hex - 1, hexes);
|
||||
THex::checkAndPush(position.hex - ( (position.hex/WN)%2 ? WN-1 : WN ), hexes);
|
||||
THex::checkAndPush(position.hex + ( (position.hex/WN)%2 ? WN : WN+1 ), hexes);
|
||||
THex::checkAndPush(position.hex + ( (position.hex/WN)%2 ? WN+1 : WN+2 ), hexes);
|
||||
}
|
||||
return hexes;
|
||||
}
|
||||
else
|
||||
{
|
||||
return position.neighbouringTiles();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<si32> CStack::activeSpells() const
|
||||
{
|
||||
std::vector<si32> ret;
|
||||
|
@ -88,6 +88,7 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode
|
||||
TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||
void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
|
||||
std::set<CStack*> getAttackedCreatures(const CSpell * s, int skillLevel, ui8 attackerOwner, THex destinationTile); //calculates stack affected by given spell
|
||||
std::set<CStack*> getAttackedCreatures(const CStack* attacker, THex destinationTile); //calculates range of multi-hex attacks
|
||||
static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower);
|
||||
CStack * generateNewStack(const CStackInstance &base, int stackID, bool attackerOwned, int slot, THex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
|
||||
CStack * generateNewStack(const CStackBasicDescriptor &base, int stackID, bool attackerOwned, int slot, THex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
|
||||
@ -185,6 +186,7 @@ public:
|
||||
THex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1
|
||||
std::vector<THex> getHexes() const; //up to two occupied hexes, starting from front
|
||||
bool coversPos(THex position) const; //checks also if unit is double-wide
|
||||
std::vector<THex> getSurroundingHexes() const; // get six or 8 surrounding hexes depending on creature size
|
||||
|
||||
void prepareAttacked(BattleStackAttacked &bsa) const; //requires bsa.damageAmout filled
|
||||
|
||||
|
@ -1304,7 +1304,7 @@ struct BattleAttack : public CPackForClient//3006
|
||||
|
||||
std::vector<BattleStackAttacked> bsa;
|
||||
ui32 stackAttacking;
|
||||
ui8 flags; //usues Eflags (below)
|
||||
ui8 flags; //uses Eflags (below)
|
||||
enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16};
|
||||
|
||||
bool shot() const//distance attack - decrease number of shots
|
||||
|
@ -508,10 +508,6 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
{
|
||||
bat.bsa.clear();
|
||||
bat.stackAttacking = att->ID;
|
||||
bat.bsa.push_back(BattleStackAttacked());
|
||||
BattleStackAttacked *bsa = &bat.bsa.back();
|
||||
bsa->stackAttacked = def->ID;
|
||||
bsa->attackerID = att->ID;
|
||||
int attackerLuck = att->LuckVal();
|
||||
const CGHeroInstance * h0 = gs->curB->heroes[0],
|
||||
* h1 = gs->curB->heroes[1];
|
||||
@ -537,19 +533,29 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
|
||||
}
|
||||
}
|
||||
// only primary target
|
||||
applyBattleEffects(bat, att, def, distance, false);
|
||||
|
||||
bsa->damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def), bat.shot(), distance, bat.lucky(), bat.ballistaDoubleDmg());//counting dealt damage
|
||||
int dmg = bsa->damageAmount;
|
||||
def->prepareAttacked(*bsa);
|
||||
|
||||
//TODO: multiple attack targets
|
||||
const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK));
|
||||
if (bonus)
|
||||
if (!bat.shot()) //multiple-hex attack - only in meele
|
||||
{
|
||||
bsa->flags |= BattleStackAttacked::EFFECT;
|
||||
bsa->effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
|
||||
std::set<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(att, def->position);
|
||||
//TODO: get exact attacked hex for defender
|
||||
|
||||
BOOST_FOREACH(CStack * stack, attackedCreatures)
|
||||
{
|
||||
if (stack != def) //do not hit same stack twice
|
||||
{
|
||||
applyBattleEffects(bat, att, stack, distance, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK));
|
||||
if (bonus && (bat.shot())) //TODO: make it work in meele?
|
||||
{
|
||||
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
|
||||
bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
|
||||
|
||||
BattleStackAttacked bss = *bsa; // copy some parameters, such as attacker
|
||||
std::set<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, def->position);
|
||||
//TODO: get exact attacked hex for defender
|
||||
|
||||
@ -557,82 +563,54 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
|
||||
{
|
||||
if (stack != def) //do not hit same stack twice
|
||||
{
|
||||
bss.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities
|
||||
bss.stackAttacked = stack->ID;
|
||||
bss.damageAmount = gs->curB->calculateDmg(att, stack, gs->curB->battleGetOwner(att),
|
||||
gs->curB->battleGetOwner(stack), bat.shot(), distance, bat.lucky(), bat.ballistaDoubleDmg());
|
||||
bat.bsa.push_back(bss); //add this stack to the list of victims
|
||||
stack->prepareAttacked(bss);
|
||||
|
||||
//life drain handling
|
||||
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && stack->isLiving())
|
||||
{
|
||||
StacksHealedOrResurrected shi;
|
||||
shi.lifeDrain = (ui8)true;
|
||||
shi.tentHealing = (ui8)false;
|
||||
shi.drainedFrom = stack->ID;
|
||||
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = stack->ID;
|
||||
hi.healedHP = std::min<int>(dmg, att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
|
||||
hi.lowLevelResurrection = false;
|
||||
shi.healedStacks.push_back(hi);
|
||||
|
||||
if (hi.healedHP > 0)
|
||||
{
|
||||
bsa->healedStacks.push_back(shi);
|
||||
}
|
||||
}
|
||||
|
||||
//fire shield handling
|
||||
if (!bat.shot() && stack->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType (Bonus::FIRE_IMMUNITY) && !bsa->killed() )
|
||||
{
|
||||
bss.stackAttacked = att->ID; //invert
|
||||
bss.attackerID = stack->ID;
|
||||
bss.flags = BattleStackAttacked::EFFECT; //not seondary - fire shield damages only one creature
|
||||
bss.effect = 11;
|
||||
|
||||
bsa->damageAmount = (dmg * stack->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
|
||||
att->prepareAttacked(*bsa);
|
||||
}
|
||||
applyBattleEffects(bat, att, stack, distance, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // only one target
|
||||
{ // TODO: move duplicated code to separate function
|
||||
//life drain handling
|
||||
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
|
||||
}
|
||||
void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary) //helper function for prepareAttack
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
if (secondary)
|
||||
bsa.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities
|
||||
bsa.attackerID = att->ID;
|
||||
bsa.stackAttacked = def->ID;
|
||||
bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def), bat.shot(), distance, bat.lucky(), bat.ballistaDoubleDmg());
|
||||
def->prepareAttacked(bsa); //calculate casualties
|
||||
bat.bsa.push_back(bsa); //add this stack to the list of victims
|
||||
|
||||
//life drain handling
|
||||
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
|
||||
{
|
||||
StacksHealedOrResurrected shi;
|
||||
shi.lifeDrain = (ui8)true;
|
||||
shi.tentHealing = (ui8)false;
|
||||
shi.drainedFrom = def->ID;
|
||||
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = att->ID;
|
||||
hi.healedHP = std::min<int>(bsa.damageAmount, att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
|
||||
hi.lowLevelResurrection = false;
|
||||
shi.healedStacks.push_back(hi);
|
||||
|
||||
if (hi.healedHP > 0)
|
||||
{
|
||||
StacksHealedOrResurrected shi;
|
||||
shi.lifeDrain = (ui8)true;
|
||||
shi.tentHealing = (ui8)false;
|
||||
shi.drainedFrom = def->ID;
|
||||
|
||||
StacksHealedOrResurrected::HealInfo hi;
|
||||
hi.stackID = att->ID;
|
||||
hi.healedHP = std::min<int>(dmg, att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
|
||||
hi.lowLevelResurrection = false;
|
||||
shi.healedStacks.push_back(hi);
|
||||
|
||||
if (hi.healedHP > 0)
|
||||
{
|
||||
bsa->healedStacks.push_back(shi);
|
||||
}
|
||||
}
|
||||
|
||||
//fire shield handling
|
||||
if ( !bat.shot() && def->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType (Bonus::FIRE_IMMUNITY) && !bsa->killed() )
|
||||
{
|
||||
bat.bsa.push_back(BattleStackAttacked());
|
||||
BattleStackAttacked *bsa = &bat.bsa.back();
|
||||
bsa->stackAttacked = att->ID;
|
||||
bsa->attackerID = def->ID;
|
||||
bsa->flags |= BattleStackAttacked::EFFECT;
|
||||
bsa->effect = 11;
|
||||
|
||||
bsa->damageAmount = (dmg * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
|
||||
att->prepareAttacked(*bsa);
|
||||
bsa.healedStacks.push_back(shi);
|
||||
}
|
||||
}
|
||||
|
||||
//fire shield handling
|
||||
if (!bat.shot() && def->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType (Bonus::FIRE_IMMUNITY) && !bsa.killed() )
|
||||
{
|
||||
BattleStackAttacked bsa;
|
||||
bsa.stackAttacked = att->ID; //invert
|
||||
bsa.attackerID = def->ID;
|
||||
bsa.flags |= BattleStackAttacked::EFFECT;
|
||||
bsa.effect = 11;
|
||||
|
||||
bsa.damageAmount = (bsa.damageAmount * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
|
||||
att->prepareAttacked(bsa);
|
||||
bat.bsa.push_back(bsa);
|
||||
}
|
||||
}
|
||||
void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
|
||||
|
@ -118,6 +118,7 @@ public:
|
||||
//
|
||||
void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
|
||||
void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance); //distance - number of hexes travelled before attacking
|
||||
void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield
|
||||
void checkForBattleEnd( std::vector<CStack*> &stacks );
|
||||
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
||||
void setBattleResult(int resultType, int victoriusSide);
|
||||
|
Loading…
x
Reference in New Issue
Block a user