CStack.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /*
  2. * CStack.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CStack.h"
  12. #include <vstd/RNG.h>
  13. #include <vcmi/ServerCallback.h>
  14. #include "CGeneralTextHandler.h"
  15. #include "battle/BattleInfo.h"
  16. #include "spells/CSpellHandler.h"
  17. #include "NetPacks.h"
  18. VCMI_LIB_NAMESPACE_BEGIN
  19. ///CStack
  20. CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, ui8 Side, const SlotID & S):
  21. CBonusSystemNode(STACK_BATTLE),
  22. base(Base),
  23. ID(I),
  24. type(Base->type),
  25. baseAmount(base->count),
  26. owner(O),
  27. slot(S),
  28. side(Side)
  29. {
  30. health.init(); //???
  31. }
  32. CStack::CStack():
  33. CBonusSystemNode(STACK_BATTLE),
  34. owner(PlayerColor::NEUTRAL),
  35. slot(SlotID(255)),
  36. initialPosition(BattleHex())
  37. {
  38. }
  39. CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, ui8 Side, const SlotID & S):
  40. CBonusSystemNode(STACK_BATTLE),
  41. ID(I),
  42. type(stack->type),
  43. baseAmount(stack->count),
  44. owner(O),
  45. slot(S),
  46. side(Side)
  47. {
  48. health.init(); //???
  49. }
  50. void CStack::localInit(BattleInfo * battleInfo)
  51. {
  52. battle = battleInfo;
  53. assert(type);
  54. exportBonuses();
  55. if(base) //stack originating from "real" stack in garrison -> attach to it
  56. {
  57. attachTo(const_cast<CStackInstance&>(*base));
  58. }
  59. else //attach directly to obj to which stack belongs and creature type
  60. {
  61. CArmedInstance * army = battle->battleGetArmyObject(side);
  62. assert(army);
  63. attachTo(*army);
  64. attachTo(const_cast<CCreature&>(*type));
  65. }
  66. nativeTerrain = getNativeTerrain(); //save nativeTerrain in the variable on the battle start to avoid dead lock
  67. CUnitState::localInit(this); //it causes execution of the CStack::isOnNativeTerrain where nativeTerrain will be considered
  68. position = initialPosition;
  69. }
  70. ui32 CStack::level() const
  71. {
  72. if(base)
  73. return base->getLevel(); //creature or commander
  74. else
  75. return std::max(1, static_cast<int>(unitType()->getLevel())); //war machine, clone etc
  76. }
  77. si32 CStack::magicResistance() const
  78. {
  79. auto magicResistance = IBonusBearer::magicResistance();
  80. si32 auraBonus = 0;
  81. for(const auto * one : battle->battleAdjacentUnits(this))
  82. {
  83. if(one->unitOwner() == owner)
  84. vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
  85. }
  86. vstd::abetween(auraBonus, 0, 100);
  87. vstd::abetween(magicResistance, 0, 100);
  88. float castChance = (100 - magicResistance) * (100 - auraBonus)/100.0;
  89. return static_cast<si32>(100 - castChance);
  90. }
  91. BattleHex::EDir CStack::destShiftDir() const
  92. {
  93. if(doubleWide())
  94. {
  95. if(side == BattleSide::ATTACKER)
  96. return BattleHex::EDir::RIGHT;
  97. else
  98. return BattleHex::EDir::LEFT;
  99. }
  100. else
  101. {
  102. return BattleHex::EDir::NONE;
  103. }
  104. }
  105. std::vector<si32> CStack::activeSpells() const
  106. {
  107. std::vector<si32> ret;
  108. std::stringstream cachingStr;
  109. cachingStr << "!type_" << Bonus::NONE << "source_" << Bonus::SPELL_EFFECT;
  110. CSelector selector = Selector::sourceType()(Bonus::SPELL_EFFECT)
  111. .And(CSelector([](const Bonus * b)->bool
  112. {
  113. return b->type != Bonus::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure();
  114. }));
  115. TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str());
  116. for(const auto & it : *spellEffects)
  117. {
  118. if(!vstd::contains(ret, it->sid)) //do not duplicate spells with multiple effects
  119. ret.push_back(it->sid);
  120. }
  121. return ret;
  122. }
  123. CStack::~CStack()
  124. {
  125. detachFromAll();
  126. }
  127. const CGHeroInstance * CStack::getMyHero() const
  128. {
  129. if(base)
  130. return dynamic_cast<const CGHeroInstance *>(base->armyObj);
  131. else //we are attached directly?
  132. for(const CBonusSystemNode * n : getParentNodes())
  133. if(n->getNodeType() == HERO)
  134. return dynamic_cast<const CGHeroInstance *>(n);
  135. return nullptr;
  136. }
  137. std::string CStack::nodeName() const
  138. {
  139. std::ostringstream oss;
  140. oss << owner.getStr();
  141. oss << " battle stack [" << ID << "]: " << getCount() << " of ";
  142. if(type)
  143. oss << type->getNamePluralTextID();
  144. else
  145. oss << "[UNDEFINED TYPE]";
  146. oss << " from slot " << slot;
  147. if(base && base->armyObj)
  148. oss << " of armyobj=" << base->armyObj->id.getNum();
  149. return oss.str();
  150. }
  151. void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const
  152. {
  153. auto newState = acquireState();
  154. prepareAttacked(bsa, rand, newState);
  155. }
  156. void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const std::shared_ptr<battle::CUnitState> & customState)
  157. {
  158. auto initialCount = customState->getCount();
  159. // compute damage and update bsa.damageAmount
  160. customState->damage(bsa.damageAmount);
  161. bsa.killedAmount = initialCount - customState->getCount();
  162. if(!customState->alive() && customState->isClone())
  163. {
  164. bsa.flags |= BattleStackAttacked::CLONE_KILLED;
  165. }
  166. else if(!customState->alive()) //stack killed
  167. {
  168. bsa.flags |= BattleStackAttacked::KILLED;
  169. auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH);
  170. if(resurrectValue > 0 && customState->canCast()) //there must be casts left
  171. {
  172. double resurrectFactor = resurrectValue / 100.0;
  173. auto baseAmount = customState->unitBaseAmount();
  174. double resurrectedRaw = baseAmount * resurrectFactor;
  175. auto resurrectedCount = static_cast<int32_t>(floor(resurrectedRaw));
  176. auto resurrectedAdd = static_cast<int32_t>(baseAmount - (resurrectedCount / resurrectFactor));
  177. auto rangeGen = rand.getInt64Range(0, 99);
  178. for(int32_t i = 0; i < resurrectedAdd; i++)
  179. {
  180. if(resurrectValue > rangeGen())
  181. resurrectedCount += 1;
  182. }
  183. if(customState->hasBonusOfType(Bonus::REBIRTH, 1))
  184. {
  185. // resurrect at least one Sacred Phoenix
  186. vstd::amax(resurrectedCount, 1);
  187. }
  188. if(resurrectedCount > 0)
  189. {
  190. customState->casts.use();
  191. bsa.flags |= BattleStackAttacked::REBIRTH;
  192. int64_t toHeal = customState->MaxHealth() * resurrectedCount;
  193. //TODO: add one-battle rebirth?
  194. customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
  195. customState->counterAttacks.use(customState->counterAttacks.available());
  196. }
  197. }
  198. }
  199. customState->save(bsa.newState.data);
  200. bsa.newState.healthDelta = -bsa.damageAmount;
  201. bsa.newState.id = customState->unitId();
  202. bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
  203. }
  204. std::vector<BattleHex> CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
  205. {
  206. int mask = 0;
  207. std::vector<BattleHex> res;
  208. if (!attackerPos.isValid())
  209. attackerPos = attacker->getPosition();
  210. if (!defenderPos.isValid())
  211. defenderPos = defender->getPosition();
  212. BattleHex otherAttackerPos = attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1);
  213. BattleHex otherDefenderPos = defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1);
  214. if(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front
  215. {
  216. if((mask & 1) == 0)
  217. {
  218. mask |= 1;
  219. res.push_back(defenderPos);
  220. }
  221. }
  222. if (attacker->doubleWide() //back <=> front
  223. && BattleHex::mutualPosition(otherAttackerPos, defenderPos) >= 0)
  224. {
  225. if((mask & 1) == 0)
  226. {
  227. mask |= 1;
  228. res.push_back(defenderPos);
  229. }
  230. }
  231. if (defender->doubleWide()//front <=> back
  232. && BattleHex::mutualPosition(attackerPos, otherDefenderPos) >= 0)
  233. {
  234. if((mask & 2) == 0)
  235. {
  236. mask |= 2;
  237. res.push_back(otherDefenderPos);
  238. }
  239. }
  240. if (defender->doubleWide() && attacker->doubleWide()//back <=> back
  241. && BattleHex::mutualPosition(otherAttackerPos, otherDefenderPos) >= 0)
  242. {
  243. if((mask & 2) == 0)
  244. {
  245. mask |= 2;
  246. res.push_back(otherDefenderPos);
  247. }
  248. }
  249. return res;
  250. }
  251. bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
  252. {
  253. return !meleeAttackHexes(attacker, defender, attackerPos, defenderPos).empty();
  254. }
  255. std::string CStack::getName() const
  256. {
  257. return (getCount() == 1) ? type->getNameSingularTranslated() : type->getNamePluralTranslated(); //War machines can't use base
  258. }
  259. bool CStack::canBeHealed() const
  260. {
  261. return getFirstHPleft() < static_cast<int32_t>(MaxHealth()) && isValidTarget() && !hasBonusOfType(Bonus::SIEGE_WEAPON);
  262. }
  263. bool CStack::isOnNativeTerrain() const
  264. {
  265. //this code is called from CreatureTerrainLimiter::limit on battle start
  266. auto res = nativeTerrain == ETerrainId::ANY_TERRAIN || nativeTerrain == battle->getTerrainType();
  267. return res;
  268. }
  269. bool CStack::isOnTerrain(TerrainId terrain) const
  270. {
  271. return battle->getTerrainType() == terrain;
  272. }
  273. const CCreature * CStack::unitType() const
  274. {
  275. return type;
  276. }
  277. int32_t CStack::unitBaseAmount() const
  278. {
  279. return baseAmount;
  280. }
  281. const IBonusBearer* CStack::getBonusBearer() const
  282. {
  283. return this;
  284. }
  285. bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
  286. {
  287. for(const CStack * st : battle->stacks)
  288. {
  289. if(battle->battleMatchOwner(st, unit, true) && st->unitType()->getId() == CreatureID::AMMO_CART)
  290. {
  291. return st->alive();
  292. }
  293. }
  294. //ammo cart works during creature bank battle while not on battlefield
  295. const auto * ownerHero = battle->battleGetOwnerHero(unit);
  296. if(ownerHero && ownerHero->artifactsWorn.find(ArtifactPosition::MACH2) != ownerHero->artifactsWorn.end())
  297. {
  298. if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->getId() == ArtifactID::AMMO_CART)
  299. {
  300. return true;
  301. }
  302. }
  303. return false; //will be always false if trying to examine enemy hero in "special battle"
  304. }
  305. PlayerColor CStack::unitEffectiveOwner(const battle::Unit * unit) const
  306. {
  307. return battle->battleGetOwner(unit);
  308. }
  309. uint32_t CStack::unitId() const
  310. {
  311. return ID;
  312. }
  313. ui8 CStack::unitSide() const
  314. {
  315. return side;
  316. }
  317. PlayerColor CStack::unitOwner() const
  318. {
  319. return owner;
  320. }
  321. SlotID CStack::unitSlot() const
  322. {
  323. return slot;
  324. }
  325. std::string CStack::getDescription() const
  326. {
  327. return nodeName();
  328. }
  329. void CStack::spendMana(ServerCallback * server, const int spellCost) const
  330. {
  331. if(spellCost != 1)
  332. logGlobal->warn("Unexpected spell cost %d for creature", spellCost);
  333. BattleSetStackProperty ssp;
  334. ssp.stackID = unitId();
  335. ssp.which = BattleSetStackProperty::CASTS;
  336. ssp.val = -spellCost;
  337. ssp.absolute = false;
  338. server->apply(&ssp);
  339. }
  340. VCMI_LIB_NAMESPACE_END