2023-10-19 16:19:09 +02:00
/*
* 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>
2024-07-20 12:55:17 +00:00
# include "texts/CGeneralTextHandler.h"
2023-10-19 16:19:09 +02:00
# include "battle/BattleInfo.h"
# include "spells/CSpellHandler.h"
2023-10-23 13:59:15 +03:00
# include "networkPacks/PacksForClientBattle.h"
2023-10-19 16:19:09 +02:00
VCMI_LIB_NAMESPACE_BEGIN
///CStack
2024-08-11 20:22:35 +00:00
CStack : : CStack ( const CStackInstance * Base , const PlayerColor & O , int I , BattleSide Side , const SlotID & S ) :
2023-10-19 16:19:09 +02:00
CBonusSystemNode ( STACK_BATTLE ) ,
base ( Base ) ,
ID ( I ) ,
2024-10-12 16:52:58 +00:00
typeID ( Base - > getId ( ) ) ,
2023-10-19 16:19:09 +02:00
baseAmount ( Base - > count ) ,
owner ( O ) ,
slot ( S ) ,
side ( Side )
{
health . init ( ) ; //???
2025-01-10 15:07:59 +00:00
doubleWideCached = battle : : CUnitState : : doubleWide ( ) ;
2023-10-19 16:19:09 +02:00
}
CStack : : CStack ( ) :
CBonusSystemNode ( STACK_BATTLE ) ,
owner ( PlayerColor : : NEUTRAL ) ,
slot ( SlotID ( 255 ) ) ,
initialPosition ( BattleHex ( ) )
{
}
2024-08-11 20:22:35 +00:00
CStack : : CStack ( const CStackBasicDescriptor * stack , const PlayerColor & O , int I , BattleSide Side , const SlotID & S ) :
2023-10-19 16:19:09 +02:00
CBonusSystemNode ( STACK_BATTLE ) ,
ID ( I ) ,
2024-10-12 16:52:58 +00:00
typeID ( stack - > getId ( ) ) ,
2023-10-19 16:19:09 +02:00
baseAmount ( stack - > count ) ,
owner ( O ) ,
slot ( S ) ,
side ( Side )
{
health . init ( ) ; //???
2025-01-10 15:07:59 +00:00
doubleWideCached = battle : : CUnitState : : doubleWide ( ) ;
2023-10-19 16:19:09 +02:00
}
void CStack : : localInit ( BattleInfo * battleInfo )
{
battle = battleInfo ;
2024-10-12 16:52:58 +00:00
assert ( typeID . hasValue ( ) ) ;
2023-10-19 16:19:09 +02:00
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 ) ;
2024-10-12 16:52:58 +00:00
attachToSource ( * typeID . toCreature ( ) ) ;
2023-10-19 16:19:09 +02:00
}
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 ;
}
}
2023-10-22 18:36:41 +03:00
std : : vector < SpellID > CStack : : activeSpells ( ) const
2023-10-19 16:19:09 +02:00
{
2023-10-22 18:36:41 +03:00
std : : vector < SpellID > ret ;
2023-10-19 16:19:09 +02:00
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
{
2023-10-22 18:36:41 +03:00
return b - > type ! = BonusType : : NONE & & b - > sid . as < SpellID > ( ) . toSpell ( ) & & ! b - > sid . as < SpellID > ( ) . toSpell ( ) - > isAdventure ( ) ;
2023-10-19 16:19:09 +02:00
} ) ) ;
TConstBonusListPtr spellEffects = getBonuses ( selector , Selector : : all , cachingStr . str ( ) ) ;
for ( const auto & it : * spellEffects )
{
2023-10-22 18:36:41 +03:00
if ( ! vstd : : contains ( ret , it - > sid . as < SpellID > ( ) ) ) //do not duplicate spells with multiple effects
ret . push_back ( it - > sid . as < SpellID > ( ) ) ;
2023-10-19 16:19:09 +02:00
}
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 " ;
2024-10-12 16:52:58 +00:00
if ( typeID . hasValue ( ) )
2025-02-14 16:23:37 +00:00
oss < < typeID . toEntity ( LIBRARY ) - > getJsonKey ( ) ;
2023-10-19 16:19:09 +02:00
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 ) ) ;
for ( int32_t i = 0 ; i < resurrectedAdd ; i + + )
{
2024-06-01 13:36:38 +00:00
if ( resurrectValue > rand . nextInt ( 0 , 99 ) )
2023-10-19 16:19:09 +02:00
resurrectedCount + = 1 ;
}
2023-10-22 18:36:41 +03:00
if ( customState - > hasBonusOfType ( BonusType : : REBIRTH , BonusCustomSubtype : : rebirthSpecial ) )
2023-10-19 16:19:09 +02:00
{
// 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 ;
}
2024-09-28 20:50:26 +02:00
BattleHexArray CStack : : meleeAttackHexes ( const battle : : Unit * attacker , const battle : : Unit * defender , BattleHex attackerPos , BattleHex defenderPos )
2023-10-19 16:19:09 +02:00
{
int mask = 0 ;
2024-09-28 20:50:26 +02:00
BattleHexArray res ;
2023-10-19 16:19:09 +02:00
if ( ! attackerPos . isValid ( ) )
attackerPos = attacker - > getPosition ( ) ;
if ( ! defenderPos . isValid ( ) )
defenderPos = defender - > getPosition ( ) ;
2025-01-06 23:05:45 +01:00
BattleHex otherAttackerPos = attackerPos . toInt ( ) + ( attacker - > unitSide ( ) = = BattleSide : : ATTACKER ? - 1 : 1 ) ;
BattleHex otherDefenderPos = defenderPos . toInt ( ) + ( defender - > unitSide ( ) = = BattleSide : : ATTACKER ? - 1 : 1 ) ;
2023-10-19 16:19:09 +02:00
if ( BattleHex : : mutualPosition ( attackerPos , defenderPos ) > = 0 ) //front <=> front
{
if ( ( mask & 1 ) = = 0 )
{
mask | = 1 ;
2024-09-28 20:50:26 +02:00
res . insert ( defenderPos ) ;
2023-10-19 16:19:09 +02:00
}
}
if ( attacker - > doubleWide ( ) //back <=> front
& & BattleHex : : mutualPosition ( otherAttackerPos , defenderPos ) > = 0 )
{
if ( ( mask & 1 ) = = 0 )
{
mask | = 1 ;
2024-09-28 20:50:26 +02:00
res . insert ( defenderPos ) ;
2023-10-19 16:19:09 +02:00
}
}
if ( defender - > doubleWide ( ) //front <=> back
& & BattleHex : : mutualPosition ( attackerPos , otherDefenderPos ) > = 0 )
{
if ( ( mask & 2 ) = = 0 )
{
mask | = 2 ;
2024-09-28 20:50:26 +02:00
res . insert ( otherDefenderPos ) ;
2023-10-19 16:19:09 +02:00
}
}
if ( defender - > doubleWide ( ) & & attacker - > doubleWide ( ) //back <=> back
& & BattleHex : : mutualPosition ( otherAttackerPos , otherDefenderPos ) > = 0 )
{
if ( ( mask & 2 ) = = 0 )
{
mask | = 2 ;
2024-09-28 20:50:26 +02:00
res . insert ( otherDefenderPos ) ;
2023-10-19 16:19:09 +02:00
}
}
return res ;
}
bool CStack : : isMeleeAttackPossible ( const battle : : Unit * attacker , const battle : : Unit * defender , BattleHex attackerPos , BattleHex defenderPos )
{
2025-01-10 21:15:37 +00:00
if ( defender - > isInvincible ( ) )
2024-09-19 03:14:45 +02:00
return false ;
2023-10-19 16:19:09 +02:00
return ! meleeAttackHexes ( attacker , defender , attackerPos , defenderPos ) . empty ( ) ;
}
std : : string CStack : : getName ( ) const
{
2025-02-14 16:23:37 +00:00
return ( getCount ( ) = = 1 ) ? typeID . toEntity ( LIBRARY ) - > getNameSingularTranslated ( ) : typeID . toEntity ( LIBRARY ) - > getNamePluralTranslated ( ) ; //War machines can't use base
2023-10-19 16:19:09 +02:00
}
bool CStack : : canBeHealed ( ) const
{
return getFirstHPleft ( ) < static_cast < int32_t > ( getMaxHealth ( ) ) & & isValidTarget ( ) & & ! hasBonusOfType ( BonusType : : SIEGE_WEAPON ) ;
}
bool CStack : : isOnNativeTerrain ( ) const
{
2025-04-13 22:23:12 +03:00
auto nativeTerrain = getNativeTerrain ( ) ;
return nativeTerrain = = ETerrainId : : ANY_TERRAIN | | getCurrentTerrain ( ) = = nativeTerrain ;
2023-10-19 16:19:09 +02:00
}
2025-04-13 22:23:12 +03:00
TerrainId CStack : : getCurrentTerrain ( ) const
2023-10-19 16:19:09 +02:00
{
2025-04-13 22:23:12 +03:00
return battle - > getTerrainType ( ) ;
2023-10-19 16:19:09 +02:00
}
const CCreature * CStack : : unitType ( ) const
{
2024-10-12 16:52:58 +00:00
return typeID . toCreature ( ) ;
2023-10-19 16:19:09 +02:00
}
int32_t CStack : : unitBaseAmount ( ) const
{
return baseAmount ;
}
const IBonusBearer * CStack : : getBonusBearer ( ) const
{
return this ;
}
bool CStack : : unitHasAmmoCart ( const battle : : Unit * unit ) const
{
2025-03-17 21:21:39 +00:00
for ( const auto & st : battle - > stacks )
2023-10-19 16:19:09 +02:00
{
2025-03-17 21:21:39 +00:00
if ( battle - > battleMatchOwner ( st . get ( ) , unit , true ) & & st - > unitType ( ) - > getId ( ) = = CreatureID : : AMMO_CART )
2023-10-19 16:19:09 +02:00
{
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 ( ) )
{
2024-10-12 08:41:59 +00:00
if ( battle - > battleGetOwnerHero ( unit ) - > artifactsWorn . at ( ArtifactPosition : : MACH2 ) . artifact - > getTypeId ( ) = = ArtifactID : : AMMO_CART )
2023-10-19 16:19:09 +02:00
{
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 ;
}
2024-08-11 20:22:35 +00:00
BattleSide CStack : : unitSide ( ) const
2023-10-19 16:19:09 +02:00
{
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 ;
2024-10-04 18:59:51 +00:00
server - > apply ( ssp ) ;
2023-10-19 16:19:09 +02:00
}
2025-01-10 15:07:59 +00:00
void CStack : : postDeserialize ( const CArmedInstance * army , const SlotID & extSlot )
{
if ( extSlot = = SlotID : : COMMANDER_SLOT_PLACEHOLDER )
{
const auto * hero = dynamic_cast < const CGHeroInstance * > ( army ) ;
assert ( hero ) ;
2025-03-09 21:51:33 +00:00
base = hero - > getCommander ( ) ;
2025-01-10 15:07:59 +00:00
}
else if ( slot = = SlotID : : SUMMONED_SLOT_PLACEHOLDER | | slot = = SlotID : : ARROW_TOWERS_SLOT | | slot = = SlotID : : WAR_MACHINES_SLOT )
{
//no external slot possible, so no base stack
base = nullptr ;
}
else if ( ! army | | extSlot = = SlotID ( ) | | ! army - > hasStackAtSlot ( extSlot ) )
{
base = nullptr ;
2025-02-14 16:23:37 +00:00
logGlobal - > warn ( " %s doesn't have a base stack! " , typeID . toEntity ( LIBRARY ) - > getNameSingularTranslated ( ) ) ;
2025-01-10 15:07:59 +00:00
}
else
{
base = & army - > getStack ( extSlot ) ;
}
doubleWideCached = battle : : CUnitState : : doubleWide ( ) ;
}
2023-10-19 16:19:09 +02:00
VCMI_LIB_NAMESPACE_END