mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
commit
ea0ceb1805
@ -29,7 +29,7 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
|
|||||||
auto attacker = AttackInfo.attacker;
|
auto attacker = AttackInfo.attacker;
|
||||||
auto enemy = AttackInfo.defender;
|
auto enemy = AttackInfo.defender;
|
||||||
|
|
||||||
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining());
|
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks.available());
|
||||||
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
|
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
|
||||||
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
|
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
|
||||||
|
|
||||||
@ -46,19 +46,15 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo
|
|||||||
if(remainingCounterAttacks <= i || counterAttacksBlocked)
|
if(remainingCounterAttacks <= i || counterAttacksBlocked)
|
||||||
ap.damageReceived = 0;
|
ap.damageReceived = 0;
|
||||||
|
|
||||||
curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first;
|
curBai.attackerHealth = attacker->healthAfterAttacked(ap.damageReceived);
|
||||||
curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first;
|
curBai.defenderHealth = enemy->healthAfterAttacked(ap.damageDealt);
|
||||||
if(!curBai.attackerCount)
|
if(curBai.attackerHealth.getCount() <= 0)
|
||||||
break;
|
break;
|
||||||
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
|
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO other damage related to attack (eg. fire shield and other abilities)
|
//TODO other damage related to attack (eg. fire shield and other abilities)
|
||||||
|
|
||||||
//Limit damages by total stack health
|
|
||||||
vstd::amin(ap.damageDealt, enemy->totalHealth());
|
|
||||||
vstd::amin(ap.damageReceived, attacker->totalHealth());
|
|
||||||
|
|
||||||
return ap;
|
return ap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
|||||||
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
|
||||||
std::map<int, const CStack*> woundHpToStack;
|
std::map<int, const CStack*> woundHpToStack;
|
||||||
for(auto stack : healingTargets)
|
for(auto stack : healingTargets)
|
||||||
if(auto woundHp = stack->MaxHealth() - stack->firstHPleft)
|
if(auto woundHp = stack->MaxHealth() - stack->getFirstHPleft())
|
||||||
woundHpToStack[woundHp] = stack;
|
woundHpToStack[woundHp] = stack;
|
||||||
if(woundHpToStack.empty())
|
if(woundHpToStack.empty())
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
|
@ -317,7 +317,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui32 speed;
|
ui32 speed = 0;
|
||||||
if(settings["session"]["spectate"].Bool())
|
if(settings["session"]["spectate"].Bool())
|
||||||
{
|
{
|
||||||
if(!settings["session"]["spectate-hero-speed"].isNull())
|
if(!settings["session"]["spectate-hero-speed"].isNull())
|
||||||
@ -699,33 +699,46 @@ void CPlayerInterface::battleStacksHealedRes(const std::vector<std::pair<ui32, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lifeDrain)
|
if(lifeDrain)
|
||||||
{
|
{
|
||||||
const CStack *attacker = cb->battleGetStackByID(healedStacks[0].first, false);
|
const CStack * attacker = cb->battleGetStackByID(healedStacks[0].first, false);
|
||||||
const CStack *defender = cb->battleGetStackByID(lifeDrainFrom, false);
|
const CStack * defender = cb->battleGetStackByID(lifeDrainFrom, false);
|
||||||
int textOff = 0;
|
|
||||||
|
|
||||||
if (attacker)
|
if(attacker && defender)
|
||||||
{
|
{
|
||||||
battleInt->displayEffect(52, attacker->position); //TODO: transparency
|
battleInt->displayEffect(52, attacker->position); //TODO: transparency
|
||||||
if (attacker->count > 1)
|
|
||||||
{
|
|
||||||
textOff += 1;
|
|
||||||
}
|
|
||||||
CCS->soundh->playSound(soundBase::DRAINLIF);
|
CCS->soundh->playSound(soundBase::DRAINLIF);
|
||||||
|
|
||||||
//print info about life drain
|
MetaString text;
|
||||||
auto txt = boost::format (CGI->generaltexth->allTexts[361 + textOff]) % attacker->getCreature()->nameSing % healedStacks[0].second % defender->getCreature()->namePl;
|
attacker->addText(text, MetaString::GENERAL_TXT, 361);
|
||||||
battleInt->console->addText(boost::to_string(txt));
|
attacker->addNameReplacement(text, false);
|
||||||
|
text.addReplacement(healedStacks[0].second);
|
||||||
|
defender->addNameReplacement(text, true);
|
||||||
|
battleInt->console->addText(text.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logGlobal->error("Unable to display life drain info");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tentHeal)
|
if(tentHeal)
|
||||||
{
|
{
|
||||||
std::string text = CGI->generaltexth->allTexts[414];
|
const CStack * healer = cb->battleGetStackByID(lifeDrainFrom, false);
|
||||||
boost::algorithm::replace_first(text, "%s", cb->battleGetStackByID(lifeDrainFrom, false)->getCreature()->nameSing);
|
const CStack * target = cb->battleGetStackByID(healedStacks[0].first, false);
|
||||||
boost::algorithm::replace_first(text, "%s", cb->battleGetStackByID(healedStacks[0].first, false)->getCreature()->nameSing);
|
|
||||||
boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(healedStacks[0].second));
|
if(healer && target)
|
||||||
battleInt->console->addText(text);
|
{
|
||||||
|
MetaString text;
|
||||||
|
text.addTxt(MetaString::GENERAL_TXT, 414);
|
||||||
|
healer->addNameReplacement(text, false);
|
||||||
|
target->addNameReplacement(text, false);
|
||||||
|
text.addReplacement(healedStacks[0].second);
|
||||||
|
battleInt->console->addText(text.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logGlobal->error("Unable to display tent heal info");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -939,53 +952,50 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
|
|||||||
|
|
||||||
battleInt->stacksAreAttacked(arg);
|
battleInt->stacksAreAttacked(arg);
|
||||||
}
|
}
|
||||||
void CPlayerInterface::battleAttack(const BattleAttack *ba)
|
void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||||
{
|
{
|
||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||||
|
|
||||||
assert(curAction);
|
assert(curAction);
|
||||||
if (ba->lucky()) //lucky hit
|
|
||||||
|
const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking);
|
||||||
|
|
||||||
|
if(!attacker)
|
||||||
{
|
{
|
||||||
const CStack *stack = cb->battleGetStackByID(ba->stackAttacking);
|
logGlobal->error("Attacking stack not found");
|
||||||
std::string hlp = CGI->generaltexth->allTexts[45];
|
return;
|
||||||
boost::algorithm::replace_first(hlp,"%s", (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str());
|
}
|
||||||
battleInt->console->addText(hlp);
|
|
||||||
battleInt->displayEffect(18, stack->position);
|
if(ba->lucky()) //lucky hit
|
||||||
|
{
|
||||||
|
battleInt->console->addText(attacker->formatGeneralMessage(-45));
|
||||||
|
battleInt->displayEffect(18, attacker->position);
|
||||||
CCS->soundh->playSound(soundBase::GOODLUCK);
|
CCS->soundh->playSound(soundBase::GOODLUCK);
|
||||||
}
|
}
|
||||||
if (ba->unlucky()) //unlucky hit
|
if(ba->unlucky()) //unlucky hit
|
||||||
{
|
{
|
||||||
const CStack *stack = cb->battleGetStackByID(ba->stackAttacking);
|
battleInt->console->addText(attacker->formatGeneralMessage(-44));
|
||||||
std::string hlp = CGI->generaltexth->allTexts[44];
|
battleInt->displayEffect(48, attacker->position);
|
||||||
boost::algorithm::replace_first(hlp,"%s", (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str());
|
|
||||||
battleInt->console->addText(hlp);
|
|
||||||
battleInt->displayEffect(48, stack->position);
|
|
||||||
CCS->soundh->playSound(soundBase::BADLUCK);
|
CCS->soundh->playSound(soundBase::BADLUCK);
|
||||||
}
|
}
|
||||||
if (ba->deathBlow())
|
if(ba->deathBlow())
|
||||||
{
|
{
|
||||||
const CStack *stack = cb->battleGetStackByID(ba->stackAttacking);
|
battleInt->console->addText(attacker->formatGeneralMessage(365));
|
||||||
std::string hlp = CGI->generaltexth->allTexts[(stack->count != 1) ? 366 : 365];
|
for(auto & elem : ba->bsa)
|
||||||
boost::algorithm::replace_first(hlp,"%s", (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str());
|
|
||||||
battleInt->console->addText(hlp);
|
|
||||||
for (auto & elem : ba->bsa)
|
|
||||||
{
|
{
|
||||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||||
battleInt->displayEffect(73, attacked->position);
|
battleInt->displayEffect(73, attacked->position);
|
||||||
}
|
}
|
||||||
CCS->soundh->playSound(soundBase::deathBlow);
|
CCS->soundh->playSound(soundBase::deathBlow);
|
||||||
|
|
||||||
}
|
}
|
||||||
battleInt->waitForAnims();
|
battleInt->waitForAnims();
|
||||||
|
|
||||||
const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking);
|
if(ba->shot())
|
||||||
|
|
||||||
if (ba->shot())
|
|
||||||
{
|
{
|
||||||
for (auto & elem : ba->bsa)
|
for(auto & elem : ba->bsa)
|
||||||
{
|
{
|
||||||
if (!elem.isSecondary()) //display projectile only for primary target
|
if(!elem.isSecondary()) //display projectile only for primary target
|
||||||
{
|
{
|
||||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||||
battleInt->stackAttacking(attacker, attacked->position, attacked, true);
|
battleInt->stackAttacking(attacker, attacked->position, attacked, true);
|
||||||
@ -995,27 +1005,27 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int shift = 0;
|
int shift = 0;
|
||||||
if (ba->counter() && BattleHex::mutualPosition(curAction->destinationTile, attacker->position) < 0)
|
if(ba->counter() && BattleHex::mutualPosition(curAction->destinationTile, attacker->position) < 0)
|
||||||
{
|
{
|
||||||
int distp = BattleHex::getDistance(curAction->destinationTile + 1, attacker->position);
|
int distp = BattleHex::getDistance(curAction->destinationTile + 1, attacker->position);
|
||||||
int distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position);
|
int distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position);
|
||||||
|
|
||||||
if ( distp < distm )
|
if(distp < distm)
|
||||||
shift = 1;
|
shift = 1;
|
||||||
else
|
else
|
||||||
shift = -1;
|
shift = -1;
|
||||||
}
|
}
|
||||||
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
|
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
|
||||||
battleInt->stackAttacking( attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false);
|
battleInt->stackAttacking(attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//battleInt->waitForAnims(); //FIXME: freeze
|
//battleInt->waitForAnims(); //FIXME: freeze
|
||||||
|
|
||||||
if (ba->spellLike())
|
if(ba->spellLike())
|
||||||
{
|
{
|
||||||
//display hit animation
|
//display hit animation
|
||||||
SpellID spellID = ba->spellID;
|
SpellID spellID = ba->spellID;
|
||||||
battleInt->displaySpellHit(spellID,curAction->destinationTile);
|
battleInt->displaySpellHit(spellID, curAction->destinationTile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
|
void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
|
||||||
|
@ -728,12 +728,12 @@ void BattleResultsApplied::applyCl(CClient *cl)
|
|||||||
INTERFACE_CALL_IF_PRESENT(PlayerColor::SPECTATOR, battleResultsApplied);
|
INTERFACE_CALL_IF_PRESENT(PlayerColor::SPECTATOR, battleResultsApplied);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StacksHealedOrResurrected::applyCl(CClient *cl)
|
void StacksHealedOrResurrected::applyCl(CClient * cl)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<ui32, ui32> > shiftedHealed;
|
std::vector<std::pair<ui32, ui32> > shiftedHealed;
|
||||||
for(auto & elem : healedStacks)
|
for(auto & elem : healedStacks)
|
||||||
{
|
{
|
||||||
shiftedHealed.push_back(std::make_pair(elem.stackID, elem.healedHP));
|
shiftedHealed.push_back(std::make_pair(elem.stackId, (ui32)elem.delta));
|
||||||
}
|
}
|
||||||
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom);
|
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom);
|
||||||
}
|
}
|
||||||
|
@ -1356,25 +1356,8 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
|
|||||||
|
|
||||||
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||||
{
|
{
|
||||||
if(sse.stacks.size() == 1 && sse.effect.size() == 2 && sse.effect.back().sid == -1)
|
for(const MetaString & line : sse.battleLog)
|
||||||
{
|
console->addText(line.toString());
|
||||||
const Bonus & bns = sse.effect.front();
|
|
||||||
if(bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
|
|
||||||
{
|
|
||||||
//defensive stance
|
|
||||||
const CStack *stack = LOCPLINT->cb->battleGetStackByID(*sse.stacks.begin());
|
|
||||||
int txtid = 120;
|
|
||||||
|
|
||||||
if(stack->count != 1)
|
|
||||||
txtid++; //move to plural text
|
|
||||||
|
|
||||||
BonusList defenseBonuses = *(stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)));
|
|
||||||
defenseBonuses.remove_if(Bonus::UntilGetsTurn); //remove bonuses gained from defensive stance
|
|
||||||
int val = stack->Defense() - defenseBonuses.totalValue();
|
|
||||||
auto txt = boost::format(CGI->generaltexth->allTexts[txtid]) % ((stack->count != 1) ? stack->getCreature()->namePl : stack->getCreature()->nameSing) % val;
|
|
||||||
console->addText(boost::to_string(txt));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(activeStack != nullptr)
|
if(activeStack != nullptr)
|
||||||
redrawBackgroundWithHexes(activeStack);
|
redrawBackgroundWithHexes(activeStack);
|
||||||
@ -1592,10 +1575,10 @@ void CBattleInterface::activateStack()
|
|||||||
//set casting flag to true if creature can use it to not check it every time
|
//set casting flag to true if creature can use it to not check it every time
|
||||||
const auto spellcaster = s->getBonusLocalFirst(Selector::type(Bonus::SPELLCASTER)),
|
const auto spellcaster = s->getBonusLocalFirst(Selector::type(Bonus::SPELLCASTER)),
|
||||||
randomSpellcaster = s->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
randomSpellcaster = s->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
|
||||||
if (s->casts && (spellcaster || randomSpellcaster))
|
if(s->canCast() && (spellcaster || randomSpellcaster))
|
||||||
{
|
{
|
||||||
stackCanCastSpell = true;
|
stackCanCastSpell = true;
|
||||||
if (randomSpellcaster)
|
if(randomSpellcaster)
|
||||||
creatureSpellToCast = -1; //spell will be set later on cast
|
creatureSpellToCast = -1; //spell will be set later on cast
|
||||||
else
|
else
|
||||||
creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
|
creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
|
||||||
@ -1696,16 +1679,16 @@ void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const boo
|
|||||||
{
|
{
|
||||||
PossibleActions notPriority = INVALID;
|
PossibleActions notPriority = INVALID;
|
||||||
//first action will be prioritized over later ones
|
//first action will be prioritized over later ones
|
||||||
if (stack->casts) //TODO: check for battlefield effects that prevent casting?
|
if(stack->canCast()) //TODO: check for battlefield effects that prevent casting?
|
||||||
{
|
{
|
||||||
if (stack->hasBonusOfType (Bonus::SPELLCASTER))
|
if(stack->hasBonusOfType (Bonus::SPELLCASTER))
|
||||||
{
|
{
|
||||||
if (creatureSpellToCast != -1)
|
if(creatureSpellToCast != -1)
|
||||||
{
|
{
|
||||||
const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
|
const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
|
||||||
PossibleActions act = getCasterAction(spell, stack, ECastingMode::CREATURE_ACTIVE_CASTING);
|
PossibleActions act = getCasterAction(spell, stack, ECastingMode::CREATURE_ACTIVE_CASTING);
|
||||||
|
|
||||||
if (forceCast)
|
if(forceCast)
|
||||||
{
|
{
|
||||||
//forced action to be only one possible
|
//forced action to be only one possible
|
||||||
possibleActions.push_back(act);
|
possibleActions.push_back(act);
|
||||||
@ -1721,10 +1704,10 @@ void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const boo
|
|||||||
if (stack->hasBonusOfType (Bonus::DAEMON_SUMMONING))
|
if (stack->hasBonusOfType (Bonus::DAEMON_SUMMONING))
|
||||||
possibleActions.push_back (RISE_DEMONS);
|
possibleActions.push_back (RISE_DEMONS);
|
||||||
}
|
}
|
||||||
if (stack->shots && stack->hasBonusOfType (Bonus::SHOOTER))
|
if(stack->canShoot())
|
||||||
possibleActions.push_back (SHOOT);
|
possibleActions.push_back(SHOOT);
|
||||||
if (stack->hasBonusOfType (Bonus::RETURN_AFTER_STRIKE))
|
if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
|
||||||
possibleActions.push_back (ATTACK_AND_RETURN);
|
possibleActions.push_back(ATTACK_AND_RETURN);
|
||||||
|
|
||||||
possibleActions.push_back(ATTACK); //all active stacks can attack
|
possibleActions.push_back(ATTACK); //all active stacks can attack
|
||||||
possibleActions.push_back(WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
|
possibleActions.push_back(WALK_AND_ATTACK); //not all stacks can always walk, but we will check this elsewhere
|
||||||
@ -1742,39 +1725,39 @@ void CBattleInterface::getPossibleActionsForStack(const CStack *stack, const boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleInterface::printConsoleAttacked( const CStack *defender, int dmg, int killed, const CStack *attacker, bool multiple )
|
void CBattleInterface::printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple)
|
||||||
{
|
{
|
||||||
std::string formattedText;
|
std::string formattedText;
|
||||||
if (attacker) //ignore if stacks were killed by spell
|
if(attacker) //ignore if stacks were killed by spell
|
||||||
{
|
{
|
||||||
boost::format txt = boost::format (CGI->generaltexth->allTexts[attacker->count > 1 ? 377 : 376]) %
|
MetaString text;
|
||||||
(attacker->count > 1 ? attacker->getCreature()->namePl : attacker->getCreature()->nameSing) % dmg;
|
attacker->addText(text, MetaString::GENERAL_TXT, 376);
|
||||||
formattedText.append(boost::to_string(txt));
|
attacker->addNameReplacement(text);
|
||||||
|
text.addReplacement(dmg);
|
||||||
|
formattedText = text.toString();
|
||||||
}
|
}
|
||||||
if (killed > 0)
|
|
||||||
|
if(killed > 0)
|
||||||
{
|
{
|
||||||
if (attacker)
|
if(attacker)
|
||||||
formattedText.append(" ");
|
formattedText.append(" ");
|
||||||
|
|
||||||
boost::format txt;
|
boost::format txt;
|
||||||
if (killed > 1)
|
if(killed > 1)
|
||||||
{
|
{
|
||||||
txt = boost::format (CGI->generaltexth->allTexts[379]) % killed % (multiple ? CGI->generaltexth->allTexts[43] : defender->getCreature()->namePl); // creatures perish
|
txt = boost::format(CGI->generaltexth->allTexts[379]) % killed % (multiple ? CGI->generaltexth->allTexts[43] : defender->getCreature()->namePl); // creatures perish
|
||||||
}
|
}
|
||||||
else //killed == 1
|
else //killed == 1
|
||||||
{
|
{
|
||||||
txt = boost::format (CGI->generaltexth->allTexts[378]) % (multiple ? CGI->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes
|
txt = boost::format(CGI->generaltexth->allTexts[378]) % (multiple ? CGI->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes
|
||||||
}
|
}
|
||||||
std::string trimmed = boost::to_string(txt);
|
std::string trimmed = boost::to_string(txt);
|
||||||
boost::algorithm::trim(trimmed); // these default h3 texts have unnecessary new lines, so get rid of them before displaying
|
boost::algorithm::trim(trimmed); // these default h3 texts have unnecessary new lines, so get rid of them before displaying
|
||||||
formattedText.append(trimmed);
|
formattedText.append(trimmed);
|
||||||
}
|
}
|
||||||
console->addText(formattedText);
|
console->addText(formattedText);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CBattleInterface::endAction(const BattleAction* action)
|
void CBattleInterface::endAction(const BattleAction* action)
|
||||||
{
|
{
|
||||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||||
@ -1960,19 +1943,11 @@ void CBattleInterface::startAction(const BattleAction* action)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txtid > 0 && stack->count != 1)
|
if(txtid != 0)
|
||||||
txtid++; //move to plural text
|
console->addText(stack->formatGeneralMessage(txtid));
|
||||||
else if (txtid < 0)
|
|
||||||
txtid = -txtid;
|
|
||||||
|
|
||||||
if (txtid)
|
|
||||||
{
|
|
||||||
std::string name = (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str();
|
|
||||||
console->addText((boost::format(CGI->generaltexth->allTexts[txtid].c_str()) % name).str());
|
|
||||||
}
|
|
||||||
|
|
||||||
//displaying special abilities
|
//displaying special abilities
|
||||||
switch (action->actionType)
|
switch(action->actionType)
|
||||||
{
|
{
|
||||||
case Battle::STACK_HEAL:
|
case Battle::STACK_HEAL:
|
||||||
displayEffect(74, action->destinationTile);
|
displayEffect(74, action->destinationTile);
|
||||||
@ -2200,7 +2175,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
|||||||
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|
||||||
|| shere->hasBonusOfType(Bonus::NON_LIVING)
|
|| shere->hasBonusOfType(Bonus::NON_LIVING)
|
||||||
|| vstd::contains(shere->state, EBattleStackState::SUMMONED)
|
|| vstd::contains(shere->state, EBattleStackState::SUMMONED)
|
||||||
|| vstd::contains(shere->state, EBattleStackState::CLONED)
|
|| shere->isClone()
|
||||||
|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
|
|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
|
||||||
))
|
))
|
||||||
legalAction = true;
|
legalAction = true;
|
||||||
@ -2304,7 +2279,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
|
|||||||
realizeAction = [=] {giveCommand(Battle::SHOOT, myNumber, activeStack->ID);};
|
realizeAction = [=] {giveCommand(Battle::SHOOT, myNumber, activeStack->ID);};
|
||||||
std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg
|
std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(CRandomGenerator::getDefault(), sactive, shere)); //calculating estimated dmg
|
||||||
//printing - Shoot %s (%d shots left, %s damage)
|
//printing - Shoot %s (%d shots left, %s damage)
|
||||||
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots % estDmgText).str();
|
consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots.available() % estDmgText).str();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AIMED_SPELL_CREATURE:
|
case AIMED_SPELL_CREATURE:
|
||||||
@ -3273,10 +3248,10 @@ void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vector<const CStack
|
|||||||
{
|
{
|
||||||
auto isAmountBoxVisible = [&](const CStack *stack) -> bool
|
auto isAmountBoxVisible = [&](const CStack *stack) -> bool
|
||||||
{
|
{
|
||||||
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->count == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
|
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(stack->count == 0) //hide box when target is going to die anyway - do not display "0 creatures"
|
if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
|
for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
|
||||||
@ -3353,7 +3328,7 @@ void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vector<const CStack
|
|||||||
//blitting amount
|
//blitting amount
|
||||||
Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->w/2,
|
Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->w/2,
|
||||||
creAnims[stack->ID]->pos.y + yAdd + amountNormal->h/2);
|
creAnims[stack->ID]->pos.y + yAdd + amountNormal->h/2);
|
||||||
graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->count), Colors::WHITE, textPos);
|
graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->getCount()), Colors::WHITE, textPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class CBattleGameInterface;
|
|||||||
struct StackAttackedInfo
|
struct StackAttackedInfo
|
||||||
{
|
{
|
||||||
const CStack *defender; //attacked stack
|
const CStack *defender; //attacked stack
|
||||||
unsigned int dmg; //damage dealt
|
int32_t dmg; //damage dealt
|
||||||
unsigned int amountKilled; //how many creatures in stack has been killed
|
unsigned int amountKilled; //how many creatures in stack has been killed
|
||||||
const CStack *attacker; //attacking stack
|
const CStack *attacker; //attacking stack
|
||||||
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
|
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
|
||||||
|
@ -595,9 +595,10 @@ void CClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
|||||||
attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
|
attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
|
||||||
attackedStack->alive())
|
attackedStack->alive())
|
||||||
{
|
{
|
||||||
const std::string & attackedName = attackedStack->count == 1 ? attackedStack->getCreature()->nameSing : attackedStack->getCreature()->namePl;
|
MetaString text;
|
||||||
auto txt = boost::format (CGI->generaltexth->allTexts[220]) % attackedName;
|
text.addTxt(MetaString::GENERAL_TXT, 220);
|
||||||
myInterface->console->alterTxt = boost::to_string(txt);
|
attackedStack->addNameReplacement(text);
|
||||||
|
myInterface->console->alterTxt = text.toString();
|
||||||
setAlterText = true;
|
setAlterText = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -744,9 +745,9 @@ void CStackQueue::StackBox::showAll(SDL_Surface * to)
|
|||||||
CIntObject::showAll(to);
|
CIntObject::showAll(to);
|
||||||
|
|
||||||
if(small)
|
if(small)
|
||||||
printAtMiddleLoc(makeNumberShort(stack->count), pos.w/2, pos.h - 7, FONT_SMALL, Colors::WHITE, to);
|
printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 7, FONT_SMALL, Colors::WHITE, to);
|
||||||
else
|
else
|
||||||
printAtMiddleLoc(makeNumberShort(stack->count), pos.w/2, pos.h - 8, FONT_MEDIUM, Colors::WHITE, to);
|
printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 8, FONT_MEDIUM, Colors::WHITE, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStackQueue::StackBox::setStack( const CStack *stack )
|
void CStackQueue::StackBox::setStack( const CStack *stack )
|
||||||
|
@ -224,41 +224,46 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
|
|||||||
new CPicture("stackWindow/icons", 117, 32);
|
new CPicture("stackWindow/icons", 117, 32);
|
||||||
|
|
||||||
const CStack * battleStack = parent->info->stack;
|
const CStack * battleStack = parent->info->stack;
|
||||||
bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
|
|
||||||
bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
|
|
||||||
|
|
||||||
if (battleStack != nullptr) // in battle
|
auto morale = new MoraleLuckBox(true, genRect(42, 42, 321, 110));
|
||||||
|
auto luck = new MoraleLuckBox(false, genRect(42, 42, 375, 110));
|
||||||
|
|
||||||
|
if(battleStack != nullptr) // in battle
|
||||||
{
|
{
|
||||||
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack());
|
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack());
|
||||||
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense());
|
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense());
|
||||||
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply);
|
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply);
|
||||||
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->valOfBonuses(Bonus::STACK_HEALTH), battleStack->valOfBonuses(Bonus::STACK_HEALTH));
|
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), battleStack->MaxHealth());
|
||||||
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed());
|
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed());
|
||||||
|
|
||||||
if (shooter)
|
if(battleStack->isShooter())
|
||||||
printStatBase(EStat::SHOTS, CGI->generaltexth->allTexts[198], battleStack->valOfBonuses(Bonus::SHOTS), battleStack->shots);
|
printStatBase(EStat::SHOTS, CGI->generaltexth->allTexts[198], battleStack->shots.total(), battleStack->shots.available());
|
||||||
if (caster)
|
if(battleStack->isCaster())
|
||||||
printStatBase(EStat::MANA, CGI->generaltexth->allTexts[399], battleStack->valOfBonuses(Bonus::CASTS), battleStack->casts);
|
printStatBase(EStat::MANA, CGI->generaltexth->allTexts[399], battleStack->casts.total(), battleStack->casts.available());
|
||||||
printStat(EStat::HEALTH_LEFT, CGI->generaltexth->allTexts[200], battleStack->firstHPleft);
|
printStat(EStat::HEALTH_LEFT, CGI->generaltexth->allTexts[200], battleStack->getFirstHPleft());
|
||||||
|
|
||||||
|
morale->set(battleStack);
|
||||||
|
luck->set(battleStack);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
|
||||||
|
const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
|
||||||
|
|
||||||
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack());
|
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack());
|
||||||
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense());
|
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense());
|
||||||
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply);
|
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply);
|
||||||
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->valOfBonuses(Bonus::STACK_HEALTH), parent->info->stackNode->valOfBonuses(Bonus::STACK_HEALTH));
|
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth());
|
||||||
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
|
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
|
||||||
|
|
||||||
if (shooter)
|
if(shooter)
|
||||||
printStat(EStat::SHOTS, CGI->generaltexth->allTexts[198], parent->info->stackNode->valOfBonuses(Bonus::SHOTS));
|
printStat(EStat::SHOTS, CGI->generaltexth->allTexts[198], parent->info->stackNode->valOfBonuses(Bonus::SHOTS));
|
||||||
if (caster)
|
if(caster)
|
||||||
printStat(EStat::MANA, CGI->generaltexth->allTexts[399], parent->info->stackNode->valOfBonuses(Bonus::CASTS));
|
printStat(EStat::MANA, CGI->generaltexth->allTexts[399], parent->info->stackNode->valOfBonuses(Bonus::CASTS));
|
||||||
}
|
|
||||||
|
|
||||||
auto morale = new MoraleLuckBox(true, genRect(42, 42, 321, 110));
|
morale->set(parent->info->stackNode);
|
||||||
morale->set(parent->info->stackNode);
|
luck->set(parent->info->stackNode);
|
||||||
auto luck = new MoraleLuckBox(false, genRect(42, 42, 375, 110));
|
}
|
||||||
luck->set(parent->info->stackNode);
|
|
||||||
|
|
||||||
if (showExp)
|
if (showExp)
|
||||||
{
|
{
|
||||||
@ -881,7 +886,7 @@ CStackWindow::CStackWindow(const CStack * stack, bool popup):
|
|||||||
info->stack = stack;
|
info->stack = stack;
|
||||||
info->stackNode = stack->base;
|
info->stackNode = stack->base;
|
||||||
info->creature = stack->type;
|
info->creature = stack->type;
|
||||||
info->creatureCount = stack->count;
|
info->creatureCount = stack->getCount();
|
||||||
info->popupWindow = popup;
|
info->popupWindow = popup;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
@ -567,7 +567,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
|
|||||||
{
|
{
|
||||||
return s->owner == player
|
return s->owner == player
|
||||||
&& vstd::contains(s->state, EBattleStackState::SUMMONED)
|
&& vstd::contains(s->state, EBattleStackState::SUMMONED)
|
||||||
&& !vstd::contains(s->state, EBattleStackState::CLONED);
|
&& !s->isClone();
|
||||||
});
|
});
|
||||||
for(const CStack * s : stacks)
|
for(const CStack * s : stacks)
|
||||||
{
|
{
|
||||||
|
@ -507,3 +507,15 @@ CGeneralTextHandler::CGeneralTextHandler()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t count) const
|
||||||
|
{
|
||||||
|
if(textIndex == 0)
|
||||||
|
return 0;
|
||||||
|
else if(textIndex < 0)
|
||||||
|
return -textIndex;
|
||||||
|
else if(count == 1)
|
||||||
|
return textIndex;
|
||||||
|
else
|
||||||
|
return textIndex + 1;
|
||||||
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace Unicode
|
|||||||
/// NOTE: usage of these functions should be avoided if possible
|
/// NOTE: usage of these functions should be avoided if possible
|
||||||
std::string DLL_LINKAGE fromUnicode(const std::string & text);
|
std::string DLL_LINKAGE fromUnicode(const std::string & text);
|
||||||
std::string DLL_LINKAGE fromUnicode(const std::string & text, const std::string & encoding);
|
std::string DLL_LINKAGE fromUnicode(const std::string & text, const std::string & encoding);
|
||||||
|
|
||||||
///delete (amount) UTF characters from right
|
///delete (amount) UTF characters from right
|
||||||
DLL_LINKAGE void trimRight(std::string & text, const size_t amount = 1);
|
DLL_LINKAGE void trimRight(std::string & text, const size_t amount = 1);
|
||||||
};
|
};
|
||||||
@ -103,7 +103,7 @@ public:
|
|||||||
std::vector<std::string> overview;//text for Kingdom Overview window
|
std::vector<std::string> overview;//text for Kingdom Overview window
|
||||||
std::vector<std::string> colors; //names of player colors ("red",...)
|
std::vector<std::string> colors; //names of player colors ("red",...)
|
||||||
std::vector<std::string> capColors; //names of player colors with first letter capitalized ("Red",...)
|
std::vector<std::string> capColors; //names of player colors with first letter capitalized ("Red",...)
|
||||||
std::vector<std::string> turnDurations; //turn durations for pregame (1 Minute ... Unlimited)
|
std::vector<std::string> turnDurations; //turn durations for pregame (1 Minute ... Unlimited)
|
||||||
|
|
||||||
//towns
|
//towns
|
||||||
std::vector<std::string> tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen
|
std::vector<std::string> tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen
|
||||||
@ -143,6 +143,8 @@ public:
|
|||||||
|
|
||||||
static void readToVector(std::string sourceName, std::vector<std::string> &dest);
|
static void readToVector(std::string sourceName, std::vector<std::string> &dest);
|
||||||
|
|
||||||
|
int32_t pluralText(const int32_t textIndex, const int32_t count) const;
|
||||||
|
|
||||||
CGeneralTextHandler();
|
CGeneralTextHandler();
|
||||||
CGeneralTextHandler(const CGeneralTextHandler&) = delete;
|
CGeneralTextHandler(const CGeneralTextHandler&) = delete;
|
||||||
CGeneralTextHandler operator=(const CGeneralTextHandler&) = delete;
|
CGeneralTextHandler operator=(const CGeneralTextHandler&) = delete;
|
||||||
|
672
lib/CStack.cpp
672
lib/CStack.cpp
@ -9,74 +9,365 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "CStack.h"
|
#include "CStack.h"
|
||||||
|
#include "CGeneralTextHandler.h"
|
||||||
#include "battle/BattleInfo.h"
|
#include "battle/BattleInfo.h"
|
||||||
#include "spells/CSpellHandler.h"
|
#include "spells/CSpellHandler.h"
|
||||||
#include "CRandomGenerator.h"
|
#include "CRandomGenerator.h"
|
||||||
#include "NetPacks.h"
|
#include "NetPacks.h"
|
||||||
|
|
||||||
|
|
||||||
CStack::CStack(const CStackInstance *Base, PlayerColor O, int I, ui8 Side, SlotID S)
|
///CAmmo
|
||||||
: base(Base), ID(I), owner(O), slot(S), side(Side),
|
CAmmo::CAmmo(const CStack * Owner, CSelector totalSelector):
|
||||||
counterAttacksPerformed(0),counterAttacksTotalCache(0), cloneID(-1),
|
CStackResource(Owner), totalProxy(Owner, totalSelector)
|
||||||
firstHPleft(-1), position(), shots(0), casts(0), resurrected(0)
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CAmmo::available() const
|
||||||
|
{
|
||||||
|
return total() - used;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAmmo::canUse(int32_t amount) const
|
||||||
|
{
|
||||||
|
return available() - amount >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAmmo::reset()
|
||||||
|
{
|
||||||
|
used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CAmmo::total() const
|
||||||
|
{
|
||||||
|
return totalProxy->totalValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAmmo::use(int32_t amount)
|
||||||
|
{
|
||||||
|
if(available() - amount < 0)
|
||||||
|
{
|
||||||
|
logGlobal->error("Stack ammo overuse");
|
||||||
|
used += available();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
used += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
///CShots
|
||||||
|
CShots::CShots(const CStack * Owner):
|
||||||
|
CAmmo(Owner, Selector::type(Bonus::SHOTS))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CShots::use(int32_t amount)
|
||||||
|
{
|
||||||
|
//don't remove ammo if we control a working ammo cart
|
||||||
|
bool hasAmmoCart = false;
|
||||||
|
|
||||||
|
for(const CStack * st : owner->battle->stacks)
|
||||||
|
{
|
||||||
|
if(owner->battle->battleMatchOwner(st, owner, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
|
||||||
|
{
|
||||||
|
hasAmmoCart = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasAmmoCart)
|
||||||
|
CAmmo::use(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
///CCasts
|
||||||
|
CCasts::CCasts(const CStack * Owner):
|
||||||
|
CAmmo(Owner, Selector::type(Bonus::CASTS))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///CRetaliations
|
||||||
|
CRetaliations::CRetaliations(const CStack * Owner):
|
||||||
|
CAmmo(Owner, Selector::type(Bonus::ADDITIONAL_RETALIATION)), totalCache(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CRetaliations::total() const
|
||||||
|
{
|
||||||
|
//after dispell bonus should remain during current round
|
||||||
|
int32_t val = 1 + totalProxy->totalValue();
|
||||||
|
vstd::amax(totalCache, val);
|
||||||
|
return totalCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRetaliations::reset()
|
||||||
|
{
|
||||||
|
CAmmo::reset();
|
||||||
|
totalCache = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///CHealth
|
||||||
|
CHealth::CHealth(const IUnitHealthInfo * Owner):
|
||||||
|
owner(Owner)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
CHealth::CHealth(const CHealth & other):
|
||||||
|
owner(other.owner),
|
||||||
|
firstHPleft(other.firstHPleft),
|
||||||
|
fullUnits(other.fullUnits),
|
||||||
|
resurrected(other.resurrected)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::init()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0;
|
||||||
|
firstHPleft = owner->unitBaseAmount() > 0 ? owner->unitMaxHealth() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::addResurrected(int32_t amount)
|
||||||
|
{
|
||||||
|
resurrected += amount;
|
||||||
|
vstd::amax(resurrected, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CHealth::available() const
|
||||||
|
{
|
||||||
|
return static_cast<int64_t>(firstHPleft) + owner->unitMaxHealth() * fullUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t CHealth::total() const
|
||||||
|
{
|
||||||
|
return static_cast<int64_t>(owner->unitMaxHealth()) * owner->unitBaseAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::damage(int32_t & amount)
|
||||||
|
{
|
||||||
|
const int32_t oldCount = getCount();
|
||||||
|
|
||||||
|
const bool withKills = amount >= firstHPleft;
|
||||||
|
|
||||||
|
if(withKills)
|
||||||
|
{
|
||||||
|
int64_t totalHealth = available();
|
||||||
|
if(amount > totalHealth)
|
||||||
|
amount = totalHealth;
|
||||||
|
totalHealth -= amount;
|
||||||
|
if(totalHealth <= 0)
|
||||||
|
{
|
||||||
|
fullUnits = 0;
|
||||||
|
firstHPleft = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setFromTotal(totalHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstHPleft -= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
addResurrected(getCount() - oldCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::heal(int32_t & amount, EHealLevel level, EHealPower power)
|
||||||
|
{
|
||||||
|
const int32_t unitHealth = owner->unitMaxHealth();
|
||||||
|
const int32_t oldCount = getCount();
|
||||||
|
|
||||||
|
int32_t maxHeal = std::numeric_limits<int32_t>::max();
|
||||||
|
|
||||||
|
switch(level)
|
||||||
|
{
|
||||||
|
case EHealLevel::HEAL:
|
||||||
|
maxHeal = std::max(0, unitHealth - firstHPleft);
|
||||||
|
break;
|
||||||
|
case EHealLevel::RESURRECT:
|
||||||
|
maxHeal = total() - available();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(level == EHealLevel::OVERHEAL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vstd::amax(maxHeal, 0);
|
||||||
|
vstd::abetween(amount, 0, maxHeal);
|
||||||
|
|
||||||
|
if(amount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int64_t availableHealth = available();
|
||||||
|
|
||||||
|
availableHealth += amount;
|
||||||
|
setFromTotal(availableHealth);
|
||||||
|
|
||||||
|
if(power == EHealPower::ONE_BATTLE)
|
||||||
|
addResurrected(getCount() - oldCount);
|
||||||
|
else
|
||||||
|
assert(power == EHealPower::PERMANENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::setFromTotal(const int64_t totalHealth)
|
||||||
|
{
|
||||||
|
const int32_t unitHealth = owner->unitMaxHealth();
|
||||||
|
firstHPleft = totalHealth % unitHealth;
|
||||||
|
fullUnits = totalHealth / unitHealth;
|
||||||
|
|
||||||
|
if(firstHPleft == 0 && fullUnits > 1)
|
||||||
|
{
|
||||||
|
firstHPleft = unitHealth;
|
||||||
|
fullUnits -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::reset()
|
||||||
|
{
|
||||||
|
fullUnits = 0;
|
||||||
|
firstHPleft = 0;
|
||||||
|
resurrected = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CHealth::getCount() const
|
||||||
|
{
|
||||||
|
return fullUnits + (firstHPleft > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CHealth::getFirstHPleft() const
|
||||||
|
{
|
||||||
|
return firstHPleft;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CHealth::getResurrected() const
|
||||||
|
{
|
||||||
|
return resurrected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::fromInfo(const CHealthInfo & info)
|
||||||
|
{
|
||||||
|
firstHPleft = info.firstHPleft;
|
||||||
|
fullUnits = info.fullUnits;
|
||||||
|
resurrected = info.resurrected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::toInfo(CHealthInfo & info) const
|
||||||
|
{
|
||||||
|
info.firstHPleft = firstHPleft;
|
||||||
|
info.fullUnits = fullUnits;
|
||||||
|
info.resurrected = resurrected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHealth::takeResurrected()
|
||||||
|
{
|
||||||
|
if(resurrected != 0)
|
||||||
|
{
|
||||||
|
int64_t totalHealth = available();
|
||||||
|
|
||||||
|
totalHealth -= resurrected * owner->unitMaxHealth();
|
||||||
|
vstd::amax(totalHealth, 0);
|
||||||
|
setFromTotal(totalHealth);
|
||||||
|
resurrected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///CStack
|
||||||
|
CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S):
|
||||||
|
base(Base), ID(I), owner(O), slot(S), side(Side),
|
||||||
|
counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
|
||||||
|
position()
|
||||||
{
|
{
|
||||||
assert(base);
|
assert(base);
|
||||||
type = base->type;
|
type = base->type;
|
||||||
count = baseAmount = base->count;
|
baseAmount = base->count;
|
||||||
|
health.init(); //???
|
||||||
setNodeType(STACK_BATTLE);
|
setNodeType(STACK_BATTLE);
|
||||||
}
|
}
|
||||||
CStack::CStack()
|
|
||||||
|
CStack::CStack():
|
||||||
|
counterAttacks(this), shots(this), casts(this), health(this)
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
setNodeType(STACK_BATTLE);
|
setNodeType(STACK_BATTLE);
|
||||||
}
|
}
|
||||||
CStack::CStack(const CStackBasicDescriptor *stack, PlayerColor O, int I, ui8 Side, SlotID S)
|
|
||||||
: base(nullptr), ID(I), owner(O), slot(S), side(Side),
|
CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S):
|
||||||
counterAttacksPerformed(0), counterAttacksTotalCache(0), cloneID(-1),
|
base(nullptr), ID(I), owner(O), slot(S), side(Side),
|
||||||
firstHPleft(-1), position(), shots(0), casts(0), resurrected(0)
|
counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
|
||||||
|
position()
|
||||||
{
|
{
|
||||||
type = stack->type;
|
type = stack->type;
|
||||||
count = baseAmount = stack->count;
|
baseAmount = stack->count;
|
||||||
|
health.init(); //???
|
||||||
setNodeType(STACK_BATTLE);
|
setNodeType(STACK_BATTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t CStack::getKilled() const
|
||||||
|
{
|
||||||
|
int32_t res = baseAmount - health.getCount() + health.getResurrected();
|
||||||
|
vstd::amax(res, 0);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CStack::getCount() const
|
||||||
|
{
|
||||||
|
return health.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CStack::getFirstHPleft() const
|
||||||
|
{
|
||||||
|
return health.getFirstHPleft();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CCreature * CStack::getCreature() const
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
void CStack::init()
|
void CStack::init()
|
||||||
{
|
{
|
||||||
base = nullptr;
|
base = nullptr;
|
||||||
type = nullptr;
|
type = nullptr;
|
||||||
ID = -1;
|
ID = -1;
|
||||||
count = baseAmount = -1;
|
baseAmount = -1;
|
||||||
firstHPleft = -1;
|
|
||||||
owner = PlayerColor::NEUTRAL;
|
owner = PlayerColor::NEUTRAL;
|
||||||
slot = SlotID(255);
|
slot = SlotID(255);
|
||||||
side = 1;
|
side = 1;
|
||||||
position = BattleHex();
|
position = BattleHex();
|
||||||
counterAttacksPerformed = 0;
|
|
||||||
counterAttacksTotalCache = 0;
|
|
||||||
cloneID = -1;
|
cloneID = -1;
|
||||||
|
|
||||||
shots = 0;
|
|
||||||
casts = 0;
|
|
||||||
resurrected = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStack::postInit()
|
void CStack::localInit(BattleInfo * battleInfo)
|
||||||
{
|
{
|
||||||
assert(type);
|
battle = battleInfo;
|
||||||
assert(getParentNodes().size());
|
|
||||||
|
|
||||||
firstHPleft = MaxHealth();
|
|
||||||
shots = getCreature()->valOfBonuses(Bonus::SHOTS);
|
|
||||||
counterAttacksPerformed = 0;
|
|
||||||
counterAttacksTotalCache = 0;
|
|
||||||
casts = valOfBonuses(Bonus::CASTS);
|
|
||||||
resurrected = 0;
|
|
||||||
cloneID = -1;
|
cloneID = -1;
|
||||||
|
assert(type);
|
||||||
|
|
||||||
|
exportBonuses();
|
||||||
|
if(base) //stack originating from "real" stack in garrison -> attach to it
|
||||||
|
{
|
||||||
|
attachTo(const_cast<CStackInstance *>(base));
|
||||||
|
}
|
||||||
|
else //attach directly to obj to which stack belongs and creature type
|
||||||
|
{
|
||||||
|
CArmedInstance * army = battle->battleGetArmyObject(side);
|
||||||
|
attachTo(army);
|
||||||
|
attachTo(const_cast<CCreature *>(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
shots.reset();
|
||||||
|
counterAttacks.reset();
|
||||||
|
casts.reset();
|
||||||
|
health.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
ui32 CStack::level() const
|
ui32 CStack::level() const
|
||||||
{
|
{
|
||||||
if (base)
|
if(base)
|
||||||
return base->getLevel(); //creatture or commander
|
return base->getLevel(); //creatture or commander
|
||||||
else
|
else
|
||||||
return std::max(1, (int)getCreature()->level); //war machine, clone etc
|
return std::max(1, (int)getCreature()->level); //war machine, clone etc
|
||||||
@ -85,19 +376,19 @@ ui32 CStack::level() const
|
|||||||
si32 CStack::magicResistance() const
|
si32 CStack::magicResistance() const
|
||||||
{
|
{
|
||||||
si32 magicResistance;
|
si32 magicResistance;
|
||||||
if (base) //TODO: make war machines receive aura of magic resistance
|
if(base) //TODO: make war machines receive aura of magic resistance
|
||||||
{
|
{
|
||||||
magicResistance = base->magicResistance();
|
magicResistance = base->magicResistance();
|
||||||
int auraBonus = 0;
|
int auraBonus = 0;
|
||||||
for (const CStack * stack : base->armyObj->battle-> batteAdjacentCreatures(this))
|
for(const CStack * stack : base->armyObj->battle-> batteAdjacentCreatures(this))
|
||||||
{
|
|
||||||
if (stack->owner == owner)
|
|
||||||
{
|
{
|
||||||
vstd::amax(auraBonus, stack->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
|
if(stack->owner == owner)
|
||||||
|
{
|
||||||
|
vstd::amax(auraBonus, stack->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
magicResistance += auraBonus;
|
magicResistance += auraBonus;
|
||||||
vstd::amin (magicResistance, 100);
|
vstd::amin(magicResistance, 100);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
magicResistance = type->magicResistance();
|
magicResistance = type->magicResistance();
|
||||||
@ -106,18 +397,38 @@ si32 CStack::magicResistance() const
|
|||||||
|
|
||||||
bool CStack::willMove(int turn /*= 0*/) const
|
bool CStack::willMove(int turn /*= 0*/) const
|
||||||
{
|
{
|
||||||
return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )
|
return (turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING))
|
||||||
&& !moved(turn)
|
&& !moved(turn)
|
||||||
&& canMove(turn);
|
&& canMove(turn);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::canMove( int turn /*= 0*/ ) const
|
bool CStack::canMove(int turn /*= 0*/) const
|
||||||
{
|
{
|
||||||
return alive()
|
return alive()
|
||||||
&& !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
|
&& !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::moved( int turn /*= 0*/ ) const
|
bool CStack::canCast() const
|
||||||
|
{
|
||||||
|
return casts.canUse(1);//do not check specific cast abilities here
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CStack::isCaster() const
|
||||||
|
{
|
||||||
|
return casts.total() > 0;//do not check specific cast abilities here
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CStack::canShoot() const
|
||||||
|
{
|
||||||
|
return shots.canUse(1) && hasBonusOfType(Bonus::SHOOTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CStack::isShooter() const
|
||||||
|
{
|
||||||
|
return shots.total() > 0 && hasBonusOfType(Bonus::SHOOTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CStack::moved(int turn /*= 0*/) const
|
||||||
{
|
{
|
||||||
if(!turn)
|
if(!turn)
|
||||||
return vstd::contains(state, EBattleStackState::MOVED);
|
return vstd::contains(state, EBattleStackState::MOVED);
|
||||||
@ -197,26 +508,27 @@ std::vector<BattleHex> CStack::getSurroundingHexes(BattleHex attackerPos) const
|
|||||||
{
|
{
|
||||||
const int WN = GameConstants::BFIELD_WIDTH;
|
const int WN = GameConstants::BFIELD_WIDTH;
|
||||||
if(side == BattleSide::ATTACKER)
|
if(side == BattleSide::ATTACKER)
|
||||||
{ //position is equal to front hex
|
{
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN+2 : WN+1 ), hexes);
|
//position is equal to front hex
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN+1 : WN ), hexes);
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 2 : WN + 1), hexes);
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN : WN-1 ), hexes);
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
|
||||||
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
|
||||||
BattleHex::checkAndPush(hex - 2, hexes);
|
BattleHex::checkAndPush(hex - 2, hexes);
|
||||||
BattleHex::checkAndPush(hex + 1, hexes);
|
BattleHex::checkAndPush(hex + 1, hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN-2 : WN-1 ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 2 : WN - 1), hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN-1 : WN ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN : WN+1 ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN+1 : WN ), hexes);
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN : WN-1 ), hexes);
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
|
||||||
BattleHex::checkAndPush(hex - ( (hex/WN)%2 ? WN-1 : WN-2 ), hexes);
|
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN - 1 : WN - 2), hexes);
|
||||||
BattleHex::checkAndPush(hex + 2, hexes);
|
BattleHex::checkAndPush(hex + 2, hexes);
|
||||||
BattleHex::checkAndPush(hex - 1, hexes);
|
BattleHex::checkAndPush(hex - 1, hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN-1 : WN ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN : WN+1 ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
|
||||||
BattleHex::checkAndPush(hex + ( (hex/WN)%2 ? WN+1 : WN+2 ), hexes);
|
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN + 1 : WN + 2), hexes);
|
||||||
}
|
}
|
||||||
return hexes;
|
return hexes;
|
||||||
}
|
}
|
||||||
@ -248,15 +560,15 @@ std::vector<si32> CStack::activeSpells() const
|
|||||||
std::stringstream cachingStr;
|
std::stringstream cachingStr;
|
||||||
cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
|
cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
|
||||||
CSelector selector = Selector::sourceType(Bonus::SPELL_EFFECT)
|
CSelector selector = Selector::sourceType(Bonus::SPELL_EFFECT)
|
||||||
.And(CSelector([](const Bonus *b)->bool
|
.And(CSelector([](const Bonus * b)->bool
|
||||||
{
|
{
|
||||||
return b->type != Bonus::NONE;
|
return b->type != Bonus::NONE;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
TBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
|
TBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
|
||||||
for(const std::shared_ptr<Bonus> it : *spellEffects)
|
for(const std::shared_ptr<Bonus> it : *spellEffects)
|
||||||
{
|
{
|
||||||
if (!vstd::contains(ret, it->sid)) //do not duplicate spells with multiple effects
|
if(!vstd::contains(ret, it->sid)) //do not duplicate spells with multiple effects
|
||||||
ret.push_back(it->sid);
|
ret.push_back(it->sid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,22 +585,17 @@ const CGHeroInstance * CStack::getMyHero() const
|
|||||||
if(base)
|
if(base)
|
||||||
return dynamic_cast<const CGHeroInstance *>(base->armyObj);
|
return dynamic_cast<const CGHeroInstance *>(base->armyObj);
|
||||||
else //we are attached directly?
|
else //we are attached directly?
|
||||||
for(const CBonusSystemNode *n : getParentNodes())
|
for(const CBonusSystemNode * n : getParentNodes())
|
||||||
if(n->getNodeType() == HERO)
|
if(n->getNodeType() == HERO)
|
||||||
return dynamic_cast<const CGHeroInstance *>(n);
|
return dynamic_cast<const CGHeroInstance *>(n);
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui32 CStack::totalHealth() const
|
|
||||||
{
|
|
||||||
return ((count > 0) ? MaxHealth() * (count-1) : 0) + firstHPleft;//do not hide possible invalid firstHPleft for dead stack
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CStack::nodeName() const
|
std::string CStack::nodeName() const
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "Battle stack [" << ID << "]: " << count << " creatures of ";
|
oss << "Battle stack [" << ID << "]: " << health.getCount() << " creatures of ";
|
||||||
if(type)
|
if(type)
|
||||||
oss << type->namePl;
|
oss << type->namePl;
|
||||||
else
|
else
|
||||||
@ -300,65 +607,79 @@ std::string CStack::nodeName() const
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int,int> CStack::countKilledByAttack(int damageReceived) const
|
CHealth CStack::healthAfterAttacked(int32_t & damage) const
|
||||||
{
|
{
|
||||||
int newRemainingHP = 0;
|
return healthAfterAttacked(damage, health);
|
||||||
int killedCount = damageReceived / MaxHealth();
|
}
|
||||||
unsigned damageFirst = damageReceived % MaxHealth();
|
|
||||||
|
|
||||||
if (damageReceived && vstd::contains(state, EBattleStackState::CLONED)) // block ability should not kill clone (0 damage)
|
CHealth CStack::healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const
|
||||||
|
{
|
||||||
|
CHealth res = customHealth;
|
||||||
|
|
||||||
|
if(isClone())
|
||||||
{
|
{
|
||||||
killedCount = count;
|
// block ability should not kill clone (0 damage)
|
||||||
|
if(damage > 0)
|
||||||
|
{
|
||||||
|
damage = 1;//??? what should be actual damage against clone?
|
||||||
|
res.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( firstHPleft <= damageFirst )
|
res.damage(damage);
|
||||||
{
|
|
||||||
killedCount++;
|
|
||||||
newRemainingHP = firstHPleft + MaxHealth() - damageFirst;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newRemainingHP = firstHPleft - damageFirst;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(killedCount == count)
|
return res;
|
||||||
newRemainingHP = 0;
|
|
||||||
|
|
||||||
return std::make_pair(killedCount, newRemainingHP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStack::prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand, boost::optional<int> customCount /*= boost::none*/) const
|
CHealth CStack::healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const
|
||||||
{
|
{
|
||||||
auto afterAttack = countKilledByAttack(bsa.damageAmount);
|
CHealth res = health;
|
||||||
|
|
||||||
bsa.killedAmount = afterAttack.first;
|
if(level == EHealLevel::HEAL && power == EHealPower::ONE_BATTLE)
|
||||||
bsa.newHP = afterAttack.second;
|
logGlobal->error("Heal for one battle does not make sense", nodeName(), toHeal);
|
||||||
|
else if(isClone())
|
||||||
|
logGlobal->error("Attempt to heal clone: %s for %d HP", nodeName(), toHeal);
|
||||||
|
else
|
||||||
|
res.heal(toHeal, level, power);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
if(bsa.damageAmount && vstd::contains(state, EBattleStackState::CLONED)) // block ability should not kill clone (0 damage)
|
void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const
|
||||||
|
{
|
||||||
|
prepareAttacked(bsa, rand, health);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const
|
||||||
|
{
|
||||||
|
CHealth afterAttack = healthAfterAttacked(bsa.damageAmount, customHealth);
|
||||||
|
|
||||||
|
bsa.killedAmount = customHealth.getCount() - afterAttack.getCount();
|
||||||
|
afterAttack.toInfo(bsa.newHealth);
|
||||||
|
bsa.newHealth.stackId = ID;
|
||||||
|
bsa.newHealth.delta = -bsa.damageAmount;
|
||||||
|
|
||||||
|
if(afterAttack.available() <= 0 && isClone())
|
||||||
{
|
{
|
||||||
bsa.flags |= BattleStackAttacked::CLONE_KILLED;
|
bsa.flags |= BattleStackAttacked::CLONE_KILLED;
|
||||||
return; // no rebirth I believe
|
return; // no rebirth I believe
|
||||||
}
|
}
|
||||||
|
|
||||||
const int countToUse = customCount ? *customCount : count;
|
if(afterAttack.available() <= 0) //stack killed
|
||||||
|
|
||||||
if(countToUse <= bsa.killedAmount) //stack killed
|
|
||||||
{
|
{
|
||||||
bsa.newAmount = 0;
|
|
||||||
bsa.flags |= BattleStackAttacked::KILLED;
|
bsa.flags |= BattleStackAttacked::KILLED;
|
||||||
bsa.killedAmount = countToUse; //we cannot kill more creatures than we have
|
|
||||||
|
|
||||||
int resurrectFactor = valOfBonuses(Bonus::REBIRTH);
|
int resurrectFactor = valOfBonuses(Bonus::REBIRTH);
|
||||||
if(resurrectFactor > 0 && casts) //there must be casts left
|
if(resurrectFactor > 0 && canCast()) //there must be casts left
|
||||||
{
|
{
|
||||||
int resurrectedStackCount = base->count * resurrectFactor / 100;
|
int resurrectedStackCount = baseAmount * resurrectFactor / 100;
|
||||||
|
|
||||||
// last stack has proportional chance to rebirth
|
// last stack has proportional chance to rebirth
|
||||||
auto diff = base->count * resurrectFactor / 100.0 - resurrectedStackCount;
|
//FIXME: diff is always 0
|
||||||
if (diff > rand.nextDouble(0, 0.99))
|
auto diff = baseAmount * resurrectFactor / 100.0 - resurrectedStackCount;
|
||||||
|
if(diff > rand.nextDouble(0, 0.99))
|
||||||
{
|
{
|
||||||
resurrectedStackCount += 1;
|
resurrectedStackCount += 1;
|
||||||
}
|
}
|
||||||
@ -372,64 +693,45 @@ void CStack::prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand,
|
|||||||
if(resurrectedStackCount > 0)
|
if(resurrectedStackCount > 0)
|
||||||
{
|
{
|
||||||
bsa.flags |= BattleStackAttacked::REBIRTH;
|
bsa.flags |= BattleStackAttacked::REBIRTH;
|
||||||
bsa.newAmount = resurrectedStackCount; //risky?
|
//TODO: use StackHealedOrResurrected
|
||||||
bsa.newHP = MaxHealth(); //resore full health
|
bsa.newHealth.firstHPleft = MaxHealth();
|
||||||
|
bsa.newHealth.fullUnits = resurrectedStackCount - 1;
|
||||||
|
bsa.newHealth.resurrected = 0; //TODO: add one-battle rebirth?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
bsa.newAmount = countToUse - bsa.killedAmount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos /*= BattleHex::INVALID*/, BattleHex defenderPos /*= BattleHex::INVALID*/)
|
bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos /*= BattleHex::INVALID*/, BattleHex defenderPos /*= BattleHex::INVALID*/)
|
||||||
{
|
{
|
||||||
if (!attackerPos.isValid())
|
if(!attackerPos.isValid())
|
||||||
{
|
|
||||||
attackerPos = attacker->position;
|
attackerPos = attacker->position;
|
||||||
}
|
if(!defenderPos.isValid())
|
||||||
if (!defenderPos.isValid())
|
|
||||||
{
|
|
||||||
defenderPos = defender->position;
|
defenderPos = defender->position;
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front
|
(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> front
|
||||||
|| (attacker->doubleWide() //back <=> front
|
|| (attacker->doubleWide()//back <=> front
|
||||||
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0)
|
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0)
|
||||||
|| (defender->doubleWide() //front <=> back
|
|| (defender->doubleWide()//front <=> back
|
||||||
&& BattleHex::mutualPosition(attackerPos, defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0)
|
&& BattleHex::mutualPosition(attackerPos, defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0)
|
||||||
|| (defender->doubleWide() && attacker->doubleWide()//back <=> back
|
|| (defender->doubleWide() && attacker->doubleWide()//back <=> back
|
||||||
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0);
|
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::ableToRetaliate() const //FIXME: crash after clone is killed
|
bool CStack::ableToRetaliate() const
|
||||||
{
|
{
|
||||||
return alive()
|
return alive()
|
||||||
&& (counterAttacksPerformed < counterAttacksTotal() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
|
&& (counterAttacks.canUse() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
|
||||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON)
|
&& !hasBonusOfType(Bonus::SIEGE_WEAPON)
|
||||||
&& !hasBonusOfType(Bonus::HYPNOTIZED)
|
&& !hasBonusOfType(Bonus::HYPNOTIZED)
|
||||||
&& !hasBonusOfType(Bonus::NO_RETALIATION);
|
&& !hasBonusOfType(Bonus::NO_RETALIATION);
|
||||||
}
|
|
||||||
|
|
||||||
ui8 CStack::counterAttacksTotal() const
|
|
||||||
{
|
|
||||||
//after dispell bonus should remain during current round
|
|
||||||
ui8 val = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION);
|
|
||||||
vstd::amax(counterAttacksTotalCache, val);
|
|
||||||
return counterAttacksTotalCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
si8 CStack::counterAttacksRemaining() const
|
|
||||||
{
|
|
||||||
return counterAttacksTotal() - counterAttacksPerformed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CStack::getName() const
|
std::string CStack::getName() const
|
||||||
{
|
{
|
||||||
return (count > 1) ? type->namePl : type->nameSing; //War machines can't use base
|
return (health.getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::isValidTarget(bool allowDead/* = false*/) const
|
bool CStack::isValidTarget(bool allowDead/* = false*/) const
|
||||||
@ -442,9 +744,14 @@ bool CStack::isDead() const
|
|||||||
return !alive() && !isGhost();
|
return !alive() && !isGhost();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CStack::isClone() const
|
||||||
|
{
|
||||||
|
return vstd::contains(state, EBattleStackState::CLONED);
|
||||||
|
}
|
||||||
|
|
||||||
bool CStack::isGhost() const
|
bool CStack::isGhost() const
|
||||||
{
|
{
|
||||||
return vstd::contains(state,EBattleStackState::GHOST);
|
return vstd::contains(state, EBattleStackState::GHOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CStack::isTurret() const
|
bool CStack::isTurret() const
|
||||||
@ -454,9 +761,9 @@ bool CStack::isTurret() const
|
|||||||
|
|
||||||
bool CStack::canBeHealed() const
|
bool CStack::canBeHealed() const
|
||||||
{
|
{
|
||||||
return firstHPleft < MaxHealth()
|
return getFirstHPleft() < MaxHealth()
|
||||||
&& isValidTarget()
|
&& isValidTarget()
|
||||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
|
&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStack::makeGhost()
|
void CStack::makeGhost()
|
||||||
@ -467,26 +774,13 @@ void CStack::makeGhost()
|
|||||||
|
|
||||||
bool CStack::alive() const //determines if stack is alive
|
bool CStack::alive() const //determines if stack is alive
|
||||||
{
|
{
|
||||||
return vstd::contains(state,EBattleStackState::ALIVE);
|
return vstd::contains(state, EBattleStackState::ALIVE);
|
||||||
}
|
|
||||||
|
|
||||||
ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const
|
|
||||||
{
|
|
||||||
if(!resurrect && !alive())
|
|
||||||
{
|
|
||||||
logGlobal->warnStream() <<"Attempt to heal corpse detected.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::min<ui32>(toHeal, MaxHealth() - firstHPleft + (resurrect ? (baseAmount - count) * MaxHealth() : 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
|
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
|
||||||
{
|
{
|
||||||
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
|
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
|
||||||
|
|
||||||
vstd::abetween(skill, 0, 3);
|
vstd::abetween(skill, 0, 3);
|
||||||
|
|
||||||
return skill;
|
return skill;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,37 +797,91 @@ int CStack::getEffectLevel(const CSpell * spell) const
|
|||||||
|
|
||||||
int CStack::getEffectPower(const CSpell * spell) const
|
int CStack::getEffectPower(const CSpell * spell) const
|
||||||
{
|
{
|
||||||
return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * count / 100;
|
return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * health.getCount() / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CStack::getEnchantPower(const CSpell * spell) const
|
int CStack::getEnchantPower(const CSpell * spell) const
|
||||||
{
|
{
|
||||||
int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
|
||||||
if(res<=0)
|
if(res <= 0)
|
||||||
res = 3;//default for creatures
|
res = 3;//default for creatures
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CStack::getEffectValue(const CSpell * spell) const
|
int CStack::getEffectValue(const CSpell * spell) const
|
||||||
{
|
{
|
||||||
return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * count;
|
return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * health.getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayerColor CStack::getOwner() const
|
const PlayerColor CStack::getOwner() const
|
||||||
{
|
{
|
||||||
return owner;
|
return battle->battleGetOwner(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStack::getCasterName(MetaString & text) const
|
void CStack::getCasterName(MetaString & text) const
|
||||||
{
|
{
|
||||||
//always plural name in case of spell cast.
|
//always plural name in case of spell cast.
|
||||||
text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
|
addNameReplacement(text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack*> & attacked, MetaString & text) const
|
void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const
|
||||||
{
|
{
|
||||||
text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
|
text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
|
||||||
//todo: use text 566 for single creature
|
//todo: use text 566 for single creature
|
||||||
getCasterName(text);
|
getCasterName(text);
|
||||||
text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
|
text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t CStack::unitMaxHealth() const
|
||||||
|
{
|
||||||
|
return MaxHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CStack::unitBaseAmount() const
|
||||||
|
{
|
||||||
|
return baseAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStack::addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural) const
|
||||||
|
{
|
||||||
|
if(boost::logic::indeterminate(plural))
|
||||||
|
serial = VLC->generaltexth->pluralText(serial, health.getCount());
|
||||||
|
else if(plural)
|
||||||
|
serial = VLC->generaltexth->pluralText(serial, 2);
|
||||||
|
else
|
||||||
|
serial = VLC->generaltexth->pluralText(serial, 1);
|
||||||
|
|
||||||
|
text.addTxt(type, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStack::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const
|
||||||
|
{
|
||||||
|
if(boost::logic::indeterminate(plural))
|
||||||
|
text.addCreReplacement(type->idNumber, health.getCount());
|
||||||
|
else if(plural)
|
||||||
|
text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
|
||||||
|
else
|
||||||
|
text.addReplacement(MetaString::CRE_SING_NAMES, type->idNumber.num);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CStack::formatGeneralMessage(const int32_t baseTextId) const
|
||||||
|
{
|
||||||
|
const int32_t textId = VLC->generaltexth->pluralText(baseTextId, health.getCount());
|
||||||
|
|
||||||
|
MetaString text;
|
||||||
|
text.addTxt(MetaString::GENERAL_TXT, textId);
|
||||||
|
text.addCreReplacement(type->idNumber, health.getCount());
|
||||||
|
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStack::setHealth(const CHealthInfo & value)
|
||||||
|
{
|
||||||
|
health.reset();
|
||||||
|
health.fromInfo(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStack::setHealth(const CHealth & value)
|
||||||
|
{
|
||||||
|
health = value;
|
||||||
|
}
|
||||||
|
218
lib/CStack.h
218
lib/CStack.h
@ -7,64 +7,186 @@
|
|||||||
* Full text of license available in license.txt file, in main folder
|
* Full text of license available in license.txt file, in main folder
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "battle/BattleHex.h"
|
#include "battle/BattleHex.h"
|
||||||
#include "CCreatureHandler.h"
|
#include "CCreatureHandler.h"
|
||||||
#include "mapObjects/CGHeroInstance.h" // for commander serialization
|
#include "mapObjects/CGHeroInstance.h" // for commander serialization
|
||||||
|
|
||||||
struct BattleStackAttacked;
|
struct BattleStackAttacked;
|
||||||
|
struct BattleInfo;
|
||||||
|
class CStack;
|
||||||
|
class CHealthInfo;
|
||||||
|
|
||||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public CStackBasicDescriptor, public ISpellCaster
|
template <typename Quantity>
|
||||||
|
class DLL_LINKAGE CStackResource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
const CStackInstance *base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
|
CStackResource(const CStack * Owner):
|
||||||
|
owner(Owner)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void reset()
|
||||||
|
{
|
||||||
|
used = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const CStack * owner;
|
||||||
|
Quantity used;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CAmmo : public CStackResource<int32_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CAmmo(const CStack * Owner, CSelector totalSelector);
|
||||||
|
|
||||||
|
int32_t available() const;
|
||||||
|
bool canUse(int32_t amount = 1) const;
|
||||||
|
virtual void reset() override;
|
||||||
|
virtual int32_t total() const;
|
||||||
|
virtual void use(int32_t amount = 1);
|
||||||
|
|
||||||
|
template <typename Handler> void serialize(Handler & h, const int version)
|
||||||
|
{
|
||||||
|
if(!h.saving)
|
||||||
|
reset();
|
||||||
|
h & used;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
CBonusProxy totalProxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CShots : public CAmmo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CShots(const CStack * Owner);
|
||||||
|
void use(int32_t amount = 1) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CCasts : public CAmmo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CCasts(const CStack * Owner);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CRetaliations : public CAmmo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CRetaliations(const CStack * Owner);
|
||||||
|
int32_t total() const override;
|
||||||
|
void reset() override;
|
||||||
|
private:
|
||||||
|
mutable int32_t totalCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE IUnitHealthInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int32_t unitMaxHealth() const = 0;
|
||||||
|
virtual int32_t unitBaseAmount() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CHealth
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CHealth(const IUnitHealthInfo * Owner);
|
||||||
|
CHealth(const CHealth & other);
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void damage(int32_t & amount);
|
||||||
|
void heal(int32_t & amount, EHealLevel level, EHealPower power);
|
||||||
|
|
||||||
|
int32_t getCount() const;
|
||||||
|
int32_t getFirstHPleft() const;
|
||||||
|
int32_t getResurrected() const;
|
||||||
|
|
||||||
|
int64_t available() const;
|
||||||
|
int64_t total() const;
|
||||||
|
|
||||||
|
void toInfo(CHealthInfo & info) const;
|
||||||
|
void fromInfo(const CHealthInfo & info);
|
||||||
|
|
||||||
|
void takeResurrected();
|
||||||
|
|
||||||
|
template <typename Handler> void serialize(Handler & h, const int version)
|
||||||
|
{
|
||||||
|
if(!h.saving)
|
||||||
|
reset();
|
||||||
|
h & firstHPleft & fullUnits & resurrected;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void addResurrected(int32_t amount);
|
||||||
|
void setFromTotal(const int64_t totalHealth);
|
||||||
|
const IUnitHealthInfo * owner;
|
||||||
|
|
||||||
|
int32_t firstHPleft;
|
||||||
|
int32_t fullUnits;
|
||||||
|
int32_t resurrected;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CStack : public CBonusSystemNode, public ISpellCaster, public IUnitHealthInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
|
||||||
|
|
||||||
ui32 ID; //unique ID of stack
|
ui32 ID; //unique ID of stack
|
||||||
ui32 baseAmount;
|
ui32 baseAmount;
|
||||||
ui32 firstHPleft; //HP of first creature in stack
|
const CCreature * type;
|
||||||
PlayerColor owner; //owner - player colour (255 for neutrals)
|
|
||||||
|
PlayerColor owner; //owner - player color (255 for neutrals)
|
||||||
SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures)
|
SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures)
|
||||||
ui8 side;
|
ui8 side;
|
||||||
BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
|
BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
|
||||||
///how many times this stack has been counterattacked this round
|
|
||||||
ui8 counterAttacksPerformed;
|
CRetaliations counterAttacks;
|
||||||
///cached total count of counterattacks; should be cleared each round;do not serialize
|
CShots shots;
|
||||||
mutable ui8 counterAttacksTotalCache;
|
CCasts casts;
|
||||||
si16 shots; //how many shots left
|
CHealth health;
|
||||||
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
|
///id of alive clone of this stack clone if any
|
||||||
si32 cloneID;
|
si32 cloneID;
|
||||||
std::set<EBattleStackState::EBattleStackState> state;
|
std::set<EBattleStackState::EBattleStackState> state;
|
||||||
//overrides
|
|
||||||
const CCreature* getCreature() const {return type;}
|
|
||||||
|
|
||||||
CStack(const CStackInstance *base, PlayerColor O, int I, ui8 Side, SlotID S); //c-tor
|
CStack(const CStackInstance * base, PlayerColor O, int I, ui8 Side, SlotID S); //c-tor
|
||||||
CStack(const CStackBasicDescriptor *stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255)); //c-tor
|
CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255)); //c-tor
|
||||||
CStack(); //c-tor
|
CStack(); //c-tor
|
||||||
~CStack();
|
~CStack();
|
||||||
|
|
||||||
|
int32_t getKilled() const;
|
||||||
|
int32_t getCount() const;
|
||||||
|
int32_t getFirstHPleft() const;
|
||||||
|
const CCreature * getCreature() const;
|
||||||
|
|
||||||
std::string nodeName() const override;
|
std::string nodeName() const override;
|
||||||
|
|
||||||
void init(); //set initial (invalid) values
|
void init(); //set initial (invalid) values
|
||||||
void postInit(); //used to finish initialization when inheriting creature parameters is working
|
void localInit(BattleInfo * battleInfo);
|
||||||
std::string getName() const; //plural or singular
|
std::string getName() const; //plural or singular
|
||||||
bool willMove(int turn = 0) const; //if stack has remaining move this turn
|
bool willMove(int turn = 0) const; //if stack has remaining move this turn
|
||||||
bool ableToRetaliate() const; //if stack can retaliate after attacked
|
bool ableToRetaliate() const; //if stack can retaliate after attacked
|
||||||
///how many times this stack can counterattack in one round
|
|
||||||
ui8 counterAttacksTotal() const;
|
|
||||||
///how many times this stack can counterattack in one round more
|
|
||||||
si8 counterAttacksRemaining() const;
|
|
||||||
bool moved(int turn = 0) const; //if stack was already moved this turn
|
bool moved(int turn = 0) const; //if stack was already moved this turn
|
||||||
bool waited(int turn = 0) const;
|
bool waited(int turn = 0) const;
|
||||||
|
|
||||||
|
bool canCast() const;
|
||||||
|
bool isCaster() const;
|
||||||
|
|
||||||
bool canMove(int turn = 0) const; //if stack can move
|
bool canMove(int turn = 0) const; //if stack can move
|
||||||
|
|
||||||
|
bool canShoot() const;
|
||||||
|
bool isShooter() const;
|
||||||
|
|
||||||
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
|
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
|
||||||
///returns actual heal value based on internal state
|
|
||||||
ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const;
|
|
||||||
ui32 level() const;
|
ui32 level() const;
|
||||||
si32 magicResistance() const override; //include aura of resistance
|
si32 magicResistance() const override; //include aura of resistance
|
||||||
std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
|
std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
|
||||||
const CGHeroInstance *getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
|
const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
|
||||||
ui32 totalHealth() const; // total health for all creatures in stack;
|
|
||||||
|
|
||||||
static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
|
static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
|
||||||
|
|
||||||
@ -79,11 +201,17 @@ public:
|
|||||||
|
|
||||||
BattleHex::EDir destShiftDir() const;
|
BattleHex::EDir destShiftDir() const;
|
||||||
|
|
||||||
std::pair<int,int> countKilledByAttack(int damageReceived) const; //returns pair<killed count, new left HP>
|
CHealth healthAfterAttacked(int32_t & damage) const;
|
||||||
void prepareAttacked(BattleStackAttacked &bsa, CRandomGenerator & rand, boost::optional<int> customCount = boost::none) const; //requires bsa.damageAmout filled
|
CHealth healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const;
|
||||||
|
|
||||||
|
CHealth healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const;
|
||||||
|
|
||||||
|
void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const; //requires bsa.damageAmout filled
|
||||||
|
void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const; //requires bsa.damageAmout filled
|
||||||
|
|
||||||
///ISpellCaster
|
///ISpellCaster
|
||||||
ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
|
|
||||||
|
ui8 getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool = nullptr) const override;
|
||||||
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
|
||||||
|
|
||||||
///default spell school level for effect calculation
|
///default spell school level for effect calculation
|
||||||
@ -99,23 +227,36 @@ public:
|
|||||||
int getEffectValue(const CSpell * spell) const override;
|
int getEffectValue(const CSpell * spell) const override;
|
||||||
|
|
||||||
const PlayerColor getOwner() const override;
|
const PlayerColor getOwner() const override;
|
||||||
|
|
||||||
void getCasterName(MetaString & text) const override;
|
void getCasterName(MetaString & text) const override;
|
||||||
|
|
||||||
void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
|
void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
|
||||||
|
|
||||||
|
///IUnitHealthInfo
|
||||||
|
|
||||||
|
int32_t unitMaxHealth() const override;
|
||||||
|
int32_t unitBaseAmount() const override;
|
||||||
|
|
||||||
|
///MetaStrings
|
||||||
|
|
||||||
|
void addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
|
||||||
|
void addNameReplacement(MetaString & text, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
|
||||||
|
std::string formatGeneralMessage(const int32_t baseTextId) const;
|
||||||
|
|
||||||
|
///Non const API for NetPacks
|
||||||
|
|
||||||
///stack will be ghost in next battle state update
|
///stack will be ghost in next battle state update
|
||||||
void makeGhost();
|
void makeGhost();
|
||||||
|
void setHealth(const CHealthInfo & value);
|
||||||
|
void setHealth(const CHealth & value);
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
template <typename Handler> void serialize(Handler & h, const int version)
|
||||||
{
|
{
|
||||||
assert(isIndependentNode());
|
assert(isIndependentNode());
|
||||||
h & static_cast<CBonusSystemNode&>(*this);
|
h & static_cast<CBonusSystemNode &>(*this);
|
||||||
h & static_cast<CStackBasicDescriptor&>(*this);
|
h & type;
|
||||||
h & ID & baseAmount & firstHPleft & owner & slot & side & position & state & counterAttacksPerformed
|
h & ID & baseAmount & owner & slot & side & position & state;
|
||||||
& shots & casts & count & resurrected;
|
h & shots & casts & counterAttacks & health;
|
||||||
|
|
||||||
const CArmedInstance *army = (base ? base->armyObj : nullptr);
|
const CArmedInstance * army = (base ? base->armyObj : nullptr);
|
||||||
SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID());
|
SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID());
|
||||||
|
|
||||||
if(h.saving)
|
if(h.saving)
|
||||||
@ -128,7 +269,7 @@ public:
|
|||||||
if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
||||||
{
|
{
|
||||||
auto hero = dynamic_cast<const CGHeroInstance *>(army);
|
auto hero = dynamic_cast<const CGHeroInstance *>(army);
|
||||||
assert (hero);
|
assert(hero);
|
||||||
base = hero->commander;
|
base = hero->commander;
|
||||||
}
|
}
|
||||||
else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT)
|
else if(slot == SlotID::SUMMONED_SLOT_PLACEHOLDER || slot == SlotID::ARROW_TOWERS_SLOT || slot == SlotID::WAR_MACHINES_SLOT)
|
||||||
@ -150,8 +291,13 @@ public:
|
|||||||
}
|
}
|
||||||
bool alive() const;
|
bool alive() const;
|
||||||
|
|
||||||
|
bool isClone() const;
|
||||||
bool isDead() const;
|
bool isDead() const;
|
||||||
bool isGhost() const; //determines if stack was removed
|
bool isGhost() const; //determines if stack was removed
|
||||||
bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
|
bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
|
||||||
bool isTurret() const;
|
bool isTurret() const;
|
||||||
|
|
||||||
|
friend class CShots; //for BattleInfo access
|
||||||
|
private:
|
||||||
|
const BattleInfo * battle; //do not serialize
|
||||||
};
|
};
|
||||||
|
@ -1089,6 +1089,19 @@ enum class EMetaclass: ui8
|
|||||||
RESOURCE
|
RESOURCE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class EHealLevel: ui8
|
||||||
|
{
|
||||||
|
HEAL,
|
||||||
|
RESURRECT,
|
||||||
|
OVERHEAL
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EHealPower : ui8
|
||||||
|
{
|
||||||
|
ONE_BATTLE,
|
||||||
|
PERMANENT
|
||||||
|
};
|
||||||
|
|
||||||
// Typedef declarations
|
// Typedef declarations
|
||||||
typedef ui8 TFaction;
|
typedef ui8 TFaction;
|
||||||
typedef si64 TExpType;
|
typedef si64 TExpType;
|
||||||
|
@ -79,6 +79,29 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
|
|||||||
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
|
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
|
||||||
}; //untested
|
}; //untested
|
||||||
|
|
||||||
|
///CBonusProxy
|
||||||
|
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
|
||||||
|
cachedLast(0), target(Target), selector(Selector), data()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TBonusListPtr CBonusProxy::get() const
|
||||||
|
{
|
||||||
|
if(CBonusSystemNode::treeChanged != cachedLast || !data)
|
||||||
|
{
|
||||||
|
//TODO: support limiters
|
||||||
|
data = target->getAllBonuses(selector, nullptr);
|
||||||
|
data->eliminateDuplicates();
|
||||||
|
cachedLast = CBonusSystemNode::treeChanged;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BonusList * CBonusProxy::operator->() const
|
||||||
|
{
|
||||||
|
return get().get();
|
||||||
|
}
|
||||||
|
|
||||||
#define BONUS_LOG_LINE(x) logBonus->traceStream() << x
|
#define BONUS_LOG_LINE(x) logBonus->traceStream() << x
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
class CCreature;
|
class CCreature;
|
||||||
struct Bonus;
|
struct Bonus;
|
||||||
|
class IBonusBearer;
|
||||||
class CBonusSystemNode;
|
class CBonusSystemNode;
|
||||||
class ILimiter;
|
class ILimiter;
|
||||||
class IPropagator;
|
class IPropagator;
|
||||||
@ -63,7 +64,20 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE CBonusProxy : public boost::noncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CBonusProxy(const IBonusBearer * Target, CSelector Selector);
|
||||||
|
|
||||||
|
TBonusListPtr get() const;
|
||||||
|
|
||||||
|
const BonusList * operator->() const;
|
||||||
|
private:
|
||||||
|
mutable int cachedLast;
|
||||||
|
const IBonusBearer * target;
|
||||||
|
CSelector selector;
|
||||||
|
mutable TBonusListPtr data;
|
||||||
|
};
|
||||||
|
|
||||||
#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
|
#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
|
||||||
|
|
||||||
@ -684,6 +698,8 @@ public:
|
|||||||
BONUS_TREE_DESERIALIZATION_FIX
|
BONUS_TREE_DESERIALIZATION_FIX
|
||||||
//h & parents & children;
|
//h & parents & children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend class CBonusProxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace NBonus
|
namespace NBonus
|
||||||
|
@ -1315,34 +1315,22 @@ struct BattleStackMoved : public CPackForClient
|
|||||||
struct StacksHealedOrResurrected : public CPackForClient
|
struct StacksHealedOrResurrected : public CPackForClient
|
||||||
{
|
{
|
||||||
StacksHealedOrResurrected()
|
StacksHealedOrResurrected()
|
||||||
:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false), canOverheal(false)
|
:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||||
void applyCl(CClient *cl);
|
void applyCl(CClient *cl);
|
||||||
|
|
||||||
struct HealInfo
|
std::vector<CHealthInfo> healedStacks;
|
||||||
{
|
|
||||||
ui32 stackID;
|
|
||||||
ui32 healedHP;
|
|
||||||
bool lowLevelResurrection; //in case this stack is resurrected by this heal, it will be marked as SUMMONED //TODO: replace with separate counter
|
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
|
||||||
{
|
|
||||||
h & stackID & healedHP & lowLevelResurrection;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<HealInfo> healedStacks;
|
|
||||||
bool lifeDrain; //if true, this heal is an effect of life drain or soul steal
|
bool lifeDrain; //if true, this heal is an effect of life drain or soul steal
|
||||||
bool tentHealing; //if true, than it's healing via First Aid Tent
|
bool tentHealing; //if true, than it's healing via First Aid Tent
|
||||||
si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer
|
si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer
|
||||||
bool cure; //archangel cast also remove negative effects
|
bool cure; //archangel cast also remove negative effects
|
||||||
bool canOverheal; //to allow healing over initial stack amount
|
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
template <typename Handler> void serialize(Handler &h, const int version)
|
||||||
{
|
{
|
||||||
h & healedStacks & lifeDrain & tentHealing & drainedFrom;
|
h & healedStacks & lifeDrain & tentHealing & drainedFrom;
|
||||||
h & cure & canOverheal;
|
h & cure;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1350,7 +1338,8 @@ struct BattleStackAttacked : public CPackForClient
|
|||||||
{
|
{
|
||||||
BattleStackAttacked():
|
BattleStackAttacked():
|
||||||
stackAttacked(0), attackerID(0),
|
stackAttacked(0), attackerID(0),
|
||||||
newAmount(0), newHP(0), killedAmount(0), damageAmount(0),
|
killedAmount(0), damageAmount(0),
|
||||||
|
newHealth(),
|
||||||
flags(0), effect(0), spellID(SpellID::NONE)
|
flags(0), effect(0), spellID(SpellID::NONE)
|
||||||
{};
|
{};
|
||||||
void applyFirstCl(CClient * cl);
|
void applyFirstCl(CClient * cl);
|
||||||
@ -1358,7 +1347,9 @@ struct BattleStackAttacked : public CPackForClient
|
|||||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||||
|
|
||||||
ui32 stackAttacked, attackerID;
|
ui32 stackAttacked, attackerID;
|
||||||
ui32 newAmount, newHP, killedAmount, damageAmount;
|
ui32 killedAmount;
|
||||||
|
si32 damageAmount;
|
||||||
|
CHealthInfo newHealth;
|
||||||
enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
|
enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
|
||||||
ui32 flags; //uses EFlags (above)
|
ui32 flags; //uses EFlags (above)
|
||||||
ui32 effect; //set only if flag EFFECT is set
|
ui32 effect; //set only if flag EFFECT is set
|
||||||
@ -1396,7 +1387,7 @@ struct BattleStackAttacked : public CPackForClient
|
|||||||
}
|
}
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
template <typename Handler> void serialize(Handler &h, const int version)
|
||||||
{
|
{
|
||||||
h & stackAttacked & attackerID & newAmount & newHP & flags & killedAmount & damageAmount & effect
|
h & stackAttacked & attackerID & newHealth & flags & killedAmount & damageAmount & effect
|
||||||
& healedStacks;
|
& healedStacks;
|
||||||
h & spellID;
|
h & spellID;
|
||||||
}
|
}
|
||||||
@ -1540,10 +1531,12 @@ struct SetStackEffect : public CPackForClient
|
|||||||
std::vector<Bonus> cumulativeEffects; //bonuses to apply
|
std::vector<Bonus> cumulativeEffects; //bonuses to apply
|
||||||
std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
|
std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
|
||||||
|
|
||||||
|
std::vector<MetaString> battleLog;
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
template <typename Handler> void serialize(Handler &h, const int version)
|
||||||
{
|
{
|
||||||
h & stacks & effect & uniqueBonuses;
|
h & stacks & effect & uniqueBonuses;
|
||||||
h & cumulativeEffects & cumulativeUniqueBonuses;
|
h & cumulativeEffects & cumulativeUniqueBonuses;
|
||||||
|
h & battleLog;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,3 +188,22 @@ struct ArtifactLocation
|
|||||||
h & artHolder & slot;
|
h & artHolder & slot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CHealthInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CHealthInfo():
|
||||||
|
stackId(0), delta(0), firstHPleft(0), fullUnits(0), resurrected(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
uint32_t stackId;
|
||||||
|
int32_t delta;
|
||||||
|
int32_t firstHPleft;
|
||||||
|
int32_t fullUnits;
|
||||||
|
int32_t resurrected;
|
||||||
|
|
||||||
|
template <typename Handler> void serialize(Handler & h, const int version)
|
||||||
|
{
|
||||||
|
h & stackId & delta & firstHPleft & fullUnits & resurrected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1247,12 +1247,11 @@ DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs)
|
|||||||
s->state -= EBattleStackState::HAD_MORALE;
|
s->state -= EBattleStackState::HAD_MORALE;
|
||||||
s->state -= EBattleStackState::FEAR;
|
s->state -= EBattleStackState::FEAR;
|
||||||
s->state -= EBattleStackState::DRAINED_MANA;
|
s->state -= EBattleStackState::DRAINED_MANA;
|
||||||
s->counterAttacksPerformed = 0;
|
s->counterAttacks.reset();
|
||||||
s->counterAttacksTotalCache = 0;
|
|
||||||
// new turn effects
|
// new turn effects
|
||||||
s->updateBonuses(Bonus::NTurns);
|
s->updateBonuses(Bonus::NTurns);
|
||||||
|
|
||||||
if(s->alive() && vstd::contains(s->state, EBattleStackState::CLONED))
|
if(s->alive() && s->isClone())
|
||||||
{
|
{
|
||||||
//cloned stack has special lifetime marker
|
//cloned stack has special lifetime marker
|
||||||
//check it after bonuses updated in battleTurnPassed()
|
//check it after bonuses updated in battleTurnPassed()
|
||||||
@ -1280,36 +1279,40 @@ DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
||||||
{
|
{
|
||||||
CStack *st = gs->curB->getStack(stackID);
|
CStack * st = gs->curB->getStack(stackID);
|
||||||
switch (effect)
|
assert(st);
|
||||||
|
switch(effect)
|
||||||
{
|
{
|
||||||
case Bonus::HP_REGENERATION:
|
case Bonus::HP_REGENERATION:
|
||||||
st->firstHPleft += val;
|
{
|
||||||
vstd::amin (st->firstHPleft, (ui32)st->MaxHealth());
|
int32_t toHeal = val;
|
||||||
break;
|
CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
|
||||||
case Bonus::MANA_DRAIN:
|
st->setHealth(health);
|
||||||
{
|
break;
|
||||||
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
|
}
|
||||||
st->state.insert (EBattleStackState::DRAINED_MANA);
|
case Bonus::MANA_DRAIN:
|
||||||
h->mana -= val;
|
{
|
||||||
vstd::amax(h->mana, 0);
|
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
|
||||||
break;
|
st->state.insert (EBattleStackState::DRAINED_MANA);
|
||||||
}
|
h->mana -= val;
|
||||||
case Bonus::POISON:
|
vstd::amax(h->mana, 0);
|
||||||
{
|
break;
|
||||||
auto b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
|
}
|
||||||
.And(Selector::type(Bonus::STACK_HEALTH)));
|
case Bonus::POISON:
|
||||||
if (b)
|
{
|
||||||
b->val = val;
|
auto b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
|
||||||
break;
|
.And(Selector::type(Bonus::STACK_HEALTH)));
|
||||||
}
|
if (b)
|
||||||
case Bonus::ENCHANTER:
|
b->val = val;
|
||||||
break;
|
break;
|
||||||
case Bonus::FEAR:
|
}
|
||||||
st->state.insert(EBattleStackState::FEAR);
|
case Bonus::ENCHANTER:
|
||||||
break;
|
break;
|
||||||
default:
|
case Bonus::FEAR:
|
||||||
logNetwork->warnStream() << "Unrecognized trigger effect type "<< effect;
|
st->state.insert(EBattleStackState::FEAR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logNetwork->warnStream() << "Unrecognized trigger effect type "<< effect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,11 +1395,14 @@ void BattleStackMoved::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
||||||
{
|
{
|
||||||
CStack *at = gs->curB->getStack(stackAttacked);
|
CStack * at = gs->curB->getStack(stackAttacked);
|
||||||
assert(at);
|
assert(at);
|
||||||
at->popBonuses(Bonus::UntilBeingAttacked);
|
at->popBonuses(Bonus::UntilBeingAttacked);
|
||||||
at->count = newAmount;
|
|
||||||
at->firstHPleft = newHP;
|
if(willRebirth())
|
||||||
|
at->health.reset();//kill stack first
|
||||||
|
else
|
||||||
|
at->setHealth(newHealth);
|
||||||
|
|
||||||
if(killed())
|
if(killed())
|
||||||
{
|
{
|
||||||
@ -1413,16 +1419,29 @@ DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//life drain handling
|
//life drain handling
|
||||||
for (auto & elem : healedStacks)
|
for(auto & elem : healedStacks)
|
||||||
{
|
|
||||||
elem.applyGs(gs);
|
elem.applyGs(gs);
|
||||||
}
|
|
||||||
if (willRebirth())
|
if(willRebirth())
|
||||||
{
|
{
|
||||||
at->casts--;
|
//TODO: handle rebirth with StacksHealedOrResurrected
|
||||||
at->state.insert(EBattleStackState::ALIVE); //hmm?
|
at->casts.use();
|
||||||
|
at->state.insert(EBattleStackState::ALIVE);
|
||||||
|
at->setHealth(newHealth);
|
||||||
|
|
||||||
|
//removing all spells effects
|
||||||
|
auto selector = [](const Bonus * b)
|
||||||
|
{
|
||||||
|
//Special case: DISRUPTING_RAY is "immune" to dispell
|
||||||
|
//Other even PERMANENT effects can be removed
|
||||||
|
if(b->source == Bonus::SPELL_EFFECT)
|
||||||
|
return b->sid != SpellID::DISRUPTING_RAY;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
at->popBonuses(selector);
|
||||||
}
|
}
|
||||||
if (cloneKilled())
|
if(cloneKilled())
|
||||||
{
|
{
|
||||||
//"hide" killed creatures instead so we keep info about it
|
//"hide" killed creatures instead so we keep info about it
|
||||||
at->makeGhost();
|
at->makeGhost();
|
||||||
@ -1436,35 +1455,20 @@ DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
//killed summoned creature should be removed like clone
|
//killed summoned creature should be removed like clone
|
||||||
if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
|
if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
|
||||||
{
|
|
||||||
at->makeGhost();
|
at->makeGhost();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_LINKAGE void BattleAttack::applyGs(CGameState *gs)
|
DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
|
||||||
{
|
{
|
||||||
CStack *attacker = gs->curB->getStack(stackAttacking);
|
CStack * attacker = gs->curB->getStack(stackAttacking);
|
||||||
|
assert(attacker);
|
||||||
|
|
||||||
if(counter())
|
if(counter())
|
||||||
attacker->counterAttacksPerformed++;
|
attacker->counterAttacks.use();
|
||||||
|
|
||||||
if(shot())
|
if(shot())
|
||||||
{
|
attacker->shots.use();
|
||||||
//don't remove ammo if we have a working ammo cart
|
|
||||||
bool hasAmmoCart = false;
|
|
||||||
for(const CStack * st : gs->curB->stacks)
|
|
||||||
{
|
|
||||||
if(st->owner == attacker->owner && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
|
|
||||||
{
|
|
||||||
hasAmmoCart = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasAmmoCart)
|
|
||||||
{
|
|
||||||
attacker->shots--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(BattleStackAttacked & stackAttacked : bsa)
|
for(BattleStackAttacked & stackAttacked : bsa)
|
||||||
stackAttacked.applyGs(gs);
|
stackAttacked.applyGs(gs);
|
||||||
|
|
||||||
@ -1619,7 +1623,8 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
|||||||
{
|
{
|
||||||
for(auto & elem : healedStacks)
|
for(auto & elem : healedStacks)
|
||||||
{
|
{
|
||||||
CStack * changedStack = gs->curB->getStack(elem.stackID, false);
|
CStack * changedStack = gs->curB->getStack(elem.stackId, false);
|
||||||
|
assert(changedStack);
|
||||||
|
|
||||||
//checking if we resurrect a stack that is under a living stack
|
//checking if we resurrect a stack that is under a living stack
|
||||||
auto accessibility = gs->curB->getAccesibility();
|
auto accessibility = gs->curB->getAccesibility();
|
||||||
@ -1634,29 +1639,14 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
|||||||
bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed
|
bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed
|
||||||
if(resurrected)
|
if(resurrected)
|
||||||
{
|
{
|
||||||
if(changedStack->count > 0 || changedStack->firstHPleft > 0)
|
if(auto totalHealth = changedStack->health.available())
|
||||||
logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), changedStack->totalHealth());
|
logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), totalHealth);
|
||||||
|
|
||||||
changedStack->state.insert(EBattleStackState::ALIVE);
|
changedStack->state.insert(EBattleStackState::ALIVE);
|
||||||
}
|
}
|
||||||
int res;
|
|
||||||
if(canOverheal) //for example WoG ghost soul steal ability allows getting more units than before battle
|
changedStack->setHealth(elem);
|
||||||
res = elem.healedHP / changedStack->MaxHealth();
|
|
||||||
else
|
|
||||||
res = std::min(elem.healedHP / changedStack->MaxHealth() , changedStack->baseAmount - changedStack->count);
|
|
||||||
changedStack->count += res;
|
|
||||||
if(elem.lowLevelResurrection)
|
|
||||||
changedStack->resurrected += res;
|
|
||||||
changedStack->firstHPleft += elem.healedHP - res * changedStack->MaxHealth();
|
|
||||||
if(changedStack->firstHPleft > changedStack->MaxHealth())
|
|
||||||
{
|
|
||||||
changedStack->firstHPleft -= changedStack->MaxHealth();
|
|
||||||
if(changedStack->baseAmount > changedStack->count)
|
|
||||||
{
|
|
||||||
changedStack->count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vstd::amin(changedStack->firstHPleft, changedStack->MaxHealth());
|
|
||||||
if(resurrected)
|
if(resurrected)
|
||||||
{
|
{
|
||||||
//removing all spells effects
|
//removing all spells effects
|
||||||
@ -1674,7 +1664,7 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
|||||||
else if(cure)
|
else if(cure)
|
||||||
{
|
{
|
||||||
//removing all effects from negative spells
|
//removing all effects from negative spells
|
||||||
auto selector = [](const Bonus* b)
|
auto selector = [](const Bonus * b)
|
||||||
{
|
{
|
||||||
//Special case: DISRUPTING_RAY is "immune" to dispell
|
//Special case: DISRUPTING_RAY is "immune" to dispell
|
||||||
//Other even PERMANENT effects can be removed
|
//Other even PERMANENT effects can be removed
|
||||||
@ -1795,7 +1785,7 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs(CGameState *gs)
|
|||||||
DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
||||||
{
|
{
|
||||||
newStackID = 0;
|
newStackID = 0;
|
||||||
if (!BattleHex(pos).isValid())
|
if(!BattleHex(pos).isValid())
|
||||||
{
|
{
|
||||||
logNetwork->warnStream() << "No place found for new stack!";
|
logNetwork->warnStream() << "No place found for new stack!";
|
||||||
return;
|
return;
|
||||||
@ -1803,33 +1793,32 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
CStackBasicDescriptor csbd(creID, amount);
|
CStackBasicDescriptor csbd(creID, amount);
|
||||||
CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
|
CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
|
||||||
if (summoned)
|
if(summoned)
|
||||||
addedStack->state.insert(EBattleStackState::SUMMONED);
|
addedStack->state.insert(EBattleStackState::SUMMONED);
|
||||||
|
|
||||||
gs->curB->localInitStack(addedStack);
|
addedStack->localInit(gs->curB.get());
|
||||||
gs->curB->stacks.push_back(addedStack); //the stack is not "SUMMONED", it is permanent
|
gs->curB->stacks.push_back(addedStack);
|
||||||
|
|
||||||
newStackID = addedStack->ID;
|
newStackID = addedStack->ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
|
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
|
||||||
{
|
{
|
||||||
CStack * stack = gs->curB->getStack(stackID);
|
CStack * stack = gs->curB->getStack(stackID);
|
||||||
switch (which)
|
switch(which)
|
||||||
{
|
{
|
||||||
case CASTS:
|
case CASTS:
|
||||||
{
|
{
|
||||||
if (absolute)
|
if(absolute)
|
||||||
stack->casts = val;
|
logNetwork->error("Can not change casts in absolute mode");
|
||||||
else
|
else
|
||||||
stack->casts += val;
|
stack->casts.use(-val);
|
||||||
vstd::amax(stack->casts, 0);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ENCHANTER_COUNTER:
|
case ENCHANTER_COUNTER:
|
||||||
{
|
{
|
||||||
auto & counter = gs->curB->sides[gs->curB->whatSide(stack->owner)].enchanterCounter;
|
auto & counter = gs->curB->sides[gs->curB->whatSide(stack->owner)].enchanterCounter;
|
||||||
if (absolute)
|
if(absolute)
|
||||||
counter = val;
|
counter = val;
|
||||||
else
|
else
|
||||||
counter += val;
|
counter += val;
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "BattleAttackInfo.h"
|
#include "BattleAttackInfo.h"
|
||||||
#include "../CStack.h"
|
|
||||||
|
|
||||||
|
|
||||||
BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting)
|
BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting):
|
||||||
|
attackerHealth(Attacker->health), defenderHealth(Defender->health)
|
||||||
{
|
{
|
||||||
attacker = Attacker;
|
attacker = Attacker;
|
||||||
defender = Defender;
|
defender = Defender;
|
||||||
@ -23,9 +23,6 @@ BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defen
|
|||||||
attackerPosition = Attacker->position;
|
attackerPosition = Attacker->position;
|
||||||
defenderPosition = Defender->position;
|
defenderPosition = Defender->position;
|
||||||
|
|
||||||
attackerCount = Attacker->count;
|
|
||||||
defenderCount = Defender->count;
|
|
||||||
|
|
||||||
shooting = Shooting;
|
shooting = Shooting;
|
||||||
chargedFields = 0;
|
chargedFields = 0;
|
||||||
|
|
||||||
@ -41,7 +38,7 @@ BattleAttackInfo BattleAttackInfo::reverse() const
|
|||||||
std::swap(ret.attacker, ret.defender);
|
std::swap(ret.attacker, ret.defender);
|
||||||
std::swap(ret.attackerBonuses, ret.defenderBonuses);
|
std::swap(ret.attackerBonuses, ret.defenderBonuses);
|
||||||
std::swap(ret.attackerPosition, ret.defenderPosition);
|
std::swap(ret.attackerPosition, ret.defenderPosition);
|
||||||
std::swap(ret.attackerCount, ret.defenderCount);
|
std::swap(ret.attackerHealth, ret.defenderHealth);
|
||||||
|
|
||||||
ret.shooting = false;
|
ret.shooting = false;
|
||||||
ret.chargedFields = 0;
|
ret.chargedFields = 0;
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "BattleHex.h"
|
#include "BattleHex.h"
|
||||||
|
#include "../CStack.h"
|
||||||
|
|
||||||
class CStack;
|
|
||||||
class IBonusBearer;
|
class IBonusBearer;
|
||||||
|
|
||||||
struct DLL_LINKAGE BattleAttackInfo
|
struct DLL_LINKAGE BattleAttackInfo
|
||||||
@ -19,7 +19,8 @@ struct DLL_LINKAGE BattleAttackInfo
|
|||||||
const CStack *attacker, *defender;
|
const CStack *attacker, *defender;
|
||||||
BattleHex attackerPosition, defenderPosition;
|
BattleHex attackerPosition, defenderPosition;
|
||||||
|
|
||||||
int attackerCount, defenderCount;
|
CHealth attackerHealth, defenderHealth;
|
||||||
|
|
||||||
bool shooting;
|
bool shooting;
|
||||||
int chargedFields;
|
int chargedFields;
|
||||||
|
|
||||||
|
@ -78,15 +78,22 @@ std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, Ba
|
|||||||
return std::make_pair(path, reachability.distances[dest]);
|
return std::make_pair(path, reachability.distances[dest]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui32 BattleInfo::calculateDmg(const CStack* attacker, const CStack* defender,
|
ui32 BattleInfo::calculateDmg(const CStack * attacker, const CStack * defender,
|
||||||
bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand)
|
bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand)
|
||||||
{
|
{
|
||||||
TDmgRange range = calculateDmgRange(attacker, defender, shooting, charge, lucky, unlucky, deathBlow, ballistaDoubleDmg);
|
BattleAttackInfo bai(attacker, defender, shooting);
|
||||||
|
bai.chargedFields = charge;
|
||||||
|
bai.luckyHit = lucky;
|
||||||
|
bai.unluckyHit = unlucky;
|
||||||
|
bai.deathBlow = deathBlow;
|
||||||
|
bai.ballistaDoubleDamage = ballistaDoubleDmg;
|
||||||
|
|
||||||
|
TDmgRange range = calculateDmgRange(bai);
|
||||||
|
|
||||||
if(range.first != range.second)
|
if(range.first != range.second)
|
||||||
{
|
{
|
||||||
ui32 sum = 0;
|
ui32 sum = 0;
|
||||||
ui32 howManyToAv = std::min<ui32>(10, attacker->count);
|
ui32 howManyToAv = std::min<ui32>(10, attacker->getCount());
|
||||||
for(int g=0; g<howManyToAv; ++g)
|
for(int g=0; g<howManyToAv; ++g)
|
||||||
sum += (ui32)rand.nextInt(range.first, range.second);
|
sum += (ui32)rand.nextInt(range.first, range.second);
|
||||||
|
|
||||||
@ -101,9 +108,8 @@ void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
|
|||||||
for(auto & elem : stacks)//setting casualties
|
for(auto & elem : stacks)//setting casualties
|
||||||
{
|
{
|
||||||
const CStack * const st = elem;
|
const CStack * const st = elem;
|
||||||
si32 killed = (st->alive() ? (st->baseAmount - st->count + st->resurrected) : st->baseAmount);
|
si32 killed = st->getKilled();
|
||||||
vstd::amax(killed, 0);
|
if(killed > 0)
|
||||||
if(killed)
|
|
||||||
casualties[st->side][st->getCreature()->idNumber] += killed;
|
casualties[st->side][st->getCreature()->idNumber] += killed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,29 +146,12 @@ void BattleInfo::localInit()
|
|||||||
armyObj->attachTo(this);
|
armyObj->attachTo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(CStack *s : stacks)
|
for(CStack * s : stacks)
|
||||||
localInitStack(s);
|
s->localInit(this);
|
||||||
|
|
||||||
exportBonuses();
|
exportBonuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleInfo::localInitStack(CStack * s)
|
|
||||||
{
|
|
||||||
s->exportBonuses();
|
|
||||||
if(s->base) //stack originating from "real" stack in garrison -> attach to it
|
|
||||||
{
|
|
||||||
s->attachTo(const_cast<CStackInstance*>(s->base));
|
|
||||||
}
|
|
||||||
else //attach directly to obj to which stack belongs and creature type
|
|
||||||
{
|
|
||||||
CArmedInstance *army = battleGetArmyObject(s->side);
|
|
||||||
s->attachTo(army);
|
|
||||||
assert(s->type);
|
|
||||||
s->attachTo(const_cast<CCreature*>(s->type));
|
|
||||||
}
|
|
||||||
s->postInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace CGH
|
namespace CGH
|
||||||
{
|
{
|
||||||
static void readBattlePositions(const JsonNode &node, std::vector< std::vector<int> > & dest)
|
static void readBattlePositions(const JsonNode &node, std::vector< std::vector<int> > & dest)
|
||||||
|
@ -72,7 +72,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
|
|||||||
|
|
||||||
void localInit();
|
void localInit();
|
||||||
|
|
||||||
void localInitStack(CStack * s);
|
|
||||||
static BattleInfo * setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
|
static BattleInfo * setupBattle(int3 tile, ETerrainType terrain, BFieldType battlefieldType, const CArmedInstance * armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance * town);
|
||||||
//bool hasNativeStack(ui8 side) const;
|
//bool hasNativeStack(ui8 side) const;
|
||||||
|
|
||||||
|
@ -529,22 +529,14 @@ bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) c
|
|||||||
if(stack->getCreature()->idNumber == CreatureID::CATAPULT && dst) //catapult cannot attack creatures
|
if(stack->getCreature()->idNumber == CreatureID::CATAPULT && dst) //catapult cannot attack creatures
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(stack->hasBonusOfType(Bonus::SHOOTER)//it's shooter
|
if(stack->canShoot()
|
||||||
&& battleMatchOwner(stack, dst)
|
&& battleMatchOwner(stack, dst)
|
||||||
&& dst->alive()
|
&& dst->alive()
|
||||||
&& (!battleIsStackBlocked(stack) || stack->hasBonusOfType(Bonus::FREE_SHOOTING))
|
&& (!battleIsStackBlocked(stack) || stack->hasBonusOfType(Bonus::FREE_SHOOTING)))
|
||||||
&& stack->shots
|
|
||||||
)
|
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting,
|
|
||||||
ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const
|
|
||||||
{
|
|
||||||
return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, unlucky, deathBlow, ballistaDoubleDmg);
|
|
||||||
}
|
|
||||||
|
|
||||||
TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
|
TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) const
|
||||||
{
|
{
|
||||||
auto battleBonusValue = [&](const IBonusBearer * bearer, CSelector selector) -> int
|
auto battleBonusValue = [&](const IBonusBearer * bearer, CSelector selector) -> int
|
||||||
@ -559,8 +551,8 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info)
|
|||||||
};
|
};
|
||||||
|
|
||||||
double additiveBonus = 1.0, multBonus = 1.0,
|
double additiveBonus = 1.0, multBonus = 1.0,
|
||||||
minDmg = info.attackerBonuses->getMinDamage() * info.attackerCount,//TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
|
minDmg = info.attackerBonuses->getMinDamage() * info.attackerHealth.getCount(),//TODO: ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT
|
||||||
maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerCount;
|
maxDmg = info.attackerBonuses->getMaxDamage() * info.attackerHealth.getCount();
|
||||||
|
|
||||||
const CCreature *attackerType = info.attacker->getCreature(),
|
const CCreature *attackerType = info.attacker->getCreature(),
|
||||||
*defenderType = info.defender->getCreature();
|
*defenderType = info.defender->getCreature();
|
||||||
@ -774,19 +766,6 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info)
|
|||||||
return returnedVal;
|
return returnedVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount,
|
|
||||||
bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const
|
|
||||||
{
|
|
||||||
BattleAttackInfo bai(attacker, defender, shooting);
|
|
||||||
bai.attackerCount = attackerCount;
|
|
||||||
bai.chargedFields = charge;
|
|
||||||
bai.luckyHit = lucky;
|
|
||||||
bai.unluckyHit = unlucky;
|
|
||||||
bai.deathBlow = deathBlow;
|
|
||||||
bai.ballistaDoubleDamage = ballistaDoubleDmg;
|
|
||||||
return calculateDmgRange(bai);
|
|
||||||
}
|
|
||||||
|
|
||||||
TDmgRange CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
|
TDmgRange CBattleInfoCallback::battleEstimateDamage(CRandomGenerator & rand, const CStack * attacker, const CStack * defender, TDmgRange * retaliationDmg) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
RETURN_IF_NOT_BATTLE(std::make_pair(0, 0));
|
||||||
@ -816,10 +795,10 @@ std::pair<ui32, ui32> CBattleInfoCallback::battleEstimateDamage(CRandomGenerator
|
|||||||
{
|
{
|
||||||
BattleStackAttacked bsa;
|
BattleStackAttacked bsa;
|
||||||
bsa.damageAmount = ret.*pairElems[i];
|
bsa.damageAmount = ret.*pairElems[i];
|
||||||
bai.defender->prepareAttacked(bsa, rand, bai.defenderCount);
|
bai.defender->prepareAttacked(bsa, rand, bai.defenderHealth);
|
||||||
|
|
||||||
auto retaliationAttack = bai.reverse();
|
auto retaliationAttack = bai.reverse();
|
||||||
retaliationAttack.attackerCount = bsa.newAmount;
|
retaliationAttack.attackerHealth = retaliationAttack.attacker->healthAfterAttacked(bsa.damageAmount);
|
||||||
retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
|
retaliationDmg->*pairElems[!i] = calculateDmgRange(retaliationAttack).*pairElems[!i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1494,7 +1473,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
|
|||||||
{
|
{
|
||||||
auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
||||||
{
|
{
|
||||||
return !stack->shots;
|
return !stack->canShoot();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!walker)
|
if (!walker)
|
||||||
@ -1505,7 +1484,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
|
|||||||
{
|
{
|
||||||
auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
|
||||||
{
|
{
|
||||||
return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
|
return stack->canShoot();
|
||||||
});
|
});
|
||||||
if (!shooter)
|
if (!shooter)
|
||||||
continue;
|
continue;
|
||||||
@ -1527,19 +1506,19 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
|
|||||||
case SpellID::CURE: //only damaged units
|
case SpellID::CURE: //only damaged units
|
||||||
{
|
{
|
||||||
//do not cast on affected by debuffs
|
//do not cast on affected by debuffs
|
||||||
if (subject->firstHPleft >= subject->MaxHealth())
|
if(!subject->canBeHealed())
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SpellID::BLOODLUST:
|
case SpellID::BLOODLUST:
|
||||||
{
|
{
|
||||||
if (subject->shots) //if can shoot - only if enemy uits are adjacent
|
if(subject->canShoot()) //TODO: if can shoot - only if enemy units are adjacent
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SpellID::PRECISION:
|
case SpellID::PRECISION:
|
||||||
{
|
{
|
||||||
if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
|
if(!subject->canShoot())
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1611,7 +1590,7 @@ int CBattleInfoCallback::battleGetSurrenderCost(PlayerColor Player) const
|
|||||||
double discount = 0;
|
double discount = 0;
|
||||||
for(const CStack * s : battleAliveStacks(side.get()))
|
for(const CStack * s : battleAliveStacks(side.get()))
|
||||||
if(s->base) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
|
if(s->base) //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines
|
||||||
ret += s->getCreature()->cost[Res::GOLD] * s->count;
|
ret += s->getCreature()->cost[Res::GOLD] * s->getCount(); //todo: extract CStack method
|
||||||
|
|
||||||
if(const CGHeroInstance * h = battleGetFightingHero(side.get()))
|
if(const CGHeroInstance * h = battleGetFightingHero(side.get()))
|
||||||
discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
|
discount += h->valOfBonuses(Bonus::SURRENDER_DISCOUNT);
|
||||||
|
@ -59,8 +59,6 @@ public:
|
|||||||
std::set<const CStack*> batteAdjacentCreatures (const CStack * stack) const;
|
std::set<const CStack*> batteAdjacentCreatures (const CStack * stack) const;
|
||||||
|
|
||||||
TDmgRange calculateDmgRange(const BattleAttackInfo & info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
TDmgRange calculateDmgRange(const BattleAttackInfo & info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
||||||
TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
|
||||||
TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
|
|
||||||
|
|
||||||
//hextowallpart //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
|
//hextowallpart //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
|
||||||
std::pair<ui32, ui32> battleEstimateDamage(CRandomGenerator & rand, const BattleAttackInfo & bai, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
|
std::pair<ui32, ui32> battleEstimateDamage(CRandomGenerator & rand, const BattleAttackInfo & bai, std::pair<ui32, ui32> * retaliationDmg = nullptr) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>
|
||||||
|
@ -360,8 +360,17 @@ bool CBattleInfoEssentials::battleMatchOwner(const CStack * attacker, const CSta
|
|||||||
return true;
|
return true;
|
||||||
else if(attacker == defender)
|
else if(attacker == defender)
|
||||||
return positivness;
|
return positivness;
|
||||||
|
else
|
||||||
|
return battleMatchOwner(battleGetOwner(attacker), defender, positivness);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleInfoEssentials::battleMatchOwner(const PlayerColor & attacker, const CStack * defender, const boost::logic::tribool positivness /* = false*/) const
|
||||||
|
{
|
||||||
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
if(boost::logic::indeterminate(positivness))
|
||||||
|
return true;
|
||||||
else if(defender->owner != battleGetOwner(defender))
|
else if(defender->owner != battleGetOwner(defender))
|
||||||
return true; //mind controlled unit is attackable for both sides
|
return true; //mind controlled unit is attackable for both sides
|
||||||
else
|
else
|
||||||
return (battleGetOwner(attacker) == battleGetOwner(defender)) == positivness;
|
return (attacker == battleGetOwner(defender)) == positivness;
|
||||||
}
|
}
|
||||||
|
@ -101,4 +101,5 @@ public:
|
|||||||
///check that stacks are controlled by same|other player(s) depending on positiveness
|
///check that stacks are controlled by same|other player(s) depending on positiveness
|
||||||
///mind control included
|
///mind control included
|
||||||
bool battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness = false) const;
|
bool battleMatchOwner(const CStack * attacker, const CStack * defender, const boost::logic::tribool positivness = false) const;
|
||||||
|
bool battleMatchOwner(const PlayerColor & attacker, const CStack * defender, const boost::logic::tribool positivness = false) const;
|
||||||
};
|
};
|
||||||
|
@ -366,7 +366,6 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor *dst /*=
|
|||||||
if(dst != this)
|
if(dst != this)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int slot = -1;
|
|
||||||
ArtifactID aid = creature->warMachine;
|
ArtifactID aid = creature->warMachine;
|
||||||
const CArtifact * art = aid.toArtifact();
|
const CArtifact * art = aid.toArtifact();
|
||||||
|
|
||||||
@ -1080,7 +1079,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
|
|||||||
const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES};
|
const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES};
|
||||||
const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY);
|
const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY);
|
||||||
const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]];
|
const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]];
|
||||||
const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH);
|
const ui32 raisedUnitHP = raisedUnitType->MaxHealth();
|
||||||
|
|
||||||
//calculate creatures raised from each defeated stack
|
//calculate creatures raised from each defeated stack
|
||||||
for (auto & casualtie : casualties)
|
for (auto & casualtie : casualties)
|
||||||
@ -1088,7 +1087,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
|
|||||||
// Get lost enemy hit points convertible to units.
|
// Get lost enemy hit points convertible to units.
|
||||||
CCreature * c = VLC->creh->creatures[casualtie.first];
|
CCreature * c = VLC->creh->creatures[casualtie.first];
|
||||||
|
|
||||||
const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill;
|
const ui32 raisedHP = c->MaxHealth() * casualtie.second * necromancySkill;
|
||||||
raisedUnits += std::min<ui32>(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count
|
raisedUnits += std::min<ui32>(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1941,7 +1941,7 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
|
|||||||
if(drown)
|
if(drown)
|
||||||
{
|
{
|
||||||
cb->changeStackCount(StackLocation(h, i->first), -drown);
|
cb->changeStackCount(StackLocation(h, i->first), -drown);
|
||||||
xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH);
|
xp += drown * i->second->type->MaxHealth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
#include "../ConstTransitivePtr.h"
|
#include "../ConstTransitivePtr.h"
|
||||||
#include "../GameConstants.h"
|
#include "../GameConstants.h"
|
||||||
|
|
||||||
const ui32 SERIALIZATION_VERSION = 774;
|
const ui32 SERIALIZATION_VERSION = 775;
|
||||||
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
||||||
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
||||||
|
|
||||||
|
@ -26,21 +26,25 @@ HealingSpellMechanics::HealingSpellMechanics(const CSpell * s):
|
|||||||
void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
|
||||||
{
|
{
|
||||||
EHealLevel healLevel = getHealLevel(parameters.effectLevel);
|
EHealLevel healLevel = getHealLevel(parameters.effectLevel);
|
||||||
|
EHealPower healPower = getHealPower(parameters.effectLevel);
|
||||||
|
|
||||||
int hpGained = calculateHealedHP(env, parameters, ctx);
|
int hpGained = calculateHealedHP(env, parameters, ctx);
|
||||||
StacksHealedOrResurrected shr;
|
StacksHealedOrResurrected shr;
|
||||||
shr.lifeDrain = false;
|
shr.lifeDrain = false;
|
||||||
shr.tentHealing = false;
|
shr.tentHealing = false;
|
||||||
|
|
||||||
//special case for Archangel
|
//special case for Archangel
|
||||||
shr.cure = parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING && owner->id == SpellID::RESURRECTION;
|
shr.cure = parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING && owner->id == SpellID::RESURRECTION;
|
||||||
|
|
||||||
const bool resurrect = (healLevel != EHealLevel::HEAL);
|
|
||||||
for(auto & attackedCre : ctx.attackedCres)
|
for(auto & attackedCre : ctx.attackedCres)
|
||||||
{
|
{
|
||||||
StacksHealedOrResurrected::HealInfo hi;
|
int32_t stackHPgained = parameters.caster->getSpellBonus(owner, hpGained, attackedCre);
|
||||||
hi.stackID = (attackedCre)->ID;
|
CHealth health = attackedCre->healthAfterHealed(stackHPgained, healLevel, healPower);
|
||||||
int stackHPgained = parameters.caster->getSpellBonus(owner, hpGained, attackedCre);
|
|
||||||
hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect);
|
CHealthInfo hi;
|
||||||
hi.lowLevelResurrection = (healLevel == EHealLevel::RESURRECT);
|
health.toInfo(hi);
|
||||||
|
hi.stackId = attackedCre->ID;
|
||||||
|
hi.delta = stackHPgained;
|
||||||
shr.healedStacks.push_back(hi);
|
shr.healedStacks.push_back(hi);
|
||||||
}
|
}
|
||||||
if(!shr.healedStacks.empty())
|
if(!shr.healedStacks.empty())
|
||||||
@ -147,7 +151,7 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
|
|||||||
bsa.side = parameters.casterSide;
|
bsa.side = parameters.casterSide;
|
||||||
bsa.summoned = true;
|
bsa.summoned = true;
|
||||||
bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, parameters.casterSide);
|
bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, parameters.casterSide);
|
||||||
bsa.amount = clonedStack->count;
|
bsa.amount = clonedStack->getCount();
|
||||||
env->sendAndApply(&bsa);
|
env->sendAndApply(&bsa);
|
||||||
|
|
||||||
BattleSetStackProperty ssp;
|
BattleSetStackProperty ssp;
|
||||||
@ -174,19 +178,16 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
|
|||||||
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
|
||||||
{
|
{
|
||||||
//can't clone already cloned creature
|
//can't clone already cloned creature
|
||||||
if(vstd::contains(obj->state, EBattleStackState::CLONED))
|
if(obj->isClone())
|
||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
|
//can`t clone if old clone still alive
|
||||||
if(obj->cloneID != -1)
|
if(obj->cloneID != -1)
|
||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
ui8 schoolLevel;
|
ui8 schoolLevel;
|
||||||
if(caster)
|
if(caster)
|
||||||
{
|
|
||||||
schoolLevel = caster->getEffectLevel(owner);
|
schoolLevel = caster->getEffectLevel(owner);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
schoolLevel = 3;//todo: remove
|
schoolLevel = 3;//todo: remove
|
||||||
}
|
|
||||||
|
|
||||||
if(schoolLevel < 3)
|
if(schoolLevel < 3)
|
||||||
{
|
{
|
||||||
@ -211,11 +212,16 @@ void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * pac
|
|||||||
doDispell(battle, packet, dispellSelector);
|
doDispell(battle, packet, dispellSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) const
|
EHealLevel CureMechanics::getHealLevel(int effectLevel) const
|
||||||
{
|
{
|
||||||
return EHealLevel::HEAL;
|
return EHealLevel::HEAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EHealPower CureMechanics::getHealPower(int effectLevel) const
|
||||||
|
{
|
||||||
|
return EHealPower::PERMANENT;
|
||||||
|
}
|
||||||
|
|
||||||
bool CureMechanics::dispellSelector(const Bonus * b)
|
bool CureMechanics::dispellSelector(const Bonus * b)
|
||||||
{
|
{
|
||||||
if(b->source == Bonus::SPELL_EFFECT)
|
if(b->source == Bonus::SPELL_EFFECT)
|
||||||
@ -436,10 +442,10 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const I
|
|||||||
if(nullptr != caster)
|
if(nullptr != caster)
|
||||||
{
|
{
|
||||||
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
|
||||||
ui32 subjectHealth = obj->totalHealth();
|
int64_t subjectHealth = obj->health.available();
|
||||||
//apply 'damage' bonus for hypnotize, including hero specialty
|
//apply 'damage' bonus for hypnotize, including hero specialty
|
||||||
ui32 maxHealth = caster->getSpellBonus(owner, owner->calculateRawEffectValue(caster->getEffectLevel(owner), caster->getEffectPower(owner)), obj);
|
int64_t maxHealth = caster->getSpellBonus(owner, owner->calculateRawEffectValue(caster->getEffectLevel(owner), caster->getEffectPower(owner)), obj);
|
||||||
if (subjectHealth > maxHealth)
|
if(subjectHealth > maxHealth)
|
||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
}
|
}
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster, obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster, obj);
|
||||||
@ -798,13 +804,18 @@ RisingSpellMechanics::RisingSpellMechanics(const CSpell * s):
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
|
EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
|
||||||
|
{
|
||||||
|
return EHealLevel::RESURRECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
EHealPower RisingSpellMechanics::getHealPower(int effectLevel) const
|
||||||
{
|
{
|
||||||
//this may be even distinct class
|
//this may be even distinct class
|
||||||
if((effectLevel <= 1) && (owner->id == SpellID::RESURRECTION))
|
if((effectLevel <= 1) && (owner->id == SpellID::RESURRECTION))
|
||||||
return EHealLevel::RESURRECT;
|
return EHealPower::ONE_BATTLE;
|
||||||
|
else
|
||||||
return EHealLevel::TRUE_RESURRECT;
|
return EHealPower::PERMANENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
///SacrificeMechanics
|
///SacrificeMechanics
|
||||||
@ -889,7 +900,7 @@ int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
|
return (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SacrificeMechanics::requiresCreatureTarget() const
|
bool SacrificeMechanics::requiresCreatureTarget() const
|
||||||
@ -905,22 +916,23 @@ SpecialRisingSpellMechanics::SpecialRisingSpellMechanics(const CSpell * s):
|
|||||||
|
|
||||||
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
|
ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
|
||||||
{
|
{
|
||||||
//find alive possible target
|
auto mainFilter = [cb, ctx, this](const CStack * s) -> bool
|
||||||
const CStack * stackToHeal = cb->getStackIf([ctx, this](const CStack * s)
|
|
||||||
{
|
{
|
||||||
const bool ownerMatches = !ctx.ti.smart || s->owner == ctx.caster->getOwner();
|
const bool ownerMatches = !ctx.ti.smart || cb->battleMatchOwner(ctx.caster->getOwner(), s, owner->getPositiveness());
|
||||||
|
return ownerMatches && s->coversPos(ctx.destination) && ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s);
|
||||||
return ownerMatches && s->isValidTarget(false) && s->coversPos(ctx.destination) && ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s);
|
};
|
||||||
|
//find alive possible target
|
||||||
|
const CStack * stackToHeal = cb->getStackIf([mainFilter](const CStack * s)
|
||||||
|
{
|
||||||
|
return s->isValidTarget(false) && mainFilter(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(nullptr == stackToHeal)
|
if(nullptr == stackToHeal)
|
||||||
{
|
{
|
||||||
//find dead possible target if there is no alive target
|
//find dead possible target if there is no alive target
|
||||||
stackToHeal = cb->getStackIf([ctx, this](const CStack * s)
|
stackToHeal = cb->getStackIf([mainFilter](const CStack * s)
|
||||||
{
|
{
|
||||||
const bool ownerMatches = !ctx.ti.smart || s->owner == ctx.caster->getOwner();
|
return s->isValidTarget(true) && mainFilter(s);
|
||||||
|
|
||||||
return ownerMatches && s->isValidTarget(true) && s->coversPos(ctx.destination) && ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//we have found dead target
|
//we have found dead target
|
||||||
@ -930,7 +942,7 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(cons
|
|||||||
{
|
{
|
||||||
const CStack * other = cb->getStackIf([hex, stackToHeal](const CStack * s)
|
const CStack * other = cb->getStackIf([hex, stackToHeal](const CStack * s)
|
||||||
{
|
{
|
||||||
return s->isValidTarget(false) && s->coversPos(hex) && s != stackToHeal;
|
return s->isValidTarget(true) && s->coversPos(hex) && s != stackToHeal;
|
||||||
});
|
});
|
||||||
if(nullptr != other)
|
if(nullptr != other)
|
||||||
return ESpellCastProblem::NO_APPROPRIATE_TARGET;//alive stack blocks resurrection
|
return ESpellCastProblem::NO_APPROPRIATE_TARGET;//alive stack blocks resurrection
|
||||||
@ -949,16 +961,22 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
|
|||||||
// following does apply to resurrect and animate dead(?) only
|
// following does apply to resurrect and animate dead(?) only
|
||||||
// for sacrifice health calculation and health limit check don't matter
|
// for sacrifice health calculation and health limit check don't matter
|
||||||
|
|
||||||
if(obj->count >= obj->baseAmount)
|
if(obj->getCount() >= obj->baseAmount)
|
||||||
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
|
|
||||||
//FIXME: Archangels can cast immune stack and this should be applied for them and not hero
|
//FIXME: code duplication with BattleSpellCastParameters
|
||||||
// if(caster)
|
auto getEffectValue = [&]() -> si32
|
||||||
// {
|
{
|
||||||
// auto maxHealth = calculateHealedHP(caster, obj, nullptr);
|
si32 effectValue = caster->getEffectValue(owner);
|
||||||
// if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
|
return (effectValue == 0) ? owner->calculateRawEffectValue(caster->getEffectLevel(owner), caster->getEffectPower(owner)) : effectValue;
|
||||||
// return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
};
|
||||||
// }
|
|
||||||
|
if(caster)
|
||||||
|
{
|
||||||
|
auto maxHealth = getEffectValue();
|
||||||
|
if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
|
||||||
|
return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
|
||||||
|
}
|
||||||
|
|
||||||
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
return DefaultSpellMechanics::isImmuneByStack(caster,obj);
|
||||||
}
|
}
|
||||||
@ -983,7 +1001,7 @@ ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInf
|
|||||||
{
|
{
|
||||||
return (st->owner == caster->getOwner())
|
return (st->owner == caster->getOwner())
|
||||||
&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
|
&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
|
||||||
&& (!vstd::contains(st->state, EBattleStackState::CLONED))
|
&& (!st->isClone())
|
||||||
&& (st->getCreature()->idNumber != creatureToSummon);
|
&& (st->getCreature()->idNumber != creatureToSummon);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,18 +18,12 @@ class SpellCreatedObstacle;
|
|||||||
class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
|
class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class EHealLevel
|
|
||||||
{
|
|
||||||
HEAL,
|
|
||||||
RESURRECT,
|
|
||||||
TRUE_RESURRECT
|
|
||||||
};
|
|
||||||
|
|
||||||
HealingSpellMechanics(const CSpell * s);
|
HealingSpellMechanics(const CSpell * s);
|
||||||
protected:
|
protected:
|
||||||
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
|
||||||
virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
|
||||||
virtual EHealLevel getHealLevel(int effectLevel) const = 0;
|
virtual EHealLevel getHealLevel(int effectLevel) const = 0;
|
||||||
|
virtual EHealPower getHealPower(int effectLevel) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
|
class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
|
||||||
@ -63,6 +57,7 @@ public:
|
|||||||
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
|
void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
|
||||||
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
|
||||||
EHealLevel getHealLevel(int effectLevel) const override final;
|
EHealLevel getHealLevel(int effectLevel) const override final;
|
||||||
|
EHealPower getHealPower(int effectLevel) const override final;
|
||||||
private:
|
private:
|
||||||
static bool dispellSelector(const Bonus * b);
|
static bool dispellSelector(const Bonus * b);
|
||||||
};
|
};
|
||||||
@ -177,7 +172,8 @@ class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RisingSpellMechanics(const CSpell * s);
|
RisingSpellMechanics(const CSpell * s);
|
||||||
EHealLevel getHealLevel(int effectLevel) const override;
|
EHealLevel getHealLevel(int effectLevel) const override final;
|
||||||
|
EHealPower getHealPower(int effectLevel) const override final;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
|
class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
|
||||||
|
@ -346,24 +346,11 @@ void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const
|
|||||||
|
|
||||||
auto attackedStack = attacked.at(0);
|
auto attackedStack = attacked.at(0);
|
||||||
|
|
||||||
auto getPluralFormat = [attackedStack](const int baseTextID) -> si32
|
auto addLogLine = [attackedStack, &logLines](const int baseTextID, const boost::logic::tribool & plural)
|
||||||
{
|
|
||||||
return attackedStack->count > 1 ? baseTextID + 1 : baseTextID;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto logSimple = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
|
|
||||||
{
|
{
|
||||||
MetaString line;
|
MetaString line;
|
||||||
line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(baseTextID));
|
attackedStack->addText(line, MetaString::GENERAL_TXT, baseTextID, plural);
|
||||||
line.addReplacement(*attackedStack);
|
attackedStack->addNameReplacement(line, plural);
|
||||||
logLines.push_back(line);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto logPlural = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
|
|
||||||
{
|
|
||||||
MetaString line;
|
|
||||||
line.addTxt(MetaString::GENERAL_TXT, baseTextID);
|
|
||||||
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
|
|
||||||
logLines.push_back(line);
|
logLines.push_back(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -372,26 +359,26 @@ void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const
|
|||||||
switch(owner->id)
|
switch(owner->id)
|
||||||
{
|
{
|
||||||
case SpellID::STONE_GAZE:
|
case SpellID::STONE_GAZE:
|
||||||
logSimple(558);
|
addLogLine(558, boost::logic::indeterminate);
|
||||||
break;
|
break;
|
||||||
case SpellID::POISON:
|
case SpellID::POISON:
|
||||||
logSimple(561);
|
addLogLine(561, boost::logic::indeterminate);
|
||||||
break;
|
break;
|
||||||
case SpellID::BIND:
|
case SpellID::BIND:
|
||||||
logPlural(560);//Roots and vines bind the %s to the ground!
|
addLogLine(-560, true);//"Roots and vines bind the %s to the ground!"
|
||||||
break;
|
break;
|
||||||
case SpellID::DISEASE:
|
case SpellID::DISEASE:
|
||||||
logSimple(553);
|
addLogLine(553, boost::logic::indeterminate);
|
||||||
break;
|
break;
|
||||||
case SpellID::PARALYZE:
|
case SpellID::PARALYZE:
|
||||||
logSimple(563);
|
addLogLine(563, boost::logic::indeterminate);
|
||||||
break;
|
break;
|
||||||
case SpellID::AGE:
|
case SpellID::AGE:
|
||||||
{
|
{
|
||||||
//The %s shrivel with age, and lose %d hit points."
|
//"The %s shrivel with age, and lose %d hit points."
|
||||||
MetaString line;
|
MetaString line;
|
||||||
line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(551));
|
attackedStack->addText(line, MetaString::GENERAL_TXT, 551);
|
||||||
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
|
attackedStack->addNameReplacement(line);
|
||||||
|
|
||||||
//todo: display effect from only this cast
|
//todo: display effect from only this cast
|
||||||
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
|
TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
|
||||||
@ -403,7 +390,7 @@ void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const
|
|||||||
break;
|
break;
|
||||||
case SpellID::THUNDERBOLT:
|
case SpellID::THUNDERBOLT:
|
||||||
{
|
{
|
||||||
logPlural(367);
|
addLogLine(-367, true);
|
||||||
MetaString line;
|
MetaString line;
|
||||||
//todo: handle newlines in metastring
|
//todo: handle newlines in metastring
|
||||||
std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
|
std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
|
||||||
@ -413,22 +400,22 @@ void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SpellID::DISPEL_HELPFUL_SPELLS:
|
case SpellID::DISPEL_HELPFUL_SPELLS:
|
||||||
logPlural(555);
|
addLogLine(-555, true);
|
||||||
break;
|
break;
|
||||||
case SpellID::DEATH_STARE:
|
case SpellID::DEATH_STARE:
|
||||||
if (damageToDisplay > 0)
|
if(damageToDisplay > 0)
|
||||||
{
|
{
|
||||||
MetaString line;
|
MetaString line;
|
||||||
if (damageToDisplay > 1)
|
if(damageToDisplay > 1)
|
||||||
{
|
{
|
||||||
line.addTxt(MetaString::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s.
|
line.addTxt(MetaString::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s.
|
||||||
line.addReplacement(damageToDisplay);
|
line.addReplacement(damageToDisplay);
|
||||||
line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
|
attackedStack->addNameReplacement(line, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
line.addTxt(MetaString::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s.
|
line.addTxt(MetaString::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s.
|
||||||
line.addReplacement(MetaString::CRE_SING_NAMES, attackedStack->getCreature()->idNumber.num);
|
attackedStack->addNameReplacement(line, false);
|
||||||
}
|
}
|
||||||
parameters.caster->getCasterName(line);
|
parameters.caster->getCasterName(line);
|
||||||
logLines.push_back(line);
|
logLines.push_back(line);
|
||||||
@ -650,9 +637,9 @@ std::vector<const CStack *> DefaultSpellMechanics::getAffectedStacks(const CBatt
|
|||||||
return attackedCres;
|
return attackedCres;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback* cb, const SpellTargetingContext& ctx) const
|
std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
|
||||||
{
|
{
|
||||||
std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
|
std::set<const CStack *> attackedCres;//std::set to exclude multiple occurrences of two hex creatures
|
||||||
|
|
||||||
const auto side = cb->playerToSide(ctx.caster->getOwner());
|
const auto side = cb->playerToSide(ctx.caster->getOwner());
|
||||||
if(!side)
|
if(!side)
|
||||||
@ -665,10 +652,9 @@ std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const
|
|||||||
|
|
||||||
auto mainFilter = [=](const CStack * s)
|
auto mainFilter = [=](const CStack * s)
|
||||||
{
|
{
|
||||||
const bool positiveToAlly = owner->isPositive() && s->owner == ctx.caster->getOwner();
|
const bool ownerMatches = cb->battleMatchOwner(ctx.caster->getOwner(), s, owner->getPositiveness());
|
||||||
const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.caster->getOwner();
|
|
||||||
const bool validTarget = s->isValidTarget(!ctx.ti.onlyAlive); //todo: this should be handled by spell class
|
const bool validTarget = s->isValidTarget(!ctx.ti.onlyAlive); //todo: this should be handled by spell class
|
||||||
const bool positivenessFlag = !ctx.ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
|
const bool positivenessFlag = !ctx.ti.smart || ownerMatches;
|
||||||
|
|
||||||
return positivenessFlag && validTarget;
|
return positivenessFlag && validTarget;
|
||||||
};
|
};
|
||||||
@ -701,7 +687,7 @@ std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const
|
|||||||
else if(ctx.ti.massive)
|
else if(ctx.ti.massive)
|
||||||
{
|
{
|
||||||
TStacks stacks = cb->battleGetStacksIf(mainFilter);
|
TStacks stacks = cb->battleGetStacksIf(mainFilter);
|
||||||
for (auto stack : stacks)
|
for(auto stack : stacks)
|
||||||
attackedCres.insert(stack);
|
attackedCres.insert(stack);
|
||||||
}
|
}
|
||||||
else //custom range from attackedHexes
|
else //custom range from attackedHexes
|
||||||
@ -732,28 +718,12 @@ ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBat
|
|||||||
{
|
{
|
||||||
std::vector<const CStack *> affected = getAffectedStacks(cb, ctx);
|
std::vector<const CStack *> affected = getAffectedStacks(cb, ctx);
|
||||||
|
|
||||||
//allow to cast spell if affects is at least one smart target
|
//allow to cast spell if it affects at least one smart target
|
||||||
bool targetExists = false;
|
bool targetExists = false;
|
||||||
|
|
||||||
for(const CStack * stack : affected)
|
for(const CStack * stack : affected)
|
||||||
{
|
{
|
||||||
bool casterStack = stack->owner == ctx.caster->getOwner();
|
targetExists = cb->battleMatchOwner(ctx.caster->getOwner(), stack, owner->getPositiveness());
|
||||||
|
|
||||||
switch (owner->positiveness)
|
|
||||||
{
|
|
||||||
case CSpell::POSITIVE:
|
|
||||||
if(casterStack)
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
case CSpell::NEUTRAL:
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
case CSpell::NEGATIVE:
|
|
||||||
if(!casterStack)
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(targetExists)
|
if(targetExists)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -219,26 +219,9 @@ ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback
|
|||||||
|
|
||||||
for(const CStack * stack : cb->battleGetAllStacks())
|
for(const CStack * stack : cb->battleGetAllStacks())
|
||||||
{
|
{
|
||||||
bool immune = !(stack->isValidTarget(!tinfo.onlyAlive) && ESpellCastProblem::OK == isImmuneByStack(caster, stack));
|
const bool immune = !(stack->isValidTarget(!tinfo.onlyAlive) && ESpellCastProblem::OK == isImmuneByStack(caster, stack));
|
||||||
bool casterStack = stack->owner == caster->getOwner();
|
const bool ownerMatches = cb->battleMatchOwner(caster->getOwner(), stack, getPositiveness());
|
||||||
|
targetExists = !immune && ownerMatches;
|
||||||
if(!immune)
|
|
||||||
{
|
|
||||||
switch (positiveness)
|
|
||||||
{
|
|
||||||
case CSpell::POSITIVE:
|
|
||||||
if(casterStack)
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
case CSpell::NEUTRAL:
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
case CSpell::NEGATIVE:
|
|
||||||
if(!casterStack)
|
|
||||||
targetExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(targetExists)
|
if(targetExists)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
|
|||||||
si32 damageToDisplay = parameters.effectPower;
|
si32 damageToDisplay = parameters.effectPower;
|
||||||
|
|
||||||
if(!ctx.attackedCres.empty())
|
if(!ctx.attackedCres.empty())
|
||||||
vstd::amin(damageToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
|
vstd::amin(damageToDisplay, (*ctx.attackedCres.begin())->getCount()); //stack is already reduced after attack
|
||||||
|
|
||||||
ctx.setDamageToDisplay(damageToDisplay);
|
ctx.setDamageToDisplay(damageToDisplay);
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
|
|||||||
BattleStackAttacked bsa;
|
BattleStackAttacked bsa;
|
||||||
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
|
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
|
||||||
bsa.spellID = owner->id;
|
bsa.spellID = owner->id;
|
||||||
bsa.damageAmount = parameters.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation
|
bsa.damageAmount = parameters.effectPower * (attackedCre)->MaxHealth();//todo: move here all DeathStare calculation
|
||||||
bsa.stackAttacked = (attackedCre)->ID;
|
bsa.stackAttacked = (attackedCre)->ID;
|
||||||
bsa.attackerID = -1;
|
bsa.attackerID = -1;
|
||||||
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
|
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
|
||||||
|
@ -950,7 +950,7 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
|
|||||||
def->prepareAttacked(bsa, getRandomGenerator()); //calculate casualties
|
def->prepareAttacked(bsa, getRandomGenerator()); //calculate casualties
|
||||||
|
|
||||||
//life drain handling
|
//life drain handling
|
||||||
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
|
if(att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
|
||||||
{
|
{
|
||||||
StacksHealedOrResurrected shi;
|
StacksHealedOrResurrected shi;
|
||||||
shi.lifeDrain = true;
|
shi.lifeDrain = true;
|
||||||
@ -958,48 +958,49 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
|
|||||||
shi.cure = false;
|
shi.cure = false;
|
||||||
shi.drainedFrom = def->ID;
|
shi.drainedFrom = def->ID;
|
||||||
|
|
||||||
StacksHealedOrResurrected::HealInfo hi;
|
int32_t toHeal = bsa.damageAmount * att->valOfBonuses(Bonus::LIFE_DRAIN) / 100;
|
||||||
hi.stackID = att->ID;
|
CHealth health = att->healthAfterHealed(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
|
||||||
hi.healedHP = att->calculateHealedHealthPoints(bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100, true);
|
|
||||||
hi.lowLevelResurrection = false;
|
CHealthInfo hi;
|
||||||
|
health.toInfo(hi);
|
||||||
|
hi.stackId = att->ID;
|
||||||
|
hi.delta = toHeal;
|
||||||
shi.healedStacks.push_back(hi);
|
shi.healedStacks.push_back(hi);
|
||||||
|
|
||||||
if (hi.healedHP > 0)
|
if(hi.delta > 0)
|
||||||
{
|
|
||||||
bsa.healedStacks.push_back(shi);
|
bsa.healedStacks.push_back(shi);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//soul steal handling
|
//soul steal handling
|
||||||
if (att->hasBonusOfType(Bonus::SOUL_STEAL) && def->isLiving())
|
if(att->hasBonusOfType(Bonus::SOUL_STEAL) && def->isLiving())
|
||||||
{
|
{
|
||||||
StacksHealedOrResurrected shi;
|
StacksHealedOrResurrected shi;
|
||||||
shi.lifeDrain = true;
|
shi.lifeDrain = true;
|
||||||
shi.tentHealing = false;
|
shi.tentHealing = false;
|
||||||
shi.cure = false;
|
shi.cure = false;
|
||||||
shi.canOverheal = true;
|
|
||||||
shi.drainedFrom = def->ID;
|
shi.drainedFrom = def->ID;
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++) //we can have two bonuses - one with subtype 0 and another with subtype 1
|
for(int i = 0; i < 2; i++) //we can have two bonuses - one with subtype 0 and another with subtype 1
|
||||||
{
|
{
|
||||||
if (att->hasBonusOfType(Bonus::SOUL_STEAL, i))
|
if(att->hasBonusOfType(Bonus::SOUL_STEAL, i))
|
||||||
{
|
{
|
||||||
StacksHealedOrResurrected::HealInfo hi;
|
int32_t toHeal = bsa.killedAmount * att->valOfBonuses(Bonus::SOUL_STEAL, i) * att->MaxHealth();
|
||||||
hi.stackID = att->ID;
|
CHealth health = att->healthAfterHealed(toHeal, EHealLevel::OVERHEAL, ((i == 0) ? EHealPower::ONE_BATTLE : EHealPower::PERMANENT));
|
||||||
hi.healedHP = bsa.killedAmount * att->valOfBonuses(Bonus::SOUL_STEAL, i) * att->MaxHealth();
|
CHealthInfo hi;
|
||||||
hi.lowLevelResurrection = (bool)i;
|
health.toInfo(hi);
|
||||||
shi.healedStacks.push_back(hi);
|
hi.stackId = att->ID;
|
||||||
|
hi.delta = toHeal;
|
||||||
|
if(hi.delta > 0)
|
||||||
|
shi.healedStacks.push_back(hi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (std::any_of(shi.healedStacks.begin(), shi.healedStacks.end(), [](StacksHealedOrResurrected::HealInfo healInfo) { return healInfo.healedHP > 0; }))
|
if(!shi.healedStacks.empty())
|
||||||
{
|
|
||||||
bsa.healedStacks.push_back(shi);
|
bsa.healedStacks.push_back(shi);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
|
bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
|
||||||
|
|
||||||
//fire shield handling
|
//fire shield handling
|
||||||
if (!bat.shot() && !vstd::contains(def->state, EBattleStackState::CLONED) &&
|
if(!bat.shot() && !def->isClone() &&
|
||||||
def->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType(Bonus::FIRE_IMMUNITY))
|
def->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType(Bonus::FIRE_IMMUNITY))
|
||||||
{
|
{
|
||||||
// TODO: Fire shield damage should be calculated separately after BattleAttack applied.
|
// TODO: Fire shield damage should be calculated separately after BattleAttack applied.
|
||||||
@ -1011,7 +1012,7 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
|
|||||||
bsa2.flags |= BattleStackAttacked::EFFECT; //FIXME: play animation upon efreet and not attacker
|
bsa2.flags |= BattleStackAttacked::EFFECT; //FIXME: play animation upon efreet and not attacker
|
||||||
bsa2.effect = 11;
|
bsa2.effect = 11;
|
||||||
|
|
||||||
bsa2.damageAmount = (std::min(def->totalHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; //TODO: scale with attack/defense
|
bsa2.damageAmount = (std::min<int64_t>(def->health.available(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; //TODO: scale with attack/defense
|
||||||
att->prepareAttacked(bsa2, getRandomGenerator());
|
att->prepareAttacked(bsa2, getRandomGenerator());
|
||||||
bat.bsa.push_back(bsa2);
|
bat.bsa.push_back(bsa2);
|
||||||
}
|
}
|
||||||
@ -3830,11 +3831,29 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
|
|||||||
}
|
}
|
||||||
case Battle::DEFEND:
|
case Battle::DEFEND:
|
||||||
{
|
{
|
||||||
//defensive stance //TODO: remove this bonus when stack becomes active
|
//defensive stance
|
||||||
SetStackEffect sse;
|
SetStackEffect sse;
|
||||||
sse.effect.push_back(Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 20, -1, PrimarySkill::DEFENSE, Bonus::PERCENT_TO_ALL));
|
Bonus bonus1(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->battleGetStackByID(ba.stackNumber)->valOfBonuses(Bonus::DEFENSIVE_STANCE),
|
Bonus bonus2(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, stack->valOfBonuses(Bonus::DEFENSIVE_STANCE),
|
||||||
-1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE));
|
-1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE);
|
||||||
|
BonusList defence = *stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE));
|
||||||
|
int oldDefenceValue = defence.totalValue();
|
||||||
|
|
||||||
|
defence.push_back(std::make_shared<Bonus>(bonus1));
|
||||||
|
defence.push_back(std::make_shared<Bonus>(bonus2));
|
||||||
|
|
||||||
|
int difference = defence.totalValue() - oldDefenceValue;
|
||||||
|
|
||||||
|
MetaString text;
|
||||||
|
stack->addText(text, MetaString::GENERAL_TXT, 120);
|
||||||
|
stack->addNameReplacement(text);
|
||||||
|
text.addReplacement(difference);
|
||||||
|
|
||||||
|
sse.battleLog.push_back(text);
|
||||||
|
|
||||||
|
sse.effect.push_back(bonus1);
|
||||||
|
sse.effect.push_back(bonus2);
|
||||||
|
|
||||||
sse.stacks.push_back(ba.stackNumber);
|
sse.stacks.push_back(ba.stackNumber);
|
||||||
sendAndApply(&sse);
|
sendAndApply(&sse);
|
||||||
|
|
||||||
@ -4005,12 +4024,12 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
|
|||||||
|
|
||||||
int additionalAttacks = stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK),
|
int additionalAttacks = stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK),
|
||||||
(Selector::effectRange(Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))))->totalValue();
|
(Selector::effectRange(Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))))->totalValue();
|
||||||
for (int i = 0; i < additionalAttacks; ++i)
|
for(int i = 0; i < additionalAttacks; ++i)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
stack->alive()
|
stack->alive()
|
||||||
&& destinationStack->alive()
|
&& destinationStack->alive()
|
||||||
&& stack->shots
|
&& stack->shots.canUse()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
BattleAttack bat;
|
BattleAttack bat;
|
||||||
@ -4177,37 +4196,37 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
|
|||||||
const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
|
const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
|
||||||
*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
|
*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
|
||||||
|
|
||||||
ui32 healed = 0;
|
|
||||||
|
|
||||||
if (healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
|
if(healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
|
||||||
{
|
{
|
||||||
complain("There is either no healer, no destination, or healer cannot heal :P");
|
complain("There is either no healer, no destination, or healer cannot heal :P");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui32 maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
|
int32_t toHeal = healer->getCount() * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
|
||||||
healed = destStack->calculateHealedHealthPoints(maxiumHeal, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (healed == 0)
|
//TODO: allow resurrection for mods
|
||||||
{
|
CHealth health = destStack->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
|
||||||
//nothing to heal.. should we complain?
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StacksHealedOrResurrected shr;
|
|
||||||
shr.lifeDrain = false;
|
|
||||||
shr.tentHealing = true;
|
|
||||||
shr.cure = false;
|
|
||||||
shr.drainedFrom = ba.stackNumber;
|
|
||||||
|
|
||||||
StacksHealedOrResurrected::HealInfo hi;
|
if(toHeal == 0)
|
||||||
hi.healedHP = healed;
|
{
|
||||||
hi.lowLevelResurrection = false;
|
logGlobal->warn("Nothing to heal");
|
||||||
hi.stackID = destStack->ID;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StacksHealedOrResurrected shr;
|
||||||
|
shr.lifeDrain = false;
|
||||||
|
shr.tentHealing = true;
|
||||||
|
shr.cure = false;
|
||||||
|
shr.drainedFrom = ba.stackNumber;
|
||||||
|
|
||||||
shr.healedStacks.push_back(hi);
|
CHealthInfo hi;
|
||||||
sendAndApply(&shr);
|
health.toInfo(hi);
|
||||||
|
hi.stackId = destStack->ID;
|
||||||
|
hi.delta = toHeal;
|
||||||
|
shr.healedStacks.push_back(hi);
|
||||||
|
sendAndApply(&shr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4223,7 +4242,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
|
|||||||
bsa.side = summoner->side;
|
bsa.side = summoner->side;
|
||||||
|
|
||||||
bsa.creID = summonedType;
|
bsa.creID = summonedType;
|
||||||
ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
|
ui64 risedHp = summoner->getCount() * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
|
||||||
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
|
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
|
||||||
|
|
||||||
ui64 canRiseHp = std::min(targetHealth, risedHp);
|
ui64 canRiseHp = std::min(targetHealth, risedHp);
|
||||||
@ -4489,12 +4508,12 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
|
|||||||
if (st->hasBonusOfType(Bonus::HP_REGENERATION))
|
if (st->hasBonusOfType(Bonus::HP_REGENERATION))
|
||||||
{
|
{
|
||||||
bte.effect = Bonus::HP_REGENERATION;
|
bte.effect = Bonus::HP_REGENERATION;
|
||||||
bte.val = std::min((int)(st->MaxHealth() - st->firstHPleft), st->valOfBonuses(Bonus::HP_REGENERATION));
|
bte.val = std::min((int)(st->MaxHealth() - st->getFirstHPleft()), st->valOfBonuses(Bonus::HP_REGENERATION));
|
||||||
}
|
}
|
||||||
if (st->hasBonusOfType(Bonus::FULL_HP_REGENERATION))
|
if (st->hasBonusOfType(Bonus::FULL_HP_REGENERATION))
|
||||||
{
|
{
|
||||||
bte.effect = Bonus::HP_REGENERATION;
|
bte.effect = Bonus::HP_REGENERATION;
|
||||||
bte.val = st->MaxHealth() - st->firstHPleft;
|
bte.val = st->MaxHealth() - st->getFirstHPleft();
|
||||||
}
|
}
|
||||||
if (bte.val) //anything to heal
|
if (bte.val) //anything to heal
|
||||||
sendAndApply(&bte);
|
sendAndApply(&bte);
|
||||||
@ -4551,7 +4570,7 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
|
|||||||
}
|
}
|
||||||
BonusList bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTER)));
|
BonusList bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTER)));
|
||||||
int side = gs->curB->whatSide(st->owner);
|
int side = gs->curB->whatSide(st->owner);
|
||||||
if (st->casts && !gs->curB->sides.at(side).enchanterCounter)
|
if(st->canCast() && !gs->curB->sides.at(side).enchanterCounter)
|
||||||
{
|
{
|
||||||
bool cast = false;
|
bool cast = false;
|
||||||
while (!bl.empty() && !cast)
|
while (!bl.empty() && !cast)
|
||||||
@ -5216,7 +5235,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
|
|||||||
const CStack * oneOfAttacked = nullptr;
|
const CStack * oneOfAttacked = nullptr;
|
||||||
for (auto & elem : bat.bsa)
|
for (auto & elem : bat.bsa)
|
||||||
{
|
{
|
||||||
if (elem.newAmount > 0 && !elem.isSecondary()) //apply effects only to first target stack if it's alive
|
if ((elem.newHealth.fullUnits > 0 || elem.newHealth.firstHPleft > 0) && !elem.isSecondary()) //apply effects only to first target stack if it's alive
|
||||||
{
|
{
|
||||||
oneOfAttacked = gs->curB->battleGetStackByID(elem.stackAttacked);
|
oneOfAttacked = gs->curB->battleGetStackByID(elem.stackAttacked);
|
||||||
break;
|
break;
|
||||||
@ -5282,7 +5301,10 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
|
|||||||
if (!attacker || bat.bsa.empty()) // can be already dead
|
if (!attacker || bat.bsa.empty()) // can be already dead
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const CStack *defender = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked);
|
const CStack * defender = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked);
|
||||||
|
|
||||||
|
if(!defender)
|
||||||
|
return;//already dead
|
||||||
|
|
||||||
auto cast = [=](SpellID spellID, int power)
|
auto cast = [=](SpellID spellID, int power)
|
||||||
{
|
{
|
||||||
@ -5291,7 +5313,7 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
|
|||||||
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
|
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
|
||||||
parameters.spellLvl = 0;
|
parameters.spellLvl = 0;
|
||||||
parameters.effectLevel = 0;
|
parameters.effectLevel = 0;
|
||||||
parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
|
parameters.aimToStack(defender);
|
||||||
parameters.effectPower = power;
|
parameters.effectPower = power;
|
||||||
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
|
||||||
parameters.cast(spellEnv);
|
parameters.cast(spellEnv);
|
||||||
@ -5299,13 +5321,13 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
|
|||||||
|
|
||||||
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
|
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
|
||||||
|
|
||||||
if (bat.bsa.at(0).newAmount <= 0)
|
if(!defender->alive())
|
||||||
{
|
{
|
||||||
//don't try death stare or acid breath on dead stack (crash!)
|
//don't try death stare or acid breath on dead stack (crash!)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker->hasBonusOfType(Bonus::DEATH_STARE) && bat.bsa.size())
|
if(attacker->hasBonusOfType(Bonus::DEATH_STARE))
|
||||||
{
|
{
|
||||||
// mechanics of Death Stare as in H3:
|
// mechanics of Death Stare as in H3:
|
||||||
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
|
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
|
||||||
@ -5314,45 +5336,50 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
|
|||||||
double chanceToKill = attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100.0f;
|
double chanceToKill = attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100.0f;
|
||||||
vstd::amin(chanceToKill, 1); //cap at 100%
|
vstd::amin(chanceToKill, 1); //cap at 100%
|
||||||
|
|
||||||
std::binomial_distribution<> distribution(attacker->count, chanceToKill);
|
std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
|
||||||
|
|
||||||
int staredCreatures = distribution(getRandomGenerator().getStdGenerator());
|
int staredCreatures = distribution(getRandomGenerator().getStdGenerator());
|
||||||
|
|
||||||
double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0
|
double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0
|
||||||
int maxToKill = (attacker->count + cap - 1) / cap; //not much more than chance * count
|
int maxToKill = (attacker->getCount() + cap - 1) / cap; //not much more than chance * count
|
||||||
vstd::amin(staredCreatures, maxToKill);
|
vstd::amin(staredCreatures, maxToKill);
|
||||||
|
|
||||||
staredCreatures += (attacker->level() * attacker->valOfBonuses(Bonus::DEATH_STARE, 1)) / gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->level();
|
staredCreatures += (attacker->level() * attacker->valOfBonuses(Bonus::DEATH_STARE, 1)) / defender->level();
|
||||||
if (staredCreatures)
|
if(staredCreatures)
|
||||||
{
|
{
|
||||||
if (bat.bsa.at(0).newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
|
//TODO: death stare was not originally available for multiple-hex attacks, but...
|
||||||
cast(SpellID::DEATH_STARE, staredCreatures);
|
cast(SpellID::DEATH_STARE, staredCreatures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!defender->alive())
|
||||||
|
return;
|
||||||
|
|
||||||
int acidDamage = 0;
|
int acidDamage = 0;
|
||||||
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
|
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
|
||||||
for (const std::shared_ptr<Bonus> b : *acidBreath)
|
for(const std::shared_ptr<Bonus> b : *acidBreath)
|
||||||
{
|
{
|
||||||
if (b->additionalInfo > getRandomGenerator().nextInt(99))
|
if(b->additionalInfo > getRandomGenerator().nextInt(99))
|
||||||
acidDamage += b->val;
|
acidDamage += b->val;
|
||||||
}
|
}
|
||||||
if (acidDamage)
|
|
||||||
{
|
|
||||||
cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->count);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attacker->hasBonusOfType(Bonus::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
|
if(acidDamage)
|
||||||
|
cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->getCount());
|
||||||
|
|
||||||
|
if(!defender->alive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(attacker->hasBonusOfType(Bonus::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
|
||||||
{
|
{
|
||||||
double chanceToTrigger = attacker->valOfBonuses(Bonus::TRANSMUTATION) / 100.0f;
|
double chanceToTrigger = attacker->valOfBonuses(Bonus::TRANSMUTATION) / 100.0f;
|
||||||
vstd::amin(chanceToTrigger, 1); //cap at 100%
|
vstd::amin(chanceToTrigger, 1); //cap at 100%
|
||||||
|
|
||||||
if (getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger)
|
if(getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int bonusAdditionalInfo = attacker->getBonus(Selector::type(Bonus::TRANSMUTATION))->additionalInfo;
|
int bonusAdditionalInfo = attacker->getBonus(Selector::type(Bonus::TRANSMUTATION))->additionalInfo;
|
||||||
|
|
||||||
if (defender->getCreature()->idNumber == bonusAdditionalInfo ||
|
if(defender->getCreature()->idNumber == bonusAdditionalInfo ||
|
||||||
(bonusAdditionalInfo == -1 && defender->getCreature()->idNumber == attacker->getCreature()->idNumber))
|
(bonusAdditionalInfo == -1 && defender->getCreature()->idNumber == attacker->getCreature()->idNumber))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -5360,17 +5387,15 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
|
|||||||
resurrectInfo.pos = defender->position;
|
resurrectInfo.pos = defender->position;
|
||||||
resurrectInfo.side = defender->side;
|
resurrectInfo.side = defender->side;
|
||||||
|
|
||||||
if (bonusAdditionalInfo != -1)
|
if(bonusAdditionalInfo != -1)
|
||||||
resurrectInfo.creID = (CreatureID)bonusAdditionalInfo;
|
resurrectInfo.creID = (CreatureID)bonusAdditionalInfo;
|
||||||
else
|
else
|
||||||
resurrectInfo.creID = attacker->getCreature()->idNumber;
|
resurrectInfo.creID = attacker->getCreature()->idNumber;
|
||||||
|
|
||||||
if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 0))
|
if(attacker->hasBonusOfType((Bonus::TRANSMUTATION), 0))
|
||||||
{
|
resurrectInfo.amount = std::max((defender->getCount() * defender->MaxHealth()) / resurrectInfo.creID.toCreature()->MaxHealth(), 1u);
|
||||||
resurrectInfo.amount = std::max((defender->count * defender->MaxHealth()) / resurrectInfo.creID.toCreature()->MaxHealth(), 1u);
|
|
||||||
}
|
|
||||||
else if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 1))
|
else if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 1))
|
||||||
resurrectInfo.amount = defender->count;
|
resurrectInfo.amount = defender->getCount();
|
||||||
else
|
else
|
||||||
return; //wrong subtype
|
return; //wrong subtype
|
||||||
|
|
||||||
@ -5647,7 +5672,7 @@ void CGameHandler::runBattle()
|
|||||||
if (accessibility.accessible(hex, guardianIsBig, stack->side)) //without this multiple creatures can occupy one hex
|
if (accessibility.accessible(hex, guardianIsBig, stack->side)) //without this multiple creatures can occupy one hex
|
||||||
{
|
{
|
||||||
BattleStackAdded newStack;
|
BattleStackAdded newStack;
|
||||||
newStack.amount = std::max(1, (int)(stack->count * 0.01 * summonInfo->val));
|
newStack.amount = std::max(1, (int)(stack->getCount() * 0.01 * summonInfo->val));
|
||||||
newStack.creID = creatureData.num;
|
newStack.creID = creatureData.num;
|
||||||
newStack.side = stack->side;
|
newStack.side = stack->side;
|
||||||
newStack.summoned = true;
|
newStack.summoned = true;
|
||||||
@ -6308,36 +6333,34 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
|
|||||||
heroWithDeadCommander = ObjectInstanceID();
|
heroWithDeadCommander = ObjectInstanceID();
|
||||||
|
|
||||||
PlayerColor color = army->tempOwner;
|
PlayerColor color = army->tempOwner;
|
||||||
if (color == PlayerColor::UNFLAGGABLE)
|
if(color == PlayerColor::UNFLAGGABLE)
|
||||||
color = PlayerColor::NEUTRAL;
|
color = PlayerColor::NEUTRAL;
|
||||||
|
|
||||||
for (CStack *st : bat->stacks)
|
for(CStack * st : bat->stacks)
|
||||||
{
|
{
|
||||||
if (vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account temporary summoned stacks
|
if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account temporary summoned stacks
|
||||||
continue;
|
continue;
|
||||||
if (st->owner != color) //remove only our stacks
|
if(st->owner != color) //remove only our stacks
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
logGlobal->debug("Calculating casualties for %s", st->nodeName());
|
logGlobal->debug("Calculating casualties for %s", st->nodeName());
|
||||||
|
|
||||||
//FIXME: this info is also used in BattleInfo::calculateCasualties, refactor
|
st->health.takeResurrected();
|
||||||
st->count = std::max (0, st->count - st->resurrected);
|
|
||||||
|
|
||||||
if (st->slot == SlotID::ARROW_TOWERS_SLOT)
|
if(st->slot == SlotID::ARROW_TOWERS_SLOT)
|
||||||
{
|
{
|
||||||
//do nothing
|
|
||||||
logGlobal->debug("Ignored arrow towers stack.");
|
logGlobal->debug("Ignored arrow towers stack.");
|
||||||
}
|
}
|
||||||
else if (st->slot == SlotID::WAR_MACHINES_SLOT)
|
else if(st->slot == SlotID::WAR_MACHINES_SLOT)
|
||||||
{
|
{
|
||||||
auto warMachine = st->type->warMachine;
|
auto warMachine = st->type->warMachine;
|
||||||
|
|
||||||
if (warMachine == ArtifactID::NONE)
|
if(warMachine == ArtifactID::NONE)
|
||||||
{
|
{
|
||||||
logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
|
logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
|
||||||
}
|
}
|
||||||
//catapult artifact remain even if "creature" killed in siege
|
//catapult artifact remain even if "creature" killed in siege
|
||||||
else if (warMachine != ArtifactID::CATAPULT && !st->count)
|
else if(warMachine != ArtifactID::CATAPULT && st->getCount() <= 0)
|
||||||
{
|
{
|
||||||
logGlobal->debug("War machine has been destroyed");
|
logGlobal->debug("War machine has been destroyed");
|
||||||
auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
|
auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
|
||||||
@ -6347,16 +6370,16 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
|
|||||||
logGlobal->error("War machine in army without hero");
|
logGlobal->error("War machine in army without hero");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER)
|
else if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER)
|
||||||
{
|
{
|
||||||
if (st->alive() && st->count > 0)
|
if(st->alive() && st->getCount() > 0)
|
||||||
{
|
{
|
||||||
logGlobal->debug("Permanently summoned %d units.", st->count);
|
logGlobal->debug("Permanently summoned %d units.", st->getCount());
|
||||||
const CreatureID summonedType = st->type->idNumber;
|
const CreatureID summonedType = st->type->idNumber;
|
||||||
summoned[summonedType] += st->count;
|
summoned[summonedType] += st->getCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (st->slot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
else if(st->slot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
||||||
{
|
{
|
||||||
if (nullptr == st->base)
|
if (nullptr == st->base)
|
||||||
{
|
{
|
||||||
@ -6365,10 +6388,10 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto c = dynamic_cast <const CCommanderInstance *>(st->base);
|
auto c = dynamic_cast <const CCommanderInstance *>(st->base);
|
||||||
if (c)
|
if(c)
|
||||||
{
|
{
|
||||||
auto h = dynamic_cast <const CGHeroInstance *>(army);
|
auto h = dynamic_cast <const CGHeroInstance *>(army);
|
||||||
if (h && h->commander == c && (st->count == 0 || !st->alive()))
|
if(h && h->commander == c && (st->getCount() == 0 || !st->alive()))
|
||||||
{
|
{
|
||||||
logGlobal->debug("Commander is dead.");
|
logGlobal->debug("Commander is dead.");
|
||||||
heroWithDeadCommander = army->id; //TODO: unify commander handling
|
heroWithDeadCommander = army->id; //TODO: unify commander handling
|
||||||
@ -6378,25 +6401,26 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
|
|||||||
logGlobal->error("Stack with invalid instance in commander slot. Stack: %s", st->nodeName());
|
logGlobal->error("Stack with invalid instance in commander slot. Stack: %s", st->nodeName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (st->base && !army->slotEmpty(st->slot))
|
else if(st->base && !army->slotEmpty(st->slot))
|
||||||
{
|
{
|
||||||
if (st->count == 0 || !st->alive())
|
logGlobal->debug("Count: %d; base count: %d", st->getCount(), army->getStackCount(st->slot));
|
||||||
|
if(st->getCount() == 0 || !st->alive())
|
||||||
{
|
{
|
||||||
logGlobal->debug("Stack has been destroyed.");
|
logGlobal->debug("Stack has been destroyed.");
|
||||||
StackLocation sl(army, st->slot);
|
StackLocation sl(army, st->slot);
|
||||||
newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
|
newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
|
||||||
}
|
}
|
||||||
else if (st->count < army->getStackCount(st->slot))
|
else if(st->getCount() < army->getStackCount(st->slot))
|
||||||
{
|
{
|
||||||
logGlobal->debug("Stack lost %d units.", army->getStackCount(st->slot) - st->count);
|
logGlobal->debug("Stack lost %d units.", army->getStackCount(st->slot) - st->getCount());
|
||||||
StackLocation sl(army, st->slot);
|
StackLocation sl(army, st->slot);
|
||||||
newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
|
newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount()));
|
||||||
}
|
}
|
||||||
else if (st->count > army->getStackCount(st->slot))
|
else if(st->getCount() > army->getStackCount(st->slot))
|
||||||
{
|
{
|
||||||
logGlobal->debug("Stack gained %d units.", st->count - army->getStackCount(st->slot));
|
logGlobal->debug("Stack gained %d units.", st->getCount() - army->getStackCount(st->slot));
|
||||||
StackLocation sl(army, st->slot);
|
StackLocation sl(army, st->slot);
|
||||||
newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
|
newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -13,6 +13,7 @@ set(test_SRCS
|
|||||||
CMemoryBufferTest.cpp
|
CMemoryBufferTest.cpp
|
||||||
CVcmiTestConfig.cpp
|
CVcmiTestConfig.cpp
|
||||||
MapComparer.cpp
|
MapComparer.cpp
|
||||||
|
battle/CHealthTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(test_HEADERS
|
set(test_HEADERS
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
<Option compile="1" />
|
<Option compile="1" />
|
||||||
<Option weight="0" />
|
<Option weight="0" />
|
||||||
</Unit>
|
</Unit>
|
||||||
|
<Unit filename="battle/CHealthTest.cpp" />
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<code_completion />
|
<code_completion />
|
||||||
<envvars />
|
<envvars />
|
||||||
|
223
test/battle/CHealthTest.cpp
Normal file
223
test/battle/CHealthTest.cpp
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* CHealthTest.cpp, 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 "StdInc.h"
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#include "../../lib/CStack.h"
|
||||||
|
|
||||||
|
static const int32_t UNIT_HEALTH = 123;
|
||||||
|
static const int32_t UNIT_AMOUNT = 300;
|
||||||
|
|
||||||
|
class CUnitHealthInfoMock : public IUnitHealthInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CUnitHealthInfoMock():
|
||||||
|
maxHealth(UNIT_HEALTH),
|
||||||
|
baseAmount(UNIT_AMOUNT),
|
||||||
|
health(this)
|
||||||
|
{
|
||||||
|
health.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t maxHealth;
|
||||||
|
int32_t baseAmount;
|
||||||
|
|
||||||
|
CHealth health;
|
||||||
|
|
||||||
|
int32_t unitMaxHealth() const override
|
||||||
|
{
|
||||||
|
return maxHealth;
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t unitBaseAmount() const override
|
||||||
|
{
|
||||||
|
return baseAmount;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static void checkTotal(const CHealth & health, const CUnitHealthInfoMock & mock)
|
||||||
|
{
|
||||||
|
BOOST_CHECK_EQUAL(health.total(), mock.maxHealth * mock.baseAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkEmptyHealth(const CHealth & health, const CUnitHealthInfoMock & mock)
|
||||||
|
{
|
||||||
|
checkTotal(health, mock);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), 0);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), 0);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
BOOST_CHECK_EQUAL(health.available(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkFullHealth(const CHealth & health, const CUnitHealthInfoMock & mock)
|
||||||
|
{
|
||||||
|
checkTotal(health, mock);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), mock.baseAmount);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), mock.maxHealth);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
BOOST_CHECK_EQUAL(health.available(), mock.maxHealth * mock.baseAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkDamage(CHealth & health, const int32_t initialDamage, const int32_t expectedDamage)
|
||||||
|
{
|
||||||
|
int32_t damage = initialDamage;
|
||||||
|
health.damage(damage);
|
||||||
|
BOOST_CHECK_EQUAL(damage, expectedDamage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkNormalDamage(CHealth & health, const int32_t initialDamage)
|
||||||
|
{
|
||||||
|
checkDamage(health, initialDamage, initialDamage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkNoDamage(CHealth & health, const int32_t initialDamage)
|
||||||
|
{
|
||||||
|
checkDamage(health, initialDamage, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkHeal(CHealth & health, EHealLevel level, EHealPower power, const int32_t initialHeal, const int32_t expectedHeal)
|
||||||
|
{
|
||||||
|
int32_t heal = initialHeal;
|
||||||
|
health.heal(heal, level, power);
|
||||||
|
BOOST_CHECK_EQUAL(heal, expectedHeal);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(CHealthTest_Suite)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(empty)
|
||||||
|
{
|
||||||
|
CUnitHealthInfoMock uhi;
|
||||||
|
CHealth health(&uhi);
|
||||||
|
checkEmptyHealth(health, uhi);
|
||||||
|
|
||||||
|
health.init();
|
||||||
|
checkFullHealth(health, uhi);
|
||||||
|
|
||||||
|
health.reset();
|
||||||
|
checkEmptyHealth(health, uhi);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(damage, CUnitHealthInfoMock)
|
||||||
|
{
|
||||||
|
checkNormalDamage(health, 0);
|
||||||
|
checkFullHealth(health, *this);
|
||||||
|
|
||||||
|
checkNormalDamage(health, maxHealth - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkNormalDamage(health, 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH * (UNIT_AMOUNT - 1));
|
||||||
|
checkEmptyHealth(health, *this);
|
||||||
|
|
||||||
|
checkNoDamage(health, 1337);
|
||||||
|
checkEmptyHealth(health, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(heal, CUnitHealthInfoMock)
|
||||||
|
{
|
||||||
|
checkNormalDamage(health, 99);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH-99);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::HEAL, EHealPower::PERMANENT, 9, 9);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH-90);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::RESURRECT, EHealPower::ONE_BATTLE, 40, 40);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH-50);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::OVERHEAL, EHealPower::PERMANENT, 50, 50);
|
||||||
|
checkFullHealth(health, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(resurrectOneBattle, CUnitHealthInfoMock)
|
||||||
|
{
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::RESURRECT, EHealPower::ONE_BATTLE, UNIT_HEALTH, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 1);
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
health.init();
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
health.takeResurrected();
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
health.init();
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH * UNIT_AMOUNT);
|
||||||
|
checkEmptyHealth(health, *this);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::RESURRECT, EHealPower::ONE_BATTLE, UNIT_HEALTH * UNIT_AMOUNT, UNIT_HEALTH * UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), UNIT_AMOUNT);
|
||||||
|
|
||||||
|
health.takeResurrected();
|
||||||
|
checkEmptyHealth(health, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(resurrectPermanent, CUnitHealthInfoMock)
|
||||||
|
{
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::RESURRECT, EHealPower::PERMANENT, UNIT_HEALTH, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getCount(), UNIT_AMOUNT - 1);
|
||||||
|
BOOST_CHECK_EQUAL(health.getFirstHPleft(), UNIT_HEALTH);
|
||||||
|
BOOST_CHECK_EQUAL(health.getResurrected(), 0);
|
||||||
|
|
||||||
|
health.init();
|
||||||
|
|
||||||
|
checkNormalDamage(health, UNIT_HEALTH * UNIT_AMOUNT);
|
||||||
|
checkEmptyHealth(health, *this);
|
||||||
|
|
||||||
|
checkHeal(health, EHealLevel::RESURRECT, EHealPower::PERMANENT, UNIT_HEALTH * UNIT_AMOUNT, UNIT_HEALTH * UNIT_AMOUNT);
|
||||||
|
checkFullHealth(health, *this);
|
||||||
|
|
||||||
|
health.takeResurrected();
|
||||||
|
checkFullHealth(health, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user