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
}
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 ;
}
}
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
{
//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
{
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
{
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 ())
{
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 );
base = hero -> commander ;
}
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