mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-25 12:14:46 +02:00
Merge remote-tracking branch 'upstream/develop' into loading-bar
# Conflicts: # server/CGameHandler.h
This commit is contained in:
commit
dbc3a93013
@ -18,6 +18,7 @@
|
|||||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../../lib/spells/CSpellHandler.h"
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
#include "../../lib/spells/ISpellMechanics.h"
|
#include "../../lib/spells/ISpellMechanics.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||||
#include "../../lib/battle/CObstacleInstance.h"
|
#include "../../lib/battle/CObstacleInstance.h"
|
||||||
#include "../../lib/CStack.h" // TODO: remove
|
#include "../../lib/CStack.h" // TODO: remove
|
||||||
@ -283,20 +284,11 @@ void CBattleAI::activeStack( const CStack * stack )
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptCastingSpell();
|
if (attemptCastingSpell())
|
||||||
|
return;
|
||||||
|
|
||||||
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
||||||
|
|
||||||
if(cb->battleIsFinished() || !stack->alive())
|
|
||||||
{
|
|
||||||
//spellcast may finish battle or kill active stack
|
|
||||||
//send special preudo-action
|
|
||||||
BattleAction cancel;
|
|
||||||
cancel.actionType = EActionType::CANCEL;
|
|
||||||
cb->battleMakeUnitAction(cancel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto action = considerFleeingOrSurrendering())
|
if(auto action = considerFleeingOrSurrendering())
|
||||||
{
|
{
|
||||||
cb->battleMakeUnitAction(*action);
|
cb->battleMakeUnitAction(*action);
|
||||||
@ -476,14 +468,14 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
|
|||||||
return attack;
|
return attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::attemptCastingSpell()
|
bool CBattleAI::attemptCastingSpell()
|
||||||
{
|
{
|
||||||
auto hero = cb->battleGetMyHero();
|
auto hero = cb->battleGetMyHero();
|
||||||
if(!hero)
|
if(!hero)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
|
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
LOGL("Casting spells sounds like fun. Let's see...");
|
LOGL("Casting spells sounds like fun. Let's see...");
|
||||||
//Get all spells we can cast
|
//Get all spells we can cast
|
||||||
@ -522,7 +514,7 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
}
|
}
|
||||||
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
||||||
if(possibleCasts.empty())
|
if(possibleCasts.empty())
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
using ValueMap = PossibleSpellcast::ValueMap;
|
using ValueMap = PossibleSpellcast::ValueMap;
|
||||||
|
|
||||||
@ -657,7 +649,7 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
if(battleIsFinishedOpt)
|
if(battleIsFinishedOpt)
|
||||||
{
|
{
|
||||||
print("No need to cast a spell. Battle will finish soon.");
|
print("No need to cast a spell. Battle will finish soon.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -780,16 +772,18 @@ void CBattleAI::attemptCastingSpell()
|
|||||||
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||||
BattleAction spellcast;
|
BattleAction spellcast;
|
||||||
spellcast.actionType = EActionType::HERO_SPELL;
|
spellcast.actionType = EActionType::HERO_SPELL;
|
||||||
spellcast.actionSubtype = castToPerform.spell->id;
|
spellcast.spell = castToPerform.spell->getId();
|
||||||
spellcast.setTarget(castToPerform.dest);
|
spellcast.setTarget(castToPerform.dest);
|
||||||
spellcast.side = side;
|
spellcast.side = side;
|
||||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||||
cb->battleMakeSpellAction(spellcast);
|
cb->battleMakeSpellAction(spellcast);
|
||||||
movesSkippedByDefense = 0;
|
movesSkippedByDefense = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ public:
|
|||||||
~CBattleAI();
|
~CBattleAI();
|
||||||
|
|
||||||
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||||
void attemptCastingSpell();
|
bool attemptCastingSpell();
|
||||||
|
|
||||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "../../lib/CRandomGenerator.h"
|
#include "../../lib/CRandomGenerator.h"
|
||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
|
||||||
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
||||||
{
|
{
|
||||||
@ -73,3 +74,8 @@ void CEmptyAI::showMapObjectSelectDialog(QueryID askID, const Component & icon,
|
|||||||
{
|
{
|
||||||
cb->selectionMade(0, askID);
|
cb->selectionMade(0, askID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<BattleAction> CEmptyAI::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
@ -32,6 +32,7 @@ public:
|
|||||||
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
||||||
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
|
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
|
||||||
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
||||||
|
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NAME "EmptyAI 0.1"
|
#define NAME "EmptyAI 0.1"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
#include "../../lib/CCreatureHandler.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
|
||||||
static std::shared_ptr<CBattleCallback> cbc;
|
static std::shared_ptr<CBattleCallback> cbc;
|
||||||
|
|
||||||
|
@ -2890,4 +2890,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<BattleAction> VCAI::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
@ -203,6 +203,7 @@ public:
|
|||||||
|
|
||||||
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
|
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
|
||||||
void battleEnd(const BattleResult * br, QueryID queryID) override;
|
void battleEnd(const BattleResult * br, QueryID queryID) override;
|
||||||
|
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||||
|
|
||||||
void makeTurn();
|
void makeTurn();
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
|
@ -206,7 +206,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
|||||||
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
assert(action.actionType == EActionType::HERO_SPELL);
|
assert(action.actionType == EActionType::HERO_SPELL);
|
||||||
MakeCustomAction mca(action);
|
MakeAction mca(action);
|
||||||
sendRequest(&mca);
|
sendRequest(&mca);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
|
|||||||
destinationTeleportPos = int3(-1);
|
destinationTeleportPos = int3(-1);
|
||||||
GH.defActionsDef = 0;
|
GH.defActionsDef = 0;
|
||||||
LOCPLINT = this;
|
LOCPLINT = this;
|
||||||
curAction = nullptr;
|
|
||||||
playerID=Player;
|
playerID=Player;
|
||||||
human=true;
|
human=true;
|
||||||
battleInt = nullptr;
|
battleInt = nullptr;
|
||||||
@ -769,8 +768,7 @@ void CPlayerInterface::actionStarted(const BattleAction &action)
|
|||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||||
|
|
||||||
curAction = new BattleAction(action);
|
battleInt->startAction(action);
|
||||||
battleInt->startAction(curAction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPlayerInterface::actionFinished(const BattleAction &action)
|
void CPlayerInterface::actionFinished(const BattleAction &action)
|
||||||
@ -778,9 +776,7 @@ void CPlayerInterface::actionFinished(const BattleAction &action)
|
|||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||||
|
|
||||||
battleInt->endAction(curAction);
|
battleInt->endAction(action);
|
||||||
delete curAction;
|
|
||||||
curAction = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn of that stack
|
void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn of that stack
|
||||||
@ -935,8 +931,6 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
|||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||||
|
|
||||||
assert(curAction);
|
|
||||||
|
|
||||||
StackAttackInfo info;
|
StackAttackInfo info;
|
||||||
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
|
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
|
||||||
info.defender = nullptr;
|
info.defender = nullptr;
|
||||||
@ -2110,3 +2104,8 @@ void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectP
|
|||||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||||
adventureInt->openWorldView(objectPositions, showTerrain );
|
adventureInt->openWorldView(objectPositions, showTerrain );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<BattleAction> CPlayerInterface::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
@ -66,7 +66,6 @@ class CPlayerInterface : public CGameInterface, public IUpdateable
|
|||||||
int autosaveCount;
|
int autosaveCount;
|
||||||
|
|
||||||
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
|
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
|
||||||
const BattleAction *curAction; //during the battle - action currently performed by active stack (or nullptr)
|
|
||||||
|
|
||||||
ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
|
ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
|
||||||
int3 destinationTeleportPos;
|
int3 destinationTeleportPos;
|
||||||
@ -173,6 +172,7 @@ protected: // Call-ins from server, should not be called directly, but only via
|
|||||||
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||||
void battleGateStateChanged(const EGateState state) override;
|
void battleGateStateChanged(const EGateState state) override;
|
||||||
void yourTacticPhase(int distance) override;
|
void yourTacticPhase(int distance) override;
|
||||||
|
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||||
|
|
||||||
public: // public interface for use by client via LOCPLINT access
|
public: // public interface for use by client via LOCPLINT access
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
#include <vcmi/Environment.h>
|
#include <vcmi/Environment.h>
|
||||||
|
|
||||||
#include "../lib/IGameCallback.h"
|
#include "../lib/IGameCallback.h"
|
||||||
#include "../lib/battle/BattleAction.h"
|
|
||||||
#include "../lib/battle/CBattleInfoCallback.h"
|
#include "../lib/battle/CBattleInfoCallback.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
@ -25,6 +24,7 @@ class CBattleGameInterface;
|
|||||||
class CGameInterface;
|
class CGameInterface;
|
||||||
class BinaryDeserializer;
|
class BinaryDeserializer;
|
||||||
class BinarySerializer;
|
class BinarySerializer;
|
||||||
|
class BattleAction;
|
||||||
|
|
||||||
template<typename T> class CApplier;
|
template<typename T> class CApplier;
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ public:
|
|||||||
|
|
||||||
std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
|
std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
|
||||||
|
|
||||||
std::optional<BattleAction> curbaction;
|
std::unique_ptr<BattleAction> currentBattleAction;
|
||||||
|
|
||||||
CClient();
|
CClient();
|
||||||
~CClient();
|
~CClient();
|
||||||
|
@ -784,7 +784,7 @@ void ApplyClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
|
|||||||
|
|
||||||
void ApplyFirstClientNetPackVisitor::visitStartAction(StartAction & pack)
|
void ApplyFirstClientNetPackVisitor::visitStartAction(StartAction & pack)
|
||||||
{
|
{
|
||||||
cl.curbaction = std::make_optional(pack.ba);
|
cl.currentBattleAction = std::make_unique<BattleAction>(pack.ba);
|
||||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionStarted, pack.ba);
|
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionStarted, pack.ba);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,8 +830,8 @@ void ApplyClientNetPackVisitor::visitCatapultAttack(CatapultAttack & pack)
|
|||||||
|
|
||||||
void ApplyClientNetPackVisitor::visitEndAction(EndAction & pack)
|
void ApplyClientNetPackVisitor::visitEndAction(EndAction & pack)
|
||||||
{
|
{
|
||||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionFinished, *cl.curbaction);
|
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionFinished, *cl.currentBattleAction);
|
||||||
cl.curbaction.reset();
|
cl.currentBattleAction.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
|
void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
|
||||||
|
@ -286,7 +286,7 @@ void BattleActionsController::castThisSpell(SpellID spellID)
|
|||||||
{
|
{
|
||||||
heroSpellToCast = std::make_shared<BattleAction>();
|
heroSpellToCast = std::make_shared<BattleAction>();
|
||||||
heroSpellToCast->actionType = EActionType::HERO_SPELL;
|
heroSpellToCast->actionType = EActionType::HERO_SPELL;
|
||||||
heroSpellToCast->actionSubtype = spellID; //spell number
|
heroSpellToCast->spell = spellID;
|
||||||
heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
|
heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
|
||||||
heroSpellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
heroSpellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||||
|
|
||||||
@ -314,7 +314,7 @@ void BattleActionsController::castThisSpell(SpellID spellID)
|
|||||||
const CSpell * BattleActionsController::getHeroSpellToCast( ) const
|
const CSpell * BattleActionsController::getHeroSpellToCast( ) const
|
||||||
{
|
{
|
||||||
if (heroSpellToCast)
|
if (heroSpellToCast)
|
||||||
return SpellID(heroSpellToCast->actionSubtype).toSpell();
|
return heroSpellToCast->spell.toSpell();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +94,13 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
|
|||||||
owner.waitForAnimations();
|
owner.waitForAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEffectsController::startAction(const BattleAction* action)
|
void BattleEffectsController::startAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
owner.checkForAnimations();
|
owner.checkForAnimations();
|
||||||
|
|
||||||
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
|
const CStack *stack = owner.curInt->cb->battleGetStackByID(action.stackNumber);
|
||||||
|
|
||||||
switch(action->actionType)
|
switch(action.actionType)
|
||||||
{
|
{
|
||||||
case EActionType::WAIT:
|
case EActionType::WAIT:
|
||||||
owner.appendBattleLog(stack->formatGeneralMessage(136));
|
owner.appendBattleLog(stack->formatGeneralMessage(136));
|
||||||
|
@ -60,7 +60,7 @@ public:
|
|||||||
|
|
||||||
BattleEffectsController(BattleInterface & owner);
|
BattleEffectsController(BattleInterface & owner);
|
||||||
|
|
||||||
void startAction(const BattleAction* action);
|
void startAction(const BattleAction & action);
|
||||||
|
|
||||||
//displays custom effect on the battlefield
|
//displays custom effect on the battlefield
|
||||||
void displayEffect(EBattleEffect effect, const BattleHex & destTile);
|
void displayEffect(EBattleEffect effect, const BattleHex & destTile);
|
||||||
|
@ -234,7 +234,7 @@ void BattleInterface::newRound(int number)
|
|||||||
console->addText(CGI->generaltexth->allTexts[412]);
|
console->addText(CGI->generaltexth->allTexts[412]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
|
void BattleInterface::giveCommand(EActionType action, BattleHex tile, SpellID spell)
|
||||||
{
|
{
|
||||||
const CStack * actor = nullptr;
|
const CStack * actor = nullptr;
|
||||||
if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
|
if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
|
||||||
@ -253,7 +253,7 @@ void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 addit
|
|||||||
ba.side = side.value();
|
ba.side = side.value();
|
||||||
ba.actionType = action;
|
ba.actionType = action;
|
||||||
ba.aimToHex(tile);
|
ba.aimToHex(tile);
|
||||||
ba.actionSubtype = additional;
|
ba.spell = spell;
|
||||||
|
|
||||||
sendCommand(ba, actor);
|
sendCommand(ba, actor);
|
||||||
}
|
}
|
||||||
@ -567,12 +567,12 @@ bool BattleInterface::makingTurn() const
|
|||||||
return stacksController->getActiveStack() != nullptr;
|
return stacksController->getActiveStack() != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleInterface::endAction(const BattleAction* action)
|
void BattleInterface::endAction(const BattleAction &action)
|
||||||
{
|
{
|
||||||
// it is possible that tactics mode ended while opening music is still playing
|
// it is possible that tactics mode ended while opening music is still playing
|
||||||
waitForAnimations();
|
waitForAnimations();
|
||||||
|
|
||||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
const CStack *stack = curInt->cb->battleGetStackByID(action.stackNumber);
|
||||||
|
|
||||||
// Activate stack from stackToActivate because this might have been temporary disabled, e.g., during spell cast
|
// Activate stack from stackToActivate because this might have been temporary disabled, e.g., during spell cast
|
||||||
activateStack();
|
activateStack();
|
||||||
@ -585,7 +585,7 @@ void BattleInterface::endAction(const BattleAction* action)
|
|||||||
tacticNextStack(stack);
|
tacticNextStack(stack);
|
||||||
|
|
||||||
//we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
|
//we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
|
||||||
if(action->actionType == EActionType::HERO_SPELL)
|
if(action.actionType == EActionType::HERO_SPELL)
|
||||||
fieldController->redrawBackgroundWithHexes();
|
fieldController->redrawBackgroundWithHexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,15 +594,15 @@ void BattleInterface::appendBattleLog(const std::string & newEntry)
|
|||||||
console->addText(newEntry);
|
console->addText(newEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleInterface::startAction(const BattleAction* action)
|
void BattleInterface::startAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
if(action->actionType == EActionType::END_TACTIC_PHASE)
|
if(action.actionType == EActionType::END_TACTIC_PHASE)
|
||||||
{
|
{
|
||||||
windowObject->tacticPhaseEnded();
|
windowObject->tacticPhaseEnded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
const CStack *stack = curInt->cb->battleGetStackByID(action.stackNumber);
|
||||||
|
|
||||||
if (stack)
|
if (stack)
|
||||||
{
|
{
|
||||||
@ -610,17 +610,17 @@ void BattleInterface::startAction(const BattleAction* action)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
|
assert(action.actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
|
||||||
}
|
}
|
||||||
|
|
||||||
stacksController->startAction(action);
|
stacksController->startAction(action);
|
||||||
|
|
||||||
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
|
if(action.actionType == EActionType::HERO_SPELL) //when hero casts spell
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!stack)
|
if (!stack)
|
||||||
{
|
{
|
||||||
logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
|
logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action.stackNumber);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ public:
|
|||||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||||
void requestAutofightingAIToTakeAction();
|
void requestAutofightingAIToTakeAction();
|
||||||
|
|
||||||
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE);
|
||||||
void sendCommand(BattleAction command, const CStack * actor = nullptr);
|
void sendCommand(BattleAction command, const CStack * actor = nullptr);
|
||||||
|
|
||||||
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
|
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
|
||||||
@ -188,7 +188,7 @@ public:
|
|||||||
void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
|
void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
|
||||||
|
|
||||||
//call-ins
|
//call-ins
|
||||||
void startAction(const BattleAction* action);
|
void startAction(const BattleAction & action);
|
||||||
void stackReset(const CStack * stack);
|
void stackReset(const CStack * stack);
|
||||||
void stackAdded(const CStack * stack); //new stack appeared on battlefield
|
void stackAdded(const CStack * stack); //new stack appeared on battlefield
|
||||||
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
||||||
@ -211,7 +211,7 @@ public:
|
|||||||
void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
||||||
void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
||||||
|
|
||||||
void endAction(const BattleAction* action);
|
void endAction(const BattleAction & action);
|
||||||
|
|
||||||
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
|
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
|
||||||
void obstacleRemoved(const std::vector<ObstacleChanges> & obstacles);
|
void obstacleRemoved(const std::vector<ObstacleChanges> & obstacles);
|
||||||
|
@ -567,12 +567,12 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
|||||||
int text = 304;
|
int text = 304;
|
||||||
switch(br.result)
|
switch(br.result)
|
||||||
{
|
{
|
||||||
case BattleResult::NORMAL:
|
case EBattleResult::NORMAL:
|
||||||
break;
|
break;
|
||||||
case BattleResult::ESCAPE:
|
case EBattleResult::ESCAPE:
|
||||||
text = 303;
|
text = 303;
|
||||||
break;
|
break;
|
||||||
case BattleResult::SURRENDER:
|
case EBattleResult::SURRENDER:
|
||||||
text = 302;
|
text = 302;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -601,14 +601,14 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
|||||||
std::string videoName = "LBSTART.BIK";
|
std::string videoName = "LBSTART.BIK";
|
||||||
switch(br.result)
|
switch(br.result)
|
||||||
{
|
{
|
||||||
case BattleResult::NORMAL:
|
case EBattleResult::NORMAL:
|
||||||
break;
|
break;
|
||||||
case BattleResult::ESCAPE:
|
case EBattleResult::ESCAPE:
|
||||||
musicName = "Music/Retreat Battle";
|
musicName = "Music/Retreat Battle";
|
||||||
videoName = "RTSTART.BIK";
|
videoName = "RTSTART.BIK";
|
||||||
text = 310;
|
text = 310;
|
||||||
break;
|
break;
|
||||||
case BattleResult::SURRENDER:
|
case EBattleResult::SURRENDER:
|
||||||
musicName = "Music/Surrender Battle";
|
musicName = "Music/Surrender Battle";
|
||||||
videoName = "SURRENDER.BIK";
|
videoName = "SURRENDER.BIK";
|
||||||
text = 309;
|
text = 309;
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
#include "../../lib/spells/ISpellMechanics.h"
|
#include "../../lib/spells/ISpellMechanics.h"
|
||||||
|
#include "../../lib/battle/BattleAction.h"
|
||||||
#include "../../lib/battle/BattleHex.h"
|
#include "../../lib/battle/BattleHex.h"
|
||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
#include "../../lib/CondSh.h"
|
#include "../../lib/CondSh.h"
|
||||||
@ -398,16 +399,8 @@ void BattleStacksController::addNewAnim(BattleAnimation *anim)
|
|||||||
void BattleStacksController::stackRemoved(uint32_t stackID)
|
void BattleStacksController::stackRemoved(uint32_t stackID)
|
||||||
{
|
{
|
||||||
if (getActiveStack() && getActiveStack()->unitId() == stackID)
|
if (getActiveStack() && getActiveStack()->unitId() == stackID)
|
||||||
{
|
|
||||||
BattleAction action;
|
|
||||||
action.side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
|
||||||
action.actionType = EActionType::CANCEL;
|
|
||||||
action.stackNumber = getActiveStack()->unitId();
|
|
||||||
|
|
||||||
LOCPLINT->cb->battleMakeUnitAction(action);
|
|
||||||
setActiveStack(nullptr);
|
setActiveStack(nullptr);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
||||||
{
|
{
|
||||||
@ -663,7 +656,7 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleStacksController::endAction(const BattleAction* action)
|
void BattleStacksController::endAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
owner.checkForAnimations();
|
owner.checkForAnimations();
|
||||||
|
|
||||||
@ -688,7 +681,7 @@ void BattleStacksController::endAction(const BattleAction* action)
|
|||||||
removeExpiredColorFilters();
|
removeExpiredColorFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleStacksController::startAction(const BattleAction* action)
|
void BattleStacksController::startAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
removeExpiredColorFilters();
|
removeExpiredColorFilters();
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,8 @@ public:
|
|||||||
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
||||||
void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
|
void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
|
||||||
|
|
||||||
void startAction(const BattleAction* action);
|
void startAction(const BattleAction & action);
|
||||||
void endAction(const BattleAction* action);
|
void endAction(const BattleAction & action);
|
||||||
|
|
||||||
void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
|
void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
@ -27,7 +28,9 @@
|
|||||||
#include "../windows/GUIClasses.h"
|
#include "../windows/GUIClasses.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../render/CAnimation.h"
|
#include "../render/CAnimation.h"
|
||||||
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IImage.h"
|
#include "../render/IImage.h"
|
||||||
|
#include "../render/Graphics.h"
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|
||||||
@ -37,9 +40,12 @@
|
|||||||
#include "../../lib/GameSettings.h"
|
#include "../../lib/GameSettings.h"
|
||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../../lib/campaign/CampaignState.h"
|
#include "../../lib/campaign/CampaignState.h"
|
||||||
|
#include "../../lib/mapping/CMap.h"
|
||||||
|
#include "../../lib/mapping/CMapService.h"
|
||||||
#include "../../lib/mapping/CMapInfo.h"
|
#include "../../lib/mapping/CMapInfo.h"
|
||||||
#include "../../lib/mapping/CMapHeader.h"
|
#include "../../lib/mapping/CMapHeader.h"
|
||||||
#include "../../lib/mapping/MapFormat.h"
|
#include "../../lib/mapping/MapFormat.h"
|
||||||
|
#include "../../lib/TerrainHandler.h"
|
||||||
#include "../../lib/serializer/Connection.h"
|
#include "../../lib/serializer/Connection.h"
|
||||||
|
|
||||||
bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
|
bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
|
||||||
@ -357,7 +363,7 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition)
|
|||||||
if(curItems[py]->date != "")
|
if(curItems[py]->date != "")
|
||||||
text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date);
|
text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date);
|
||||||
|
|
||||||
CRClickPopup::createAndPush(text);
|
GH.windows().createAndPushWindow<CMapInfoTooltipBox>(text, ResourceID(curItems[py]->fileURI), tabType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,6 +812,121 @@ std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int re
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourceID resource, ESelectionScreen tabType)
|
||||||
|
: CWindowObject(BORDERED | RCLICK_POPUP)
|
||||||
|
{
|
||||||
|
drawPlayerElements = tabType == ESelectionScreen::newGame;
|
||||||
|
renderImage = tabType == ESelectionScreen::newGame && settings["lobby"]["mapPreview"].Bool();
|
||||||
|
|
||||||
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<IImage>> mapLayerImages;
|
||||||
|
if(renderImage)
|
||||||
|
mapLayerImages = createMinimaps(ResourceID(resource.getName(), EResType::MAP), IMAGE_SIZE);
|
||||||
|
|
||||||
|
if(mapLayerImages.size() == 0)
|
||||||
|
renderImage = false;
|
||||||
|
|
||||||
|
pos = Rect(0, 0, 3 * BORDER + 2 * IMAGE_SIZE, 2000);
|
||||||
|
|
||||||
|
auto drawLabel = [&]() {
|
||||||
|
label = std::make_shared<CTextBox>(text, Rect(BORDER, BORDER, BORDER + 2 * IMAGE_SIZE, 350), 0, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
|
||||||
|
if(!label->slider)
|
||||||
|
label->resize(Point(BORDER + 2 * IMAGE_SIZE, label->label->textSize.y));
|
||||||
|
};
|
||||||
|
drawLabel();
|
||||||
|
|
||||||
|
int textHeight = std::min(350, label->label->textSize.y);
|
||||||
|
pos.h = BORDER + textHeight + BORDER;
|
||||||
|
if(renderImage)
|
||||||
|
pos.h += IMAGE_SIZE + BORDER;
|
||||||
|
backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
|
||||||
|
updateShadow();
|
||||||
|
|
||||||
|
drawLabel();
|
||||||
|
|
||||||
|
if(renderImage)
|
||||||
|
{
|
||||||
|
if(mapLayerImages.size() == 1)
|
||||||
|
image1 = std::make_shared<CPicture>(mapLayerImages[0], Point(BORDER + (BORDER + IMAGE_SIZE) / 2, textHeight + 2 * BORDER));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image1 = std::make_shared<CPicture>(mapLayerImages[0], Point(BORDER, textHeight + 2 * BORDER));
|
||||||
|
image2 = std::make_shared<CPicture>(mapLayerImages[1], Point(BORDER + IMAGE_SIZE + BORDER, textHeight + 2 * BORDER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
center(GH.getCursorPosition()); //center on mouse
|
||||||
|
#ifdef VCMI_MOBILE
|
||||||
|
moveBy({0, -pos.h / 2});
|
||||||
|
#endif
|
||||||
|
fitToScreen(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas SelectionTab::CMapInfoTooltipBox::createMinimapForLayer(std::unique_ptr<CMap> & map, int layer)
|
||||||
|
{
|
||||||
|
Canvas canvas = Canvas(Point(map->width, map->height));
|
||||||
|
|
||||||
|
for (int y = 0; y < map->height; ++y)
|
||||||
|
for (int x = 0; x < map->width; ++x)
|
||||||
|
{
|
||||||
|
TerrainTile & tile = map->getTile(int3(x, y, layer));
|
||||||
|
|
||||||
|
ColorRGBA color = tile.terType->minimapUnblocked;
|
||||||
|
if (tile.blocked && (!tile.visitable))
|
||||||
|
color = tile.terType->minimapBlocked;
|
||||||
|
|
||||||
|
if(drawPlayerElements)
|
||||||
|
// if object at tile is owned - it will be colored as its owner
|
||||||
|
for (const CGObjectInstance *obj : tile.blockingObjects)
|
||||||
|
{
|
||||||
|
PlayerColor player = obj->getOwner();
|
||||||
|
if(player == PlayerColor::NEUTRAL)
|
||||||
|
{
|
||||||
|
color = graphics->neutralColor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (player < PlayerColor::PLAYER_LIMIT)
|
||||||
|
{
|
||||||
|
color = graphics->playerColors[player.getNum()];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPoint(Point(x, y), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<IImage>> SelectionTab::CMapInfoTooltipBox::createMinimaps(ResourceID resource, int size)
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<IImage>> ret = std::vector<std::shared_ptr<IImage>>();
|
||||||
|
|
||||||
|
CMapService mapService;
|
||||||
|
std::unique_ptr<CMap> map;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
map = mapService.loadMap(resource);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < (map->twoLevel ? 2 : 1); i++)
|
||||||
|
{
|
||||||
|
Canvas canvas = createMinimapForLayer(map, i);
|
||||||
|
Canvas canvasScaled = Canvas(Point(size, size));
|
||||||
|
canvasScaled.drawScaled(canvas, Point(0, 0), Point(size, size));
|
||||||
|
std::shared_ptr<IImage> img = IImage::createFromSurface(canvasScaled.getInternalSurface());
|
||||||
|
|
||||||
|
ret.push_back(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
|
SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
|
||||||
: CIntObject(LCLICK, position)
|
: CIntObject(LCLICK, position)
|
||||||
{
|
{
|
||||||
|
@ -10,10 +10,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CSelectionBase.h"
|
#include "CSelectionBase.h"
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
class CMap;
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
#include "../../lib/mapping/CMapInfo.h"
|
#include "../../lib/mapping/CMapInfo.h"
|
||||||
|
|
||||||
class CSlider;
|
class CSlider;
|
||||||
class CLabel;
|
class CLabel;
|
||||||
|
class CPicture;
|
||||||
|
class IImage;
|
||||||
|
|
||||||
enum ESortBy
|
enum ESortBy
|
||||||
{
|
{
|
||||||
@ -62,6 +67,24 @@ class SelectionTab : public CIntObject
|
|||||||
std::shared_ptr<CAnimation> iconsVictoryCondition;
|
std::shared_ptr<CAnimation> iconsVictoryCondition;
|
||||||
std::shared_ptr<CAnimation> iconsLossCondition;
|
std::shared_ptr<CAnimation> iconsLossCondition;
|
||||||
|
|
||||||
|
class CMapInfoTooltipBox : public CWindowObject
|
||||||
|
{
|
||||||
|
const int IMAGE_SIZE = 169;
|
||||||
|
const int BORDER = 30;
|
||||||
|
|
||||||
|
bool drawPlayerElements;
|
||||||
|
bool renderImage;
|
||||||
|
|
||||||
|
std::shared_ptr<CFilledTexture> backgroundTexture;
|
||||||
|
std::shared_ptr<CTextBox> label;
|
||||||
|
std::shared_ptr<CPicture> image1;
|
||||||
|
std::shared_ptr<CPicture> image2;
|
||||||
|
|
||||||
|
Canvas createMinimapForLayer(std::unique_ptr<CMap> & map, int layer);
|
||||||
|
std::vector<std::shared_ptr<IImage>> createMinimaps(ResourceID resource, int size);
|
||||||
|
public:
|
||||||
|
CMapInfoTooltipBox(std::string text, ResourceID resource, ESelectionScreen tabType);
|
||||||
|
};
|
||||||
public:
|
public:
|
||||||
std::vector<std::shared_ptr<ElementInfo>> allItems;
|
std::vector<std::shared_ptr<ElementInfo>> allItems;
|
||||||
std::vector<std::shared_ptr<ElementInfo>> curItems;
|
std::vector<std::shared_ptr<ElementInfo>> curItems;
|
||||||
@ -100,7 +123,6 @@ public:
|
|||||||
void restoreLastSelection();
|
void restoreLastSelection();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::shared_ptr<CPicture> background;
|
std::shared_ptr<CPicture> background;
|
||||||
std::shared_ptr<CSlider> slider;
|
std::shared_ptr<CSlider> slider;
|
||||||
std::vector<std::shared_ptr<CButton>> buttonsSortBy;
|
std::vector<std::shared_ptr<CButton>> buttonsSortBy;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
"type" : "object",
|
"type" : "object",
|
||||||
"$schema" : "http://json-schema.org/draft-04/schema",
|
"$schema" : "http://json-schema.org/draft-04/schema",
|
||||||
"required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "gameTweaks" ],
|
"required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "lobby", "gameTweaks" ],
|
||||||
"definitions" : {
|
"definitions" : {
|
||||||
"logLevelEnum" : {
|
"logLevelEnum" : {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
@ -526,6 +526,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lobby" : {
|
||||||
|
"type" : "object",
|
||||||
|
"additionalProperties" : false,
|
||||||
|
"default" : {},
|
||||||
|
"required" : [ "mapPreview" ],
|
||||||
|
"properties" : {
|
||||||
|
"mapPreview" : {
|
||||||
|
"type" : "boolean",
|
||||||
|
"default" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gameTweaks" : {
|
"gameTweaks" : {
|
||||||
"type" : "object",
|
"type" : "object",
|
||||||
"default" : {},
|
"default" : {},
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "battle/BattleAction.h"
|
|
||||||
#include "IGameEventsReceiver.h"
|
#include "IGameEventsReceiver.h"
|
||||||
|
|
||||||
#include "spells/ViewSpellInt.h"
|
#include "spells/ViewSpellInt.h"
|
||||||
@ -36,6 +35,7 @@ class CCreatureSet;
|
|||||||
class CArmedInstance;
|
class CArmedInstance;
|
||||||
class IShipyard;
|
class IShipyard;
|
||||||
class IMarket;
|
class IMarket;
|
||||||
|
class BattleAction;
|
||||||
struct BattleResult;
|
struct BattleResult;
|
||||||
struct BattleAttack;
|
struct BattleAttack;
|
||||||
struct BattleStackAttacked;
|
struct BattleStackAttacked;
|
||||||
@ -107,10 +107,7 @@ public:
|
|||||||
|
|
||||||
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain){};
|
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain){};
|
||||||
|
|
||||||
virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) = 0;
|
||||||
{
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void saveGame(BinarySerializer & h, const int version) = 0;
|
virtual void saveGame(BinarySerializer & h, const int version) = 0;
|
||||||
virtual void loadGame(BinaryDeserializer & h, const int version) = 0;
|
virtual void loadGame(BinaryDeserializer & h, const int version) = 0;
|
||||||
|
@ -248,7 +248,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType)
|
|||||||
static const std::map<EActionType, std::string> actionTypeToString =
|
static const std::map<EActionType, std::string> actionTypeToString =
|
||||||
{
|
{
|
||||||
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
||||||
{EActionType::INVALID, "Invalid"},
|
|
||||||
{EActionType::NO_ACTION, "No action"},
|
{EActionType::NO_ACTION, "No action"},
|
||||||
{EActionType::HERO_SPELL, "Hero spell"},
|
{EActionType::HERO_SPELL, "Hero spell"},
|
||||||
{EActionType::WALK, "Walk"},
|
{EActionType::WALK, "Walk"},
|
||||||
|
@ -998,20 +998,21 @@ namespace Date
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EActionType : int32_t
|
enum class EActionType : int8_t
|
||||||
{
|
{
|
||||||
CANCEL = -3,
|
NO_ACTION,
|
||||||
END_TACTIC_PHASE = -2,
|
|
||||||
INVALID = -1,
|
END_TACTIC_PHASE,
|
||||||
NO_ACTION = 0,
|
|
||||||
HERO_SPELL,
|
|
||||||
WALK,
|
|
||||||
DEFEND,
|
|
||||||
RETREAT,
|
RETREAT,
|
||||||
SURRENDER,
|
SURRENDER,
|
||||||
|
|
||||||
|
HERO_SPELL,
|
||||||
|
|
||||||
|
WALK,
|
||||||
|
WAIT,
|
||||||
|
DEFEND,
|
||||||
WALK_AND_ATTACK,
|
WALK_AND_ATTACK,
|
||||||
SHOOT,
|
SHOOT,
|
||||||
WAIT,
|
|
||||||
CATAPULT,
|
CATAPULT,
|
||||||
MONSTER_SPELL,
|
MONSTER_SPELL,
|
||||||
BAD_MORALE,
|
BAD_MORALE,
|
||||||
@ -1379,6 +1380,13 @@ enum class EHealPower : ui8
|
|||||||
PERMANENT
|
PERMANENT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class EBattleResult : int8_t
|
||||||
|
{
|
||||||
|
NORMAL = 0,
|
||||||
|
ESCAPE = 1,
|
||||||
|
SURRENDER = 2
|
||||||
|
};
|
||||||
|
|
||||||
// Typedef declarations
|
// Typedef declarations
|
||||||
using TExpType = si64;
|
using TExpType = si64;
|
||||||
using TQuantity = si32;
|
using TQuantity = si32;
|
||||||
|
@ -134,7 +134,6 @@ public:
|
|||||||
virtual void visitBuildBoat(BuildBoat & pack) {}
|
virtual void visitBuildBoat(BuildBoat & pack) {}
|
||||||
virtual void visitQueryReply(QueryReply & pack) {}
|
virtual void visitQueryReply(QueryReply & pack) {}
|
||||||
virtual void visitMakeAction(MakeAction & pack) {}
|
virtual void visitMakeAction(MakeAction & pack) {}
|
||||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) {}
|
|
||||||
virtual void visitDigWithHero(DigWithHero & pack) {}
|
virtual void visitDigWithHero(DigWithHero & pack) {}
|
||||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) {}
|
virtual void visitCastAdvSpell(CastAdvSpell & pack) {}
|
||||||
virtual void visitSaveGame(SaveGame & pack) {}
|
virtual void visitSaveGame(SaveGame & pack) {}
|
||||||
|
@ -1524,11 +1524,9 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
|
|||||||
|
|
||||||
struct DLL_LINKAGE BattleResult : public Query
|
struct DLL_LINKAGE BattleResult : public Query
|
||||||
{
|
{
|
||||||
enum EResult { NORMAL = 0, ESCAPE = 1, SURRENDER = 2 };
|
|
||||||
|
|
||||||
void applyFirstCl(CClient * cl);
|
void applyFirstCl(CClient * cl);
|
||||||
|
|
||||||
EResult result = NORMAL;
|
EBattleResult result = EBattleResult::NORMAL;
|
||||||
ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
|
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
|
std::map<ui32, si32> casualties[2]; //first => casualties of attackers - map crid => number
|
||||||
TExpType exp[2] = {0, 0}; //exp for attacker and defender
|
TExpType exp[2] = {0, 0}; //exp for attacker and defender
|
||||||
@ -2515,24 +2513,6 @@ struct DLL_LINKAGE MakeAction : public CPackForServer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DLL_LINKAGE MakeCustomAction : public CPackForServer
|
|
||||||
{
|
|
||||||
MakeCustomAction() = default;
|
|
||||||
MakeCustomAction(BattleAction BA)
|
|
||||||
: ba(std::move(BA))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
BattleAction ba;
|
|
||||||
|
|
||||||
virtual void visitTyped(ICPackVisitor & visitor) override;
|
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler & h, const int version)
|
|
||||||
{
|
|
||||||
h & static_cast<CPackForServer &>(*this);
|
|
||||||
h & ba;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DLL_LINKAGE DigWithHero : public CPackForServer
|
struct DLL_LINKAGE DigWithHero : public CPackForServer
|
||||||
{
|
{
|
||||||
ObjectInstanceID id; //digging hero id
|
ObjectInstanceID id; //digging hero id
|
||||||
|
@ -638,11 +638,6 @@ void MakeAction::visitTyped(ICPackVisitor & visitor)
|
|||||||
visitor.visitMakeAction(*this);
|
visitor.visitMakeAction(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MakeCustomAction::visitTyped(ICPackVisitor & visitor)
|
|
||||||
{
|
|
||||||
visitor.visitMakeCustomAction(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
||||||
{
|
{
|
||||||
visitor.visitDigWithHero(*this);
|
visitor.visitDigWithHero(*this);
|
||||||
@ -2294,14 +2289,9 @@ void StartAction::applyGs(CGameState *gs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ba.actionType != EActionType::HERO_SPELL) //don't check for stack if it's custom action by hero
|
if (ba.isUnitAction())
|
||||||
{
|
{
|
||||||
assert(st);
|
assert(st); // stack must exists for all non-hero actions
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gs->curB->sides[ba.side].usedSpellsHistory.emplace_back(ba.actionSubtype);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(ba.actionType)
|
switch(ba.actionType)
|
||||||
{
|
{
|
||||||
@ -2324,6 +2314,12 @@ void StartAction::applyGs(CGameState *gs)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(ba.actionType == EActionType::HERO_SPELL)
|
||||||
|
gs->curB->sides[ba.side].usedSpellsHistory.push_back(ba.spell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BattleSpellCast::applyGs(CGameState * gs) const
|
void BattleSpellCast::applyGs(CGameState * gs) const
|
||||||
{
|
{
|
||||||
|
@ -20,8 +20,7 @@ static const int32_t INVALID_UNIT_ID = -1000;
|
|||||||
BattleAction::BattleAction():
|
BattleAction::BattleAction():
|
||||||
side(-1),
|
side(-1),
|
||||||
stackNumber(-1),
|
stackNumber(-1),
|
||||||
actionType(EActionType::INVALID),
|
actionType(EActionType::NO_ACTION)
|
||||||
actionSubtype(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +79,7 @@ BattleAction BattleAction::makeCreatureSpellcast(const battle::Unit * stack, con
|
|||||||
{
|
{
|
||||||
BattleAction ba;
|
BattleAction ba;
|
||||||
ba.actionType = EActionType::MONSTER_SPELL;
|
ba.actionType = EActionType::MONSTER_SPELL;
|
||||||
ba.actionSubtype = spellID;
|
ba.spell = spellID;
|
||||||
ba.setTarget(target);
|
ba.setTarget(target);
|
||||||
ba.side = stack->unitSide();
|
ba.side = stack->unitSide();
|
||||||
ba.stackNumber = stack->unitId();
|
ba.stackNumber = stack->unitId();
|
||||||
@ -144,7 +143,7 @@ std::string BattleAction::toString() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', actionSubtype '%d', target {%s}}");
|
boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', actionSubtype '%d', target {%s}}");
|
||||||
fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % actionSubtype % targetStream.str();
|
fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % spell.getNum() % targetStream.str();
|
||||||
return fmt.str();
|
return fmt.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +192,44 @@ void BattleAction::setTarget(const battle::Target & target_)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BattleAction::isUnitAction() const
|
||||||
|
{
|
||||||
|
static const std::array<EActionType, 9> actions = {
|
||||||
|
EActionType::WALK,
|
||||||
|
EActionType::WAIT,
|
||||||
|
EActionType::DEFEND,
|
||||||
|
EActionType::WALK_AND_ATTACK,
|
||||||
|
EActionType::SHOOT,
|
||||||
|
EActionType::CATAPULT,
|
||||||
|
EActionType::MONSTER_SPELL,
|
||||||
|
EActionType::BAD_MORALE,
|
||||||
|
EActionType::STACK_HEAL
|
||||||
|
};
|
||||||
|
|
||||||
|
return vstd::contains(actions, actionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleAction::isSpellAction() const
|
||||||
|
{
|
||||||
|
static const std::array<EActionType, 2> actions = {
|
||||||
|
EActionType::HERO_SPELL,
|
||||||
|
EActionType::MONSTER_SPELL
|
||||||
|
};
|
||||||
|
|
||||||
|
return vstd::contains(actions, actionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleAction::isTacticsAction() const
|
||||||
|
{
|
||||||
|
static const std::array<EActionType, 9> actions = {
|
||||||
|
EActionType::WALK,
|
||||||
|
EActionType::END_TACTIC_PHASE,
|
||||||
|
EActionType::RETREAT,
|
||||||
|
EActionType::SURRENDER
|
||||||
|
};
|
||||||
|
|
||||||
|
return vstd::contains(actions, actionType);
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream & operator<<(std::ostream & os, const BattleAction & ba)
|
std::ostream & operator<<(std::ostream & os, const BattleAction & ba)
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ public:
|
|||||||
ui32 stackNumber; //stack ID, -1 left hero, -2 right hero,
|
ui32 stackNumber; //stack ID, -1 left hero, -2 right hero,
|
||||||
EActionType actionType; //use ActionType enum for values
|
EActionType actionType; //use ActionType enum for values
|
||||||
|
|
||||||
si32 actionSubtype;
|
SpellID spell;
|
||||||
|
|
||||||
BattleAction();
|
BattleAction();
|
||||||
|
|
||||||
@ -43,6 +43,9 @@ public:
|
|||||||
static BattleAction makeRetreat(ui8 side);
|
static BattleAction makeRetreat(ui8 side);
|
||||||
static BattleAction makeSurrender(ui8 side);
|
static BattleAction makeSurrender(ui8 side);
|
||||||
|
|
||||||
|
bool isTacticsAction() const;
|
||||||
|
bool isUnitAction() const;
|
||||||
|
bool isSpellAction() const;
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
void aimToHex(const BattleHex & destination);
|
void aimToHex(const BattleHex & destination);
|
||||||
@ -56,7 +59,7 @@ public:
|
|||||||
h & side;
|
h & side;
|
||||||
h & stackNumber;
|
h & stackNumber;
|
||||||
h & actionType;
|
h & actionType;
|
||||||
h & actionSubtype;
|
h & spell;
|
||||||
h & target;
|
h & target;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
@ -352,7 +352,6 @@ void registerTypesServerPacks(Serializer &s)
|
|||||||
s.template registerType<CPackForServer, BuildBoat>();
|
s.template registerType<CPackForServer, BuildBoat>();
|
||||||
s.template registerType<CPackForServer, QueryReply>();
|
s.template registerType<CPackForServer, QueryReply>();
|
||||||
s.template registerType<CPackForServer, MakeAction>();
|
s.template registerType<CPackForServer, MakeAction>();
|
||||||
s.template registerType<CPackForServer, MakeCustomAction>();
|
|
||||||
s.template registerType<CPackForServer, DigWithHero>();
|
s.template registerType<CPackForServer, DigWithHero>();
|
||||||
s.template registerType<CPackForServer, CastAdvSpell>();
|
s.template registerType<CPackForServer, CastAdvSpell>();
|
||||||
s.template registerType<CPackForServer, CastleTeleportHero>();
|
s.template registerType<CPackForServer, CastleTeleportHero>();
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -11,30 +11,26 @@
|
|||||||
|
|
||||||
#include <vcmi/Environment.h>
|
#include <vcmi/Environment.h>
|
||||||
|
|
||||||
#include "../lib/FunctionList.h"
|
|
||||||
#include "../lib/IGameCallback.h"
|
#include "../lib/IGameCallback.h"
|
||||||
#include "../lib/battle/CBattleInfoCallback.h"
|
#include "../lib/battle/CBattleInfoCallback.h"
|
||||||
#include "../lib/battle/BattleAction.h"
|
|
||||||
#include "../lib/LoadProgress.h"
|
#include "../lib/LoadProgress.h"
|
||||||
#include "../lib/ScriptHandler.h"
|
#include "../lib/ScriptHandler.h"
|
||||||
#include "CQuery.h"
|
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
class CGameState;
|
|
||||||
struct StartInfo;
|
|
||||||
struct BattleResult;
|
|
||||||
struct SideInBattle;
|
struct SideInBattle;
|
||||||
struct BattleAttack;
|
class IMarket;
|
||||||
struct BattleStackAttacked;
|
class SpellCastEnvironment;
|
||||||
|
class CConnection;
|
||||||
|
class CCommanderInstance;
|
||||||
|
class EVictoryLossCheckResult;
|
||||||
|
|
||||||
struct CPack;
|
struct CPack;
|
||||||
struct Query;
|
struct CPackForServer;
|
||||||
|
struct NewTurn;
|
||||||
|
struct CGarrisonOperationPack;
|
||||||
struct SetResources;
|
struct SetResources;
|
||||||
struct NewStructures;
|
struct NewStructures;
|
||||||
class CGHeroInstance;
|
|
||||||
class IMarket;
|
|
||||||
|
|
||||||
class SpellCastEnvironment;
|
|
||||||
|
|
||||||
#if SCRIPTING_ENABLED
|
#if SCRIPTING_ENABLED
|
||||||
namespace scripting
|
namespace scripting
|
||||||
@ -43,16 +39,17 @@ namespace scripting
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
template<typename T> class CApplier;
|
template<typename T> class CApplier;
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
class HeroPoolProcessor;
|
class HeroPoolProcessor;
|
||||||
class CGameHandler;
|
|
||||||
class CVCMIServer;
|
class CVCMIServer;
|
||||||
class CBaseForGHApply;
|
class CBaseForGHApply;
|
||||||
class PlayerMessageProcessor;
|
class PlayerMessageProcessor;
|
||||||
|
class BattleProcessor;
|
||||||
|
class QueriesProcessor;
|
||||||
|
class CObjectVisitQuery;
|
||||||
|
|
||||||
struct PlayerStatus
|
struct PlayerStatus
|
||||||
{
|
{
|
||||||
@ -81,33 +78,18 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
class CGameHandler : public IGameCallback, public CBattleInfoCallback, public Environment
|
class CGameHandler : public IGameCallback, public CBattleInfoCallback, public Environment
|
||||||
{
|
{
|
||||||
CVCMIServer * lobby;
|
CVCMIServer * lobby;
|
||||||
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
|
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
|
||||||
std::unique_ptr<boost::thread> battleThread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
boost::recursive_mutex battleActionMutex;
|
using CCallbackBase::setBattle;
|
||||||
|
|
||||||
std::unique_ptr<HeroPoolProcessor> heroPool;
|
std::unique_ptr<HeroPoolProcessor> heroPool;
|
||||||
|
std::unique_ptr<BattleProcessor> battles;
|
||||||
|
std::unique_ptr<QueriesProcessor> queries;
|
||||||
|
|
||||||
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
|
|
||||||
//use enums as parameters, because doMove(sth, true, false, true) is not readable
|
//use enums as parameters, because doMove(sth, true, false, true) is not readable
|
||||||
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
|
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
|
||||||
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
|
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
|
||||||
@ -121,7 +103,7 @@ public:
|
|||||||
//queries stuff
|
//queries stuff
|
||||||
boost::recursive_mutex gsm;
|
boost::recursive_mutex gsm;
|
||||||
ui32 QID;
|
ui32 QID;
|
||||||
Queries queries;
|
|
||||||
|
|
||||||
SpellCastEnvironment * spellEnv;
|
SpellCastEnvironment * spellEnv;
|
||||||
|
|
||||||
@ -136,26 +118,6 @@ public:
|
|||||||
bool isBlockedByQueries(const CPack *pack, PlayerColor player);
|
bool isBlockedByQueries(const CPack *pack, PlayerColor player);
|
||||||
bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
|
bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
|
||||||
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
|
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
|
||||||
int moveStack(int stack, BattleHex dest); //returned value - travelled distance
|
|
||||||
void runBattle();
|
|
||||||
|
|
||||||
////used only in endBattle - don't touch elsewhere
|
|
||||||
bool visitObjectAfterVictory;
|
|
||||||
//
|
|
||||||
void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
|
|
||||||
void endBattleConfirm(const BattleInfo * battleInfo);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
CGameHandler() = default;
|
CGameHandler() = default;
|
||||||
CGameHandler(CVCMIServer * lobby);
|
CGameHandler(CVCMIServer * lobby);
|
||||||
@ -243,14 +205,6 @@ public:
|
|||||||
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
|
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
|
||||||
bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
|
bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
|
||||||
|
|
||||||
void updateGateState();
|
|
||||||
bool makeBattleAction(BattleAction &ba);
|
|
||||||
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
|
||||||
bool makeCustomAction(BattleAction &ba);
|
|
||||||
void stackEnchantedTrigger(const CStack * stack);
|
|
||||||
void stackTurnTrigger(const CStack *stack);
|
|
||||||
|
|
||||||
void removeObstacle(const CObstacleInstance &obstacle);
|
|
||||||
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
|
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
|
||||||
bool buildBoat( ObjectInstanceID objid, PlayerColor player );
|
bool buildBoat( ObjectInstanceID objid, PlayerColor player );
|
||||||
bool setFormation( ObjectInstanceID hid, ui8 formation );
|
bool setFormation( ObjectInstanceID hid, ui8 formation );
|
||||||
@ -285,7 +239,6 @@ public:
|
|||||||
bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
|
bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
|
||||||
void objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h );
|
void objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h );
|
||||||
void objectVisitEnded(const CObjectVisitQuery &query);
|
void objectVisitEnded(const CObjectVisitQuery &query);
|
||||||
void engageIntoBattle( PlayerColor player );
|
|
||||||
bool dig(const CGHeroInstance *h);
|
bool dig(const CGHeroInstance *h);
|
||||||
void moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging);
|
void moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging);
|
||||||
|
|
||||||
@ -293,7 +246,7 @@ public:
|
|||||||
{
|
{
|
||||||
h & QID;
|
h & QID;
|
||||||
h & states;
|
h & states;
|
||||||
h & finishingBattle;
|
h & battles;
|
||||||
h & heroPool;
|
h & heroPool;
|
||||||
h & getRandomGenerator();
|
h & getRandomGenerator();
|
||||||
h & playerMessages;
|
h & playerMessages;
|
||||||
@ -325,39 +278,8 @@ public:
|
|||||||
void throwAndComplain(CPackForServer * pack, std::string txt);
|
void throwAndComplain(CPackForServer * pack, std::string txt);
|
||||||
bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id);
|
bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
|
||||||
|
|
||||||
void battleAfterLevelUp(const BattleResult &result);
|
|
||||||
|
|
||||||
void run(bool resume);
|
void run(bool resume);
|
||||||
void newTurn();
|
void newTurn();
|
||||||
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);
|
|
||||||
bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
|
bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
|
||||||
void spawnWanderingMonsters(CreatureID creatureID);
|
void spawnWanderingMonsters(CreatureID creatureID);
|
||||||
|
|
||||||
@ -385,8 +307,6 @@ private:
|
|||||||
void reinitScripting();
|
void reinitScripting();
|
||||||
void deserializationFix();
|
void deserializationFix();
|
||||||
|
|
||||||
|
|
||||||
void makeStackDoNothing(const CStack * next);
|
|
||||||
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
|
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
|
||||||
|
|
||||||
const std::string complainNoCreatures;
|
const std::string complainNoCreatures;
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
set(server_SRCS
|
set(server_SRCS
|
||||||
StdInc.cpp
|
StdInc.cpp
|
||||||
|
|
||||||
|
battles/BattleActionProcessor.cpp
|
||||||
|
battles/BattleFlowProcessor.cpp
|
||||||
|
battles/BattleProcessor.cpp
|
||||||
|
battles/BattleResultProcessor.cpp
|
||||||
|
|
||||||
|
queries/BattleQueries.cpp
|
||||||
|
queries/CQuery.cpp
|
||||||
|
queries/MapQueries.cpp
|
||||||
|
queries/QueriesProcessor.cpp
|
||||||
|
|
||||||
|
processors/HeroPoolProcessor.cpp
|
||||||
|
processors/PlayerMessageProcessor.cpp
|
||||||
|
|
||||||
CGameHandler.cpp
|
CGameHandler.cpp
|
||||||
HeroPoolProcessor.cpp
|
|
||||||
PlayerMessageProcessor.cpp
|
|
||||||
ServerSpellCastEnvironment.cpp
|
ServerSpellCastEnvironment.cpp
|
||||||
CQuery.cpp
|
|
||||||
CVCMIServer.cpp
|
CVCMIServer.cpp
|
||||||
NetPacksServer.cpp
|
NetPacksServer.cpp
|
||||||
NetPacksLobbyServer.cpp
|
NetPacksLobbyServer.cpp
|
||||||
@ -14,11 +24,21 @@ set(server_SRCS
|
|||||||
set(server_HEADERS
|
set(server_HEADERS
|
||||||
StdInc.h
|
StdInc.h
|
||||||
|
|
||||||
|
battles/BattleActionProcessor.h
|
||||||
|
battles/BattleFlowProcessor.h
|
||||||
|
battles/BattleProcessor.h
|
||||||
|
battles/BattleResultProcessor.h
|
||||||
|
|
||||||
|
queries/BattleQueries.h
|
||||||
|
queries/CQuery.h
|
||||||
|
queries/MapQueries.h
|
||||||
|
queries/QueriesProcessor.h
|
||||||
|
|
||||||
|
processors/HeroPoolProcessor.h
|
||||||
|
processors/PlayerMessageProcessor.h
|
||||||
|
|
||||||
CGameHandler.h
|
CGameHandler.h
|
||||||
HeroPoolProcessor.h
|
|
||||||
PlayerMessageProcessor.h
|
|
||||||
ServerSpellCastEnvironment.h
|
ServerSpellCastEnvironment.h
|
||||||
CQuery.h
|
|
||||||
CVCMIServer.h
|
CVCMIServer.h
|
||||||
LobbyNetPackVisitors.h
|
LobbyNetPackVisitors.h
|
||||||
ServerNetPackVisitors.h
|
ServerNetPackVisitors.h
|
||||||
|
@ -1,584 +0,0 @@
|
|||||||
/*
|
|
||||||
* CQuery.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 "CQuery.h"
|
|
||||||
#include "CGameHandler.h"
|
|
||||||
#include "../lib/battle/BattleInfo.h"
|
|
||||||
#include "../lib/mapObjects/MiscObjects.h"
|
|
||||||
#include "../lib/serializer/Cast.h"
|
|
||||||
|
|
||||||
boost::mutex Queries::mx;
|
|
||||||
|
|
||||||
template <typename Container>
|
|
||||||
std::string formatContainer(const Container & c, std::string delimeter = ", ", std::string opener = "(", std::string closer=")")
|
|
||||||
{
|
|
||||||
std::string ret = opener;
|
|
||||||
auto itr = std::begin(c);
|
|
||||||
if(itr != std::end(c))
|
|
||||||
{
|
|
||||||
ret += std::to_string(*itr);
|
|
||||||
while(++itr != std::end(c))
|
|
||||||
{
|
|
||||||
ret += delimeter;
|
|
||||||
ret += std::to_string(*itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret += closer;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream & operator<<(std::ostream & out, const CQuery & query)
|
|
||||||
{
|
|
||||||
return out << query.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream & operator<<(std::ostream & out, QueryPtr query)
|
|
||||||
{
|
|
||||||
return out << "[" << query.get() << "] " << query->toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
CQuery::CQuery(Queries * Owner):
|
|
||||||
owner(Owner)
|
|
||||||
{
|
|
||||||
boost::unique_lock<boost::mutex> l(Queries::mx);
|
|
||||||
|
|
||||||
static QueryID QID = QueryID(0);
|
|
||||||
|
|
||||||
queryID = ++QID;
|
|
||||||
logGlobal->trace("Created a new query with id %d", queryID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CQuery::~CQuery()
|
|
||||||
{
|
|
||||||
logGlobal->trace("Destructed the query with id %d", queryID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::addPlayer(PlayerColor color)
|
|
||||||
{
|
|
||||||
if(color.isValidPlayer())
|
|
||||||
players.push_back(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CQuery::toString() const
|
|
||||||
{
|
|
||||||
const auto size = players.size();
|
|
||||||
const std::string plural = size > 1 ? "s" : "";
|
|
||||||
std::string names;
|
|
||||||
|
|
||||||
for(size_t i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
names += boost::to_upper_copy<std::string>(players[i].getStr());
|
|
||||||
|
|
||||||
if(i < size - 2)
|
|
||||||
names += ", ";
|
|
||||||
else if(size > 1 && i == size - 2)
|
|
||||||
names += " and ";
|
|
||||||
}
|
|
||||||
std::string ret = boost::str(boost::format("A query of type '%s' and qid = %d affecting player%s %s")
|
|
||||||
% typeid(*this).name()
|
|
||||||
% queryID
|
|
||||||
% plural
|
|
||||||
% names
|
|
||||||
);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CQuery::endsByPlayerAnswer() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CQuery::blocksPack(const CPack * pack) const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::onExposure(QueryPtr topQuery)
|
|
||||||
{
|
|
||||||
logGlobal->trace("Exposed query with id %d", queryID);
|
|
||||||
owner->popQuery(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::onAdding(PlayerColor color)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::onAdded(PlayerColor color)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CQuery::setReply(const JsonNode & reply)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CQuery::blockAllButReply(const CPack * pack) const
|
|
||||||
{
|
|
||||||
//We accept only query replies from correct player
|
|
||||||
if(auto reply = dynamic_ptr_cast<QueryReply>(pack))
|
|
||||||
return !vstd::contains(players, reply->player);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGhQuery::CGhQuery(CGameHandler * owner):
|
|
||||||
CQuery(&owner->queries), gh(owner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile):
|
|
||||||
CGhQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
|
|
||||||
{
|
|
||||||
addPlayer(Hero->tempOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CObjectVisitQuery::blocksPack(const CPack *pack) const
|
|
||||||
{
|
|
||||||
//During the visit itself ALL actions are blocked.
|
|
||||||
//(However, the visit may trigger a query above that'll pass some.)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CObjectVisitQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
gh->objectVisitEnded(*this);
|
|
||||||
|
|
||||||
//TODO or should it be destructor?
|
|
||||||
//Can object visit affect 2 players and what would be desired behavior?
|
|
||||||
if(removeObjectAfterVisit)
|
|
||||||
gh->removeObject(visitedObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CObjectVisitQuery::onExposure(QueryPtr topQuery)
|
|
||||||
{
|
|
||||||
//Object may have been removed and deleted.
|
|
||||||
if(gh->isValidObject(visitedObject))
|
|
||||||
topQuery->notifyObjectAboutRemoval(*this);
|
|
||||||
|
|
||||||
owner->popIfTop(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::popQuery(PlayerColor player, QueryPtr query)
|
|
||||||
{
|
|
||||||
LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
|
|
||||||
if(topQuery(player) != query)
|
|
||||||
{
|
|
||||||
logGlobal->trace("Cannot remove, not a top!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
queries[player] -= query;
|
|
||||||
auto nextQuery = topQuery(player);
|
|
||||||
|
|
||||||
query->onRemoval(player);
|
|
||||||
|
|
||||||
//Exposure on query below happens only if removal didn't trigger any new query
|
|
||||||
if(nextQuery && nextQuery == topQuery(player))
|
|
||||||
nextQuery->onExposure(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::popQuery(const CQuery &query)
|
|
||||||
{
|
|
||||||
LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
|
|
||||||
|
|
||||||
assert(query.players.size());
|
|
||||||
for(auto player : query.players)
|
|
||||||
{
|
|
||||||
auto top = topQuery(player);
|
|
||||||
if(top.get() == &query)
|
|
||||||
popQuery(top);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logGlobal->trace("Cannot remove query %s", query.toString());
|
|
||||||
logGlobal->trace("Queries found:");
|
|
||||||
for(auto q : queries[player])
|
|
||||||
{
|
|
||||||
logGlobal->trace(q->toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::popQuery(QueryPtr query)
|
|
||||||
{
|
|
||||||
for(auto player : query->players)
|
|
||||||
popQuery(player, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::addQuery(QueryPtr query)
|
|
||||||
{
|
|
||||||
for(auto player : query->players)
|
|
||||||
addQuery(player, query);
|
|
||||||
|
|
||||||
for(auto player : query->players)
|
|
||||||
query->onAdded(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::addQuery(PlayerColor player, QueryPtr query)
|
|
||||||
{
|
|
||||||
LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
|
|
||||||
query->onAdding(player);
|
|
||||||
queries[player].push_back(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryPtr Queries::topQuery(PlayerColor player)
|
|
||||||
{
|
|
||||||
return vstd::backOrNull(queries[player]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::popIfTop(QueryPtr query)
|
|
||||||
{
|
|
||||||
LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
|
|
||||||
if(!query)
|
|
||||||
logGlobal->error("The query is nullptr! Ignoring.");
|
|
||||||
|
|
||||||
popIfTop(*query);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Queries::popIfTop(const CQuery & query)
|
|
||||||
{
|
|
||||||
for(PlayerColor color : query.players)
|
|
||||||
if(topQuery(color).get() == &query)
|
|
||||||
popQuery(color, topQuery(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<const CQuery>> Queries::allQueries() const
|
|
||||||
{
|
|
||||||
std::vector<std::shared_ptr<const CQuery>> ret;
|
|
||||||
for(auto & playerQueries : queries)
|
|
||||||
for(auto & query : playerQueries.second)
|
|
||||||
ret.push_back(query);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<QueryPtr> Queries::allQueries()
|
|
||||||
{
|
|
||||||
//TODO code duplication with const function :(
|
|
||||||
std::vector<QueryPtr> ret;
|
|
||||||
for(auto & playerQueries : queries)
|
|
||||||
for(auto & query : playerQueries.second)
|
|
||||||
ret.push_back(query);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryPtr Queries::getQuery(QueryID queryID)
|
|
||||||
{
|
|
||||||
for(auto & playerQueries : queries)
|
|
||||||
for(auto & query : playerQueries.second)
|
|
||||||
if(query->queryID == queryID)
|
|
||||||
return query;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
if(result)
|
|
||||||
objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
|
|
||||||
}
|
|
||||||
|
|
||||||
CBattleQuery::CBattleQuery(CGameHandler * owner, const BattleInfo * Bi):
|
|
||||||
CGhQuery(owner)
|
|
||||||
{
|
|
||||||
belligerents[0] = Bi->sides[0].armyObject;
|
|
||||||
belligerents[1] = Bi->sides[1].armyObject;
|
|
||||||
|
|
||||||
bi = Bi;
|
|
||||||
|
|
||||||
for(auto & side : bi->sides)
|
|
||||||
addPlayer(side.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
CBattleQuery::CBattleQuery(CGameHandler * owner):
|
|
||||||
CGhQuery(owner), bi(nullptr)
|
|
||||||
{
|
|
||||||
belligerents[0] = belligerents[1] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CBattleQuery::blocksPack(const CPack * pack) const
|
|
||||||
{
|
|
||||||
const char * name = typeid(*pack).name();
|
|
||||||
return strcmp(name, typeid(MakeAction).name()) && strcmp(name, typeid(MakeCustomAction).name());
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBattleQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
if(result)
|
|
||||||
gh->battleAfterLevelUp(*result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
objectVisit.visitedObject->garrisonDialogClosed(objectVisit.visitingHero);
|
|
||||||
}
|
|
||||||
|
|
||||||
CGarrisonDialogQuery::CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance * up, const CArmedInstance * down):
|
|
||||||
CDialogQuery(owner)
|
|
||||||
{
|
|
||||||
exchangingArmies[0] = up;
|
|
||||||
exchangingArmies[1] = down;
|
|
||||||
|
|
||||||
addPlayer(up->tempOwner);
|
|
||||||
addPlayer(down->tempOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const
|
|
||||||
{
|
|
||||||
std::set<ObjectInstanceID> ourIds;
|
|
||||||
ourIds.insert(this->exchangingArmies[0]->id);
|
|
||||||
ourIds.insert(this->exchangingArmies[1]->id);
|
|
||||||
|
|
||||||
if(auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack))
|
|
||||||
return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2);
|
|
||||||
|
|
||||||
if(auto stacks = dynamic_ptr_cast<BulkSplitStack>(pack))
|
|
||||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
|
||||||
|
|
||||||
if(auto stacks = dynamic_ptr_cast<BulkMergeStacks>(pack))
|
|
||||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
|
||||||
|
|
||||||
if(auto stacks = dynamic_ptr_cast<BulkSmartSplitStack>(pack))
|
|
||||||
return !vstd::contains(ourIds, stacks->srcOwner);
|
|
||||||
|
|
||||||
if(auto stacks = dynamic_ptr_cast<BulkMoveArmy>(pack))
|
|
||||||
return !vstd::contains(ourIds, stacks->srcArmy) || !vstd::contains(ourIds, stacks->destArmy);
|
|
||||||
|
|
||||||
if(auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack))
|
|
||||||
{
|
|
||||||
if(auto id1 = std::visit(GetEngagedHeroIds(), arts->src.artHolder))
|
|
||||||
if(!vstd::contains(ourIds, *id1))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if(auto id2 = std::visit(GetEngagedHeroIds(), arts->dst.artHolder))
|
|
||||||
if(!vstd::contains(ourIds, *id2))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(auto dismiss = dynamic_ptr_cast<DisbandCreature>(pack))
|
|
||||||
return !vstd::contains(ourIds, dismiss->id);
|
|
||||||
|
|
||||||
if(auto arts = dynamic_ptr_cast<BulkExchangeArtifacts>(pack))
|
|
||||||
return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero);
|
|
||||||
|
|
||||||
if(auto art = dynamic_ptr_cast<EraseArtifactByClient>(pack))
|
|
||||||
{
|
|
||||||
if (auto id = std::visit(GetEngagedHeroIds(), art->al.artHolder))
|
|
||||||
return !vstd::contains(ourIds, *id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto dismiss = dynamic_ptr_cast<AssembleArtifacts>(pack))
|
|
||||||
return !vstd::contains(ourIds, dismiss->heroID);
|
|
||||||
|
|
||||||
if(auto upgrade = dynamic_ptr_cast<UpgradeCreature>(pack))
|
|
||||||
return !vstd::contains(ourIds, upgrade->id);
|
|
||||||
|
|
||||||
if(auto formation = dynamic_ptr_cast<SetFormation>(pack))
|
|
||||||
return !vstd::contains(ourIds, formation->hid);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
assert(answer);
|
|
||||||
objectVisit.visitedObject->blockingDialogAnswered(objectVisit.visitingHero, *answer);
|
|
||||||
}
|
|
||||||
|
|
||||||
CBlockingDialogQuery::CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog & bd):
|
|
||||||
CDialogQuery(owner)
|
|
||||||
{
|
|
||||||
this->bd = bd;
|
|
||||||
addPlayer(bd.player);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
// do not change to dynamic_ptr_cast - SIGSEGV!
|
|
||||||
auto obj = dynamic_cast<const CGTeleport*>(objectVisit.visitedObject);
|
|
||||||
if(obj)
|
|
||||||
obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits);
|
|
||||||
else
|
|
||||||
logGlobal->error("Invalid instance in teleport query");
|
|
||||||
}
|
|
||||||
|
|
||||||
CTeleportDialogQuery::CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog & td):
|
|
||||||
CDialogQuery(owner)
|
|
||||||
{
|
|
||||||
this->td = td;
|
|
||||||
addPlayer(td.player);
|
|
||||||
}
|
|
||||||
|
|
||||||
CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp & Hlu, const CGHeroInstance * Hero):
|
|
||||||
CDialogQuery(owner), hero(Hero)
|
|
||||||
{
|
|
||||||
hlu = Hlu;
|
|
||||||
addPlayer(hero->tempOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHeroLevelUpDialogQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
assert(answer);
|
|
||||||
logGlobal->trace("Completing hero level-up query. %s gains skill %d", hero->getObjectName(), answer.value());
|
|
||||||
gh->levelUpHero(hero, hlu.skills[*answer]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
|
|
||||||
}
|
|
||||||
|
|
||||||
CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp & Clu, const CGHeroInstance * Hero):
|
|
||||||
CDialogQuery(owner), hero(Hero)
|
|
||||||
{
|
|
||||||
clu = Clu;
|
|
||||||
addPlayer(hero->tempOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CCommanderLevelUpDialogQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
assert(answer);
|
|
||||||
logGlobal->trace("Completing commander level-up query. Commander of hero %s gains skill %s", hero->getObjectName(), answer.value());
|
|
||||||
gh->levelUpCommander(hero->commander, clu.skills[*answer]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
|
||||||
{
|
|
||||||
objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
|
|
||||||
}
|
|
||||||
|
|
||||||
CDialogQuery::CDialogQuery(CGameHandler * owner):
|
|
||||||
CGhQuery(owner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CDialogQuery::endsByPlayerAnswer() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CDialogQuery::blocksPack(const CPack * pack) const
|
|
||||||
{
|
|
||||||
return blockAllButReply(pack);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CDialogQuery::setReply(const JsonNode & reply)
|
|
||||||
{
|
|
||||||
if(reply.getType() == JsonNode::JsonType::DATA_INTEGER)
|
|
||||||
answer = reply.Integer();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory):
|
|
||||||
CGhQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
|
|
||||||
{
|
|
||||||
players.push_back(hero->tempOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHeroMovementQuery::onExposure(QueryPtr topQuery)
|
|
||||||
{
|
|
||||||
assert(players.size() == 1);
|
|
||||||
|
|
||||||
if(visitDestAfterVictory && hero->tempOwner == players[0]) //hero still alive, so he won with the guard
|
|
||||||
//TODO what if there were H4-like escape? we should also check pos
|
|
||||||
{
|
|
||||||
logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->getNameTranslated(), tmh.end.toString());
|
|
||||||
//finish movement
|
|
||||||
visitDestAfterVictory = false;
|
|
||||||
gh->visitObjectOnTile(*gh->getTile(hero->convertToVisitablePos(tmh.end)), hero);
|
|
||||||
}
|
|
||||||
|
|
||||||
owner->popIfTop(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHeroMovementQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
PlayerBlocked pb;
|
|
||||||
pb.player = color;
|
|
||||||
pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
|
|
||||||
pb.startOrEnd = PlayerBlocked::BLOCKADE_ENDED;
|
|
||||||
gh->sendAndApply(&pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHeroMovementQuery::onAdding(PlayerColor color)
|
|
||||||
{
|
|
||||||
PlayerBlocked pb;
|
|
||||||
pb.player = color;
|
|
||||||
pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
|
|
||||||
pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
|
|
||||||
gh->sendAndApply(&pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
CGenericQuery::CGenericQuery(Queries * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback):
|
|
||||||
CQuery(Owner), callback(Callback)
|
|
||||||
{
|
|
||||||
addPlayer(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CGenericQuery::blocksPack(const CPack * pack) const
|
|
||||||
{
|
|
||||||
return blockAllButReply(pack);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CGenericQuery::endsByPlayerAnswer() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CGenericQuery::onExposure(QueryPtr topQuery)
|
|
||||||
{
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
void CGenericQuery::setReply(const JsonNode & reply)
|
|
||||||
{
|
|
||||||
this->reply = reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CGenericQuery::onRemoval(PlayerColor color)
|
|
||||||
{
|
|
||||||
callback(reply);
|
|
||||||
}
|
|
242
server/CQuery.h
242
server/CQuery.h
@ -1,242 +0,0 @@
|
|||||||
/*
|
|
||||||
* CQuery.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/int3.h"
|
|
||||||
#include "../lib/NetPacks.h"
|
|
||||||
#include "JsonNode.h"
|
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
class CGObjectInstance;
|
|
||||||
class CGHeroInstance;
|
|
||||||
class CArmedInstance;
|
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
|
||||||
|
|
||||||
class CGameHandler;
|
|
||||||
class CObjectVisitQuery;
|
|
||||||
class CQuery;
|
|
||||||
class Queries;
|
|
||||||
|
|
||||||
using QueryPtr = std::shared_ptr<CQuery>;
|
|
||||||
|
|
||||||
// This class represents any kind of prolonged interaction that may need to do something special after it is over.
|
|
||||||
// It does not necessarily has to be "query" requiring player action, it can be also used internally within server.
|
|
||||||
// Examples:
|
|
||||||
// - all kinds of blocking dialog windows
|
|
||||||
// - battle
|
|
||||||
// - object visit
|
|
||||||
// - hero movement
|
|
||||||
// Queries can cause another queries, forming a stack of queries for each player. Eg: hero movement -> object visit -> dialog.
|
|
||||||
class CQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::vector<PlayerColor> players; //players that are affected (often "blocked") by query
|
|
||||||
QueryID queryID;
|
|
||||||
|
|
||||||
CQuery(Queries * Owner);
|
|
||||||
|
|
||||||
|
|
||||||
virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle.
|
|
||||||
|
|
||||||
virtual bool endsByPlayerAnswer() const; //query is removed after player gives answer (like dialogs)
|
|
||||||
virtual void onAdding(PlayerColor color); //called just before query is pushed on stack
|
|
||||||
virtual void onAdded(PlayerColor color); //called right after query is pushed on stack
|
|
||||||
virtual void onRemoval(PlayerColor color); //called after query is removed from stack
|
|
||||||
virtual void onExposure(QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top)
|
|
||||||
virtual std::string toString() const;
|
|
||||||
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const;
|
|
||||||
|
|
||||||
virtual void setReply(const JsonNode & reply);
|
|
||||||
|
|
||||||
virtual ~CQuery();
|
|
||||||
protected:
|
|
||||||
Queries * owner;
|
|
||||||
void addPlayer(PlayerColor color);
|
|
||||||
bool blockAllButReply(const CPack * pack) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &out, const CQuery &query);
|
|
||||||
std::ostream &operator<<(std::ostream &out, QueryPtr query);
|
|
||||||
|
|
||||||
class CGhQuery : public CQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CGhQuery(CGameHandler * owner);
|
|
||||||
protected:
|
|
||||||
CGameHandler * gh;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Created when hero visits object.
|
|
||||||
//Removed when query above is resolved (or immediately after visit if no queries were created)
|
|
||||||
class CObjectVisitQuery : public CGhQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
const CGObjectInstance *visitedObject;
|
|
||||||
const CGHeroInstance *visitingHero;
|
|
||||||
int3 tile; //may be different than hero pos -> eg. visit via teleport
|
|
||||||
bool removeObjectAfterVisit;
|
|
||||||
|
|
||||||
CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile);
|
|
||||||
|
|
||||||
virtual bool blocksPack(const CPack *pack) const override;
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
virtual void onExposure(QueryPtr topQuery) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CBattleQuery : public CGhQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::array<const CArmedInstance *,2> belligerents;
|
|
||||||
std::array<int, 2> initialHeroMana;
|
|
||||||
|
|
||||||
const BattleInfo *bi;
|
|
||||||
std::optional<BattleResult> result;
|
|
||||||
|
|
||||||
CBattleQuery(CGameHandler * owner);
|
|
||||||
CBattleQuery(CGameHandler * owner, const BattleInfo * Bi); //TODO
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
virtual bool blocksPack(const CPack *pack) const override;
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Created when hero attempts move and something happens
|
|
||||||
//(not necessarily position change, could be just an object interaction).
|
|
||||||
class CHeroMovementQuery : public CGhQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TryMoveHero tmh;
|
|
||||||
bool visitDestAfterVictory; //if hero moved to guarded tile and it should be visited once guard is defeated
|
|
||||||
const CGHeroInstance *hero;
|
|
||||||
|
|
||||||
virtual void onExposure(QueryPtr topQuery) override;
|
|
||||||
|
|
||||||
CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory = false);
|
|
||||||
virtual void onAdding(PlayerColor color) override;
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CDialogQuery : public CGhQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CDialogQuery(CGameHandler * owner);
|
|
||||||
virtual bool endsByPlayerAnswer() const override;
|
|
||||||
virtual bool blocksPack(const CPack *pack) const override;
|
|
||||||
void setReply(const JsonNode & reply) override;
|
|
||||||
protected:
|
|
||||||
std::optional<ui32> answer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CGarrisonDialogQuery : public CDialogQuery //used also for hero exchange dialogs
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::array<const CArmedInstance *,2> exchangingArmies;
|
|
||||||
|
|
||||||
CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down);
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
virtual bool blocksPack(const CPack *pack) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CBattleDialogQuery : public CDialogQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi);
|
|
||||||
|
|
||||||
const BattleInfo * bi;
|
|
||||||
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//yes/no and component selection dialogs
|
|
||||||
class CBlockingDialogQuery : public CDialogQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BlockingDialog bd; //copy of pack... debug purposes
|
|
||||||
|
|
||||||
CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog &bd);
|
|
||||||
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CTeleportDialogQuery : public CDialogQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TeleportDialog td; //copy of pack... debug purposes
|
|
||||||
|
|
||||||
CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog &td);
|
|
||||||
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CHeroLevelUpDialogQuery : public CDialogQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp &Hlu, const CGHeroInstance * Hero);
|
|
||||||
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
|
|
||||||
HeroLevelUp hlu;
|
|
||||||
const CGHeroInstance * hero;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CCommanderLevelUpDialogQuery : public CDialogQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp &Clu, const CGHeroInstance * Hero);
|
|
||||||
|
|
||||||
virtual void onRemoval(PlayerColor color) override;
|
|
||||||
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
|
||||||
|
|
||||||
CommanderLevelUp clu;
|
|
||||||
const CGHeroInstance * hero;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CGenericQuery : public CQuery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CGenericQuery(Queries * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback);
|
|
||||||
|
|
||||||
bool blocksPack(const CPack * pack) const override;
|
|
||||||
bool endsByPlayerAnswer() const override;
|
|
||||||
void onExposure(QueryPtr topQuery) override;
|
|
||||||
void setReply(const JsonNode & reply) override;
|
|
||||||
void onRemoval(PlayerColor color) override;
|
|
||||||
private:
|
|
||||||
std::function<void(const JsonNode &)> callback;
|
|
||||||
JsonNode reply;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Queries
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
void addQuery(PlayerColor player, QueryPtr query);
|
|
||||||
void popQuery(PlayerColor player, QueryPtr query);
|
|
||||||
|
|
||||||
std::map<PlayerColor, std::vector<QueryPtr>> queries; //player => stack of queries
|
|
||||||
|
|
||||||
public:
|
|
||||||
static boost::mutex mx;
|
|
||||||
|
|
||||||
void addQuery(QueryPtr query);
|
|
||||||
void popQuery(const CQuery &query);
|
|
||||||
void popQuery(QueryPtr query);
|
|
||||||
void popIfTop(const CQuery &query); //removes this query if it is at the top (otherwise, do nothing)
|
|
||||||
void popIfTop(QueryPtr query); //removes this query if it is at the top (otherwise, do nothing)
|
|
||||||
|
|
||||||
QueryPtr topQuery(PlayerColor player);
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<const CQuery>> allQueries() const;
|
|
||||||
std::vector<QueryPtr> allQueries();
|
|
||||||
QueryPtr getQuery(QueryID queryID);
|
|
||||||
//void removeQuery
|
|
||||||
};
|
|
@ -36,7 +36,7 @@
|
|||||||
#include "../lib/VCMI_Lib.h"
|
#include "../lib/VCMI_Lib.h"
|
||||||
#include "../lib/VCMIDirs.h"
|
#include "../lib/VCMIDirs.h"
|
||||||
#include "CGameHandler.h"
|
#include "CGameHandler.h"
|
||||||
#include "PlayerMessageProcessor.h"
|
#include "processors/PlayerMessageProcessor.h"
|
||||||
#include "../lib/mapping/CMapInfo.h"
|
#include "../lib/mapping/CMapInfo.h"
|
||||||
#include "../lib/GameConstants.h"
|
#include "../lib/GameConstants.h"
|
||||||
#include "../lib/logging/CBasicLogConfigurator.h"
|
#include "../lib/logging/CBasicLogConfigurator.h"
|
||||||
|
@ -11,8 +11,10 @@
|
|||||||
#include "ServerNetPackVisitors.h"
|
#include "ServerNetPackVisitors.h"
|
||||||
|
|
||||||
#include "CGameHandler.h"
|
#include "CGameHandler.h"
|
||||||
#include "HeroPoolProcessor.h"
|
#include "battles/BattleProcessor.h"
|
||||||
#include "PlayerMessageProcessor.h"
|
#include "processors/HeroPoolProcessor.h"
|
||||||
|
#include "processors/PlayerMessageProcessor.h"
|
||||||
|
#include "queries/QueriesProcessor.h"
|
||||||
|
|
||||||
#include "../lib/IGameCallback.h"
|
#include "../lib/IGameCallback.h"
|
||||||
#include "../lib/mapObjects/CGTownInstance.h"
|
#include "../lib/mapObjects/CGTownInstance.h"
|
||||||
@ -47,7 +49,7 @@ void ApplyGhNetPackVisitor::visitEndTurn(EndTurn & pack)
|
|||||||
}
|
}
|
||||||
|
|
||||||
gh.throwOnWrongPlayer(&pack, pack.player);
|
gh.throwOnWrongPlayer(&pack, pack.player);
|
||||||
if(gh.queries.topQuery(pack.player))
|
if(gh.queries->topQuery(pack.player))
|
||||||
gh.throwAndComplain(&pack, "Cannot end turn before resolving queries!");
|
gh.throwAndComplain(&pack, "Cannot end turn before resolving queries!");
|
||||||
|
|
||||||
gh.states.setFlag(gs.currentPlayer, &PlayerStatus::makingTurn, false);
|
gh.states.setFlag(gs.currentPlayer, &PlayerStatus::makingTurn, false);
|
||||||
@ -280,52 +282,10 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack)
|
|||||||
|
|
||||||
void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
|
void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
|
||||||
{
|
{
|
||||||
boost::unique_lock lock(gh.battleActionMutex);
|
if (!gh.hasPlayerAt(pack.player, pack.c))
|
||||||
|
gh.throwAndComplain(&pack, "No such pack.player!");
|
||||||
|
|
||||||
const BattleInfo * b = gs.curB;
|
result = gh.battles->makePlayerBattleAction(pack.player, pack.ba);
|
||||||
if(!b)
|
|
||||||
gh.throwAndComplain(&pack, "Can not make action - there is no battle ongoing!");
|
|
||||||
|
|
||||||
if(b->tacticDistance)
|
|
||||||
{
|
|
||||||
if(pack.ba.actionType != EActionType::WALK && pack.ba.actionType != EActionType::END_TACTIC_PHASE
|
|
||||||
&& pack.ba.actionType != EActionType::RETREAT && pack.ba.actionType != EActionType::SURRENDER)
|
|
||||||
gh.throwAndComplain(&pack, "Can not make actions while in tactics mode!");
|
|
||||||
if(!vstd::contains(gh.connections[b->sides[b->tacticsSide].color], pack.c))
|
|
||||||
gh.throwAndComplain(&pack, "Can not make actions in battles you are not part of!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto active = b->battleActiveUnit();
|
|
||||||
if(!active)
|
|
||||||
gh.throwAndComplain(&pack, "No active unit in battle!");
|
|
||||||
auto unitOwner = b->battleGetOwner(active);
|
|
||||||
if(!vstd::contains(gh.connections[unitOwner], pack.c))
|
|
||||||
gh.throwAndComplain(&pack, "Can not make actions in battles you are not part of!");
|
|
||||||
}
|
|
||||||
|
|
||||||
result = gh.makeBattleAction(pack.ba);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyGhNetPackVisitor::visitMakeCustomAction(MakeCustomAction & pack)
|
|
||||||
{
|
|
||||||
boost::unique_lock lock(gh.battleActionMutex);
|
|
||||||
|
|
||||||
const BattleInfo * b = gs.curB;
|
|
||||||
if(!b)
|
|
||||||
gh.throwNotAllowedAction(&pack);
|
|
||||||
if(b->tacticDistance)
|
|
||||||
gh.throwNotAllowedAction(&pack);
|
|
||||||
auto active = b->battleActiveUnit();
|
|
||||||
if(!active)
|
|
||||||
gh.throwNotAllowedAction(&pack);
|
|
||||||
auto unitOwner = b->battleGetOwner(active);
|
|
||||||
if(!vstd::contains(gh.connections[unitOwner], pack.c))
|
|
||||||
gh.throwNotAllowedAction(&pack);
|
|
||||||
if(pack.ba.actionType != EActionType::HERO_SPELL)
|
|
||||||
gh.throwNotAllowedAction(&pack);
|
|
||||||
|
|
||||||
result = gh.makeCustomAction(pack.ba);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
|
void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
|
||||||
|
@ -55,7 +55,6 @@ public:
|
|||||||
virtual void visitBuildBoat(BuildBoat & pack) override;
|
virtual void visitBuildBoat(BuildBoat & pack) override;
|
||||||
virtual void visitQueryReply(QueryReply & pack) override;
|
virtual void visitQueryReply(QueryReply & pack) override;
|
||||||
virtual void visitMakeAction(MakeAction & pack) override;
|
virtual void visitMakeAction(MakeAction & pack) override;
|
||||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) override;
|
|
||||||
virtual void visitDigWithHero(DigWithHero & pack) override;
|
virtual void visitDigWithHero(DigWithHero & pack) override;
|
||||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
||||||
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
||||||
|
@ -8,10 +8,15 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "../lib/gameState/CGameState.h"
|
|
||||||
#include "CGameHandler.h"
|
|
||||||
#include "ServerSpellCastEnvironment.h"
|
#include "ServerSpellCastEnvironment.h"
|
||||||
|
|
||||||
|
#include "CGameHandler.h"
|
||||||
|
#include "queries/QueriesProcessor.h"
|
||||||
|
#include "queries/CQuery.h"
|
||||||
|
|
||||||
|
#include "../lib/gameState/CGameState.h"
|
||||||
|
#include "../lib/NetPacks.h"
|
||||||
|
|
||||||
///ServerSpellCastEnvironment
|
///ServerSpellCastEnvironment
|
||||||
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh)
|
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh)
|
||||||
: gh(gh)
|
: gh(gh)
|
||||||
@ -90,8 +95,8 @@ bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool t
|
|||||||
|
|
||||||
void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode&)> callback)
|
void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode&)> callback)
|
||||||
{
|
{
|
||||||
auto query = std::make_shared<CGenericQuery>(&gh->queries, color, callback);
|
auto query = std::make_shared<CGenericQuery>(gh->queries.get(), color, callback);
|
||||||
request->queryID = query->queryID;
|
request->queryID = query->queryID;
|
||||||
gh->queries.addQuery(query);
|
gh->queries->addQuery(query);
|
||||||
gh->sendAndApply(request);
|
gh->sendAndApply(request);
|
||||||
}
|
}
|
||||||
|
1431
server/battles/BattleActionProcessor.cpp
Normal file
1431
server/battles/BattleActionProcessor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
79
server/battles/BattleActionProcessor.h
Normal file
79
server/battles/BattleActionProcessor.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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 BattleAction;
|
||||||
|
struct BattleHex;
|
||||||
|
class CStack;
|
||||||
|
class PlayerColor;
|
||||||
|
enum class BonusType;
|
||||||
|
|
||||||
|
namespace battle
|
||||||
|
{
|
||||||
|
class Unit;
|
||||||
|
class CUnitState;
|
||||||
|
}
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class CGameHandler;
|
||||||
|
class BattleProcessor;
|
||||||
|
|
||||||
|
/// Processes incoming battle action queries and applies requested action(s)
|
||||||
|
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);
|
||||||
|
|
||||||
|
bool canStackAct(const CStack * stack);
|
||||||
|
|
||||||
|
bool doEmptyAction(const BattleAction & ba);
|
||||||
|
bool doEndTacticsAction(const BattleAction & ba);
|
||||||
|
bool doRetreatAction(const BattleAction & ba);
|
||||||
|
bool doSurrenderAction(const BattleAction & ba);
|
||||||
|
bool doHeroSpellAction(const BattleAction & ba);
|
||||||
|
bool doWalkAction(const BattleAction & ba);
|
||||||
|
bool doWaitAction(const BattleAction & ba);
|
||||||
|
bool doDefendAction(const BattleAction & ba);
|
||||||
|
bool doAttackAction(const BattleAction & ba);
|
||||||
|
bool doShootAction(const BattleAction & ba);
|
||||||
|
bool doCatapultAction(const BattleAction & ba);
|
||||||
|
bool doUnitSpellAction(const BattleAction & ba);
|
||||||
|
bool doHealAction(const BattleAction & ba);
|
||||||
|
|
||||||
|
bool dispatchBattleAction(const BattleAction & ba);
|
||||||
|
bool makeBattleActionImpl(const BattleAction & ba);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BattleActionProcessor(BattleProcessor * owner);
|
||||||
|
void setGameHandler(CGameHandler * newGameHandler);
|
||||||
|
|
||||||
|
bool makeAutomaticBattleAction(const BattleAction & ba);
|
||||||
|
bool makePlayerBattleAction(PlayerColor player, const BattleAction & ba);
|
||||||
|
};
|
741
server/battles/BattleFlowProcessor.cpp
Normal file
741
server/battles/BattleFlowProcessor.cpp
Normal file
@ -0,0 +1,741 @@
|
|||||||
|
/*
|
||||||
|
* 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 "../../lib/CStack.h"
|
||||||
|
#include "../../lib/GameSettings.h"
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
#include "../../lib/gameState/CGameState.h"
|
||||||
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
#include "../../lib/NetPacks.h"
|
||||||
|
#include "../../lib/spells/BonusCaster.h"
|
||||||
|
#include "../../lib/spells/ISpellMechanics.h"
|
||||||
|
#include "../../lib/spells/ObstacleCasterProxy.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::tryPlaceMoats()
|
||||||
|
{
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleFlowProcessor::onBattleStarted()
|
||||||
|
{
|
||||||
|
gameHandler->setBattle(gameHandler->gameState()->curB);
|
||||||
|
assert(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
|
tryPlaceMoats();
|
||||||
|
|
||||||
|
if (gameHandler->gameState()->curB->tacticDistance == 0)
|
||||||
|
onTacticsEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleFlowProcessor::trySummonGuardians(const CStack * stack)
|
||||||
|
{
|
||||||
|
if (!stack->hasBonusOfType(BonusType::SUMMON_GUARDIANS))
|
||||||
|
return;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleFlowProcessor::castOpeningSpells()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
auto h = gameHandler->gameState()->curB->battleGetFightingHero(i);
|
||||||
|
if (!h)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
trySummonGuardians(stack);
|
||||||
|
stackEnchantedTrigger(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
castOpeningSpells();
|
||||||
|
|
||||||
|
// it is possible that due to opening spells one side was eliminated -> check for end of battle
|
||||||
|
if (owner->checkBattleStateChanges())
|
||||||
|
return;
|
||||||
|
|
||||||
|
startNextRound(true);
|
||||||
|
activateNextStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Find next stack that requires manual control
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// battle has ended
|
||||||
|
if (owner->checkBattleStateChanges())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const CStack * next = getNextStack();
|
||||||
|
|
||||||
|
if (!next)
|
||||||
|
{
|
||||||
|
// No stacks to move - start next round
|
||||||
|
startNextRound(false);
|
||||||
|
next = getNextStack();
|
||||||
|
if (!next)
|
||||||
|
throw std::runtime_error("Failed to find valid stack to act!");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!tryMakeAutomaticAction(next))
|
||||||
|
{
|
||||||
|
setActiveStack(next);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.empty())
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleFlowProcessor::rollGoodMorale(const CStack * next)
|
||||||
|
{
|
||||||
|
//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
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleFlowProcessor::onActionMade(const BattleAction &ba)
|
||||||
|
{
|
||||||
|
const auto & battle = gameHandler->gameState()->curB;
|
||||||
|
|
||||||
|
const CStack * actedStack = battle->battleGetStackByID(ba.stackNumber, false);
|
||||||
|
const CStack * activeStack = battle->battleGetStackByID(battle->getActiveStackID(), false);
|
||||||
|
|
||||||
|
//we're after action, all results applied
|
||||||
|
|
||||||
|
// check whether action has ended the battle
|
||||||
|
if(owner->checkBattleStateChanges())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// tactics - next stack will be selected by player
|
||||||
|
if(battle->tacticDistance != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ba.isUnitAction())
|
||||||
|
{
|
||||||
|
assert(activeStack != nullptr);
|
||||||
|
assert(actedStack != nullptr);
|
||||||
|
|
||||||
|
if (rollGoodMorale(actedStack))
|
||||||
|
{
|
||||||
|
// Good morale - same stack makes 2nd turn
|
||||||
|
setActiveStack(actedStack);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (activeStack && activeStack->alive())
|
||||||
|
{
|
||||||
|
// this is action made by hero AND unit is alive (e.g. not killed by casted spell)
|
||||||
|
// keep current active stack for next action
|
||||||
|
setActiveStack(activeStack);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activateNextStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
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->makeAutomaticBattleAction(ba);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleFlowProcessor::setActiveStack(const CStack * stack)
|
||||||
|
{
|
||||||
|
assert(stack);
|
||||||
|
|
||||||
|
logGlobal->trace("Activating %s", stack->nodeName());
|
||||||
|
BattleSetActiveStack sas;
|
||||||
|
sas.stack = stack->unitId();
|
||||||
|
gameHandler->sendAndApply(&sas);
|
||||||
|
}
|
55
server/battles/BattleFlowProcessor.h
Normal file
55
server/battles/BattleFlowProcessor.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions
|
||||||
|
class BattleFlowProcessor : boost::noncopyable
|
||||||
|
{
|
||||||
|
BattleProcessor * owner;
|
||||||
|
CGameHandler * gameHandler;
|
||||||
|
|
||||||
|
const CStack * getNextStack();
|
||||||
|
|
||||||
|
bool rollGoodMorale(const CStack * stack);
|
||||||
|
bool tryMakeAutomaticAction(const CStack * stack);
|
||||||
|
|
||||||
|
void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
|
||||||
|
void trySummonGuardians(const CStack * stack);
|
||||||
|
void tryPlaceMoats();
|
||||||
|
void castOpeningSpells();
|
||||||
|
void activateNextStack();
|
||||||
|
void startNextRound(bool isFirstRound);
|
||||||
|
|
||||||
|
void stackEnchantedTrigger(const CStack * stack);
|
||||||
|
void removeObstacle(const CObstacleInstance & obstacle);
|
||||||
|
void stackTurnTrigger(const CStack * stack);
|
||||||
|
void setActiveStack(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:
|
||||||
|
explicit BattleFlowProcessor(BattleProcessor * owner);
|
||||||
|
void setGameHandler(CGameHandler * newGameHandler);
|
||||||
|
|
||||||
|
void onBattleStarted();
|
||||||
|
void onTacticsEnded();
|
||||||
|
void onActionMade(const BattleAction & ba);
|
||||||
|
};
|
250
server/battles/BattleProcessor.cpp
Normal file
250
server/battles/BattleProcessor.cpp
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* BattleProcessor.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 "BattleProcessor.h"
|
||||||
|
|
||||||
|
#include "BattleActionProcessor.h"
|
||||||
|
#include "BattleFlowProcessor.h"
|
||||||
|
#include "BattleResultProcessor.h"
|
||||||
|
|
||||||
|
#include "../CGameHandler.h"
|
||||||
|
#include "../queries/QueriesProcessor.h"
|
||||||
|
#include "../queries/BattleQueries.h"
|
||||||
|
|
||||||
|
#include "../../lib/TerrainHandler.h"
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
#include "../../lib/gameState/CGameState.h"
|
||||||
|
#include "../../lib/mapping/CMap.h"
|
||||||
|
#include "../../lib/modding/IdentifierStorage.h"
|
||||||
|
|
||||||
|
BattleProcessor::BattleProcessor(CGameHandler * gameHandler)
|
||||||
|
: gameHandler(gameHandler)
|
||||||
|
, flowProcessor(std::make_unique<BattleFlowProcessor>(this))
|
||||||
|
, actionsProcessor(std::make_unique<BattleActionProcessor>(this))
|
||||||
|
, resultProcessor(std::make_unique<BattleResultProcessor>(this))
|
||||||
|
{
|
||||||
|
setGameHandler(gameHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleProcessor::BattleProcessor():
|
||||||
|
BattleProcessor(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleProcessor::~BattleProcessor() = default;
|
||||||
|
|
||||||
|
void BattleProcessor::engageIntoBattle(PlayerColor player)
|
||||||
|
{
|
||||||
|
//notify interfaces
|
||||||
|
PlayerBlocked pb;
|
||||||
|
pb.player = player;
|
||||||
|
pb.reason = PlayerBlocked::UPCOMING_BATTLE;
|
||||||
|
pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
|
||||||
|
gameHandler->sendAndApply(&pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::startBattlePrimary(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
|
||||||
|
{
|
||||||
|
if(gameHandler->gameState()->curB)
|
||||||
|
gameHandler->gameState()->curB.dellNull();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
|
||||||
|
|
||||||
|
auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(gameHandler->gameState()->curB->sides[0].color));
|
||||||
|
|
||||||
|
//existing battle query for retying auto-combat
|
||||||
|
if(lastBattleQuery)
|
||||||
|
{
|
||||||
|
for(int i : {0, 1})
|
||||||
|
{
|
||||||
|
if(heroes[i])
|
||||||
|
{
|
||||||
|
SetMana restoreInitialMana;
|
||||||
|
restoreInitialMana.val = lastBattleQuery->initialHeroMana[i];
|
||||||
|
restoreInitialMana.hid = heroes[i]->id;
|
||||||
|
gameHandler->sendAndApply(&restoreInitialMana);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBattleQuery->bi = gameHandler->gameState()->curB;
|
||||||
|
lastBattleQuery->result = std::nullopt;
|
||||||
|
lastBattleQuery->belligerents[0] = gameHandler->gameState()->curB->sides[0].armyObject;
|
||||||
|
lastBattleQuery->belligerents[1] = gameHandler->gameState()->curB->sides[1].armyObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextBattleQuery = std::make_shared<CBattleQuery>(gameHandler, gameHandler->gameState()->curB);
|
||||||
|
for(int i : {0, 1})
|
||||||
|
{
|
||||||
|
if(heroes[i])
|
||||||
|
{
|
||||||
|
nextBattleQuery->initialHeroMana[i] = heroes[i]->mana;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gameHandler->queries->addQuery(nextBattleQuery);
|
||||||
|
|
||||||
|
flowProcessor->onBattleStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank)
|
||||||
|
{
|
||||||
|
startBattlePrimary(army1, army2, tile,
|
||||||
|
army1->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army1) : nullptr,
|
||||||
|
army2->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army2) : nullptr,
|
||||||
|
creatureBank);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank)
|
||||||
|
{
|
||||||
|
startBattleI(army1, army2, army2->visitablePos(), creatureBank);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town)
|
||||||
|
{
|
||||||
|
const auto & t = *gameHandler->getTile(tile);
|
||||||
|
TerrainId terrain = t.terType->getId();
|
||||||
|
if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground
|
||||||
|
terrain = ETerrainId::SAND;
|
||||||
|
|
||||||
|
BattleField terType = gameHandler->gameState()->battleGetBattlefieldType(tile, gameHandler->getRandomGenerator());
|
||||||
|
if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat)
|
||||||
|
terType = BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.ship_to_ship"));
|
||||||
|
|
||||||
|
//send info about battles
|
||||||
|
BattleStart bs;
|
||||||
|
bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
|
||||||
|
|
||||||
|
engageIntoBattle(bs.info->sides[0].color);
|
||||||
|
engageIntoBattle(bs.info->sides[1].color);
|
||||||
|
|
||||||
|
auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(bs.info->sides[0].color));
|
||||||
|
bs.info->replayAllowed = lastBattleQuery == nullptr && !bs.info->sides[1].color.isValidPlayer();
|
||||||
|
|
||||||
|
gameHandler->sendAndApply(&bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleProcessor::checkBattleStateChanges()
|
||||||
|
{
|
||||||
|
//check if drawbridge state need to be changes
|
||||||
|
if (gameHandler->battleGetSiegeLevel() > 0)
|
||||||
|
updateGateState();
|
||||||
|
|
||||||
|
//check if battle ended
|
||||||
|
if (auto result = gameHandler->battleIsFinished())
|
||||||
|
{
|
||||||
|
setBattleResult(EBattleResult::NORMAL, *result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::updateGateState()
|
||||||
|
{
|
||||||
|
// GATE_BRIDGE - leftmost tile, located over moat
|
||||||
|
// GATE_OUTER - central tile, mostly covered by gate image
|
||||||
|
// GATE_INNER - rightmost tile, inside the walls
|
||||||
|
|
||||||
|
// GATE_OUTER or GATE_INNER:
|
||||||
|
// - if defender moves unit on these tiles, bridge will open
|
||||||
|
// - if there is a creature (dead or alive) on these tiles, bridge will always remain open
|
||||||
|
// - blocked to attacker if bridge is closed
|
||||||
|
|
||||||
|
// GATE_BRIDGE
|
||||||
|
// - if there is a unit or corpse here, bridge can't open (and can't close in fortress)
|
||||||
|
// - if Force Field is cast here, bridge can't open (but can close, in any town)
|
||||||
|
// - deals moat damage to attacker if bridge is closed (fortress only)
|
||||||
|
|
||||||
|
bool hasForceFieldOnBridge = !gameHandler->battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty();
|
||||||
|
bool hasStackAtGateInner = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr;
|
||||||
|
bool hasStackAtGateOuter = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr;
|
||||||
|
bool hasStackAtGateBridge = gameHandler->gameState()->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr;
|
||||||
|
bool hasWideMoat = vstd::contains_if(gameHandler->battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
|
||||||
|
{
|
||||||
|
return obst->obstacleType == CObstacleInstance::MOAT;
|
||||||
|
});
|
||||||
|
|
||||||
|
BattleUpdateGateState db;
|
||||||
|
db.state = gameHandler->gameState()->curB->si.gateState;
|
||||||
|
if (gameHandler->gameState()->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED)
|
||||||
|
{
|
||||||
|
db.state = EGateState::DESTROYED;
|
||||||
|
}
|
||||||
|
else if (db.state == EGateState::OPENED)
|
||||||
|
{
|
||||||
|
bool hasStackOnLongBridge = hasStackAtGateBridge && hasWideMoat;
|
||||||
|
bool gateCanClose = !hasStackAtGateInner && !hasStackAtGateOuter && !hasStackOnLongBridge;
|
||||||
|
|
||||||
|
if (gateCanClose)
|
||||||
|
db.state = EGateState::CLOSED;
|
||||||
|
else
|
||||||
|
db.state = EGateState::OPENED;
|
||||||
|
}
|
||||||
|
else // CLOSED or BLOCKED
|
||||||
|
{
|
||||||
|
bool gateBlocked = hasForceFieldOnBridge || hasStackAtGateBridge;
|
||||||
|
|
||||||
|
if (gateBlocked)
|
||||||
|
db.state = EGateState::BLOCKED;
|
||||||
|
else
|
||||||
|
db.state = EGateState::CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.state != gameHandler->gameState()->curB->si.gateState)
|
||||||
|
gameHandler->sendAndApply(&db);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleProcessor::makePlayerBattleAction(PlayerColor player, const BattleAction &ba)
|
||||||
|
{
|
||||||
|
bool result = actionsProcessor->makePlayerBattleAction(player, ba);
|
||||||
|
flowProcessor->onActionMade(ba);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
|
||||||
|
{
|
||||||
|
resultProcessor->setBattleResult(resultType, victoriusSide);
|
||||||
|
resultProcessor->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleProcessor::makeAutomaticBattleAction(const BattleAction &ba)
|
||||||
|
{
|
||||||
|
return actionsProcessor->makeAutomaticBattleAction(ba);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo)
|
||||||
|
{
|
||||||
|
resultProcessor->endBattleConfirm(battleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::battleAfterLevelUp(const BattleResult &result)
|
||||||
|
{
|
||||||
|
resultProcessor->battleAfterLevelUp(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattleProcessor::setGameHandler(CGameHandler * newGameHandler)
|
||||||
|
{
|
||||||
|
gameHandler = newGameHandler;
|
||||||
|
|
||||||
|
actionsProcessor->setGameHandler(newGameHandler);
|
||||||
|
flowProcessor->setGameHandler(newGameHandler);
|
||||||
|
resultProcessor->setGameHandler(newGameHandler);
|
||||||
|
}
|
80
server/battles/BattleProcessor.h
Normal file
80
server/battles/BattleProcessor.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
class CGHeroInstance;
|
||||||
|
class CGTownInstance;
|
||||||
|
class CArmedInstance;
|
||||||
|
class BattleAction;
|
||||||
|
class int3;
|
||||||
|
class BattleInfo;
|
||||||
|
struct BattleResult;
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class CGameHandler;
|
||||||
|
class CBattleQuery;
|
||||||
|
class BattleActionProcessor;
|
||||||
|
class BattleFlowProcessor;
|
||||||
|
class BattleResultProcessor;
|
||||||
|
|
||||||
|
/// Main class for battle handling. Contains all public interface for battles that is accessible from outside, e.g. for CGameHandler
|
||||||
|
class BattleProcessor : boost::noncopyable
|
||||||
|
{
|
||||||
|
friend class BattleActionProcessor;
|
||||||
|
friend class BattleFlowProcessor;
|
||||||
|
friend class BattleResultProcessor;
|
||||||
|
|
||||||
|
CGameHandler * gameHandler;
|
||||||
|
std::unique_ptr<BattleActionProcessor> actionsProcessor;
|
||||||
|
std::unique_ptr<BattleFlowProcessor> flowProcessor;
|
||||||
|
std::unique_ptr<BattleResultProcessor> resultProcessor;
|
||||||
|
|
||||||
|
void updateGateState();
|
||||||
|
void engageIntoBattle(PlayerColor player);
|
||||||
|
|
||||||
|
bool checkBattleStateChanges();
|
||||||
|
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
||||||
|
|
||||||
|
bool makeAutomaticBattleAction(const BattleAction & ba);
|
||||||
|
|
||||||
|
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BattleProcessor(CGameHandler * gameHandler);
|
||||||
|
BattleProcessor();
|
||||||
|
~BattleProcessor();
|
||||||
|
|
||||||
|
void setGameHandler(CGameHandler * gameHandler);
|
||||||
|
|
||||||
|
/// Starts battle with specified parameters
|
||||||
|
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr);
|
||||||
|
/// Starts battle between two armies (which can also be heroes) at specified tile
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// Processing of incoming battle action netpack
|
||||||
|
bool makePlayerBattleAction(PlayerColor player, const BattleAction & ba);
|
||||||
|
|
||||||
|
/// Applies results of a battle once player agrees to them
|
||||||
|
void endBattleConfirm(const BattleInfo * battleInfo);
|
||||||
|
/// Applies results of a battle after potential levelup
|
||||||
|
void battleAfterLevelUp(const BattleResult & result);
|
||||||
|
|
||||||
|
template <typename Handler> void serialize(Handler &h, const int version)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
539
server/battles/BattleResultProcessor.cpp
Normal file
539
server/battles/BattleResultProcessor.cpp
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* 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 "../processors/HeroPoolProcessor.h"
|
||||||
|
#include "../queries/QueriesProcessor.h"
|
||||||
|
#include "../queries/BattleQueries.h"
|
||||||
|
|
||||||
|
#include "../../lib/ArtifactUtils.h"
|
||||||
|
#include "../../lib/CStack.h"
|
||||||
|
#include "../../lib/GameSettings.h"
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
#include "../../lib/gameState/CGameState.h"
|
||||||
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
#include "../../lib/serializer/Cast.h"
|
||||||
|
#include "../../lib/spells/CSpellHandler.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:
|
||||||
|
explicit 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);
|
||||||
|
};
|
@ -10,17 +10,17 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "HeroPoolProcessor.h"
|
#include "HeroPoolProcessor.h"
|
||||||
|
|
||||||
#include "CGameHandler.h"
|
#include "../CGameHandler.h"
|
||||||
|
|
||||||
#include "../lib/CHeroHandler.h"
|
#include "../../lib/CHeroHandler.h"
|
||||||
#include "../lib/CPlayerState.h"
|
#include "../../lib/CPlayerState.h"
|
||||||
#include "../lib/GameSettings.h"
|
#include "../../lib/GameSettings.h"
|
||||||
#include "../lib/NetPacks.h"
|
#include "../../lib/NetPacks.h"
|
||||||
#include "../lib/StartInfo.h"
|
#include "../../lib/StartInfo.h"
|
||||||
#include "../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../lib/gameState/CGameState.h"
|
#include "../../lib/gameState/CGameState.h"
|
||||||
#include "../lib/gameState/TavernHeroesPool.h"
|
#include "../../lib/gameState/TavernHeroesPool.h"
|
||||||
#include "../lib/gameState/TavernSlot.h"
|
#include "../../lib/gameState/TavernSlot.h"
|
||||||
|
|
||||||
HeroPoolProcessor::HeroPoolProcessor()
|
HeroPoolProcessor::HeroPoolProcessor()
|
||||||
: gameHandler(nullptr)
|
: gameHandler(nullptr)
|
@ -10,18 +10,19 @@
|
|||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "PlayerMessageProcessor.h"
|
#include "PlayerMessageProcessor.h"
|
||||||
|
|
||||||
#include "CGameHandler.h"
|
#include "../CGameHandler.h"
|
||||||
#include "CVCMIServer.h"
|
#include "../CVCMIServer.h"
|
||||||
|
|
||||||
#include "../lib/serializer/Connection.h"
|
#include "../../lib/serializer/Connection.h"
|
||||||
#include "../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
#include "../lib/CHeroHandler.h"
|
#include "../../lib/CHeroHandler.h"
|
||||||
#include "../lib/CPlayerState.h"
|
#include "../../lib/modding/IdentifierStorage.h"
|
||||||
#include "../lib/GameConstants.h"
|
#include "../../lib/CPlayerState.h"
|
||||||
#include "../lib/NetPacks.h"
|
#include "../../lib/GameConstants.h"
|
||||||
#include "../lib/StartInfo.h"
|
#include "../../lib/NetPacks.h"
|
||||||
#include "../lib/gameState/CGameState.h"
|
#include "../../lib/StartInfo.h"
|
||||||
#include "../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/gameState/CGameState.h"
|
||||||
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
#include "../lib/modding/IdentifierStorage.h"
|
#include "../lib/modding/IdentifierStorage.h"
|
||||||
#include "../lib/modding/ModScope.h"
|
#include "../lib/modding/ModScope.h"
|
||||||
|
|
||||||
@ -212,17 +213,29 @@ void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroI
|
|||||||
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
|
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero)
|
void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
|
||||||
{
|
{
|
||||||
if (!hero)
|
if (!hero)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!words.empty())
|
||||||
|
{
|
||||||
|
for (auto const & word : words)
|
||||||
|
{
|
||||||
|
auto artID = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "artifact", word, false);
|
||||||
|
if(artID && VLC->arth->objects[*artID])
|
||||||
|
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[*artID], ArtifactPosition::FIRST_AVAILABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
|
for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
|
||||||
{
|
{
|
||||||
if(VLC->arth->objects[g]->canBePutAt(hero))
|
if(VLC->arth->objects[g]->canBePutAt(hero))
|
||||||
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
|
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
|
void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
|
||||||
{
|
{
|
||||||
@ -433,7 +446,7 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
|
|||||||
const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); };
|
const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); };
|
||||||
const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); };
|
const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); };
|
||||||
const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); };
|
const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); };
|
||||||
const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero); };
|
const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero, words); };
|
||||||
const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); };
|
const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); };
|
||||||
const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); };
|
const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); };
|
||||||
const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); };
|
const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); };
|
@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../lib/GameConstants.h"
|
#include "../../lib/GameConstants.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
class CGHeroInstance;
|
class CGHeroInstance;
|
||||||
@ -31,7 +31,7 @@ class PlayerMessageProcessor
|
|||||||
void cheatBuildTown(PlayerColor player, const CGTownInstance * town);
|
void cheatBuildTown(PlayerColor player, const CGTownInstance * town);
|
||||||
void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
||||||
void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero);
|
void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero);
|
||||||
void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero);
|
void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
||||||
void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
||||||
void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
||||||
void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
75
server/queries/BattleQueries.cpp
Normal file
75
server/queries/BattleQueries.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* BattleQueries.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 "BattleQueries.h"
|
||||||
|
#include "MapQueries.h"
|
||||||
|
|
||||||
|
#include "../CGameHandler.h"
|
||||||
|
#include "../battles/BattleProcessor.h"
|
||||||
|
|
||||||
|
#include "../../lib/battle/BattleInfo.h"
|
||||||
|
|
||||||
|
void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
if(result)
|
||||||
|
objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBattleQuery::CBattleQuery(CGameHandler * owner, const BattleInfo * Bi):
|
||||||
|
CGhQuery(owner)
|
||||||
|
{
|
||||||
|
belligerents[0] = Bi->sides[0].armyObject;
|
||||||
|
belligerents[1] = Bi->sides[1].armyObject;
|
||||||
|
|
||||||
|
bi = Bi;
|
||||||
|
|
||||||
|
for(auto & side : bi->sides)
|
||||||
|
addPlayer(side.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBattleQuery::CBattleQuery(CGameHandler * owner):
|
||||||
|
CGhQuery(owner), bi(nullptr)
|
||||||
|
{
|
||||||
|
belligerents[0] = belligerents[1] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleQuery::blocksPack(const CPack * pack) const
|
||||||
|
{
|
||||||
|
const char * name = typeid(*pack).name();
|
||||||
|
return strcmp(name, typeid(MakeAction).name()) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBattleQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
if(result)
|
||||||
|
gh->battles->battleAfterLevelUp(*result);
|
||||||
|
}
|
||||||
|
|
||||||
|
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->battles->endBattleConfirm(bi);
|
||||||
|
}
|
||||||
|
}
|
40
server/queries/BattleQueries.h
Normal file
40
server/queries/BattleQueries.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* BattleQueries.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 "CQuery.h"
|
||||||
|
|
||||||
|
#include "../../lib/NetPacks.h"
|
||||||
|
|
||||||
|
class CBattleQuery : public CGhQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::array<const CArmedInstance *,2> belligerents;
|
||||||
|
std::array<int, 2> initialHeroMana;
|
||||||
|
|
||||||
|
const BattleInfo *bi;
|
||||||
|
std::optional<BattleResult> result;
|
||||||
|
|
||||||
|
CBattleQuery(CGameHandler * owner);
|
||||||
|
CBattleQuery(CGameHandler * owner, const BattleInfo * Bi); //TODO
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
virtual bool blocksPack(const CPack *pack) const override;
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CBattleDialogQuery : public CDialogQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CBattleDialogQuery(CGameHandler * owner, const BattleInfo * Bi);
|
||||||
|
|
||||||
|
const BattleInfo * bi;
|
||||||
|
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
};
|
201
server/queries/CQuery.cpp
Normal file
201
server/queries/CQuery.cpp
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* CQuery.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 "CQuery.h"
|
||||||
|
|
||||||
|
#include "QueriesProcessor.h"
|
||||||
|
|
||||||
|
#include "../CGameHandler.h"
|
||||||
|
|
||||||
|
#include "../../lib/serializer/Cast.h"
|
||||||
|
#include "../../lib/NetPacks.h"
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
std::string formatContainer(const Container & c, std::string delimeter = ", ", std::string opener = "(", std::string closer=")")
|
||||||
|
{
|
||||||
|
std::string ret = opener;
|
||||||
|
auto itr = std::begin(c);
|
||||||
|
if(itr != std::end(c))
|
||||||
|
{
|
||||||
|
ret += std::to_string(*itr);
|
||||||
|
while(++itr != std::end(c))
|
||||||
|
{
|
||||||
|
ret += delimeter;
|
||||||
|
ret += std::to_string(*itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret += closer;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream & operator<<(std::ostream & out, const CQuery & query)
|
||||||
|
{
|
||||||
|
return out << query.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream & operator<<(std::ostream & out, QueryPtr query)
|
||||||
|
{
|
||||||
|
return out << "[" << query.get() << "] " << query->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
CQuery::CQuery(QueriesProcessor * Owner):
|
||||||
|
owner(Owner)
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::mutex> l(QueriesProcessor::mx);
|
||||||
|
|
||||||
|
static QueryID QID = QueryID(0);
|
||||||
|
|
||||||
|
queryID = ++QID;
|
||||||
|
logGlobal->trace("Created a new query with id %d", queryID);
|
||||||
|
}
|
||||||
|
|
||||||
|
CQuery::~CQuery()
|
||||||
|
{
|
||||||
|
logGlobal->trace("Destructed the query with id %d", queryID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::addPlayer(PlayerColor color)
|
||||||
|
{
|
||||||
|
if(color.isValidPlayer())
|
||||||
|
players.push_back(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CQuery::toString() const
|
||||||
|
{
|
||||||
|
const auto size = players.size();
|
||||||
|
const std::string plural = size > 1 ? "s" : "";
|
||||||
|
std::string names;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
names += boost::to_upper_copy<std::string>(players[i].getStr());
|
||||||
|
|
||||||
|
if(i < size - 2)
|
||||||
|
names += ", ";
|
||||||
|
else if(size > 1 && i == size - 2)
|
||||||
|
names += " and ";
|
||||||
|
}
|
||||||
|
std::string ret = boost::str(boost::format("A query of type '%s' and qid = %d affecting player%s %s")
|
||||||
|
% typeid(*this).name()
|
||||||
|
% queryID
|
||||||
|
% plural
|
||||||
|
% names
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CQuery::endsByPlayerAnswer() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CQuery::blocksPack(const CPack * pack) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::onExposure(QueryPtr topQuery)
|
||||||
|
{
|
||||||
|
logGlobal->trace("Exposed query with id %d", queryID);
|
||||||
|
owner->popQuery(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::onAdding(PlayerColor color)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::onAdded(PlayerColor color)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuery::setReply(const JsonNode & reply)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CQuery::blockAllButReply(const CPack * pack) const
|
||||||
|
{
|
||||||
|
//We accept only query replies from correct player
|
||||||
|
if(auto reply = dynamic_ptr_cast<QueryReply>(pack))
|
||||||
|
return !vstd::contains(players, reply->player);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGhQuery::CGhQuery(CGameHandler * owner):
|
||||||
|
CQuery(owner->queries.get()), gh(owner)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CDialogQuery::CDialogQuery(CGameHandler * owner):
|
||||||
|
CGhQuery(owner)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDialogQuery::endsByPlayerAnswer() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDialogQuery::blocksPack(const CPack * pack) const
|
||||||
|
{
|
||||||
|
return blockAllButReply(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDialogQuery::setReply(const JsonNode & reply)
|
||||||
|
{
|
||||||
|
if(reply.getType() == JsonNode::JsonType::DATA_INTEGER)
|
||||||
|
answer = reply.Integer();
|
||||||
|
}
|
||||||
|
|
||||||
|
CGenericQuery::CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback):
|
||||||
|
CQuery(Owner), callback(Callback)
|
||||||
|
{
|
||||||
|
addPlayer(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CGenericQuery::blocksPack(const CPack * pack) const
|
||||||
|
{
|
||||||
|
return blockAllButReply(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CGenericQuery::endsByPlayerAnswer() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGenericQuery::onExposure(QueryPtr topQuery)
|
||||||
|
{
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGenericQuery::setReply(const JsonNode & reply)
|
||||||
|
{
|
||||||
|
this->reply = reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGenericQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
callback(reply);
|
||||||
|
}
|
99
server/queries/CQuery.h
Normal file
99
server/queries/CQuery.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* CQuery.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/JsonNode.h"
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct CPack;
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class CObjectVisitQuery;
|
||||||
|
class QueriesProcessor;
|
||||||
|
class CQuery;
|
||||||
|
|
||||||
|
using QueryPtr = std::shared_ptr<CQuery>;
|
||||||
|
|
||||||
|
// This class represents any kind of prolonged interaction that may need to do something special after it is over.
|
||||||
|
// It does not necessarily has to be "query" requiring player action, it can be also used internally within server.
|
||||||
|
// Examples:
|
||||||
|
// - all kinds of blocking dialog windows
|
||||||
|
// - battle
|
||||||
|
// - object visit
|
||||||
|
// - hero movement
|
||||||
|
// Queries can cause another queries, forming a stack of queries for each player. Eg: hero movement -> object visit -> dialog.
|
||||||
|
class CQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<PlayerColor> players; //players that are affected (often "blocked") by query
|
||||||
|
QueryID queryID;
|
||||||
|
|
||||||
|
CQuery(QueriesProcessor * Owner);
|
||||||
|
|
||||||
|
|
||||||
|
virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle.
|
||||||
|
|
||||||
|
virtual bool endsByPlayerAnswer() const; //query is removed after player gives answer (like dialogs)
|
||||||
|
virtual void onAdding(PlayerColor color); //called just before query is pushed on stack
|
||||||
|
virtual void onAdded(PlayerColor color); //called right after query is pushed on stack
|
||||||
|
virtual void onRemoval(PlayerColor color); //called after query is removed from stack
|
||||||
|
virtual void onExposure(QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top)
|
||||||
|
virtual std::string toString() const;
|
||||||
|
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const;
|
||||||
|
|
||||||
|
virtual void setReply(const JsonNode & reply);
|
||||||
|
|
||||||
|
virtual ~CQuery();
|
||||||
|
protected:
|
||||||
|
QueriesProcessor * owner;
|
||||||
|
void addPlayer(PlayerColor color);
|
||||||
|
bool blockAllButReply(const CPack * pack) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &out, const CQuery &query);
|
||||||
|
std::ostream &operator<<(std::ostream &out, QueryPtr query);
|
||||||
|
|
||||||
|
class CGhQuery : public CQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CGhQuery(CGameHandler * owner);
|
||||||
|
protected:
|
||||||
|
CGameHandler * gh;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CDialogQuery : public CGhQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CDialogQuery(CGameHandler * owner);
|
||||||
|
virtual bool endsByPlayerAnswer() const override;
|
||||||
|
virtual bool blocksPack(const CPack *pack) const override;
|
||||||
|
void setReply(const JsonNode & reply) override;
|
||||||
|
protected:
|
||||||
|
std::optional<ui32> answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CGenericQuery : public CQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CGenericQuery(QueriesProcessor * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback);
|
||||||
|
|
||||||
|
bool blocksPack(const CPack * pack) const override;
|
||||||
|
bool endsByPlayerAnswer() const override;
|
||||||
|
void onExposure(QueryPtr topQuery) override;
|
||||||
|
void setReply(const JsonNode & reply) override;
|
||||||
|
void onRemoval(PlayerColor color) override;
|
||||||
|
private:
|
||||||
|
std::function<void(const JsonNode &)> callback;
|
||||||
|
JsonNode reply;
|
||||||
|
};
|
227
server/queries/MapQueries.cpp
Normal file
227
server/queries/MapQueries.cpp
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* MapQueries.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 "MapQueries.h"
|
||||||
|
|
||||||
|
#include "QueriesProcessor.h"
|
||||||
|
#include "../CGameHandler.h"
|
||||||
|
#include "../../lib/mapObjects/MiscObjects.h"
|
||||||
|
#include "../../lib/serializer/Cast.h"
|
||||||
|
|
||||||
|
CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile):
|
||||||
|
CGhQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
|
||||||
|
{
|
||||||
|
addPlayer(Hero->tempOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CObjectVisitQuery::blocksPack(const CPack *pack) const
|
||||||
|
{
|
||||||
|
//During the visit itself ALL actions are blocked.
|
||||||
|
//(However, the visit may trigger a query above that'll pass some.)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CObjectVisitQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
gh->objectVisitEnded(*this);
|
||||||
|
|
||||||
|
//TODO or should it be destructor?
|
||||||
|
//Can object visit affect 2 players and what would be desired behavior?
|
||||||
|
if(removeObjectAfterVisit)
|
||||||
|
gh->removeObject(visitedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CObjectVisitQuery::onExposure(QueryPtr topQuery)
|
||||||
|
{
|
||||||
|
//Object may have been removed and deleted.
|
||||||
|
if(gh->isValidObject(visitedObject))
|
||||||
|
topQuery->notifyObjectAboutRemoval(*this);
|
||||||
|
|
||||||
|
owner->popIfTop(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
objectVisit.visitedObject->garrisonDialogClosed(objectVisit.visitingHero);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGarrisonDialogQuery::CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance * up, const CArmedInstance * down):
|
||||||
|
CDialogQuery(owner)
|
||||||
|
{
|
||||||
|
exchangingArmies[0] = up;
|
||||||
|
exchangingArmies[1] = down;
|
||||||
|
|
||||||
|
addPlayer(up->tempOwner);
|
||||||
|
addPlayer(down->tempOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const
|
||||||
|
{
|
||||||
|
std::set<ObjectInstanceID> ourIds;
|
||||||
|
ourIds.insert(this->exchangingArmies[0]->id);
|
||||||
|
ourIds.insert(this->exchangingArmies[1]->id);
|
||||||
|
|
||||||
|
if(auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack))
|
||||||
|
return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2);
|
||||||
|
|
||||||
|
if(auto stacks = dynamic_ptr_cast<BulkSplitStack>(pack))
|
||||||
|
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||||
|
|
||||||
|
if(auto stacks = dynamic_ptr_cast<BulkMergeStacks>(pack))
|
||||||
|
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||||
|
|
||||||
|
if(auto stacks = dynamic_ptr_cast<BulkSmartSplitStack>(pack))
|
||||||
|
return !vstd::contains(ourIds, stacks->srcOwner);
|
||||||
|
|
||||||
|
if(auto stacks = dynamic_ptr_cast<BulkMoveArmy>(pack))
|
||||||
|
return !vstd::contains(ourIds, stacks->srcArmy) || !vstd::contains(ourIds, stacks->destArmy);
|
||||||
|
|
||||||
|
if(auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack))
|
||||||
|
{
|
||||||
|
if(auto id1 = std::visit(GetEngagedHeroIds(), arts->src.artHolder))
|
||||||
|
if(!vstd::contains(ourIds, *id1))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if(auto id2 = std::visit(GetEngagedHeroIds(), arts->dst.artHolder))
|
||||||
|
if(!vstd::contains(ourIds, *id2))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(auto dismiss = dynamic_ptr_cast<DisbandCreature>(pack))
|
||||||
|
return !vstd::contains(ourIds, dismiss->id);
|
||||||
|
|
||||||
|
if(auto arts = dynamic_ptr_cast<BulkExchangeArtifacts>(pack))
|
||||||
|
return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero);
|
||||||
|
|
||||||
|
if(auto art = dynamic_ptr_cast<EraseArtifactByClient>(pack))
|
||||||
|
{
|
||||||
|
if (auto id = std::visit(GetEngagedHeroIds(), art->al.artHolder))
|
||||||
|
return !vstd::contains(ourIds, *id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto dismiss = dynamic_ptr_cast<AssembleArtifacts>(pack))
|
||||||
|
return !vstd::contains(ourIds, dismiss->heroID);
|
||||||
|
|
||||||
|
if(auto upgrade = dynamic_ptr_cast<UpgradeCreature>(pack))
|
||||||
|
return !vstd::contains(ourIds, upgrade->id);
|
||||||
|
|
||||||
|
if(auto formation = dynamic_ptr_cast<SetFormation>(pack))
|
||||||
|
return !vstd::contains(ourIds, formation->hid);
|
||||||
|
|
||||||
|
return CDialogQuery::blocksPack(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
assert(answer);
|
||||||
|
objectVisit.visitedObject->blockingDialogAnswered(objectVisit.visitingHero, *answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBlockingDialogQuery::CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog & bd):
|
||||||
|
CDialogQuery(owner)
|
||||||
|
{
|
||||||
|
this->bd = bd;
|
||||||
|
addPlayer(bd.player);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
// do not change to dynamic_ptr_cast - SIGSEGV!
|
||||||
|
auto obj = dynamic_cast<const CGTeleport*>(objectVisit.visitedObject);
|
||||||
|
if(obj)
|
||||||
|
obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits);
|
||||||
|
else
|
||||||
|
logGlobal->error("Invalid instance in teleport query");
|
||||||
|
}
|
||||||
|
|
||||||
|
CTeleportDialogQuery::CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog & td):
|
||||||
|
CDialogQuery(owner)
|
||||||
|
{
|
||||||
|
this->td = td;
|
||||||
|
addPlayer(td.player);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp & Hlu, const CGHeroInstance * Hero):
|
||||||
|
CDialogQuery(owner), hero(Hero)
|
||||||
|
{
|
||||||
|
hlu = Hlu;
|
||||||
|
addPlayer(hero->tempOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHeroLevelUpDialogQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
assert(answer);
|
||||||
|
logGlobal->trace("Completing hero level-up query. %s gains skill %d", hero->getObjectName(), answer.value());
|
||||||
|
gh->levelUpHero(hero, hlu.skills[*answer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
|
||||||
|
}
|
||||||
|
|
||||||
|
CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp & Clu, const CGHeroInstance * Hero):
|
||||||
|
CDialogQuery(owner), hero(Hero)
|
||||||
|
{
|
||||||
|
clu = Clu;
|
||||||
|
addPlayer(hero->tempOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCommanderLevelUpDialogQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
assert(answer);
|
||||||
|
logGlobal->trace("Completing commander level-up query. Commander of hero %s gains skill %s", hero->getObjectName(), answer.value());
|
||||||
|
gh->levelUpCommander(hero->commander, clu.skills[*answer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
|
||||||
|
{
|
||||||
|
objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory):
|
||||||
|
CGhQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
|
||||||
|
{
|
||||||
|
players.push_back(hero->tempOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHeroMovementQuery::onExposure(QueryPtr topQuery)
|
||||||
|
{
|
||||||
|
assert(players.size() == 1);
|
||||||
|
|
||||||
|
if(visitDestAfterVictory && hero->tempOwner == players[0]) //hero still alive, so he won with the guard
|
||||||
|
//TODO what if there were H4-like escape? we should also check pos
|
||||||
|
{
|
||||||
|
logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->getNameTranslated(), tmh.end.toString());
|
||||||
|
//finish movement
|
||||||
|
visitDestAfterVictory = false;
|
||||||
|
gh->visitObjectOnTile(*gh->getTile(hero->convertToVisitablePos(tmh.end)), hero);
|
||||||
|
}
|
||||||
|
|
||||||
|
owner->popIfTop(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHeroMovementQuery::onRemoval(PlayerColor color)
|
||||||
|
{
|
||||||
|
PlayerBlocked pb;
|
||||||
|
pb.player = color;
|
||||||
|
pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
|
||||||
|
pb.startOrEnd = PlayerBlocked::BLOCKADE_ENDED;
|
||||||
|
gh->sendAndApply(&pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHeroMovementQuery::onAdding(PlayerColor color)
|
||||||
|
{
|
||||||
|
PlayerBlocked pb;
|
||||||
|
pb.player = color;
|
||||||
|
pb.reason = PlayerBlocked::ONGOING_MOVEMENT;
|
||||||
|
pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
|
||||||
|
gh->sendAndApply(&pb);
|
||||||
|
}
|
103
server/queries/MapQueries.h
Normal file
103
server/queries/MapQueries.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* MapQueries.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 "CQuery.h"
|
||||||
|
|
||||||
|
#include "../../lib/NetPacks.h"
|
||||||
|
|
||||||
|
//Created when hero visits object.
|
||||||
|
//Removed when query above is resolved (or immediately after visit if no queries were created)
|
||||||
|
class CObjectVisitQuery : public CGhQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const CGObjectInstance *visitedObject;
|
||||||
|
const CGHeroInstance *visitingHero;
|
||||||
|
int3 tile; //may be different than hero pos -> eg. visit via teleport
|
||||||
|
bool removeObjectAfterVisit;
|
||||||
|
|
||||||
|
CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile);
|
||||||
|
|
||||||
|
virtual bool blocksPack(const CPack *pack) const override;
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
virtual void onExposure(QueryPtr topQuery) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Created when hero attempts move and something happens
|
||||||
|
//(not necessarily position change, could be just an object interaction).
|
||||||
|
class CHeroMovementQuery : public CGhQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TryMoveHero tmh;
|
||||||
|
bool visitDestAfterVictory; //if hero moved to guarded tile and it should be visited once guard is defeated
|
||||||
|
const CGHeroInstance *hero;
|
||||||
|
|
||||||
|
virtual void onExposure(QueryPtr topQuery) override;
|
||||||
|
|
||||||
|
CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory = false);
|
||||||
|
virtual void onAdding(PlayerColor color) override;
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CGarrisonDialogQuery : public CDialogQuery //used also for hero exchange dialogs
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::array<const CArmedInstance *,2> exchangingArmies;
|
||||||
|
|
||||||
|
CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down);
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
virtual bool blocksPack(const CPack *pack) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//yes/no and component selection dialogs
|
||||||
|
class CBlockingDialogQuery : public CDialogQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BlockingDialog bd; //copy of pack... debug purposes
|
||||||
|
|
||||||
|
CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog &bd);
|
||||||
|
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CTeleportDialogQuery : public CDialogQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TeleportDialog td; //copy of pack... debug purposes
|
||||||
|
|
||||||
|
CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog &td);
|
||||||
|
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CHeroLevelUpDialogQuery : public CDialogQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp &Hlu, const CGHeroInstance * Hero);
|
||||||
|
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
|
||||||
|
HeroLevelUp hlu;
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CCommanderLevelUpDialogQuery : public CDialogQuery
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp &Clu, const CGHeroInstance * Hero);
|
||||||
|
|
||||||
|
virtual void onRemoval(PlayerColor color) override;
|
||||||
|
virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
|
||||||
|
|
||||||
|
CommanderLevelUp clu;
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
};
|
129
server/queries/QueriesProcessor.cpp
Normal file
129
server/queries/QueriesProcessor.cpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* CQuery.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 "QueriesProcessor.h"
|
||||||
|
|
||||||
|
#include "CQuery.h"
|
||||||
|
|
||||||
|
boost::mutex QueriesProcessor::mx;
|
||||||
|
|
||||||
|
void QueriesProcessor::popQuery(PlayerColor player, QueryPtr query)
|
||||||
|
{
|
||||||
|
LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
|
||||||
|
if(topQuery(player) != query)
|
||||||
|
{
|
||||||
|
logGlobal->trace("Cannot remove, not a top!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queries[player] -= query;
|
||||||
|
auto nextQuery = topQuery(player);
|
||||||
|
|
||||||
|
query->onRemoval(player);
|
||||||
|
|
||||||
|
//Exposure on query below happens only if removal didn't trigger any new query
|
||||||
|
if(nextQuery && nextQuery == topQuery(player))
|
||||||
|
nextQuery->onExposure(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::popQuery(const CQuery &query)
|
||||||
|
{
|
||||||
|
LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
|
||||||
|
|
||||||
|
assert(query.players.size());
|
||||||
|
for(auto player : query.players)
|
||||||
|
{
|
||||||
|
auto top = topQuery(player);
|
||||||
|
if(top.get() == &query)
|
||||||
|
popQuery(top);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logGlobal->trace("Cannot remove query %s", query.toString());
|
||||||
|
logGlobal->trace("Queries found:");
|
||||||
|
for(auto q : queries[player])
|
||||||
|
{
|
||||||
|
logGlobal->trace(q->toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::popQuery(QueryPtr query)
|
||||||
|
{
|
||||||
|
for(auto player : query->players)
|
||||||
|
popQuery(player, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::addQuery(QueryPtr query)
|
||||||
|
{
|
||||||
|
for(auto player : query->players)
|
||||||
|
addQuery(player, query);
|
||||||
|
|
||||||
|
for(auto player : query->players)
|
||||||
|
query->onAdded(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::addQuery(PlayerColor player, QueryPtr query)
|
||||||
|
{
|
||||||
|
LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
|
||||||
|
query->onAdding(player);
|
||||||
|
queries[player].push_back(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryPtr QueriesProcessor::topQuery(PlayerColor player)
|
||||||
|
{
|
||||||
|
return vstd::backOrNull(queries[player]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::popIfTop(QueryPtr query)
|
||||||
|
{
|
||||||
|
LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
|
||||||
|
if(!query)
|
||||||
|
logGlobal->error("The query is nullptr! Ignoring.");
|
||||||
|
|
||||||
|
popIfTop(*query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueriesProcessor::popIfTop(const CQuery & query)
|
||||||
|
{
|
||||||
|
for(PlayerColor color : query.players)
|
||||||
|
if(topQuery(color).get() == &query)
|
||||||
|
popQuery(color, topQuery(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const CQuery>> QueriesProcessor::allQueries() const
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<const CQuery>> ret;
|
||||||
|
for(auto & playerQueries : queries)
|
||||||
|
for(auto & query : playerQueries.second)
|
||||||
|
ret.push_back(query);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<QueryPtr> QueriesProcessor::allQueries()
|
||||||
|
{
|
||||||
|
//TODO code duplication with const function :(
|
||||||
|
std::vector<QueryPtr> ret;
|
||||||
|
for(auto & playerQueries : queries)
|
||||||
|
for(auto & query : playerQueries.second)
|
||||||
|
ret.push_back(query);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryPtr QueriesProcessor::getQuery(QueryID queryID)
|
||||||
|
{
|
||||||
|
for(auto & playerQueries : queries)
|
||||||
|
for(auto & query : playerQueries.second)
|
||||||
|
if(query->queryID == queryID)
|
||||||
|
return query;
|
||||||
|
return nullptr;
|
||||||
|
}
|
40
server/queries/QueriesProcessor.h
Normal file
40
server/queries/QueriesProcessor.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* QueriesProcessor.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"
|
||||||
|
|
||||||
|
class CQuery;
|
||||||
|
using QueryPtr = std::shared_ptr<CQuery>;
|
||||||
|
|
||||||
|
class QueriesProcessor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
void addQuery(PlayerColor player, QueryPtr query);
|
||||||
|
void popQuery(PlayerColor player, QueryPtr query);
|
||||||
|
|
||||||
|
std::map<PlayerColor, std::vector<QueryPtr>> queries; //player => stack of queries
|
||||||
|
|
||||||
|
public:
|
||||||
|
static boost::mutex mx;
|
||||||
|
|
||||||
|
void addQuery(QueryPtr query);
|
||||||
|
void popQuery(const CQuery &query);
|
||||||
|
void popQuery(QueryPtr query);
|
||||||
|
void popIfTop(const CQuery &query); //removes this query if it is at the top (otherwise, do nothing)
|
||||||
|
void popIfTop(QueryPtr query); //removes this query if it is at the top (otherwise, do nothing)
|
||||||
|
|
||||||
|
QueryPtr topQuery(PlayerColor player);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<const CQuery>> allQueries() const;
|
||||||
|
std::vector<QueryPtr> allQueries();
|
||||||
|
QueryPtr getQuery(QueryID queryID);
|
||||||
|
//void removeQuery
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user