diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 452b41d76..c1ea5ec45 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -2163,7 +2163,15 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) break; case RISE_DEMONS: if (shere && ourStack && !shere->alive()) - legalAction = true; + { + if(!(shere->hasBonusOfType(Bonus::UNDEAD) + || shere->hasBonusOfType(Bonus::NON_LIVING) + || vstd::contains(shere->state, EBattleStackState::SUMMONED) + || vstd::contains(shere->state, EBattleStackState::CLONED) + || shere->hasBonusOfType(Bonus::SIEGE_WEAPON) + )) + legalAction = true; + } break; } if (legalAction) @@ -2320,7 +2328,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) break; case RISE_DEMONS: cursorType = ECursor::SPELLBOOK; - realizeAction = [=]{ giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); }; + realizeAction = [=] + { + giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); + }; break; case CATAPULT: cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT; diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 9a48b476f..3b5acc577 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -19,6 +19,7 @@ #include "spells/CSpellHandler.h" const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); +const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(255); const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253); const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254); const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 05d3128eb..98e344d6b 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -216,6 +216,7 @@ class SlotID : public BaseForID friend class CNonConstInfoCallback; DLL_LINKAGE static const SlotID COMMANDER_SLOT_PLACEHOLDER; + DLL_LINKAGE static const SlotID SUMMONED_SLOT_PLACEHOLDER; ///curB->generateNewStack(csbd, attacker, SlotID(255), pos); //TODO: netpacks? + CStack * addedStack = gs->curB->generateNewStack(csbd, attacker, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks? if (summoned) addedStack->state.insert(EBattleStackState::SUMMONED); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4634ae5c7..f747a4bc5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -651,8 +651,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer sendAndApply(&cs); } - cab1.takeFromArmy(this); - cab2.takeFromArmy(this); //take casualties after battle is deleted + cab1.updateArmy(this); + cab2.updateArmy(this); //take casualties after battle is deleted //if one hero has lost we will erase him if(battleResult.data->winner!=0 && hero1) @@ -3848,18 +3848,16 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) //TODO: From Strategija: //Summon Demon is a level 2 spell. { - StartAction start_action(ba); - sendAndApply(&start_action); - const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber), *destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false); + CreatureID summonedType(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype);//in case summoner can summon more than one type of monsters... scream! BattleStackAdded bsa; bsa.attacker = summoner->attackerOwned; - bsa.creID = CreatureID(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype); //in case summoner can summon more than one type of monsters... scream! + bsa.creID = summonedType; ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum()); - ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount; + ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect ui64 canRiseHp = std::min(targetHealth, risedHp); ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth(); @@ -3871,6 +3869,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) if (bsa.amount) //there's rare possibility single creature cannot rise desired type { + StartAction start_action(ba); + sendAndApply(&start_action); + BattleStacksRemoved bsr; //remove body bsr.stackIDs.insert(destStack->ID); sendAndApply(&bsr); @@ -3882,9 +3883,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) ssp.val = -1; ssp.absolute = false; sendAndApply(&ssp); - } - sendAndApply(&end_action); + sendAndApply(&end_action); + } break; } case Battle::MONSTER_SPELL: @@ -5804,13 +5805,52 @@ void CGameHandler::duelFinished() return; } -CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat) +CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat): + army(_army) { heroWithDeadCommander = ObjectInstanceID(); PlayerColor color = army->tempOwner; if(color == PlayerColor::UNFLAGGABLE) color = PlayerColor::NEUTRAL; + + auto killStack = [&, this](const SlotID slot, const CStackInstance * instance) + { + StackLocation sl(army, slot); + newStackCounts.push_back(TStackAndItsNewCount(sl, 0)); + if(nullptr == instance) + return; + auto c = dynamic_cast (instance); + if (c) //switch commander status to dead + { + auto h = dynamic_cast (army); + if (h && h->commander == c) + heroWithDeadCommander = army->id; //TODO: unify commander handling + } + }; + + //1. Find removed stacks. + for(const auto & slotInfo : army->stacks) + { + const SlotID slot = slotInfo.first; + const CStackInstance * instance = slotInfo.second; + + if(nullptr != instance)//just in case + { + bool found = false; + for(const CStack * sta : bat->stacks) + { + if(sta->base == instance) + { + found = true; + break; + } + } + //stack in this slot was removed == it is dead + if(!found) + killStack(slot, instance); + } + } for(CStack *st : bat->stacks) { @@ -5822,7 +5862,7 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleI //FIXME: this info is also used in BattleInfo::calculateCasualties, refactor st->count = std::max (0, st->count - st->resurrected); - if (!st->count && !st->base) //we can imagine stacks of war mahcines that are not spawned by artifacts? + if (!st->count && !st->base) //we can imagine stacks of war machines that are not spawned by artifacts? { auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber); if (warMachine != ArtifactID::NONE) @@ -5832,29 +5872,32 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleI removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true))); } } - - if(!army->slotEmpty(st->slot) && st->count < army->getStackCount(st->slot)) + + if(army->slotEmpty(st->slot)) { - StackLocation sl(army, st->slot); - if(st->alive()) - newStackCounts.push_back(std::pair(sl, st->count)); - else - newStackCounts.push_back(std::pair(sl, 0)); - } - if (st->base && !st->count) - { - auto c = dynamic_cast (st->base); - if (c) //switch commander status to dead + if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER && !vstd::contains(st->state, EBattleStackState::SUMMONED) && st->alive() && st->count > 0) { - auto h = dynamic_cast (army); - if (h && h->commander == c) - heroWithDeadCommander = army->id; //TODO: unify commander handling + //this stack was permanently summoned + const CreatureID summonedType = st->type->idNumber; + summoned[summonedType] += st->count; + } + } + else + { + if(st->count == 0 || !st->alive()) + { + killStack(st->slot, st->base); + } + else if(st->count < army->getStackCount(st->slot)) + { + StackLocation sl(army, st->slot); + newStackCounts.push_back(TStackAndItsNewCount(sl, st->count)); } } } } -void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh) +void CasualtiesAfterBattle::updateArmy(CGameHandler *gh) { for(TStackAndItsNewCount &ncount : newStackCounts) { @@ -5863,6 +5906,21 @@ void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh) else gh->eraseStack(ncount.first, true); } + for(auto summoned_iter : summoned) + { + SlotID slot = army->getSlotFor(summoned_iter.first); + if(slot.validSlot()) + { + StackLocation location(army, slot); + gh->addToSlot(location, summoned_iter.first.toCreature(), summoned_iter.second); + } + else + { + //even if it will be possible to summon anything permanently it should be checked for free slot + //necromancy is handled separately + gh->complain("No free slot to put summoned creature"); + } + } for (auto al : removedWarMachines) { gh->removeArtifact(al); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index acf1698a2..bc2a336a7 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -71,13 +71,16 @@ public: struct CasualtiesAfterBattle { typedef std::pair TStackAndItsNewCount; + typedef std::map TSummoned; enum {ERASE = -1}; + const CArmedInstance * army; std::vector newStackCounts; std::vector removedWarMachines; - ObjectInstanceID heroWithDeadCommander; //TODO: unify stack loactions + TSummoned summoned; + ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations - CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat); - void takeFromArmy(CGameHandler *gh); + CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat); + void updateArmy(CGameHandler *gh); }; class CGameHandler : public IGameCallback, CBattleInfoCallback