mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
Split BattleProcessor into few more parts
This commit is contained in:
parent
5c78060a07
commit
44832f3797
@ -567,12 +567,12 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
||||
int text = 304;
|
||||
switch(br.result)
|
||||
{
|
||||
case BattleResult::NORMAL:
|
||||
case EBattleResult::NORMAL:
|
||||
break;
|
||||
case BattleResult::ESCAPE:
|
||||
case EBattleResult::ESCAPE:
|
||||
text = 303;
|
||||
break;
|
||||
case BattleResult::SURRENDER:
|
||||
case EBattleResult::SURRENDER:
|
||||
text = 302;
|
||||
break;
|
||||
default:
|
||||
@ -601,14 +601,14 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
||||
std::string videoName = "LBSTART.BIK";
|
||||
switch(br.result)
|
||||
{
|
||||
case BattleResult::NORMAL:
|
||||
case EBattleResult::NORMAL:
|
||||
break;
|
||||
case BattleResult::ESCAPE:
|
||||
case EBattleResult::ESCAPE:
|
||||
musicName = "Music/Retreat Battle";
|
||||
videoName = "RTSTART.BIK";
|
||||
text = 310;
|
||||
break;
|
||||
case BattleResult::SURRENDER:
|
||||
case EBattleResult::SURRENDER:
|
||||
musicName = "Music/Surrender Battle";
|
||||
videoName = "SURRENDER.BIK";
|
||||
text = 309;
|
||||
|
@ -1379,6 +1379,13 @@ enum class EHealPower : ui8
|
||||
PERMANENT
|
||||
};
|
||||
|
||||
enum class EBattleResult : ui8
|
||||
{
|
||||
NORMAL = 0,
|
||||
ESCAPE = 1,
|
||||
SURRENDER = 2
|
||||
};
|
||||
|
||||
// Typedef declarations
|
||||
using TExpType = si64;
|
||||
using TQuantity = si32;
|
||||
|
@ -1524,11 +1524,9 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
|
||||
|
||||
struct DLL_LINKAGE BattleResult : public Query
|
||||
{
|
||||
enum EResult { NORMAL = 0, ESCAPE = 1, SURRENDER = 2 };
|
||||
|
||||
void applyFirstCl(CClient * cl);
|
||||
|
||||
EResult result = NORMAL;
|
||||
EBattleResult result = EBattleResult::NORMAL;
|
||||
ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
|
||||
std::map<ui32, si32> casualties[2]; //first => casualties of attackers - map crid => number
|
||||
TExpType exp[2] = {0, 0}; //exp for attacker and defender
|
||||
|
@ -4212,7 +4212,7 @@ void CGameHandler::deserializationFix()
|
||||
//FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization
|
||||
// restore any places that requires such pointer manually
|
||||
heroPool->gameHandler = this;
|
||||
battles->gameHandler = this;
|
||||
battles->setGameHandler(this);
|
||||
playerMessages->gameHandler = this;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
set(server_SRCS
|
||||
StdInc.cpp
|
||||
|
||||
battles/BattleActionProcessor.cpp
|
||||
battles/BattleFlowProcessor.cpp
|
||||
battles/BattleProcessor.cpp
|
||||
battles/BattleResultProcessor.cpp
|
||||
|
||||
queries/BattleQueries.cpp
|
||||
queries/CQuery.cpp
|
||||
@ -21,7 +24,10 @@ set(server_SRCS
|
||||
set(server_HEADERS
|
||||
StdInc.h
|
||||
|
||||
battles/BattleActionProcessor.h
|
||||
battles/BattleFlowProcessor.h
|
||||
battles/BattleProcessor.h
|
||||
battles/BattleResultProcessor.h
|
||||
|
||||
queries/BattleQueries.h
|
||||
queries/CQuery.h
|
||||
|
1355
server/battles/BattleActionProcessor.cpp
Normal file
1355
server/battles/BattleActionProcessor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
58
server/battles/BattleActionProcessor.h
Normal file
58
server/battles/BattleActionProcessor.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* BattleActionProcessor.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct BattleLogMessage;
|
||||
struct BattleAttack;
|
||||
class BattleProcessor;
|
||||
class BattleAction;
|
||||
struct BattleHex;
|
||||
class CStack;
|
||||
enum class BonusType;
|
||||
|
||||
namespace battle {
|
||||
class Unit;
|
||||
class CUnitState;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CGameHandler;
|
||||
|
||||
class BattleActionProcessor : boost::noncopyable
|
||||
{
|
||||
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
|
||||
|
||||
BattleProcessor * owner;
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
int moveStack(int stack, BattleHex dest); //returned value - travelled distance
|
||||
void makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter);
|
||||
|
||||
void handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender);
|
||||
void handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender);
|
||||
void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
|
||||
|
||||
// damage, drain life & fire shield; returns amount of drained life
|
||||
int64_t applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary);
|
||||
|
||||
void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
|
||||
void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
|
||||
|
||||
public:
|
||||
BattleActionProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
bool makeBattleAction(BattleAction &ba);
|
||||
bool makeCustomAction(BattleAction &ba);
|
||||
};
|
||||
|
687
server/battles/BattleFlowProcessor.cpp
Normal file
687
server/battles/BattleFlowProcessor.cpp
Normal file
@ -0,0 +1,687 @@
|
||||
/*
|
||||
* BattleFlowProcessor.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleFlowProcessor.h"
|
||||
|
||||
#include "BattleProcessor.h"
|
||||
|
||||
#include "../CGameHandler.h"
|
||||
#include "../CVCMIServer.h"
|
||||
#include "../processors/HeroPoolProcessor.h"
|
||||
#include "../queries/QueriesProcessor.h"
|
||||
#include "../queries/BattleQueries.h"
|
||||
|
||||
#include "../../lib/ArtifactUtils.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/ScopeGuard.h"
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/modding/IdentifierStorage.h"
|
||||
#include "../../lib/serializer/Cast.h"
|
||||
#include "../../lib/spells/AbilityCaster.h"
|
||||
#include "../../lib/spells/BonusCaster.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/ObstacleCasterProxy.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
|
||||
BattleFlowProcessor::BattleFlowProcessor(BattleProcessor * owner)
|
||||
: owner(owner)
|
||||
, gameHandler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::setGameHandler(CGameHandler * newGameHandler)
|
||||
{
|
||||
gameHandler = newGameHandler;
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
|
||||
{
|
||||
int x = targetPosition.getX();
|
||||
int y = targetPosition.getY();
|
||||
|
||||
const bool targetIsAttacker = side == BattleSide::ATTACKER;
|
||||
|
||||
if (targetIsAttacker) //handle front guardians, TODO: should we handle situation when units start battle near opposite side of the battlefield? Cannot happen in normal H3...
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output);
|
||||
else
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output);
|
||||
|
||||
//guardian spawn locations for four default position cases for attacker and defender, non-default starting location for att and def is handled in first two if's
|
||||
if (targetIsAttacker && ((y % 2 == 0) || (x > 1)))
|
||||
{
|
||||
if (targetIsTwoHex && (y % 2 == 1) && (x == 2)) //handle exceptional case
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
|
||||
}
|
||||
else
|
||||
{ //add back-side guardians for two-hex target, side guardians for one-hex
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false), output);
|
||||
|
||||
if (!targetIsTwoHex && x > 2) //back guard for one-hex
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false), output);
|
||||
else if (targetIsTwoHex)//front-side guardians for two-hex target
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
|
||||
if (x > 3) //back guard for two-hex
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else if (!targetIsAttacker && ((y % 2 == 1) || (x < GameConstants::BFIELD_WIDTH - 2)))
|
||||
{
|
||||
if (targetIsTwoHex && (y % 2 == 0) && (x == GameConstants::BFIELD_WIDTH - 3)) //handle exceptional case... equivalent for above for defender side
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
|
||||
}
|
||||
else
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false), output);
|
||||
|
||||
if (!targetIsTwoHex && x < GameConstants::BFIELD_WIDTH - 3)
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false), output);
|
||||
else if (targetIsTwoHex)
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
|
||||
if (x < GameConstants::BFIELD_WIDTH - 4)
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (!targetIsAttacker && y % 2 == 0)
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output);
|
||||
}
|
||||
|
||||
else if (targetIsAttacker && y % 2 == 1)
|
||||
{
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output);
|
||||
BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::onBattleStarted()
|
||||
{
|
||||
gameHandler->setBattle(gameHandler->gameState()->curB);
|
||||
assert(gameHandler->gameState()->curB);
|
||||
//TODO: pre-tactic stuff, call scripts etc.
|
||||
|
||||
//Moat should be initialized here, because only here we can use spellcasting
|
||||
if (gameHandler->gameState()->curB->town && gameHandler->gameState()->curB->town->fortLevel() >= CGTownInstance::CITADEL)
|
||||
{
|
||||
const auto * h = gameHandler->gameState()->curB->battleGetFightingHero(BattleSide::DEFENDER);
|
||||
const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr;
|
||||
auto moatCaster = spells::SilentCaster(gameHandler->gameState()->curB->getSidePlayer(BattleSide::DEFENDER), actualCaster);
|
||||
auto cast = spells::BattleCast(gameHandler->gameState()->curB, &moatCaster, spells::Mode::PASSIVE, gameHandler->gameState()->curB->town->town->moatAbility.toSpell());
|
||||
auto target = spells::Target();
|
||||
cast.cast(gameHandler->spellEnv, target);
|
||||
}
|
||||
|
||||
if (gameHandler->gameState()->curB->tacticDistance == 0)
|
||||
onTacticsEnded();
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::onTacticsEnded()
|
||||
{
|
||||
//initial stacks appearance triggers, e.g. built-in bonus spells
|
||||
auto initialStacks = gameHandler->gameState()->curB->stacks; //use temporary variable to outclude summoned stacks added to gameHandler->gameState()->curB->stacks from processing
|
||||
|
||||
for (CStack * stack : initialStacks)
|
||||
{
|
||||
if (stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
|
||||
{
|
||||
std::shared_ptr<const Bonus> summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS));
|
||||
auto accessibility = gameHandler->getAccesibility();
|
||||
CreatureID creatureData = CreatureID(summonInfo->subtype);
|
||||
std::vector<BattleHex> targetHexes;
|
||||
const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard
|
||||
const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
|
||||
|
||||
/*Chosen idea for two hex units was to cover all possible surrounding hexes of target unit with as small number of stacks as possible.
|
||||
For one-hex targets there are four guardians - front, back and one per side (up + down).
|
||||
Two-hex targets are wider and the difference is there are two guardians per side to cover 3 hexes + extra hex in the front
|
||||
Additionally, there are special cases for starting positions etc., where guardians would be outside of battlefield if spawned normally*/
|
||||
if (!guardianIsBig)
|
||||
targetHexes = stack->getSurroundingHexes();
|
||||
else
|
||||
summonGuardiansHelper(targetHexes, stack->getPosition(), stack->unitSide(), targetIsBig);
|
||||
|
||||
for(auto hex : targetHexes)
|
||||
{
|
||||
if(accessibility.accessible(hex, guardianIsBig, stack->unitSide())) //without this multiple creatures can occupy one hex
|
||||
{
|
||||
battle::UnitInfo info;
|
||||
info.id = gameHandler->gameState()->curB->battleNextUnitId();
|
||||
info.count = std::max(1, (int)(stack->getCount() * 0.01 * summonInfo->val));
|
||||
info.type = creatureData;
|
||||
info.side = stack->unitSide();
|
||||
info.position = hex;
|
||||
info.summoned = true;
|
||||
|
||||
BattleUnitsChanged pack;
|
||||
pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD);
|
||||
info.save(pack.changedStacks.back().data);
|
||||
gameHandler->sendAndApply(&pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stackEnchantedTrigger(stack);
|
||||
}
|
||||
|
||||
//spells opening battle
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
auto h = gameHandler->gameState()->curB->battleGetFightingHero(i);
|
||||
if (h)
|
||||
{
|
||||
TConstBonusListPtr bl = h->getBonuses(Selector::type()(BonusType::OPENING_BATTLE_SPELL));
|
||||
|
||||
for (auto b : *bl)
|
||||
{
|
||||
spells::BonusCaster caster(h, b);
|
||||
|
||||
const CSpell * spell = SpellID(b->subtype).toSpell();
|
||||
|
||||
spells::BattleCast parameters(gameHandler->gameState()->curB, &caster, spells::Mode::PASSIVE, spell);
|
||||
parameters.setSpellLevel(3);
|
||||
parameters.setEffectDuration(b->val);
|
||||
parameters.massive = true;
|
||||
parameters.castIfPossible(gameHandler->spellEnv, spells::Target());
|
||||
}
|
||||
}
|
||||
}
|
||||
// it is possible that due to opening spells one side was eliminated -> check for end of battle
|
||||
owner->checkBattleStateChanges();
|
||||
|
||||
startNextRound(true);
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::startNextRound(bool isFirstRound)
|
||||
{
|
||||
BattleNextRound bnr;
|
||||
bnr.round = gameHandler->gameState()->curB->round + 1;
|
||||
logGlobal->debug("Round %d", bnr.round);
|
||||
gameHandler->sendAndApply(&bnr);
|
||||
|
||||
auto obstacles = gameHandler->gameState()->curB->obstacles; //we copy container, because we're going to modify it
|
||||
for (auto &obstPtr : obstacles)
|
||||
{
|
||||
if (const SpellCreatedObstacle *sco = dynamic_cast<const SpellCreatedObstacle *>(obstPtr.get()))
|
||||
if (sco->turnsRemaining == 0)
|
||||
removeObstacle(*obstPtr);
|
||||
}
|
||||
|
||||
const BattleInfo & curB = *gameHandler->gameState()->curB;
|
||||
|
||||
for(auto stack : curB.stacks)
|
||||
{
|
||||
if(stack->alive() && !isFirstRound)
|
||||
stackEnchantedTrigger(stack);
|
||||
}
|
||||
|
||||
activateNextStack();
|
||||
}
|
||||
|
||||
const CStack * BattleFlowProcessor::getNextStack()
|
||||
{
|
||||
std::vector<battle::Units> q;
|
||||
gameHandler->gameState()->curB->battleGetTurnOrder(q, 1, 0, -1); //todo: get rid of "turn -1"
|
||||
|
||||
if(q.empty())
|
||||
return nullptr;
|
||||
|
||||
if(q.front().empty())
|
||||
return nullptr;
|
||||
|
||||
auto next = q.front().front();
|
||||
const auto stack = dynamic_cast<const CStack *>(next);
|
||||
|
||||
// regeneration takes place before everything else but only during first turn attempt in each round
|
||||
// also works under blind and similar effects
|
||||
if(stack && stack->alive() && !stack->waiting)
|
||||
{
|
||||
BattleTriggerEffect bte;
|
||||
bte.stackID = stack->unitId();
|
||||
bte.effect = vstd::to_underlying(BonusType::HP_REGENERATION);
|
||||
|
||||
const int32_t lostHealth = stack->getMaxHealth() - stack->getFirstHPleft();
|
||||
if(stack->hasBonusOfType(BonusType::HP_REGENERATION))
|
||||
bte.val = std::min(lostHealth, stack->valOfBonuses(BonusType::HP_REGENERATION));
|
||||
|
||||
if(bte.val) // anything to heal
|
||||
gameHandler->sendAndApply(&bte);
|
||||
}
|
||||
|
||||
if(!next->willMove())
|
||||
return nullptr;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::activateNextStack()
|
||||
{
|
||||
//TODO: activate next round if next == nullptr
|
||||
const auto & curB = *gameHandler->gameState()->curB;
|
||||
|
||||
const CStack * next = getNextStack();
|
||||
|
||||
if (!next)
|
||||
return;
|
||||
|
||||
BattleUnitsChanged removeGhosts;
|
||||
|
||||
for(auto stack : curB.stacks)
|
||||
{
|
||||
if(stack->ghostPending)
|
||||
removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
|
||||
}
|
||||
|
||||
if(!removeGhosts.changedStacks.empty())
|
||||
gameHandler->sendAndApply(&removeGhosts);
|
||||
}
|
||||
|
||||
bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
||||
{
|
||||
const auto & curB = *gameHandler->gameState()->curB;
|
||||
|
||||
// check for bad morale => freeze
|
||||
int nextStackMorale = next->moraleVal();
|
||||
if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
|
||||
{
|
||||
auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE);
|
||||
size_t diceIndex = std::min<size_t>(diceSize.size()-1, -nextStackMorale);
|
||||
|
||||
if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
|
||||
{
|
||||
//unit loses its turn - empty freeze action
|
||||
BattleAction ba;
|
||||
ba.actionType = EActionType::BAD_MORALE;
|
||||
ba.side = next->unitSide();
|
||||
ba.stackNumber = next->unitId();
|
||||
|
||||
makeAutomaticAction(next, ba);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (next->hasBonusOfType(BonusType::ATTACKS_NEAREST_CREATURE)) //while in berserk
|
||||
{
|
||||
logGlobal->trace("Handle Berserk effect");
|
||||
std::pair<const battle::Unit *, BattleHex> attackInfo = curB.getNearestStack(next);
|
||||
if (attackInfo.first != nullptr)
|
||||
{
|
||||
BattleAction attack;
|
||||
attack.actionType = EActionType::WALK_AND_ATTACK;
|
||||
attack.side = next->unitSide();
|
||||
attack.stackNumber = next->unitId();
|
||||
attack.aimToHex(attackInfo.second);
|
||||
attack.aimToUnit(attackInfo.first);
|
||||
|
||||
makeAutomaticAction(next, attack);
|
||||
logGlobal->trace("Attacked nearest target %s", attackInfo.first->getDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
makeStackDoNothing(next);
|
||||
logGlobal->trace("No target found");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const CGHeroInstance * curOwner = gameHandler->battleGetOwnerHero(next);
|
||||
const int stackCreatureId = next->unitType()->getId();
|
||||
|
||||
if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
|
||||
&& (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, stackCreatureId)))
|
||||
{
|
||||
BattleAction attack;
|
||||
attack.actionType = EActionType::SHOOT;
|
||||
attack.side = next->unitSide();
|
||||
attack.stackNumber = next->unitId();
|
||||
|
||||
//TODO: select target by priority
|
||||
|
||||
const battle::Unit * target = nullptr;
|
||||
|
||||
for(auto & elem : gameHandler->gameState()->curB->stacks)
|
||||
{
|
||||
if(elem->unitType()->getId() != CreatureID::CATAPULT
|
||||
&& elem->unitOwner() != next->unitOwner()
|
||||
&& elem->isValidTarget()
|
||||
&& gameHandler->gameState()->curB->battleCanShoot(next, elem->getPosition()))
|
||||
{
|
||||
target = elem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(target == nullptr)
|
||||
{
|
||||
makeStackDoNothing(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
attack.aimToUnit(target);
|
||||
makeAutomaticAction(next, attack);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (next->unitType()->getId() == CreatureID::CATAPULT)
|
||||
{
|
||||
const auto & attackableBattleHexes = curB.getAttackableBattleHexes();
|
||||
|
||||
if (attackableBattleHexes.empty())
|
||||
{
|
||||
makeStackDoNothing(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, CreatureID::CATAPULT))
|
||||
{
|
||||
BattleAction attack;
|
||||
attack.actionType = EActionType::CATAPULT;
|
||||
attack.side = next->unitSide();
|
||||
attack.stackNumber = next->unitId();
|
||||
|
||||
makeAutomaticAction(next, attack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (next->unitType()->getId() == CreatureID::FIRST_AID_TENT)
|
||||
{
|
||||
TStacks possibleStacks = gameHandler->battleGetStacksIf([=](const CStack * s)
|
||||
{
|
||||
return s->unitOwner() == next->unitOwner() && s->canBeHealed();
|
||||
});
|
||||
|
||||
if (!possibleStacks.size())
|
||||
{
|
||||
makeStackDoNothing(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!curOwner || gameHandler->getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, CreatureID::FIRST_AID_TENT))
|
||||
{
|
||||
RandomGeneratorUtil::randomShuffle(possibleStacks, gameHandler->getRandomGenerator());
|
||||
const CStack * toBeHealed = possibleStacks.front();
|
||||
|
||||
BattleAction heal;
|
||||
heal.actionType = EActionType::STACK_HEAL;
|
||||
heal.aimToUnit(toBeHealed);
|
||||
heal.side = next->unitSide();
|
||||
heal.stackNumber = next->unitId();
|
||||
|
||||
makeAutomaticAction(next, heal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
stackTurnTrigger(next); //various effects
|
||||
|
||||
if(next->fear)
|
||||
{
|
||||
makeStackDoNothing(next); //end immediately if stack was affected by fear
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->trace("Activating %s", next->nodeName());
|
||||
auto nextId = next->unitId();
|
||||
BattleSetActiveStack sas;
|
||||
sas.stack = nextId;
|
||||
gameHandler->sendAndApply(&sas);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::onActionMade(const CStack *next)
|
||||
{
|
||||
//we're after action, all results applied
|
||||
owner->checkBattleStateChanges(); //check if this action ended the battle
|
||||
|
||||
if(next == nullptr)
|
||||
return;
|
||||
|
||||
//check for good morale
|
||||
auto nextStackMorale = next->moraleVal();
|
||||
if( !next->hadMorale
|
||||
&& !next->defending
|
||||
&& !next->waited()
|
||||
&& !next->fear
|
||||
&& next->alive()
|
||||
&& nextStackMorale > 0)
|
||||
{
|
||||
auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE);
|
||||
size_t diceIndex = std::min<size_t>(diceSize.size()-1, nextStackMorale);
|
||||
|
||||
if(diceSize.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, diceSize[diceIndex]) == 1)
|
||||
{
|
||||
BattleTriggerEffect bte;
|
||||
bte.stackID = next->unitId();
|
||||
bte.effect = vstd::to_underlying(BonusType::MORALE);
|
||||
bte.val = 1;
|
||||
bte.additionalInfo = 0;
|
||||
gameHandler->sendAndApply(&bte); //play animation
|
||||
}
|
||||
}
|
||||
|
||||
if (gameHandler->gameLobby()->state != EServerState::SHUTDOWN)
|
||||
owner->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::makeStackDoNothing(const CStack * next)
|
||||
{
|
||||
BattleAction doNothing;
|
||||
doNothing.actionType = EActionType::NO_ACTION;
|
||||
doNothing.side = next->unitSide();
|
||||
doNothing.stackNumber = next->unitId();
|
||||
|
||||
makeAutomaticAction(next, doNothing);
|
||||
}
|
||||
|
||||
bool BattleFlowProcessor::makeAutomaticAction(const CStack *stack, BattleAction &ba)
|
||||
{
|
||||
BattleSetActiveStack bsa;
|
||||
bsa.stack = stack->unitId();
|
||||
bsa.askPlayerInterface = false;
|
||||
gameHandler->sendAndApply(&bsa);
|
||||
|
||||
bool ret = owner->makeBattleAction(ba);
|
||||
owner->checkBattleStateChanges();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::stackEnchantedTrigger(const CStack * st)
|
||||
{
|
||||
auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED)));
|
||||
for(auto b : bl)
|
||||
{
|
||||
const CSpell * sp = SpellID(b->subtype).toSpell();
|
||||
if(!sp)
|
||||
continue;
|
||||
|
||||
const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
|
||||
const int32_t level = ((val > 3) ? (val - 3) : val);
|
||||
|
||||
spells::BattleCast battleCast(gameHandler->gameState()->curB, st, spells::Mode::PASSIVE, sp);
|
||||
//this makes effect accumulate for at most 50 turns by default, but effect may be permanent and last till the end of battle
|
||||
battleCast.setEffectDuration(50);
|
||||
battleCast.setSpellLevel(level);
|
||||
spells::Target target;
|
||||
|
||||
if(val > 3)
|
||||
{
|
||||
for(auto s : gameHandler->gameState()->curB->battleGetAllStacks())
|
||||
if(gameHandler->battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
|
||||
target.emplace_back(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.emplace_back(st);
|
||||
}
|
||||
battleCast.applyEffects(gameHandler->spellEnv, target, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::removeObstacle(const CObstacleInstance & obstacle)
|
||||
{
|
||||
BattleObstaclesChanged obsRem;
|
||||
obsRem.changes.emplace_back(obstacle.uniqueID, ObstacleChanges::EOperation::REMOVE);
|
||||
gameHandler->sendAndApply(&obsRem);
|
||||
}
|
||||
|
||||
void BattleFlowProcessor::stackTurnTrigger(const CStack *st)
|
||||
{
|
||||
BattleTriggerEffect bte;
|
||||
bte.stackID = st->unitId();
|
||||
bte.effect = -1;
|
||||
bte.val = 0;
|
||||
bte.additionalInfo = 0;
|
||||
if (st->alive())
|
||||
{
|
||||
//unbind
|
||||
if (st->hasBonus(Selector::type()(BonusType::BIND_EFFECT)))
|
||||
{
|
||||
bool unbind = true;
|
||||
BonusList bl = *(st->getBonuses(Selector::type()(BonusType::BIND_EFFECT)));
|
||||
auto adjacent = gameHandler->gameState()->curB->battleAdjacentUnits(st);
|
||||
|
||||
for (auto b : bl)
|
||||
{
|
||||
if(b->additionalInfo != CAddInfo::NONE)
|
||||
{
|
||||
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(b->additionalInfo[0]); //binding stack must be alive and adjacent
|
||||
if(stack)
|
||||
{
|
||||
if(vstd::contains(adjacent, stack)) //binding stack is still present
|
||||
unbind = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unbind = false;
|
||||
}
|
||||
}
|
||||
if (unbind)
|
||||
{
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.which = BattleSetStackProperty::UNBIND;
|
||||
ssp.stackID = st->unitId();
|
||||
gameHandler->sendAndApply(&ssp);
|
||||
}
|
||||
}
|
||||
|
||||
if (st->hasBonusOfType(BonusType::POISON))
|
||||
{
|
||||
std::shared_ptr<const Bonus> b = st->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, SpellID::POISON).And(Selector::type()(BonusType::STACK_HEALTH)));
|
||||
if (b) //TODO: what if not?...
|
||||
{
|
||||
bte.val = std::max (b->val - 10, -(st->valOfBonuses(BonusType::POISON)));
|
||||
if (bte.val < b->val) //(negative) poison effect increases - update it
|
||||
{
|
||||
bte.effect = vstd::to_underlying(BonusType::POISON);
|
||||
gameHandler->sendAndApply(&bte);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(st->hasBonusOfType(BonusType::MANA_DRAIN) && !st->drainedMana)
|
||||
{
|
||||
const PlayerColor opponent = gameHandler->gameState()->curB->otherPlayer(gameHandler->gameState()->curB->battleGetOwner(st));
|
||||
const CGHeroInstance * opponentHero = gameHandler->gameState()->curB->getHero(opponent);
|
||||
if(opponentHero)
|
||||
{
|
||||
ui32 manaDrained = st->valOfBonuses(BonusType::MANA_DRAIN);
|
||||
vstd::amin(manaDrained, opponentHero->mana);
|
||||
if(manaDrained)
|
||||
{
|
||||
bte.effect = vstd::to_underlying(BonusType::MANA_DRAIN);
|
||||
bte.val = manaDrained;
|
||||
bte.additionalInfo = opponentHero->id.getNum(); //for sanity
|
||||
gameHandler->sendAndApply(&bte);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (st->isLiving() && !st->hasBonusOfType(BonusType::FEARLESS))
|
||||
{
|
||||
bool fearsomeCreature = false;
|
||||
for (CStack * stack : gameHandler->gameState()->curB->stacks)
|
||||
{
|
||||
if (gameHandler->battleMatchOwner(st, stack) && stack->alive() && stack->hasBonusOfType(BonusType::FEAR))
|
||||
{
|
||||
fearsomeCreature = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fearsomeCreature)
|
||||
{
|
||||
if (gameHandler->getRandomGenerator().nextInt(99) < 10) //fixed 10%
|
||||
{
|
||||
bte.effect = vstd::to_underlying(BonusType::FEAR);
|
||||
gameHandler->sendAndApply(&bte);
|
||||
}
|
||||
}
|
||||
}
|
||||
BonusList bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTER)));
|
||||
int side = gameHandler->gameState()->curB->whatSide(st->unitOwner());
|
||||
if(st->canCast() && gameHandler->gameState()->curB->battleGetEnchanterCounter(side) == 0)
|
||||
{
|
||||
bool cast = false;
|
||||
while(!bl.empty() && !cast)
|
||||
{
|
||||
auto bonus = *RandomGeneratorUtil::nextItem(bl, gameHandler->getRandomGenerator());
|
||||
auto spellID = SpellID(bonus->subtype);
|
||||
const CSpell * spell = SpellID(spellID).toSpell();
|
||||
bl.remove_if([&bonus](const Bonus * b)
|
||||
{
|
||||
return b == bonus.get();
|
||||
});
|
||||
spells::BattleCast parameters(gameHandler->gameState()->curB, st, spells::Mode::ENCHANTER, spell);
|
||||
parameters.setSpellLevel(bonus->val);
|
||||
parameters.massive = true;
|
||||
parameters.smart = true;
|
||||
//todo: recheck effect level
|
||||
if(parameters.castIfPossible(gameHandler->spellEnv, spells::Target(1, spells::Destination())))
|
||||
{
|
||||
cast = true;
|
||||
|
||||
int cooldown = bonus->additionalInfo[0];
|
||||
BattleSetStackProperty ssp;
|
||||
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
|
||||
ssp.absolute = false;
|
||||
ssp.val = cooldown;
|
||||
ssp.stackID = st->unitId();
|
||||
gameHandler->sendAndApply(&ssp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
server/battles/BattleFlowProcessor.h
Normal file
49
server/battles/BattleFlowProcessor.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* BattleFlowProcessor.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CStack;
|
||||
struct BattleHex;
|
||||
class BattleAction;
|
||||
struct CObstacleInstance;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CGameHandler;
|
||||
class BattleProcessor;
|
||||
|
||||
class BattleFlowProcessor : boost::noncopyable
|
||||
{
|
||||
BattleProcessor * owner;
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
const CStack * getNextStack();
|
||||
|
||||
bool tryMakeAutomaticAction(const CStack * stack);
|
||||
|
||||
void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
|
||||
void activateNextStack();
|
||||
void startNextRound(bool isFirstRound);
|
||||
|
||||
void stackEnchantedTrigger(const CStack * stack);
|
||||
void removeObstacle(const CObstacleInstance &obstacle);
|
||||
void stackTurnTrigger(const CStack *stack);
|
||||
|
||||
void makeStackDoNothing(const CStack * next);
|
||||
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
||||
|
||||
public:
|
||||
BattleFlowProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
void onBattleStarted();
|
||||
void onTacticsEnded();
|
||||
void onActionMade(const CStack *stack);
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -10,118 +10,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
struct SideInBattle;
|
||||
|
||||
namespace battle {
|
||||
class CUnitState;
|
||||
}
|
||||
|
||||
class CGHeroInstance;
|
||||
class CGTownInstance;
|
||||
class CArmedInstance;
|
||||
class BattleAction;
|
||||
class int3;
|
||||
class BattleInfo;
|
||||
struct BattleResult;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CGameHandler;
|
||||
class CBattleQuery;
|
||||
|
||||
struct CasualtiesAfterBattle
|
||||
{
|
||||
using TStackAndItsNewCount = std::pair<StackLocation, int>;
|
||||
using TSummoned = std::map<CreatureID, TQuantity>;
|
||||
enum {ERASE = -1};
|
||||
const CArmedInstance * army;
|
||||
std::vector<TStackAndItsNewCount> newStackCounts;
|
||||
std::vector<ArtifactLocation> removedWarMachines;
|
||||
TSummoned summoned;
|
||||
ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations
|
||||
|
||||
CasualtiesAfterBattle(const SideInBattle & battleSide, const BattleInfo * bat);
|
||||
void updateArmy(CGameHandler *gh);
|
||||
};
|
||||
|
||||
struct FinishingBattleHelper
|
||||
{
|
||||
FinishingBattleHelper();
|
||||
FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, int RemainingBattleQueriesCount);
|
||||
|
||||
inline bool isDraw() const {return winnerSide == 2;}
|
||||
|
||||
const CGHeroInstance *winnerHero, *loserHero;
|
||||
PlayerColor victor, loser;
|
||||
ui8 winnerSide;
|
||||
|
||||
int remainingBattleQueriesCount;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & winnerHero;
|
||||
h & loserHero;
|
||||
h & victor;
|
||||
h & loser;
|
||||
h & winnerSide;
|
||||
h & remainingBattleQueriesCount;
|
||||
}
|
||||
};
|
||||
|
||||
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
|
||||
class BattleActionProcessor;
|
||||
class BattleFlowProcessor;
|
||||
class BattleResultProcessor;
|
||||
|
||||
class BattleProcessor : boost::noncopyable
|
||||
{
|
||||
////used only in endBattle - don't touch elsewhere
|
||||
bool visitObjectAfterVictory;
|
||||
friend class BattleActionProcessor;
|
||||
friend class BattleFlowProcessor;
|
||||
friend class BattleResultProcessor;
|
||||
|
||||
std::unique_ptr<boost::thread> battleThread;
|
||||
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
||||
CGameHandler * gameHandler;
|
||||
std::unique_ptr<BattleActionProcessor> actionsProcessor;
|
||||
std::unique_ptr<BattleFlowProcessor> flowProcessor;
|
||||
std::unique_ptr<BattleResultProcessor> resultProcessor;
|
||||
|
||||
void removeObstacle(const CObstacleInstance &obstacle);
|
||||
void makeStackDoNothing(const CStack * next);
|
||||
void updateGateState();
|
||||
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
||||
void stackEnchantedTrigger(const CStack * stack);
|
||||
void stackTurnTrigger(const CStack *stack);
|
||||
void engageIntoBattle( PlayerColor player );
|
||||
void handleAttackBeforeCasting(bool ranged, const CStack * attacker, const CStack * defender);
|
||||
void handleAfterAttackCasting(bool ranged, const CStack * attacker, const CStack * defender);
|
||||
void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
|
||||
|
||||
int moveStack(int stack, BattleHex dest); //returned value - travelled distance
|
||||
void runBattle();
|
||||
|
||||
void makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter);
|
||||
|
||||
// damage, drain life & fire shield; returns amount of drained life
|
||||
int64_t applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary);
|
||||
|
||||
void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
|
||||
void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
|
||||
|
||||
void checkBattleStateChanges();
|
||||
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
||||
void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
|
||||
|
||||
bool makeBattleActionImpl(BattleAction &ba);
|
||||
bool makeCustomActionImpl(BattleAction &ba);
|
||||
bool makeBattleAction(BattleAction &ba);
|
||||
bool makeCustomAction(BattleAction &ba);
|
||||
|
||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||
public:
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
BattleProcessor(CGameHandler * gameHandler);
|
||||
explicit BattleProcessor(CGameHandler * gameHandler);
|
||||
BattleProcessor();
|
||||
|
||||
~BattleProcessor();
|
||||
|
||||
void setGameHandler(CGameHandler * gameHandler);
|
||||
|
||||
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr); //use hero=nullptr for no hero
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false); //if any of armies is hero, hero will be used
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
|
||||
void battleAfterLevelUp(const BattleResult &result);
|
||||
|
||||
bool makeBattleAction(PlayerColor player, BattleAction &ba);
|
||||
bool makeCustomAction(PlayerColor player, BattleAction &ba);
|
||||
|
||||
void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
|
||||
void endBattleConfirm(const BattleInfo * battleInfo);
|
||||
void battleAfterLevelUp(const BattleResult &result);
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
|
554
server/battles/BattleResultProcessor.cpp
Normal file
554
server/battles/BattleResultProcessor.cpp
Normal file
@ -0,0 +1,554 @@
|
||||
/*
|
||||
* BattleResultProcessor.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleResultProcessor.h"
|
||||
|
||||
#include "../CGameHandler.h"
|
||||
#include "../CVCMIServer.h"
|
||||
#include "../processors/HeroPoolProcessor.h"
|
||||
#include "../queries/QueriesProcessor.h"
|
||||
#include "../queries/BattleQueries.h"
|
||||
|
||||
#include "../../lib/ArtifactUtils.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/ScopeGuard.h"
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/modding/IdentifierStorage.h"
|
||||
#include "../../lib/serializer/Cast.h"
|
||||
#include "../../lib/spells/AbilityCaster.h"
|
||||
#include "../../lib/spells/BonusCaster.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/ObstacleCasterProxy.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
|
||||
BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner)
|
||||
: owner(owner)
|
||||
, gameHandler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void BattleResultProcessor::setGameHandler(CGameHandler * newGameHandler)
|
||||
{
|
||||
gameHandler = newGameHandler;
|
||||
}
|
||||
|
||||
CasualtiesAfterBattle::CasualtiesAfterBattle(const SideInBattle & battleSide, const BattleInfo * bat):
|
||||
army(battleSide.armyObject)
|
||||
{
|
||||
heroWithDeadCommander = ObjectInstanceID();
|
||||
|
||||
PlayerColor color = battleSide.color;
|
||||
|
||||
for(CStack * st : bat->stacks)
|
||||
{
|
||||
if(st->summoned) //don't take into account temporary summoned stacks
|
||||
continue;
|
||||
if(st->unitOwner() != color) //remove only our stacks
|
||||
continue;
|
||||
|
||||
logGlobal->debug("Calculating casualties for %s", st->nodeName());
|
||||
|
||||
st->health.takeResurrected();
|
||||
|
||||
if(st->unitSlot() == SlotID::ARROW_TOWERS_SLOT)
|
||||
{
|
||||
logGlobal->debug("Ignored arrow towers stack.");
|
||||
}
|
||||
else if(st->unitSlot() == SlotID::WAR_MACHINES_SLOT)
|
||||
{
|
||||
auto warMachine = st->unitType()->warMachine;
|
||||
|
||||
if(warMachine == ArtifactID::NONE)
|
||||
{
|
||||
logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
|
||||
}
|
||||
//catapult artifact remain even if "creature" killed in siege
|
||||
else if(warMachine != ArtifactID::CATAPULT && st->getCount() <= 0)
|
||||
{
|
||||
logGlobal->debug("War machine has been destroyed");
|
||||
auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
|
||||
if (hero)
|
||||
removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true)));
|
||||
else
|
||||
logGlobal->error("War machine in army without hero");
|
||||
}
|
||||
}
|
||||
else if(st->unitSlot() == SlotID::SUMMONED_SLOT_PLACEHOLDER)
|
||||
{
|
||||
if(st->alive() && st->getCount() > 0)
|
||||
{
|
||||
logGlobal->debug("Permanently summoned %d units.", st->getCount());
|
||||
const CreatureID summonedType = st->creatureId();
|
||||
summoned[summonedType] += st->getCount();
|
||||
}
|
||||
}
|
||||
else if(st->unitSlot() == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
||||
{
|
||||
if (nullptr == st->base)
|
||||
{
|
||||
logGlobal->error("Stack with no base in commander slot. Stack: %s", st->nodeName());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto c = dynamic_cast <const CCommanderInstance *>(st->base);
|
||||
if(c)
|
||||
{
|
||||
auto h = dynamic_cast <const CGHeroInstance *>(army);
|
||||
if(h && h->commander == c && (st->getCount() == 0 || !st->alive()))
|
||||
{
|
||||
logGlobal->debug("Commander is dead.");
|
||||
heroWithDeadCommander = army->id; //TODO: unify commander handling
|
||||
}
|
||||
}
|
||||
else
|
||||
logGlobal->error("Stack with invalid instance in commander slot. Stack: %s", st->nodeName());
|
||||
}
|
||||
}
|
||||
else if(st->base && !army->slotEmpty(st->unitSlot()))
|
||||
{
|
||||
logGlobal->debug("Count: %d; base count: %d", st->getCount(), army->getStackCount(st->unitSlot()));
|
||||
if(st->getCount() == 0 || !st->alive())
|
||||
{
|
||||
logGlobal->debug("Stack has been destroyed.");
|
||||
StackLocation sl(army, st->unitSlot());
|
||||
newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
|
||||
}
|
||||
else if(st->getCount() < army->getStackCount(st->unitSlot()))
|
||||
{
|
||||
logGlobal->debug("Stack lost %d units.", army->getStackCount(st->unitSlot()) - st->getCount());
|
||||
StackLocation sl(army, st->unitSlot());
|
||||
newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount()));
|
||||
}
|
||||
else if(st->getCount() > army->getStackCount(st->unitSlot()))
|
||||
{
|
||||
logGlobal->debug("Stack gained %d units.", st->getCount() - army->getStackCount(st->unitSlot()));
|
||||
StackLocation sl(army, st->unitSlot());
|
||||
newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->warn("Unable to process stack: %s", st->nodeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CasualtiesAfterBattle::updateArmy(CGameHandler *gh)
|
||||
{
|
||||
for (TStackAndItsNewCount &ncount : newStackCounts)
|
||||
{
|
||||
if (ncount.second > 0)
|
||||
gh->changeStackCount(ncount.first, ncount.second, true);
|
||||
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);
|
||||
}
|
||||
if (heroWithDeadCommander != ObjectInstanceID())
|
||||
{
|
||||
SetCommanderProperty scp;
|
||||
scp.heroid = heroWithDeadCommander;
|
||||
scp.which = SetCommanderProperty::ALIVE;
|
||||
scp.amount = 0;
|
||||
gh->sendAndApply(&scp);
|
||||
}
|
||||
}
|
||||
|
||||
FinishingBattleHelper::FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, int remainingBattleQueriesCount)
|
||||
{
|
||||
assert(Query->result);
|
||||
assert(Query->bi);
|
||||
auto &result = *Query->result;
|
||||
auto &info = *Query->bi;
|
||||
|
||||
winnerHero = result.winner != 0 ? info.sides[1].hero : info.sides[0].hero;
|
||||
loserHero = result.winner != 0 ? info.sides[0].hero : info.sides[1].hero;
|
||||
victor = info.sides[result.winner].color;
|
||||
loser = info.sides[!result.winner].color;
|
||||
winnerSide = result.winner;
|
||||
this->remainingBattleQueriesCount = remainingBattleQueriesCount;
|
||||
}
|
||||
|
||||
FinishingBattleHelper::FinishingBattleHelper()
|
||||
{
|
||||
winnerHero = loserHero = nullptr;
|
||||
winnerSide = 0;
|
||||
remainingBattleQueriesCount = 0;
|
||||
}
|
||||
|
||||
void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAttacker, const CGHeroInstance * heroDefender)
|
||||
{
|
||||
auto const & giveExp = [](BattleResult &r)
|
||||
{
|
||||
if (r.winner > 1)
|
||||
{
|
||||
// draw
|
||||
return;
|
||||
}
|
||||
r.exp[0] = 0;
|
||||
r.exp[1] = 0;
|
||||
for (auto i = r.casualties[!r.winner].begin(); i!=r.casualties[!r.winner].end(); i++)
|
||||
{
|
||||
r.exp[r.winner] += VLC->creh->objects.at(i->first)->valOfBonuses(BonusType::STACK_HEALTH) * i->second;
|
||||
}
|
||||
};
|
||||
|
||||
LOG_TRACE(logGlobal);
|
||||
|
||||
//Fill BattleResult structure with exp info
|
||||
giveExp(*battleResult);
|
||||
|
||||
if (battleResult->result == EBattleResult::NORMAL) // give 500 exp for defeating hero, unless he escaped
|
||||
{
|
||||
if(heroAttacker)
|
||||
battleResult->exp[1] += 500;
|
||||
if(heroDefender)
|
||||
battleResult->exp[0] += 500;
|
||||
}
|
||||
|
||||
if(heroAttacker)
|
||||
battleResult->exp[0] = heroAttacker->calculateXp(battleResult->exp[0]);//scholar skill
|
||||
if(heroDefender)
|
||||
battleResult->exp[1] = heroDefender->calculateXp(battleResult->exp[1]);
|
||||
|
||||
auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(gameHandler->gameState()->curB->sides[0].color));
|
||||
if (!battleQuery)
|
||||
{
|
||||
logGlobal->error("Cannot find battle query!");
|
||||
gameHandler->complain("Player " + boost::lexical_cast<std::string>(gameHandler->gameState()->curB->sides[0].color) + " has no battle query at the top!");
|
||||
return;
|
||||
}
|
||||
|
||||
battleQuery->result = std::make_optional(*battleResult);
|
||||
|
||||
//Check how many battle gameHandler->queries were created (number of players blocked by battle)
|
||||
const int queriedPlayers = battleQuery ? (int)boost::count(gameHandler->queries->allQueries(), battleQuery) : 0;
|
||||
finishingBattle = std::make_unique<FinishingBattleHelper>(battleQuery, queriedPlayers);
|
||||
|
||||
// in battles against neutrals, 1st player can ask to replay battle manually
|
||||
if (!gameHandler->gameState()->curB->sides[1].color.isValidPlayer())
|
||||
{
|
||||
auto battleDialogQuery = std::make_shared<CBattleDialogQuery>(gameHandler, gameHandler->gameState()->curB);
|
||||
battleResult->queryID = battleDialogQuery->queryID;
|
||||
gameHandler->queries->addQuery(battleDialogQuery);
|
||||
}
|
||||
else
|
||||
battleResult->queryID = -1;
|
||||
|
||||
//set same battle result for all gameHandler->queries
|
||||
for(auto q : gameHandler->queries->allQueries())
|
||||
{
|
||||
auto otherBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(q);
|
||||
if(otherBattleQuery)
|
||||
otherBattleQuery->result = battleQuery->result;
|
||||
}
|
||||
|
||||
gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed
|
||||
|
||||
if (battleResult->queryID == -1)
|
||||
endBattleConfirm(gameHandler->gameState()->curB);
|
||||
}
|
||||
|
||||
void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo)
|
||||
{
|
||||
auto battleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battleInfo->sides.at(0).color));
|
||||
if(!battleQuery)
|
||||
{
|
||||
logGlobal->trace("No battle query, battle end was confirmed by another player");
|
||||
return;
|
||||
}
|
||||
|
||||
const EBattleResult result = battleResult.get()->result;
|
||||
|
||||
CasualtiesAfterBattle cab1(battleInfo->sides.at(0), battleInfo), cab2(battleInfo->sides.at(1), battleInfo); //calculate casualties before deleting battle
|
||||
ChangeSpells cs; //for Eagle Eye
|
||||
|
||||
if(!finishingBattle->isDraw() && finishingBattle->winnerHero)
|
||||
{
|
||||
if (int eagleEyeLevel = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT, -1))
|
||||
{
|
||||
double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE, 0);
|
||||
for(auto & spellId : battleInfo->sides.at(!battleResult->winner).usedSpellsHistory)
|
||||
{
|
||||
auto spell = spellId.toSpell(VLC->spells());
|
||||
if(spell && spell->getLevel() <= eagleEyeLevel && !finishingBattle->winnerHero->spellbookContainsSpell(spell->getId()) && gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
|
||||
cs.spells.insert(spell->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<const CArtifactInstance *> arts; //display them in window
|
||||
|
||||
if(result == EBattleResult::NORMAL && !finishingBattle->isDraw() && finishingBattle->winnerHero)
|
||||
{
|
||||
auto sendMoveArtifact = [&](const CArtifactInstance *art, MoveArtifact *ma)
|
||||
{
|
||||
const auto slot = ArtifactUtils::getArtAnyPosition(finishingBattle->winnerHero, art->getTypeId());
|
||||
if(slot != ArtifactPosition::PRE_FIRST)
|
||||
{
|
||||
arts.push_back(art);
|
||||
ma->dst = ArtifactLocation(finishingBattle->winnerHero, slot);
|
||||
if(ArtifactUtils::isSlotBackpack(slot))
|
||||
ma->askAssemble = false;
|
||||
gameHandler->sendAndApply(ma);
|
||||
}
|
||||
};
|
||||
|
||||
if (finishingBattle->loserHero)
|
||||
{
|
||||
//TODO: wrap it into a function, somehow (std::variant -_-)
|
||||
auto artifactsWorn = finishingBattle->loserHero->artifactsWorn;
|
||||
for (auto artSlot : artifactsWorn)
|
||||
{
|
||||
MoveArtifact ma;
|
||||
ma.src = ArtifactLocation(finishingBattle->loserHero, artSlot.first);
|
||||
const CArtifactInstance * art = ma.src.getArt();
|
||||
if (art && !art->artType->isBig() &&
|
||||
art->artType->getId() != ArtifactID::SPELLBOOK)
|
||||
// don't move war machines or locked arts (spellbook)
|
||||
{
|
||||
sendMoveArtifact(art, &ma);
|
||||
}
|
||||
}
|
||||
for(int slotNumber = finishingBattle->loserHero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--)
|
||||
{
|
||||
//we assume that no big artifacts can be found
|
||||
MoveArtifact ma;
|
||||
ma.src = ArtifactLocation(finishingBattle->loserHero,
|
||||
ArtifactPosition(GameConstants::BACKPACK_START + slotNumber)); //backpack automatically shifts arts to beginning
|
||||
const CArtifactInstance * art = ma.src.getArt();
|
||||
if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won
|
||||
{
|
||||
sendMoveArtifact(art, &ma);
|
||||
}
|
||||
}
|
||||
if (finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero?
|
||||
{
|
||||
artifactsWorn = finishingBattle->loserHero->commander->artifactsWorn;
|
||||
for (auto artSlot : artifactsWorn)
|
||||
{
|
||||
MoveArtifact ma;
|
||||
ma.src = ArtifactLocation(finishingBattle->loserHero->commander.get(), artSlot.first);
|
||||
const CArtifactInstance * art = ma.src.getArt();
|
||||
if (art && !art->artType->isBig())
|
||||
{
|
||||
sendMoveArtifact(art, &ma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto armySlot : battleInfo->sides.at(!battleResult->winner).armyObject->stacks)
|
||||
{
|
||||
auto artifactsWorn = armySlot.second->artifactsWorn;
|
||||
for (auto artSlot : artifactsWorn)
|
||||
{
|
||||
MoveArtifact ma;
|
||||
ma.src = ArtifactLocation(armySlot.second, artSlot.first);
|
||||
const CArtifactInstance * art = ma.src.getArt();
|
||||
if (art && !art->artType->isBig())
|
||||
{
|
||||
sendMoveArtifact(art, &ma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arts.size()) //display loot
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = finishingBattle->winnerHero->tempOwner;
|
||||
|
||||
iw.text.appendLocalString (EMetaText::GENERAL_TXT, 30); //You have captured enemy artifact
|
||||
|
||||
for (auto art : arts) //TODO; separate function to display loot for various ojects?
|
||||
{
|
||||
iw.components.emplace_back(
|
||||
Component::EComponentType::ARTIFACT, art->artType->getId(),
|
||||
art->artType->getId() == ArtifactID::SPELL_SCROLL? art->getScrollSpellID() : 0, 0);
|
||||
if (iw.components.size() >= 14)
|
||||
{
|
||||
gameHandler->sendAndApply(&iw);
|
||||
iw.components.clear();
|
||||
}
|
||||
}
|
||||
if (iw.components.size())
|
||||
{
|
||||
gameHandler->sendAndApply(&iw);
|
||||
}
|
||||
}
|
||||
//Eagle Eye secondary skill handling
|
||||
if (!cs.spells.empty())
|
||||
{
|
||||
cs.learn = 1;
|
||||
cs.hid = finishingBattle->winnerHero->id;
|
||||
|
||||
InfoWindow iw;
|
||||
iw.player = finishingBattle->winnerHero->tempOwner;
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
|
||||
iw.text.replaceRawString(finishingBattle->winnerHero->getNameTranslated());
|
||||
|
||||
std::ostringstream names;
|
||||
for (int i = 0; i < cs.spells.size(); i++)
|
||||
{
|
||||
names << "%s";
|
||||
if (i < cs.spells.size() - 2)
|
||||
names << ", ";
|
||||
else if (i < cs.spells.size() - 1)
|
||||
names << "%s";
|
||||
}
|
||||
names << ".";
|
||||
|
||||
iw.text.replaceRawString(names.str());
|
||||
|
||||
auto it = cs.spells.begin();
|
||||
for (int i = 0; i < cs.spells.size(); i++, it++)
|
||||
{
|
||||
iw.text.replaceLocalString(EMetaText::SPELL_NAME, it->toEnum());
|
||||
if (i == cs.spells.size() - 2) //we just added pre-last name
|
||||
iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and "
|
||||
iw.components.emplace_back(Component::EComponentType::SPELL, *it, 0, 0);
|
||||
}
|
||||
gameHandler->sendAndApply(&iw);
|
||||
gameHandler->sendAndApply(&cs);
|
||||
}
|
||||
cab1.updateArmy(gameHandler);
|
||||
cab2.updateArmy(gameHandler); //take casualties after battle is deleted
|
||||
|
||||
if(finishingBattle->loserHero) //remove beaten hero
|
||||
{
|
||||
RemoveObject ro(finishingBattle->loserHero->id);
|
||||
gameHandler->sendAndApply(&ro);
|
||||
}
|
||||
if(finishingBattle->isDraw() && finishingBattle->winnerHero) //for draw case both heroes should be removed
|
||||
{
|
||||
RemoveObject ro(finishingBattle->winnerHero->id);
|
||||
gameHandler->sendAndApply(&ro);
|
||||
}
|
||||
|
||||
if(battleResult->winner == BattleSide::DEFENDER
|
||||
&& finishingBattle->winnerHero
|
||||
&& finishingBattle->winnerHero->visitedTown
|
||||
&& !finishingBattle->winnerHero->inTownGarrison
|
||||
&& finishingBattle->winnerHero->visitedTown->garrisonHero == finishingBattle->winnerHero)
|
||||
{
|
||||
gameHandler->swapGarrisonOnSiege(finishingBattle->winnerHero->visitedTown->id); //return defending visitor from garrison to its rightful place
|
||||
}
|
||||
//give exp
|
||||
if(!finishingBattle->isDraw() && battleResult->exp[finishingBattle->winnerSide] && finishingBattle->winnerHero)
|
||||
gameHandler->changePrimSkill(finishingBattle->winnerHero, PrimarySkill::EXPERIENCE, battleResult->exp[finishingBattle->winnerSide]);
|
||||
|
||||
BattleResultAccepted raccepted;
|
||||
raccepted.heroResult[0].army = const_cast<CArmedInstance*>(battleInfo->sides.at(0).armyObject);
|
||||
raccepted.heroResult[1].army = const_cast<CArmedInstance*>(battleInfo->sides.at(1).armyObject);
|
||||
raccepted.heroResult[0].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(0).hero);
|
||||
raccepted.heroResult[1].hero = const_cast<CGHeroInstance*>(battleInfo->sides.at(1).hero);
|
||||
raccepted.heroResult[0].exp = battleResult->exp[0];
|
||||
raccepted.heroResult[1].exp = battleResult->exp[1];
|
||||
raccepted.winnerSide = finishingBattle->winnerSide;
|
||||
gameHandler->sendAndApply(&raccepted);
|
||||
|
||||
gameHandler->queries->popIfTop(battleQuery);
|
||||
//--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query
|
||||
}
|
||||
|
||||
void BattleResultProcessor::battleAfterLevelUp(const BattleResult &result)
|
||||
{
|
||||
LOG_TRACE(logGlobal);
|
||||
|
||||
if(!finishingBattle)
|
||||
return;
|
||||
|
||||
finishingBattle->remainingBattleQueriesCount--;
|
||||
logGlobal->trace("Decremented gameHandler->queries count to %d", finishingBattle->remainingBattleQueriesCount);
|
||||
|
||||
if (finishingBattle->remainingBattleQueriesCount > 0)
|
||||
//Battle results will be handled when all battle gameHandler->queries are closed
|
||||
return;
|
||||
|
||||
//TODO consider if we really want it to work like above. ATM each player as unblocked as soon as possible
|
||||
// but the battle consequences are applied after final player is unblocked. Hard to abuse...
|
||||
// Still, it looks like a hole.
|
||||
|
||||
// Necromancy if applicable.
|
||||
const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(*battleResult) : CStackBasicDescriptor();
|
||||
// Give raised units to winner and show dialog, if any were raised,
|
||||
// units will be given after casualties are taken
|
||||
const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID();
|
||||
|
||||
if (necroSlot != SlotID())
|
||||
{
|
||||
finishingBattle->winnerHero->showNecromancyDialog(raisedStack, gameHandler->getRandomGenerator());
|
||||
gameHandler->addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.type, raisedStack.count);
|
||||
}
|
||||
|
||||
BattleResultsApplied resultsApplied;
|
||||
resultsApplied.player1 = finishingBattle->victor;
|
||||
resultsApplied.player2 = finishingBattle->loser;
|
||||
gameHandler->sendAndApply(&resultsApplied);
|
||||
|
||||
gameHandler->setBattle(nullptr);
|
||||
|
||||
//handle victory/loss of engaged players
|
||||
std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor};
|
||||
gameHandler->checkVictoryLossConditions(playerColors);
|
||||
|
||||
if (result.result == EBattleResult::SURRENDER)
|
||||
gameHandler->heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero);
|
||||
|
||||
if (result.result == EBattleResult::ESCAPE)
|
||||
gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero);
|
||||
|
||||
if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty()
|
||||
&& (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive))
|
||||
{
|
||||
RemoveObject ro(finishingBattle->winnerHero->id);
|
||||
gameHandler->sendAndApply(&ro);
|
||||
|
||||
if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
|
||||
gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
|
||||
}
|
||||
|
||||
finishingBattle.reset();
|
||||
}
|
||||
|
||||
void BattleResultProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
|
||||
{
|
||||
battleResult = std::make_unique<BattleResult>();
|
||||
battleResult->result = resultType;
|
||||
battleResult->winner = victoriusSide; //surrendering side loses
|
||||
gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties);
|
||||
|
||||
}
|
78
server/battles/BattleResultProcessor.h
Normal file
78
server/battles/BattleResultProcessor.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* BattleProcessor.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
struct SideInBattle;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CBattleQuery;
|
||||
class BattleProcessor;
|
||||
class CGameHandler;
|
||||
|
||||
struct CasualtiesAfterBattle
|
||||
{
|
||||
using TStackAndItsNewCount = std::pair<StackLocation, int>;
|
||||
using TSummoned = std::map<CreatureID, TQuantity>;
|
||||
enum {ERASE = -1};
|
||||
const CArmedInstance * army;
|
||||
std::vector<TStackAndItsNewCount> newStackCounts;
|
||||
std::vector<ArtifactLocation> removedWarMachines;
|
||||
TSummoned summoned;
|
||||
ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations
|
||||
|
||||
CasualtiesAfterBattle(const SideInBattle & battleSide, const BattleInfo * bat);
|
||||
void updateArmy(CGameHandler *gh);
|
||||
};
|
||||
|
||||
struct FinishingBattleHelper
|
||||
{
|
||||
FinishingBattleHelper();
|
||||
FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, int RemainingBattleQueriesCount);
|
||||
|
||||
inline bool isDraw() const {return winnerSide == 2;}
|
||||
|
||||
const CGHeroInstance *winnerHero, *loserHero;
|
||||
PlayerColor victor, loser;
|
||||
ui8 winnerSide;
|
||||
|
||||
int remainingBattleQueriesCount;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & winnerHero;
|
||||
h & loserHero;
|
||||
h & victor;
|
||||
h & loser;
|
||||
h & winnerSide;
|
||||
h & remainingBattleQueriesCount;
|
||||
}
|
||||
};
|
||||
|
||||
class BattleResultProcessor : boost::noncopyable
|
||||
{
|
||||
BattleProcessor * owner;
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
std::unique_ptr<BattleResult> battleResult;
|
||||
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
||||
|
||||
public:
|
||||
BattleResultProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||
void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
|
||||
void endBattleConfirm(const BattleInfo * battleInfo);
|
||||
void battleAfterLevelUp(const BattleResult &result);
|
||||
};
|
Loading…
Reference in New Issue
Block a user