mirror of
https://github.com/vcmi/vcmi.git
synced 2025-06-17 00:07:41 +02:00
Unified CStack ammo, casts and counterattacks
* it is possible now to add casts and shoots OTF (f.e. with spell bonus) Centralized stack 'ammo' loading from bonus system. * introduced small proxy class for local bonus cache (no need to use global cache if particular selector used on node only in one place) * handle killing resurrected creatures * use IBonusBearer::MaxHealth() where possible * Fixed https://bugs.vcmi.eu/view.php?id=2486 * Possible fix for 0 HP after resurrection. * Hack-fixed https://bugs.vcmi.eu/view.php?id=2584 * Unified CStack health API * Use CHealth for CStack count and health points * increased SERIALIZATION_VERSION
This commit is contained in:
@ -1248,12 +1248,11 @@ DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs)
|
||||
s->state -= EBattleStackState::HAD_MORALE;
|
||||
s->state -= EBattleStackState::FEAR;
|
||||
s->state -= EBattleStackState::DRAINED_MANA;
|
||||
s->counterAttacksPerformed = 0;
|
||||
s->counterAttacksTotalCache = 0;
|
||||
s->counterAttacks.reset();
|
||||
// new turn effects
|
||||
s->updateBonuses(Bonus::NTurns);
|
||||
|
||||
if(s->alive() && vstd::contains(s->state, EBattleStackState::CLONED))
|
||||
if(s->alive() && s->isClone())
|
||||
{
|
||||
//cloned stack has special lifetime marker
|
||||
//check it after bonuses updated in battleTurnPassed()
|
||||
@ -1281,36 +1280,40 @@ DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs)
|
||||
|
||||
DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
||||
{
|
||||
CStack *st = gs->curB->getStack(stackID);
|
||||
switch (effect)
|
||||
CStack * st = gs->curB->getStack(stackID);
|
||||
assert(st);
|
||||
switch(effect)
|
||||
{
|
||||
case Bonus::HP_REGENERATION:
|
||||
st->firstHPleft += val;
|
||||
vstd::amin (st->firstHPleft, (ui32)st->MaxHealth());
|
||||
break;
|
||||
case Bonus::MANA_DRAIN:
|
||||
{
|
||||
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
|
||||
st->state.insert (EBattleStackState::DRAINED_MANA);
|
||||
h->mana -= val;
|
||||
vstd::amax(h->mana, 0);
|
||||
break;
|
||||
}
|
||||
case Bonus::POISON:
|
||||
{
|
||||
auto b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
|
||||
.And(Selector::type(Bonus::STACK_HEALTH)));
|
||||
if (b)
|
||||
b->val = val;
|
||||
break;
|
||||
}
|
||||
case Bonus::ENCHANTER:
|
||||
break;
|
||||
case Bonus::FEAR:
|
||||
st->state.insert(EBattleStackState::FEAR);
|
||||
break;
|
||||
default:
|
||||
logNetwork->warnStream() << "Unrecognized trigger effect type "<< effect;
|
||||
case Bonus::HP_REGENERATION:
|
||||
{
|
||||
int32_t toHeal = val;
|
||||
CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
|
||||
st->setHealth(health);
|
||||
break;
|
||||
}
|
||||
case Bonus::MANA_DRAIN:
|
||||
{
|
||||
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
|
||||
st->state.insert (EBattleStackState::DRAINED_MANA);
|
||||
h->mana -= val;
|
||||
vstd::amax(h->mana, 0);
|
||||
break;
|
||||
}
|
||||
case Bonus::POISON:
|
||||
{
|
||||
auto b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
|
||||
.And(Selector::type(Bonus::STACK_HEALTH)));
|
||||
if (b)
|
||||
b->val = val;
|
||||
break;
|
||||
}
|
||||
case Bonus::ENCHANTER:
|
||||
break;
|
||||
case Bonus::FEAR:
|
||||
st->state.insert(EBattleStackState::FEAR);
|
||||
break;
|
||||
default:
|
||||
logNetwork->warnStream() << "Unrecognized trigger effect type "<< effect;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1393,11 +1396,14 @@ void BattleStackMoved::applyGs(CGameState *gs)
|
||||
|
||||
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
||||
{
|
||||
CStack *at = gs->curB->getStack(stackAttacked);
|
||||
CStack * at = gs->curB->getStack(stackAttacked);
|
||||
assert(at);
|
||||
at->popBonuses(Bonus::UntilBeingAttacked);
|
||||
at->count = newAmount;
|
||||
at->firstHPleft = newHP;
|
||||
|
||||
if(willRebirth())
|
||||
at->health.reset();//kill stack first
|
||||
else
|
||||
at->setHealth(newHealth);
|
||||
|
||||
if(killed())
|
||||
{
|
||||
@ -1414,16 +1420,29 @@ DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
||||
}
|
||||
}
|
||||
//life drain handling
|
||||
for (auto & elem : healedStacks)
|
||||
{
|
||||
for(auto & elem : healedStacks)
|
||||
elem.applyGs(gs);
|
||||
}
|
||||
if (willRebirth())
|
||||
|
||||
if(willRebirth())
|
||||
{
|
||||
at->casts--;
|
||||
at->state.insert(EBattleStackState::ALIVE); //hmm?
|
||||
//TODO: handle rebirth with StacksHealedOrResurrected
|
||||
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
|
||||
at->makeGhost();
|
||||
@ -1437,35 +1456,20 @@ DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
||||
|
||||
//killed summoned creature should be removed like clone
|
||||
if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
|
||||
{
|
||||
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())
|
||||
attacker->counterAttacksPerformed++;
|
||||
attacker->counterAttacks.use();
|
||||
|
||||
if(shot())
|
||||
{
|
||||
//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;
|
||||
}
|
||||
}
|
||||
attacker->shots.use();
|
||||
|
||||
if (!hasAmmoCart)
|
||||
{
|
||||
attacker->shots--;
|
||||
}
|
||||
}
|
||||
for(BattleStackAttacked & stackAttacked : bsa)
|
||||
stackAttacked.applyGs(gs);
|
||||
|
||||
@ -1620,7 +1624,8 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
||||
{
|
||||
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
|
||||
auto accessibility = gs->curB->getAccesibility();
|
||||
@ -1635,29 +1640,14 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
||||
bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed
|
||||
if(resurrected)
|
||||
{
|
||||
if(changedStack->count > 0 || changedStack->firstHPleft > 0)
|
||||
if(changedStack->getCount() > 0 || changedStack->getFirstHPleft() > 0)
|
||||
logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), changedStack->totalHealth());
|
||||
|
||||
changedStack->state.insert(EBattleStackState::ALIVE);
|
||||
}
|
||||
int res;
|
||||
if(canOverheal) //for example WoG ghost soul steal ability allows getting more units than before battle
|
||||
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());
|
||||
|
||||
changedStack->setHealth(elem);
|
||||
|
||||
if(resurrected)
|
||||
{
|
||||
//removing all spells effects
|
||||
@ -1675,7 +1665,7 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
|
||||
else if(cure)
|
||||
{
|
||||
//removing all effects from negative spells
|
||||
auto selector = [](const Bonus* b)
|
||||
auto selector = [](const Bonus * b)
|
||||
{
|
||||
//Special case: DISRUPTING_RAY is "immune" to dispell
|
||||
//Other even PERMANENT effects can be removed
|
||||
@ -1796,7 +1786,7 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs(CGameState *gs)
|
||||
DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
||||
{
|
||||
newStackID = 0;
|
||||
if (!BattleHex(pos).isValid())
|
||||
if(!BattleHex(pos).isValid())
|
||||
{
|
||||
logNetwork->warnStream() << "No place found for new stack!";
|
||||
return;
|
||||
@ -1804,33 +1794,32 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
|
||||
|
||||
CStackBasicDescriptor csbd(creID, amount);
|
||||
CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
|
||||
if (summoned)
|
||||
if(summoned)
|
||||
addedStack->state.insert(EBattleStackState::SUMMONED);
|
||||
|
||||
gs->curB->localInitStack(addedStack);
|
||||
gs->curB->stacks.push_back(addedStack); //the stack is not "SUMMONED", it is permanent
|
||||
addedStack->localInit(gs->curB.get());
|
||||
gs->curB->stacks.push_back(addedStack);
|
||||
|
||||
newStackID = addedStack->ID;
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
|
||||
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
|
||||
{
|
||||
CStack * stack = gs->curB->getStack(stackID);
|
||||
switch (which)
|
||||
switch(which)
|
||||
{
|
||||
case CASTS:
|
||||
{
|
||||
if (absolute)
|
||||
stack->casts = val;
|
||||
if(absolute)
|
||||
logNetwork->error("Can not change casts in absolute mode");
|
||||
else
|
||||
stack->casts += val;
|
||||
vstd::amax(stack->casts, 0);
|
||||
stack->casts.use(val);
|
||||
break;
|
||||
}
|
||||
case ENCHANTER_COUNTER:
|
||||
{
|
||||
auto & counter = gs->curB->sides[gs->curB->whatSide(stack->owner)].enchanterCounter;
|
||||
if (absolute)
|
||||
if(absolute)
|
||||
counter = val;
|
||||
else
|
||||
counter += val;
|
||||
|
Reference in New Issue
Block a user