diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 4b60b32af..6c776078e 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -182,7 +182,7 @@ "type" : "object", "additionalProperties" : false, "default": {}, - "required" : [ "heroMoveTime", "enemyMoveTime", "scrollSpeedPixels", "heroReminder", "quickCombat", "objectAnimation", "terrainAnimation" ], + "required" : [ "heroMoveTime", "enemyMoveTime", "scrollSpeedPixels", "heroReminder", "quickCombat", "objectAnimation", "terrainAnimation", "alwaysSkipCombat" ], "properties" : { "heroMoveTime" : { "type" : "number", @@ -211,6 +211,10 @@ "terrainAnimation" : { "type" : "boolean", "default" : true + }, + "alwaysSkipCombat" : { + "type" : "boolean", + "default" : false } } }, diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 65b0746d4..9072bc4cb 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -205,6 +205,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const curB->battlefieldType = battlefieldType; curB->round = -2; curB->activeStack = -1; + curB->creatureBank = creatureBank; if(town) { diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 0e12cb93d..5fe37342a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -614,44 +614,56 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con if(heroDefender) battleResult.data->exp[1] = heroDefender->calculateXp(battleResult.data->exp[1]); - const CArmedInstance *bEndArmy1 = gs->curB->sides.at(0).armyObject; - const CArmedInstance *bEndArmy2 = gs->curB->sides.at(1).armyObject; - const BattleResult::EResult result = battleResult.get()->result; - - auto findBattleQuery = [this]() -> std::shared_ptr - { - for (auto &q : queries.allQueries()) - { - if (auto bq = std::dynamic_pointer_cast(q)) - if (bq->bi == gs->curB) - return bq; - } - return std::shared_ptr(); - }; - - auto battleQuery = findBattleQuery(); + auto battleQuery = std::dynamic_pointer_cast(queries.topQuery(gs->curB->sides[0].color)); if (!battleQuery) { logGlobal->error("Cannot find battle query!"); + complain("Player " + boost::lexical_cast(gs->curB->sides[0].color) + " has no battle query at the top!"); + return; } - if (battleQuery != queries.topQuery(gs->curB->sides[0].color)) - complain("Player " + boost::lexical_cast(gs->curB->sides[0].color) + " although in battle has no battle query at the top!"); battleQuery->result = boost::make_optional(*battleResult.data); + + //set same battle result for all queries + for(auto q : queries.allQueries()) + { + auto otherBattleQuery = std::dynamic_pointer_cast(q); + if(otherBattleQuery && otherBattleQuery->bi == battleQuery->bi) + otherBattleQuery->result = battleQuery->result; + } //Check how many battle queries were created (number of players blocked by battle) const int queriedPlayers = battleQuery ? (int)boost::count(queries.allQueries(), battleQuery) : 0; - finishingBattle = std::make_unique(battleQuery, queriedPlayers); + finishingBattle = make_unique(battleQuery, queriedPlayers); + + auto battleDialogQuery = std::make_shared(this, gs->curB); + battleResult.data->queryID = battleDialogQuery->queryID; + queries.addQuery(battleDialogQuery); + sendAndApply(battleResult.data); //after this point casualties objects are destroyed +} - CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle +void CGameHandler::endBattleConfirm(const BattleInfo * battleInfo) +{ + auto battleQuery = std::dynamic_pointer_cast(queries.topQuery(battleInfo->sides.at(0).color)); + if(!battleQuery) + { + logGlobal->trace("No battle query, battle end was confirmed by another player"); + return; + } + + const CArmedInstance *bEndArmy1 = battleInfo->sides.at(0).armyObject; + const CArmedInstance *bEndArmy2 = battleInfo->sides.at(1).armyObject; + const BattleResult::EResult result = battleResult.get()->result; + + CasualtiesAfterBattle cab1(bEndArmy1, battleInfo), cab2(bEndArmy2, battleInfo); //calculate casualties before deleting battle ChangeSpells cs; //for Eagle Eye - if (finishingBattle->winnerHero) + if(!finishingBattle->isDraw() && finishingBattle->winnerHero) { if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1)) { double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::LEARN_BATTLE_SPELL_CHANCE, 0); - for(auto & spellId : gs->curB->sides.at(!battleResult.data->winner).usedSpellsHistory) + for(auto & spellId : battleInfo->sides.at(!battleResult.data->winner).usedSpellsHistory) { auto spell = spellId.toSpell(VLC->spells()); if(spell && spell->getLevel() <= eagleEyeLevel && !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId()) && getRandomGenerator().nextInt(99) < eagleEyeChance) @@ -661,7 +673,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con } std::vector arts; //display them in window - if (result == BattleResult::NORMAL && finishingBattle->winnerHero) + if(result == BattleResult::NORMAL && !finishingBattle->isDraw() && finishingBattle->winnerHero) { auto sendMoveArtifact = [&](const CArtifactInstance *art, MoveArtifact *ma) { @@ -697,7 +709,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con //we assume that no big artifacts can be found MoveArtifact ma; ma.src = ArtifactLocation(finishingBattle->loserHero, - ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning + ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning const CArtifactInstance * art = ma.src.getArt(); if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won { @@ -719,7 +731,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con } } } - for (auto armySlot : gs->curB->battleGetArmyObject(!battleResult.data->winner)->stacks) + for (auto armySlot : battleInfo->sides.at(!battleResult.data->winner).armyObject->stacks) { auto artifactsWorn = armySlot.second->artifactsWorn; for (auto artSlot : artifactsWorn) @@ -734,8 +746,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con } } } - sendAndApply(battleResult.data); //after this point casualties objects are destroyed - + if (arts.size()) //display loot { InfoWindow iw; @@ -796,36 +807,41 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con } cab1.updateArmy(this); cab2.updateArmy(this); //take casualties after battle is deleted - - if(battleResult.data->winner != BattleSide::ATTACKER && heroAttacker) //remove beaten Attacker + + if(finishingBattle->loserHero) //remove beaten hero { - RemoveObject ro(heroAttacker->id); + RemoveObject ro(finishingBattle->loserHero->id); sendAndApply(&ro); } - - if(battleResult.data->winner != BattleSide::DEFENDER && heroDefender) //remove beaten Defender + if(finishingBattle->isDraw() && finishingBattle->winnerHero) //for draw case both heroes should be removed { - RemoveObject ro(heroDefender->id); + RemoveObject ro(finishingBattle->winnerHero->id); sendAndApply(&ro); } - - if(battleResult.data->winner == BattleSide::DEFENDER - && heroDefender - && heroDefender->visitedTown - && !heroDefender->inTownGarrison - && heroDefender->visitedTown->garrisonHero == heroDefender) + + if(battleResult.data->winner == BattleSide::DEFENDER + && finishingBattle->winnerHero + && finishingBattle->winnerHero->visitedTown + && !finishingBattle->winnerHero->inTownGarrison + && finishingBattle->winnerHero->visitedTown->garrisonHero == finishingBattle->winnerHero) { - swapGarrisonOnSiege(heroDefender->visitedTown->id); //return defending visitor from garrison to its rightful place + swapGarrisonOnSiege(finishingBattle->winnerHero->visitedTown->id); //return defending visitor from garrison to its rightful place } //give exp - if(battleResult.data->exp[0] && heroAttacker && battleResult.get()->winner == BattleSide::ATTACKER) - changePrimSkill(heroAttacker, PrimarySkill::EXPERIENCE, battleResult.data->exp[0]); - else if(battleResult.data->exp[1] && heroDefender && battleResult.get()->winner == BattleSide::DEFENDER) - changePrimSkill(heroDefender, PrimarySkill::EXPERIENCE, battleResult.data->exp[1]); + if(!finishingBattle->isDraw() && battleResult.data->exp[finishingBattle->winnerSide] && finishingBattle->winnerHero) + changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult.data->exp[finishingBattle->winnerSide]); + + BattleResultAccepted raccepted; + raccepted.army1 = const_cast(bEndArmy1); + raccepted.army2 = const_cast(bEndArmy2); + raccepted.hero1 = const_cast(battleInfo->sides.at(0).hero); + raccepted.hero2 = const_cast(battleInfo->sides.at(1).hero); + raccepted.exp[0] = battleResult.data->exp[0]; + raccepted.exp[1] = battleResult.data->exp[1]; + sendAndApply(&raccepted); queries.popIfTop(battleQuery); - - //--> continuation (battleAfterLevelUp) occurs after level-up queries are handled or on removing query (above) + //--> continuation (battleAfterLevelUp) occurs after level-up queries are handled or on removing query } void CGameHandler::battleAfterLevelUp(const BattleResult &result) diff --git a/server/CQuery.cpp b/server/CQuery.cpp index b8fc54bd9..f0b97879c 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -386,6 +386,28 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const return CDialogQuery::blocksPack(pack); } + +CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi): + CDialogQuery(owner) +{ + bi = Bi; + + for(auto & side : bi->sides) + addPlayer(side.color); +} + +void CBattleDialogQuery::onRemoval(PlayerColor color) +{ + assert(answer); + if(*answer == 1) + { + gh->startBattlePrimary(bi->sides[0].armyObject, bi->sides[1].armyObject, bi->tile, bi->sides[0].hero, bi->sides[1].hero, bi->creatureBank, bi->town); + } + else + { + gh->endBattleConfirm(bi); + } +} CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi): CDialogQuery(owner)