diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index b50aeefc2..62cf11f5f 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -522,7 +522,7 @@ void CGCreature::battleFinished(IGameEventCallback & gameEvents, const CGHeroIns else if(result.winner == BattleSide::NONE) // draw { // guarded reward is lost forever on draw - gameEvents.removeObject(this, hero->getOwner()); + gameEvents.removeObject(this, result.attacker); } else { diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index ec402caea..bf002e0fe 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -294,6 +294,18 @@ void CGEvent::init() } } +void CGEvent::battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == BattleSide::ATTACKER) + { + CRewardableObject::onHeroVisit(gameEvents, hero); + } + if(result.winner == BattleSide::NONE && removeAfterVisit) //rewards are lost if therer is a draw and an event is not repeatable + { + gameEvents.removeObject(this, result.attacker); + } +} + void CGEvent::grantRewardWithMessage(IGameEventCallback & gameEvents, const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const { CRewardableObject::grantRewardWithMessage(gameEvents, contextHero, rewardIndex, markAsVisit); diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 803e25b60..5fe4139c1 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -65,6 +65,7 @@ public: } void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override; + void battleFinished(IGameEventCallback & gameEvents, const CGHeroInstance *hero, const BattleResult &result) const override; protected: void grantRewardWithMessage(IGameEventCallback & gameEvents, const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const override; diff --git a/lib/networkPacks/PacksForClientBattle.h b/lib/networkPacks/PacksForClientBattle.h index 0af85b92f..6d20cb7e1 100644 --- a/lib/networkPacks/PacksForClientBattle.h +++ b/lib/networkPacks/PacksForClientBattle.h @@ -121,7 +121,8 @@ struct DLL_LINKAGE BattleResult : public Query { BattleID battleID = BattleID::NONE; EBattleResult result = EBattleResult::NORMAL; - BattleSide winner = BattleSide::NONE; //0 - attacker, 1 - defender, [2 - draw (should be possible?)] + BattleSide winner = BattleSide::NONE; //0 - attacker, 1 - defender, 2 - draw + PlayerColor attacker; //used in case of a draw BattleSideArray> casualties; //first => casualties of attackers - map crid => number BattleSideArray exp{0,0}; //exp for attacker and defender diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index e99b86858..050216e63 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -320,6 +320,9 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) cab1.updateArmy(gameHandler); cab2.updateArmy(gameHandler); //take casualties after battle is deleted + const auto attackerHero = battle.battleGetFightingHero(BattleSide::ATTACKER); + const auto defenderHero = battle.battleGetFightingHero(BattleSide::DEFENDER); + const auto winnerHero = battle.battleGetFightingHero(finishingBattle->winnerSide); const auto loserHero = battle.battleGetFightingHero(CBattleInfoEssentials::otherSide(finishingBattle->winnerSide)); @@ -358,8 +361,8 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) BattleResultAccepted raccepted; raccepted.battleID = battle.getBattle()->getBattleID(); - raccepted.heroResult[finishingBattle->winnerSide].heroID = winnerHero ? winnerHero->id : ObjectInstanceID::NONE; - raccepted.heroResult[CBattleInfoEssentials::otherSide(finishingBattle->winnerSide)].heroID = loserHero ? loserHero->id : ObjectInstanceID::NONE; + raccepted.heroResult[BattleSide::ATTACKER].heroID = attackerHero ? attackerHero->id : ObjectInstanceID::NONE; + raccepted.heroResult[BattleSide::DEFENDER].heroID = defenderHero ? defenderHero->id : ObjectInstanceID::NONE; raccepted.heroResult[BattleSide::ATTACKER].armyID = battle.battleGetArmyObject(BattleSide::ATTACKER)->id; raccepted.heroResult[BattleSide::DEFENDER].armyID = battle.battleGetArmyObject(BattleSide::DEFENDER)->id; raccepted.heroResult[BattleSide::ATTACKER].exp = battleResult->exp[BattleSide::ATTACKER]; @@ -399,13 +402,42 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt }); assert(battle != gameHandler->gameState().currentBattles.end()); - const auto winnerHero = (*battle)->battleGetFightingHero(finishingBattle->winnerSide); - const auto loserHero = (*battle)->battleGetFightingHero(CBattleInfoEssentials::otherSide(finishingBattle->winnerSide)); + const CGHeroInstance * winnerHero = nullptr; + const CGHeroInstance * loserHero = nullptr; + + const auto attackerHero = (*battle)->battleGetFightingHero(BattleSide::ATTACKER); + const auto defenderHero = (*battle)->battleGetFightingHero(BattleSide::DEFENDER); + const auto attackerSide = (*battle)->getSidePlayer(BattleSide::ATTACKER); + const auto defenderSide = (*battle)->getSidePlayer(BattleSide::DEFENDER); + bool winnerHasUnitsLeft = true; + + if (!finishingBattle->isDraw()) + { + winnerHero = (*battle)->battleGetFightingHero(finishingBattle->winnerSide); + loserHero = (*battle)->battleGetFightingHero(CBattleInfoEssentials::otherSide(finishingBattle->winnerSide)); + winnerHasUnitsLeft = winnerHero ? winnerHero->stacksCount() > 0 : true; + } + BattleResultsApplied resultsApplied; - // Eagle Eye handling - if(!finishingBattle->isDraw() && winnerHero) + const auto addArtifactToDischarging = [&resultsApplied](const std::map & artMap, + const ObjectInstanceID & id, const std::optional & creature = std::nullopt) { + for(const auto & [slot, slotInfo] : artMap) + { + auto artInst = slotInfo.getArt(); + assert(artInst); + if(const auto condition = artInst->getType()->getDischargeCondition(); condition == DischargeArtifactCondition::BATTLE) + { + auto & discharging = resultsApplied.dischargingArtifacts.emplace_back(artInst->getId(), 1); + discharging.artLoc.emplace(id, creature, slot); + } + } + }; + + if(winnerHero && winnerHasUnitsLeft) + { + // Eagle Eye handling if(auto eagleEyeLevel = winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT)) { resultsApplied.learnedSpells.learn = 1; @@ -422,10 +454,46 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt } } } + + // Growing artifacts handling + const auto addArtifactToGrowing = [&resultsApplied](const std::map & artMap) + { + for(const auto & [slot, slotInfo] : artMap) + { + const auto artInst = slotInfo.getArt(); + assert(artInst); + if(artInst->getType()->isGrowing()) + resultsApplied.growingArtifacts.emplace_back(artInst->getId()); + } + }; + + if(const auto commander = winnerHero->getCommander(); commander && commander->alive) + addArtifactToGrowing(commander->artifactsWorn); + addArtifactToGrowing(winnerHero->artifactsWorn); + + // Charged artifacts handling + addArtifactToDischarging(winnerHero->artifactsWorn, winnerHero->id); + if(const auto commander = winnerHero->getCommander()) + addArtifactToDischarging(commander->artifactsWorn, winnerHero->id, winnerHero->findStack(winnerHero->getCommander())); + + // Necromancy handling + // Give raised units to winner, if any were raised, units will be given after casualties are taken + resultsApplied.raisedStack = winnerHero->calculateNecromancy(result); + const SlotID necroSlot = resultsApplied.raisedStack.getCreature() ? winnerHero->getSlotFor(resultsApplied.raisedStack.getCreature()) : SlotID(); + if(necroSlot != SlotID() && !finishingBattle->isDraw()) + gameHandler->addToSlot(StackLocation(finishingBattle->winnerId, necroSlot), resultsApplied.raisedStack.getCreature(), resultsApplied.raisedStack.getCount()); + } + + if(loserHero) + { + // Charged artifacts handling + addArtifactToDischarging(loserHero->artifactsWorn, loserHero->id); + if(const auto commander = loserHero->getCommander()) + addArtifactToDischarging(commander->artifactsWorn, loserHero->id, loserHero->findStack(loserHero->getCommander())); } // Moving artifacts handling - if(result.result == EBattleResult::NORMAL && !finishingBattle->isDraw() && winnerHero) + if(result.result == EBattleResult::NORMAL && winnerHero && winnerHasUnitsLeft) { CArtifactFittingSet artFittingSet(*winnerHero); const auto addArtifactToTransfer = [&artFittingSet](BulkMoveArtifacts & pack, const ArtifactPosition & srcSlot, const CArtifactInstance * art) @@ -441,7 +509,7 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt } }; - if(loserHero) + if (loserHero) { BulkMoveArtifacts packHero(finishingBattle->victor, finishingBattle->loserId, finishingBattle->winnerId, false); packHero.srcArtHolder = finishingBattle->loserId; @@ -486,63 +554,6 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt } } - // Growing artifacts handling - if(!finishingBattle->isDraw() && winnerHero) - { - const auto addArtifactToGrowing = [&resultsApplied](const std::map & artMap) - { - for(const auto & [slot, slotInfo] : artMap) - { - const auto artInst = slotInfo.getArt(); - assert(artInst); - if(artInst->getType()->isGrowing()) - resultsApplied.growingArtifacts.emplace_back(artInst->getId()); - } - }; - - if(const auto commander = winnerHero->getCommander(); commander && commander->alive) - addArtifactToGrowing(commander->artifactsWorn); - addArtifactToGrowing(winnerHero->artifactsWorn); - } - - // Charged artifacts handling - const auto addArtifactToDischarging = [&resultsApplied](const std::map & artMap, - const ObjectInstanceID & id, const std::optional & creature = std::nullopt) - { - for(const auto & [slot, slotInfo] : artMap) - { - auto artInst = slotInfo.getArt(); - assert(artInst); - if(const auto condition = artInst->getType()->getDischargeCondition(); condition == DischargeArtifactCondition::BATTLE) - { - auto & discharging = resultsApplied.dischargingArtifacts.emplace_back(artInst->getId(), 1); - discharging.artLoc.emplace(id, creature, slot); - } - } - }; - if(winnerHero) - { - addArtifactToDischarging(winnerHero->artifactsWorn, winnerHero->id); - if(const auto commander = winnerHero->getCommander()) - addArtifactToDischarging(commander->artifactsWorn, winnerHero->id, winnerHero->findStack(winnerHero->getCommander())); - } - if(loserHero) - { - addArtifactToDischarging(loserHero->artifactsWorn, loserHero->id); - if(const auto commander = loserHero->getCommander()) - addArtifactToDischarging(commander->artifactsWorn, loserHero->id, loserHero->findStack(loserHero->getCommander())); - } - - // Necromancy handling - // Give raised units to winner, if any were raised, units will be given after casualties are taken - if(winnerHero) - { - resultsApplied.raisedStack = winnerHero->calculateNecromancy(result); - const SlotID necroSlot = resultsApplied.raisedStack.getCreature() ? winnerHero->getSlotFor(resultsApplied.raisedStack.getCreature()) : SlotID(); - if(necroSlot != SlotID() && !finishingBattle->isDraw()) - gameHandler->addToSlot(StackLocation(finishingBattle->winnerId, necroSlot), resultsApplied.raisedStack.getCreature(), resultsApplied.raisedStack.getCount()); - } - resultsApplied.battleID = battleID; resultsApplied.victor = finishingBattle->victor; resultsApplied.loser = finishingBattle->loser; @@ -554,17 +565,39 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt RemoveObject ro(loserHero->id, finishingBattle->victor); gameHandler->sendAndApply(ro); } - // For draw case both heroes should be removed - if(finishingBattle->isDraw() && winnerHero) + + //retreat the victor if he/she has no pernament creatures left + if (winnerHero && !winnerHasUnitsLeft) { RemoveObject ro(winnerHero->id, finishingBattle->loser); gameHandler->sendAndApply(ro); - if(gameHandler->gameInfo().getSettings().getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS)) - gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, winnerHero); + gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, winnerHero); } - //handle victory/loss of engaged players - gameHandler->checkVictoryLossConditions({finishingBattle->loser, finishingBattle->victor}); + // For draw case both heroes should be removed + if(finishingBattle->isDraw()) + { + + if (attackerHero) + { + RemoveObject ro(attackerHero->id, defenderSide); + gameHandler->sendAndApply(ro); + } + + if (defenderHero) + { + RemoveObject ro(defenderHero->id, attackerSide); + gameHandler->sendAndApply(ro); + } + + if(gameHandler->gameInfo().getSettings().getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS)) + { + if (attackerHero) + gameHandler->heroPool->onHeroEscaped(attackerSide, attackerHero); + if (defenderHero) + gameHandler->heroPool->onHeroEscaped(defenderSide, defenderHero); + } + } if (result.result == EBattleResult::SURRENDER) { @@ -578,6 +611,9 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, loserHero); } + //handle victory/loss of engaged players + gameHandler->checkVictoryLossConditions({finishingBattle->loser, finishingBattle->victor}); + finishingBattles.erase(battleID); battleResults.erase(battleID); } @@ -592,6 +628,7 @@ void BattleResultProcessor::setBattleResult(const CBattleInfoCallback & battle, battleResult->battleID = battle.getBattle()->getBattleID(); battleResult->result = resultType; battleResult->winner = victoriusSide; //surrendering side loses + battleResult->attacker = battle.getBattle()->getSidePlayer(BattleSide::ATTACKER); auto allStacks = battle.battleGetStacksIf([](const CStack * stack){