1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Fixed battle replay

This commit is contained in:
Ivan Savenko 2023-09-05 17:22:11 +03:00
parent 8bdddd1324
commit 1f1f978328
11 changed files with 110 additions and 34 deletions

View File

@ -1530,6 +1530,19 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
}
};
struct DLL_LINKAGE BattleCancelled: public CPackForClient
{
void applyGs(CGameState * gs) const;
BattleID battleID = BattleID::NONE;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & battleID;
assert(battleID != BattleID::NONE);
}
};
struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
{
void applyGs(CGameState * gs) const;

View File

@ -2119,7 +2119,7 @@ void BattleStart::applyGs(CGameState * gs) const
info->battleID = gs->nextBattleID;
info->localInit();
vstd::next(gs->nextBattleID, 1);
gs->nextBattleID = vstd::next(gs->nextBattleID, 1);
}
void BattleNextRound::applyGs(CGameState * gs) const
@ -2177,6 +2177,17 @@ void BattleUpdateGateState::applyGs(CGameState * gs) const
gs->getBattle(battleID)->si.gateState = state;
}
void BattleCancelled::applyGs(CGameState * gs) const
{
auto currentBattle = boost::range::find_if(gs->currentBattles, [&](const auto & battle)
{
return battle->battleID == battleID;
});
assert(currentBattle != gs->currentBattles.end());
gs->currentBattles.erase(currentBattle);
}
void BattleResultAccepted::applyGs(CGameState * gs) const
{
// Remove any "until next battle" bonuses

View File

@ -286,6 +286,7 @@ void registerTypesClientPacks2(Serializer &s)
s.template registerType<CPackForClient, BattleSetActiveStack>();
s.template registerType<CPackForClient, BattleResult>();
s.template registerType<CPackForClient, BattleResultAccepted>();
s.template registerType<CPackForClient, BattleCancelled>();
s.template registerType<CPackForClient, BattleLogMessage>();
s.template registerType<CPackForClient, BattleStackMoved>();
s.template registerType<CPackForClient, BattleAttack>();

View File

@ -137,6 +137,7 @@ void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const
attack.damageDealt = getRandomDamage(server);
CatapultAttack ca; //package for clients
ca.battleID = m->battle()->getBattle()->getBattleID();
ca.attacker = m->caster->getHeroCaster() ? -1 : m->caster->getCasterUnitId();
ca.attackedParts.push_back(attack);
server->apply(&ca);

View File

@ -254,12 +254,15 @@ void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime)
std::lock_guard<std::recursive_mutex> guard(mx);
const auto * gs = gameHandler.gameState();
const auto * si = gameHandler.getStartInfo();
if(!si || !gs || !si->turnTimerInfo.isBattleEnabled())
if(!si || !gs)
{
assert(0);
return;
}
if (!si->turnTimerInfo.isBattleEnabled())
return;
ui8 side = 0;
const CStack * stack = nullptr;
bool isTactisPhase = gs->getBattle(battleID)->battleTacticDist() > 0;
@ -279,6 +282,7 @@ void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime)
return;
const auto * state = gameHandler.getPlayerState(player);
assert(state && state->status != EPlayerStatus::INGAME);
if(!state || state->status != EPlayerStatus::INGAME || !state->human)
return;

View File

@ -904,7 +904,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
bat.flags |= BattleAttack::DEATH_BLOW;
}
const auto * owner = battle.battleGetFightingHero(attacker->unitOwner());
const auto * owner = battle.battleGetFightingHero(attacker->unitSide());
if(owner)
{
int chance = owner->valOfBonuses(BonusType::BONUS_DAMAGE_CHANCE, attacker->creatureIndex());

View File

@ -51,33 +51,23 @@ void BattleProcessor::engageIntoBattle(PlayerColor player)
gameHandler->sendAndApply(&pb);
}
void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
void BattleProcessor::restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
const CGTownInstance *town) //use hero=nullptr for no hero
const CGTownInstance *town)
{
assert(gameHandler->gameState()->getBattle(army1->getOwner()) == nullptr);
assert(gameHandler->gameState()->getBattle(army2->getOwner()) == nullptr);
engageIntoBattle(army1->tempOwner);
engageIntoBattle(army2->tempOwner);
static const CArmedInstance *armies[2];
armies[0] = army1;
armies[1] = army2;
static const CGHeroInstance*heroes[2];
heroes[0] = hero1;
heroes[1] = hero2;
auto battleID = setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
const auto * battle = gameHandler->gameState()->getBattle(battleID);
assert(battle);
auto battle = gameHandler->gameState()->getBattle(battleID);
auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle->sides[0].color));
assert(lastBattleQuery);
//existing battle query for retying auto-combat
if(lastBattleQuery)
{
const CGHeroInstance*heroes[2];
heroes[0] = hero1;
heroes[1] = hero2;
for(int i : {0, 1})
{
if(heroes[i])
@ -89,21 +79,58 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
}
}
lastBattleQuery->battleID = battle->getBattleID();
lastBattleQuery->result = std::nullopt;
lastBattleQuery->belligerents[0] = battle->sides[0].armyObject;
lastBattleQuery->belligerents[1] = battle->sides[1].armyObject;
assert(lastBattleQuery->belligerents[0] == battle->sides[0].armyObject);
assert(lastBattleQuery->belligerents[1] == battle->sides[1].armyObject);
}
auto nextBattleQuery = std::make_shared<CBattleQuery>(gameHandler, battle);
for(int i : {0, 1})
BattleCancelled bc;
bc.battleID = battleID;
gameHandler->sendAndApply(&bc);
startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town);
}
void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
const CGTownInstance *town)
{
assert(gameHandler->gameState()->getBattle(army1->getOwner()) == nullptr);
assert(gameHandler->gameState()->getBattle(army2->getOwner()) == nullptr);
engageIntoBattle(army1->tempOwner);
engageIntoBattle(army2->tempOwner);
const CArmedInstance *armies[2];
armies[0] = army1;
armies[1] = army2;
const CGHeroInstance*heroes[2];
heroes[0] = hero1;
heroes[1] = hero2;
auto battleID = setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
const auto * battle = gameHandler->gameState()->getBattle(battleID);
assert(battle);
auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle->sides[0].color));
if (lastBattleQuery)
{
if(heroes[i])
{
nextBattleQuery->initialHeroMana[i] = heroes[i]->mana;
}
lastBattleQuery->battleID = battleID;
}
else
{
auto newBattleQuery = std::make_shared<CBattleQuery>(gameHandler, battle);
// store initial mana to reset if battle has been restarted
for(int i : {0, 1})
if(heroes[i])
newBattleQuery->initialHeroMana[i] = heroes[i]->mana;
gameHandler->queries->addQuery(newBattleQuery);
}
gameHandler->queries->addQuery(nextBattleQuery);
flowProcessor->onBattleStarted(*battle);
}

View File

@ -63,6 +63,8 @@ public:
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false);
/// Starts battle between two armies (which can also be heroes) at position of 2nd object
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false);
/// Restart ongoing battle and end previous battle
void restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr);
/// Processing of incoming battle action netpack
bool makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction & ba);

View File

@ -278,11 +278,12 @@ void BattleResultProcessor::endBattle(const CBattleInfoCallback & battle)
for(auto q : gameHandler->queries->allQueries())
{
auto otherBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(q);
if(otherBattleQuery)
if(otherBattleQuery && otherBattleQuery->battleID == battle.getBattle()->getBattleID())
otherBattleQuery->result = battleQuery->result;
}
gameHandler->turnTimerHandler.onBattleEnd(battle.getBattle()->getBattleID());
gameHandler->sendAndApply(battleResult);
if (battleResult->queryID == QueryID::NONE)
endBattleConfirm(battle);

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "BattleQueries.h"
#include "MapQueries.h"
#include "QueriesProcessor.h"
#include "../CGameHandler.h"
#include "../battles/BattleProcessor.h"
@ -18,6 +19,8 @@
void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
{
assert(result);
if(result)
objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
}
@ -47,10 +50,21 @@ bool CBattleQuery::blocksPack(const CPack * pack) const
void CBattleQuery::onRemoval(PlayerColor color)
{
assert(result);
if(result)
gh->battles->battleAfterLevelUp(battleID, *result);
}
void CBattleQuery::onExposure(QueryPtr topQuery)
{
// this method may be called in two cases:
// 1) when requesting battle replay (but before replay starts -> no valid result)
// 2) when aswering on levelup queries after accepting battle result -> valid result
if(result)
owner->popQuery(*this);
}
CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * bi):
CDialogQuery(owner),
bi(bi)
@ -64,7 +78,8 @@ void CBattleDialogQuery::onRemoval(PlayerColor color)
assert(answer);
if(*answer == 1)
{
gh->startBattlePrimary(
gh->battles->restartBattlePrimary(
bi->getBattleID(),
bi->getSideArmy(0),
bi->getSideArmy(1),
bi->getLocation(),

View File

@ -31,6 +31,7 @@ public:
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
virtual bool blocksPack(const CPack *pack) const override;
virtual void onRemoval(PlayerColor color) override;
virtual void onExposure(QueryPtr topQuery) override;
};
class CBattleDialogQuery : public CDialogQuery