1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-30 08:57:00 +02:00
vcmi/lib/CStack.cpp
2024-01-19 13:54:49 +02:00

407 lines
10 KiB
C++

/*
* CStack.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 "CStack.h"
#include <vstd/RNG.h>
#include <vcmi/Entity.h>
#include <vcmi/ServerCallback.h>
#include "CGeneralTextHandler.h"
#include "battle/BattleInfo.h"
#include "spells/CSpellHandler.h"
#include "networkPacks/PacksForClientBattle.h"
VCMI_LIB_NAMESPACE_BEGIN
///CStack
CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, ui8 Side, const SlotID & S):
CBonusSystemNode(STACK_BATTLE),
base(Base),
ID(I),
type(Base->type),
baseAmount(Base->count),
owner(O),
slot(S),
side(Side)
{
health.init(); //???
}
CStack::CStack():
CBonusSystemNode(STACK_BATTLE),
owner(PlayerColor::NEUTRAL),
slot(SlotID(255)),
initialPosition(BattleHex())
{
}
CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, ui8 Side, const SlotID & S):
CBonusSystemNode(STACK_BATTLE),
ID(I),
type(stack->type),
baseAmount(stack->count),
owner(O),
slot(S),
side(Side)
{
health.init(); //???
}
void CStack::localInit(BattleInfo * battleInfo)
{
battle = battleInfo;
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);
assert(army);
attachTo(*army);
attachToSource(*type);
}
nativeTerrain = getNativeTerrain(); //save nativeTerrain in the variable on the battle start to avoid dead lock
CUnitState::localInit(this); //it causes execution of the CStack::isOnNativeTerrain where nativeTerrain will be considered
position = initialPosition;
}
ui32 CStack::level() const
{
if(base)
return base->getLevel(); //creature or commander
else
return std::max(1, static_cast<int>(unitType()->getLevel())); //war machine, clone etc
}
si32 CStack::magicResistance() const
{
auto magicResistance = AFactionMember::magicResistance();
si32 auraBonus = 0;
for(const auto * one : battle->battleAdjacentUnits(this))
{
if(one->unitOwner() == owner)
vstd::amax(auraBonus, one->valOfBonuses(BonusType::SPELL_RESISTANCE_AURA)); //max value
}
vstd::abetween(auraBonus, 0, 100);
vstd::abetween(magicResistance, 0, 100);
float castChance = (100 - magicResistance) * (100 - auraBonus)/100.0;
return static_cast<si32>(100 - castChance);
}
BattleHex::EDir CStack::destShiftDir() const
{
if(doubleWide())
{
if(side == BattleSide::ATTACKER)
return BattleHex::EDir::RIGHT;
else
return BattleHex::EDir::LEFT;
}
else
{
return BattleHex::EDir::NONE;
}
}
std::vector<SpellID> CStack::activeSpells() const
{
std::vector<SpellID> ret;
std::stringstream cachingStr;
cachingStr << "!type_" << vstd::to_underlying(BonusType::NONE) << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT);
CSelector selector = Selector::sourceType()(BonusSource::SPELL_EFFECT)
.And(CSelector([](const Bonus * b)->bool
{
return b->type != BonusType::NONE && b->sid.as<SpellID>().toSpell() && !b->sid.as<SpellID>().toSpell()->isAdventure();
}));
TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
for(const auto & it : *spellEffects)
{
if(!vstd::contains(ret, it->sid.as<SpellID>())) //do not duplicate spells with multiple effects
ret.push_back(it->sid.as<SpellID>());
}
return ret;
}
CStack::~CStack()
{
detachFromAll();
}
const CGHeroInstance * CStack::getMyHero() const
{
if(base)
return dynamic_cast<const CGHeroInstance *>(base->armyObj);
else //we are attached directly?
for(const CBonusSystemNode * n : getParentNodes())
if(n->getNodeType() == HERO)
return dynamic_cast<const CGHeroInstance *>(n);
return nullptr;
}
std::string CStack::nodeName() const
{
std::ostringstream oss;
oss << owner.toString();
oss << " battle stack [" << ID << "]: " << getCount() << " of ";
if(type)
oss << type->getNamePluralTextID();
else
oss << "[UNDEFINED TYPE]";
oss << " from slot " << slot;
if(base && base->armyObj)
oss << " of armyobj=" << base->armyObj->id.getNum();
return oss.str();
}
void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const
{
auto newState = acquireState();
prepareAttacked(bsa, rand, newState);
}
void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const std::shared_ptr<battle::CUnitState> & customState)
{
auto initialCount = customState->getCount();
// compute damage and update bsa.damageAmount
customState->damage(bsa.damageAmount);
bsa.killedAmount = initialCount - customState->getCount();
if(!customState->alive() && customState->isClone())
{
bsa.flags |= BattleStackAttacked::CLONE_KILLED;
}
else if(!customState->alive()) //stack killed
{
bsa.flags |= BattleStackAttacked::KILLED;
auto resurrectValue = customState->valOfBonuses(BonusType::REBIRTH);
if(resurrectValue > 0 && customState->canCast()) //there must be casts left
{
double resurrectFactor = resurrectValue / 100.0;
auto baseAmount = customState->unitBaseAmount();
double resurrectedRaw = baseAmount * resurrectFactor;
auto resurrectedCount = static_cast<int32_t>(floor(resurrectedRaw));
auto resurrectedAdd = static_cast<int32_t>(baseAmount - (resurrectedCount / resurrectFactor));
auto rangeGen = rand.getInt64Range(0, 99);
for(int32_t i = 0; i < resurrectedAdd; i++)
{
if(resurrectValue > rangeGen())
resurrectedCount += 1;
}
if(customState->hasBonusOfType(BonusType::REBIRTH, BonusCustomSubtype::rebirthSpecial))
{
// resurrect at least one Sacred Phoenix
vstd::amax(resurrectedCount, 1);
}
if(resurrectedCount > 0)
{
customState->casts.use();
bsa.flags |= BattleStackAttacked::REBIRTH;
int64_t toHeal = customState->getMaxHealth() * resurrectedCount;
//TODO: add one-battle rebirth?
customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
customState->counterAttacks.use(customState->counterAttacks.available());
}
}
}
customState->save(bsa.newState.data);
bsa.newState.healthDelta = -bsa.damageAmount;
bsa.newState.id = customState->unitId();
bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
}
std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
{
int mask = 0;
std::vector<BattleHex> res;
if (!attackerPos.isValid())
attackerPos = attacker->getPosition();
if (!defenderPos.isValid())
defenderPos = defender->getPosition();
BattleHex otherAttackerPos = attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1);
BattleHex otherDefenderPos = defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1);
if(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front
{
if((mask & 1) == 0)
{
mask |= 1;
res.push_back(defenderPos);
}
}
if (attacker->doubleWide() //back <=> front
&& BattleHex::mutualPosition(otherAttackerPos, defenderPos) >= 0)
{
if((mask & 1) == 0)
{
mask |= 1;
res.push_back(defenderPos);
}
}
if (defender->doubleWide()//front <=> back
&& BattleHex::mutualPosition(attackerPos, otherDefenderPos) >= 0)
{
if((mask & 2) == 0)
{
mask |= 2;
res.push_back(otherDefenderPos);
}
}
if (defender->doubleWide() && attacker->doubleWide()//back <=> back
&& BattleHex::mutualPosition(otherAttackerPos, otherDefenderPos) >= 0)
{
if((mask & 2) == 0)
{
mask |= 2;
res.push_back(otherDefenderPos);
}
}
return res;
}
bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
{
return !meleeAttackHexes(attacker, defender, attackerPos, defenderPos).empty();
}
std::string CStack::getName() const
{
return (getCount() == 1) ? type->getNameSingularTranslated() : type->getNamePluralTranslated(); //War machines can't use base
}
bool CStack::canBeHealed() const
{
return getFirstHPleft() < static_cast<int32_t>(getMaxHealth()) && isValidTarget() && !hasBonusOfType(BonusType::SIEGE_WEAPON);
}
bool CStack::isOnNativeTerrain() const
{
//this code is called from CreatureTerrainLimiter::limit on battle start
auto res = nativeTerrain == ETerrainId::ANY_TERRAIN || nativeTerrain == battle->getTerrainType();
return res;
}
bool CStack::isOnTerrain(TerrainId terrain) const
{
return battle->getTerrainType() == terrain;
}
const CCreature * CStack::unitType() const
{
return type;
}
int32_t CStack::unitBaseAmount() const
{
return baseAmount;
}
const IBonusBearer* CStack::getBonusBearer() const
{
return this;
}
bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
{
for(const CStack * st : battle->stacks)
{
if(battle->battleMatchOwner(st, unit, true) && st->unitType()->getId() == CreatureID::AMMO_CART)
{
return st->alive();
}
}
//ammo cart works during creature bank battle while not on battlefield
const auto * ownerHero = battle->battleGetOwnerHero(unit);
if(ownerHero && ownerHero->artifactsWorn.find(ArtifactPosition::MACH2) != ownerHero->artifactsWorn.end())
{
if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->getId() == ArtifactID::AMMO_CART)
{
return true;
}
}
return false; //will be always false if trying to examine enemy hero in "special battle"
}
PlayerColor CStack::unitEffectiveOwner(const battle::Unit * unit) const
{
return battle->battleGetOwner(unit);
}
uint32_t CStack::unitId() const
{
return ID;
}
ui8 CStack::unitSide() const
{
return side;
}
PlayerColor CStack::unitOwner() const
{
return owner;
}
SlotID CStack::unitSlot() const
{
return slot;
}
std::string CStack::getDescription() const
{
return nodeName();
}
void CStack::spendMana(ServerCallback * server, const int spellCost) const
{
if(spellCost != 1)
logGlobal->warn("Unexpected spell cost %d for creature", spellCost);
BattleSetStackProperty ssp;
ssp.battleID = battle->battleID;
ssp.stackID = unitId();
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -spellCost;
ssp.absolute = false;
server->apply(&ssp);
}
VCMI_LIB_NAMESPACE_END