mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-14 02:33:51 +02:00
Merge pull request #2579 from IvanSavenko/server_battle_processor
(develop) Refactoring of server-side battles code
This commit is contained in:
commit
78780bb20c
@ -18,6 +18,7 @@
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
@ -283,20 +284,11 @@ void CBattleAI::activeStack( const CStack * stack )
|
||||
return;
|
||||
}
|
||||
|
||||
attemptCastingSpell();
|
||||
if (attemptCastingSpell())
|
||||
return;
|
||||
|
||||
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())
|
||||
{
|
||||
cb->battleMakeUnitAction(*action);
|
||||
@ -476,14 +468,14 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
|
||||
return attack;
|
||||
}
|
||||
|
||||
void CBattleAI::attemptCastingSpell()
|
||||
bool CBattleAI::attemptCastingSpell()
|
||||
{
|
||||
auto hero = cb->battleGetMyHero();
|
||||
if(!hero)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
|
||||
return;
|
||||
return false;
|
||||
|
||||
LOGL("Casting spells sounds like fun. Let's see...");
|
||||
//Get all spells we can cast
|
||||
@ -522,7 +514,7 @@ void CBattleAI::attemptCastingSpell()
|
||||
}
|
||||
LOGFL("Found %d spell-target combinations.", possibleCasts.size());
|
||||
if(possibleCasts.empty())
|
||||
return;
|
||||
return false;
|
||||
|
||||
using ValueMap = PossibleSpellcast::ValueMap;
|
||||
|
||||
@ -657,7 +649,7 @@ void CBattleAI::attemptCastingSpell()
|
||||
if(battleIsFinishedOpt)
|
||||
{
|
||||
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);
|
||||
BattleAction spellcast;
|
||||
spellcast.actionType = EActionType::HERO_SPELL;
|
||||
spellcast.actionSubtype = castToPerform.spell->id;
|
||||
spellcast.spell = castToPerform.spell->getId();
|
||||
spellcast.setTarget(castToPerform.dest);
|
||||
spellcast.side = side;
|
||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||
cb->battleMakeSpellAction(spellcast);
|
||||
movesSkippedByDefense = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
|
||||
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
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
|
||||
void CEmptyAI::saveGame(BinarySerializer & h, const int version)
|
||||
{
|
||||
@ -73,3 +74,8 @@ void CEmptyAI::showMapObjectSelectDialog(QueryID askID, const Component & icon,
|
||||
{
|
||||
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 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;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||
};
|
||||
|
||||
#define NAME "EmptyAI 0.1"
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
|
||||
static std::shared_ptr<CBattleCallback> cbc;
|
||||
|
||||
|
@ -2890,4 +2890,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
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 battleEnd(const BattleResult * br, QueryID queryID) override;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||
|
||||
void makeTurn();
|
||||
void mainLoop();
|
||||
|
@ -206,7 +206,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
||||
{
|
||||
assert(action.actionType == EActionType::HERO_SPELL);
|
||||
MakeCustomAction mca(action);
|
||||
MakeAction mca(action);
|
||||
sendRequest(&mca);
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
|
||||
destinationTeleportPos = int3(-1);
|
||||
GH.defActionsDef = 0;
|
||||
LOCPLINT = this;
|
||||
curAction = nullptr;
|
||||
playerID=Player;
|
||||
human=true;
|
||||
battleInt = nullptr;
|
||||
@ -769,8 +768,7 @@ void CPlayerInterface::actionStarted(const BattleAction &action)
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
curAction = new BattleAction(action);
|
||||
battleInt->startAction(curAction);
|
||||
battleInt->startAction(action);
|
||||
}
|
||||
|
||||
void CPlayerInterface::actionFinished(const BattleAction &action)
|
||||
@ -778,9 +776,7 @@ void CPlayerInterface::actionFinished(const BattleAction &action)
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
battleInt->endAction(curAction);
|
||||
delete curAction;
|
||||
curAction = nullptr;
|
||||
battleInt->endAction(action);
|
||||
}
|
||||
|
||||
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;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
assert(curAction);
|
||||
|
||||
StackAttackInfo info;
|
||||
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
|
||||
info.defender = nullptr;
|
||||
@ -2110,3 +2104,8 @@ void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectP
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
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;
|
||||
|
||||
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
|
||||
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 battleGateStateChanged(const EGateState state) override;
|
||||
void yourTacticPhase(int distance) override;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
|
||||
|
||||
public: // public interface for use by client via LOCPLINT access
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <vcmi/Environment.h>
|
||||
|
||||
#include "../lib/IGameCallback.h"
|
||||
#include "../lib/battle/BattleAction.h"
|
||||
#include "../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -25,6 +24,7 @@ class CBattleGameInterface;
|
||||
class CGameInterface;
|
||||
class BinaryDeserializer;
|
||||
class BinarySerializer;
|
||||
class BattleAction;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
|
||||
@ -118,7 +118,7 @@ public:
|
||||
|
||||
std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
|
||||
|
||||
std::optional<BattleAction> curbaction;
|
||||
std::unique_ptr<BattleAction> currentBattleAction;
|
||||
|
||||
CClient();
|
||||
~CClient();
|
||||
|
@ -784,7 +784,7 @@ void ApplyClientNetPackVisitor::visitBattleAttack(BattleAttack & 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);
|
||||
}
|
||||
|
||||
@ -830,8 +830,8 @@ void ApplyClientNetPackVisitor::visitCatapultAttack(CatapultAttack & pack)
|
||||
|
||||
void ApplyClientNetPackVisitor::visitEndAction(EndAction & pack)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionFinished, *cl.curbaction);
|
||||
cl.curbaction.reset();
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionFinished, *cl.currentBattleAction);
|
||||
cl.currentBattleAction.reset();
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
|
||||
|
@ -286,7 +286,7 @@ void BattleActionsController::castThisSpell(SpellID spellID)
|
||||
{
|
||||
heroSpellToCast = std::make_shared<BattleAction>();
|
||||
heroSpellToCast->actionType = EActionType::HERO_SPELL;
|
||||
heroSpellToCast->actionSubtype = spellID; //spell number
|
||||
heroSpellToCast->spell = spellID;
|
||||
heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
|
||||
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
|
||||
{
|
||||
if (heroSpellToCast)
|
||||
return SpellID(heroSpellToCast->actionSubtype).toSpell();
|
||||
return heroSpellToCast->spell.toSpell();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -94,13 +94,13 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
|
||||
owner.waitForAnimations();
|
||||
}
|
||||
|
||||
void BattleEffectsController::startAction(const BattleAction* action)
|
||||
void BattleEffectsController::startAction(const BattleAction & action)
|
||||
{
|
||||
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:
|
||||
owner.appendBattleLog(stack->formatGeneralMessage(136));
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
|
||||
BattleEffectsController(BattleInterface & owner);
|
||||
|
||||
void startAction(const BattleAction* action);
|
||||
void startAction(const BattleAction & action);
|
||||
|
||||
//displays custom effect on the battlefield
|
||||
void displayEffect(EBattleEffect effect, const BattleHex & destTile);
|
||||
|
@ -234,7 +234,7 @@ void BattleInterface::newRound(int number)
|
||||
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;
|
||||
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.actionType = action;
|
||||
ba.aimToHex(tile);
|
||||
ba.actionSubtype = additional;
|
||||
ba.spell = spell;
|
||||
|
||||
sendCommand(ba, actor);
|
||||
}
|
||||
@ -567,12 +567,12 @@ bool BattleInterface::makingTurn() const
|
||||
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
|
||||
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
|
||||
activateStack();
|
||||
@ -585,7 +585,7 @@ void BattleInterface::endAction(const BattleAction* action)
|
||||
tacticNextStack(stack);
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
@ -594,15 +594,15 @@ void BattleInterface::appendBattleLog(const std::string & 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();
|
||||
return;
|
||||
}
|
||||
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action.stackNumber);
|
||||
|
||||
if (stack)
|
||||
{
|
||||
@ -610,17 +610,17 @@ void BattleInterface::startAction(const BattleAction* action)
|
||||
}
|
||||
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);
|
||||
|
||||
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
|
||||
if(action.actionType == EActionType::HERO_SPELL) //when hero casts spell
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ public:
|
||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||
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);
|
||||
|
||||
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
|
||||
@ -188,7 +188,7 @@ public:
|
||||
void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
|
||||
|
||||
//call-ins
|
||||
void startAction(const BattleAction* action);
|
||||
void startAction(const BattleAction & action);
|
||||
void stackReset(const CStack * stack);
|
||||
void stackAdded(const CStack * stack); //new stack appeared on battlefield
|
||||
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 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 obstacleRemoved(const std::vector<ObstacleChanges> & obstacles);
|
||||
|
@ -567,12 +567,12 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
||||
int text = 304;
|
||||
switch(br.result)
|
||||
{
|
||||
case BattleResult::NORMAL:
|
||||
case EBattleResult::NORMAL:
|
||||
break;
|
||||
case BattleResult::ESCAPE:
|
||||
case EBattleResult::ESCAPE:
|
||||
text = 303;
|
||||
break;
|
||||
case BattleResult::SURRENDER:
|
||||
case EBattleResult::SURRENDER:
|
||||
text = 302;
|
||||
break;
|
||||
default:
|
||||
@ -601,14 +601,14 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
||||
std::string videoName = "LBSTART.BIK";
|
||||
switch(br.result)
|
||||
{
|
||||
case BattleResult::NORMAL:
|
||||
case EBattleResult::NORMAL:
|
||||
break;
|
||||
case BattleResult::ESCAPE:
|
||||
case EBattleResult::ESCAPE:
|
||||
musicName = "Music/Retreat Battle";
|
||||
videoName = "RTSTART.BIK";
|
||||
text = 310;
|
||||
break;
|
||||
case BattleResult::SURRENDER:
|
||||
case EBattleResult::SURRENDER:
|
||||
musicName = "Music/Surrender Battle";
|
||||
videoName = "SURRENDER.BIK";
|
||||
text = 309;
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
@ -398,15 +399,7 @@ void BattleStacksController::addNewAnim(BattleAnimation *anim)
|
||||
void BattleStacksController::stackRemoved(uint32_t 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);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
||||
@ -663,7 +656,7 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleStacksController::endAction(const BattleAction* action)
|
||||
void BattleStacksController::endAction(const BattleAction & action)
|
||||
{
|
||||
owner.checkForAnimations();
|
||||
|
||||
@ -688,7 +681,7 @@ void BattleStacksController::endAction(const BattleAction* action)
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
||||
void BattleStacksController::startAction(const BattleAction* action)
|
||||
void BattleStacksController::startAction(const BattleAction & action)
|
||||
{
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
@ -115,8 +115,8 @@ public:
|
||||
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 startAction(const BattleAction* action);
|
||||
void endAction(const BattleAction* action);
|
||||
void startAction(const BattleAction & action);
|
||||
void endAction(const BattleAction & action);
|
||||
|
||||
void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "battle/BattleAction.h"
|
||||
#include "IGameEventsReceiver.h"
|
||||
|
||||
#include "spells/ViewSpellInt.h"
|
||||
@ -36,6 +35,7 @@ class CCreatureSet;
|
||||
class CArmedInstance;
|
||||
class IShipyard;
|
||||
class IMarket;
|
||||
class BattleAction;
|
||||
struct BattleResult;
|
||||
struct BattleAttack;
|
||||
struct BattleStackAttacked;
|
||||
@ -107,10 +107,7 @@ public:
|
||||
|
||||
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain){};
|
||||
|
||||
virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) = 0;
|
||||
|
||||
virtual void saveGame(BinarySerializer & 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 =
|
||||
{
|
||||
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
||||
{EActionType::INVALID, "Invalid"},
|
||||
{EActionType::NO_ACTION, "No action"},
|
||||
{EActionType::HERO_SPELL, "Hero spell"},
|
||||
{EActionType::WALK, "Walk"},
|
||||
|
@ -998,20 +998,21 @@ namespace Date
|
||||
};
|
||||
}
|
||||
|
||||
enum class EActionType : int32_t
|
||||
enum class EActionType : int8_t
|
||||
{
|
||||
CANCEL = -3,
|
||||
END_TACTIC_PHASE = -2,
|
||||
INVALID = -1,
|
||||
NO_ACTION = 0,
|
||||
HERO_SPELL,
|
||||
WALK,
|
||||
DEFEND,
|
||||
NO_ACTION,
|
||||
|
||||
END_TACTIC_PHASE,
|
||||
RETREAT,
|
||||
SURRENDER,
|
||||
|
||||
HERO_SPELL,
|
||||
|
||||
WALK,
|
||||
WAIT,
|
||||
DEFEND,
|
||||
WALK_AND_ATTACK,
|
||||
SHOOT,
|
||||
WAIT,
|
||||
CATAPULT,
|
||||
MONSTER_SPELL,
|
||||
BAD_MORALE,
|
||||
@ -1379,6 +1380,13 @@ enum class EHealPower : ui8
|
||||
PERMANENT
|
||||
};
|
||||
|
||||
enum class EBattleResult : int8_t
|
||||
{
|
||||
NORMAL = 0,
|
||||
ESCAPE = 1,
|
||||
SURRENDER = 2
|
||||
};
|
||||
|
||||
// Typedef declarations
|
||||
using TExpType = si64;
|
||||
using TQuantity = si32;
|
||||
|
@ -134,7 +134,6 @@ public:
|
||||
virtual void visitBuildBoat(BuildBoat & pack) {}
|
||||
virtual void visitQueryReply(QueryReply & pack) {}
|
||||
virtual void visitMakeAction(MakeAction & pack) {}
|
||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) {}
|
||||
virtual void visitDigWithHero(DigWithHero & pack) {}
|
||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) {}
|
||||
virtual void visitSaveGame(SaveGame & pack) {}
|
||||
|
@ -1524,11 +1524,9 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
|
||||
|
||||
struct DLL_LINKAGE BattleResult : public Query
|
||||
{
|
||||
enum EResult { NORMAL = 0, ESCAPE = 1, SURRENDER = 2 };
|
||||
|
||||
void applyFirstCl(CClient * cl);
|
||||
|
||||
EResult result = NORMAL;
|
||||
EBattleResult result = EBattleResult::NORMAL;
|
||||
ui8 winner = 2; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
|
||||
std::map<ui32, si32> casualties[2]; //first => casualties of attackers - map crid => number
|
||||
TExpType exp[2] = {0, 0}; //exp for attacker and defender
|
||||
@ -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
|
||||
{
|
||||
ObjectInstanceID id; //digging hero id
|
||||
|
@ -638,11 +638,6 @@ void MakeAction::visitTyped(ICPackVisitor & visitor)
|
||||
visitor.visitMakeAction(*this);
|
||||
}
|
||||
|
||||
void MakeCustomAction::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitMakeCustomAction(*this);
|
||||
}
|
||||
|
||||
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitDigWithHero(*this);
|
||||
@ -2289,34 +2284,35 @@ void StartAction::applyGs(CGameState *gs)
|
||||
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
|
||||
|
||||
switch(ba.actionType)
|
||||
{
|
||||
case EActionType::DEFEND:
|
||||
st->waiting = false;
|
||||
st->defending = true;
|
||||
st->defendingAnim = true;
|
||||
break;
|
||||
case EActionType::WAIT:
|
||||
st->defendingAnim = false;
|
||||
st->waiting = true;
|
||||
st->waitedThisTurn = true;
|
||||
break;
|
||||
case EActionType::HERO_SPELL: //no change in current stack state
|
||||
break;
|
||||
default: //any active stack action - attack, catapult, heal, spell...
|
||||
st->waiting = false;
|
||||
st->defendingAnim = false;
|
||||
st->movedThisRound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gs->curB->sides[ba.side].usedSpellsHistory.emplace_back(ba.actionSubtype);
|
||||
}
|
||||
|
||||
switch(ba.actionType)
|
||||
{
|
||||
case EActionType::DEFEND:
|
||||
st->waiting = false;
|
||||
st->defending = true;
|
||||
st->defendingAnim = true;
|
||||
break;
|
||||
case EActionType::WAIT:
|
||||
st->defendingAnim = false;
|
||||
st->waiting = true;
|
||||
st->waitedThisTurn = true;
|
||||
break;
|
||||
case EActionType::HERO_SPELL: //no change in current stack state
|
||||
break;
|
||||
default: //any active stack action - attack, catapult, heal, spell...
|
||||
st->waiting = false;
|
||||
st->defendingAnim = false;
|
||||
st->movedThisRound = true;
|
||||
break;
|
||||
if(ba.actionType == EActionType::HERO_SPELL)
|
||||
gs->curB->sides[ba.side].usedSpellsHistory.push_back(ba.spell);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,7 @@ static const int32_t INVALID_UNIT_ID = -1000;
|
||||
BattleAction::BattleAction():
|
||||
side(-1),
|
||||
stackNumber(-1),
|
||||
actionType(EActionType::INVALID),
|
||||
actionSubtype(-1)
|
||||
actionType(EActionType::NO_ACTION)
|
||||
{
|
||||
}
|
||||
|
||||
@ -80,7 +79,7 @@ BattleAction BattleAction::makeCreatureSpellcast(const battle::Unit * stack, con
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.actionType = EActionType::MONSTER_SPELL;
|
||||
ba.actionSubtype = spellID;
|
||||
ba.spell = spellID;
|
||||
ba.setTarget(target);
|
||||
ba.side = stack->unitSide();
|
||||
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}}");
|
||||
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();
|
||||
}
|
||||
|
||||
@ -183,7 +182,7 @@ battle::Target BattleAction::getTarget(const CBattleInfoCallback * cb) const
|
||||
|
||||
void BattleAction::setTarget(const battle::Target & target_)
|
||||
{
|
||||
target.clear();
|
||||
target.clear();
|
||||
for(const auto & destination : target_)
|
||||
{
|
||||
if(destination.unitValue == nullptr)
|
||||
@ -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)
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
ui32 stackNumber; //stack ID, -1 left hero, -2 right hero,
|
||||
EActionType actionType; //use ActionType enum for values
|
||||
|
||||
si32 actionSubtype;
|
||||
SpellID spell;
|
||||
|
||||
BattleAction();
|
||||
|
||||
@ -43,6 +43,9 @@ public:
|
||||
static BattleAction makeRetreat(ui8 side);
|
||||
static BattleAction makeSurrender(ui8 side);
|
||||
|
||||
bool isTacticsAction() const;
|
||||
bool isUnitAction() const;
|
||||
bool isSpellAction() const;
|
||||
std::string toString() const;
|
||||
|
||||
void aimToHex(const BattleHex & destination);
|
||||
@ -56,7 +59,7 @@ public:
|
||||
h & side;
|
||||
h & stackNumber;
|
||||
h & actionType;
|
||||
h & actionSubtype;
|
||||
h & spell;
|
||||
h & target;
|
||||
}
|
||||
private:
|
||||
|
@ -352,7 +352,6 @@ void registerTypesServerPacks(Serializer &s)
|
||||
s.template registerType<CPackForServer, BuildBoat>();
|
||||
s.template registerType<CPackForServer, QueryReply>();
|
||||
s.template registerType<CPackForServer, MakeAction>();
|
||||
s.template registerType<CPackForServer, MakeCustomAction>();
|
||||
s.template registerType<CPackForServer, DigWithHero>();
|
||||
s.template registerType<CPackForServer, CastAdvSpell>();
|
||||
s.template registerType<CPackForServer, CastleTeleportHero>();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,29 +11,25 @@
|
||||
|
||||
#include <vcmi/Environment.h>
|
||||
|
||||
#include "../lib/FunctionList.h"
|
||||
#include "../lib/IGameCallback.h"
|
||||
#include "../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../lib/battle/BattleAction.h"
|
||||
#include "../lib/ScriptHandler.h"
|
||||
#include "CQuery.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CGameState;
|
||||
struct StartInfo;
|
||||
struct BattleResult;
|
||||
struct SideInBattle;
|
||||
struct BattleAttack;
|
||||
struct BattleStackAttacked;
|
||||
class IMarket;
|
||||
class SpellCastEnvironment;
|
||||
class CConnection;
|
||||
class CCommanderInstance;
|
||||
class EVictoryLossCheckResult;
|
||||
|
||||
struct CPack;
|
||||
struct Query;
|
||||
struct CPackForServer;
|
||||
struct NewTurn;
|
||||
struct CGarrisonOperationPack;
|
||||
struct SetResources;
|
||||
struct NewStructures;
|
||||
class CGHeroInstance;
|
||||
class IMarket;
|
||||
|
||||
class SpellCastEnvironment;
|
||||
|
||||
#if SCRIPTING_ENABLED
|
||||
namespace scripting
|
||||
@ -42,16 +38,17 @@ namespace scripting
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
template<typename T> class CApplier;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class HeroPoolProcessor;
|
||||
class CGameHandler;
|
||||
class CVCMIServer;
|
||||
class CBaseForGHApply;
|
||||
class PlayerMessageProcessor;
|
||||
class BattleProcessor;
|
||||
class QueriesProcessor;
|
||||
class CObjectVisitQuery;
|
||||
|
||||
struct PlayerStatus
|
||||
{
|
||||
@ -80,33 +77,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
|
||||
{
|
||||
CVCMIServer * lobby;
|
||||
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
|
||||
std::unique_ptr<boost::thread> battleThread;
|
||||
|
||||
public:
|
||||
boost::recursive_mutex battleActionMutex;
|
||||
using CCallbackBase::setBattle;
|
||||
|
||||
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
|
||||
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
|
||||
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
|
||||
@ -120,7 +102,7 @@ public:
|
||||
//queries stuff
|
||||
boost::recursive_mutex gsm;
|
||||
ui32 QID;
|
||||
Queries queries;
|
||||
|
||||
|
||||
SpellCastEnvironment * spellEnv;
|
||||
|
||||
@ -135,26 +117,6 @@ public:
|
||||
bool isBlockedByQueries(const CPack *pack, PlayerColor player);
|
||||
bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
|
||||
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(CVCMIServer * lobby);
|
||||
@ -242,14 +204,6 @@ public:
|
||||
PlayerColor getPlayerAt(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 buildBoat( ObjectInstanceID objid, PlayerColor player );
|
||||
bool setFormation( ObjectInstanceID hid, ui8 formation );
|
||||
@ -284,7 +238,6 @@ public:
|
||||
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 objectVisitEnded(const CObjectVisitQuery &query);
|
||||
void engageIntoBattle( PlayerColor player );
|
||||
bool dig(const CGHeroInstance *h);
|
||||
void moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging);
|
||||
|
||||
@ -292,7 +245,7 @@ public:
|
||||
{
|
||||
h & QID;
|
||||
h & states;
|
||||
h & finishingBattle;
|
||||
h & battles;
|
||||
h & heroPool;
|
||||
h & getRandomGenerator();
|
||||
h & playerMessages;
|
||||
@ -324,39 +277,8 @@ public:
|
||||
void throwAndComplain(CPackForServer * pack, std::string txt);
|
||||
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 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);
|
||||
void spawnWanderingMonsters(CreatureID creatureID);
|
||||
|
||||
@ -384,8 +306,6 @@ private:
|
||||
void reinitScripting();
|
||||
void deserializationFix();
|
||||
|
||||
|
||||
void makeStackDoNothing(const CStack * next);
|
||||
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
|
||||
|
||||
const std::string complainNoCreatures;
|
||||
|
@ -1,11 +1,21 @@
|
||||
set(server_SRCS
|
||||
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
|
||||
HeroPoolProcessor.cpp
|
||||
PlayerMessageProcessor.cpp
|
||||
ServerSpellCastEnvironment.cpp
|
||||
CQuery.cpp
|
||||
CVCMIServer.cpp
|
||||
NetPacksServer.cpp
|
||||
NetPacksLobbyServer.cpp
|
||||
@ -14,11 +24,21 @@ set(server_SRCS
|
||||
set(server_HEADERS
|
||||
StdInc.h
|
||||
|
||||
battles/BattleActionProcessor.h
|
||||
battles/BattleFlowProcessor.h
|
||||
battles/BattleProcessor.h
|
||||
battles/BattleResultProcessor.h
|
||||
|
||||
queries/BattleQueries.h
|
||||
queries/CQuery.h
|
||||
queries/MapQueries.h
|
||||
queries/QueriesProcessor.h
|
||||
|
||||
processors/HeroPoolProcessor.h
|
||||
processors/PlayerMessageProcessor.h
|
||||
|
||||
CGameHandler.h
|
||||
HeroPoolProcessor.h
|
||||
PlayerMessageProcessor.h
|
||||
ServerSpellCastEnvironment.h
|
||||
CQuery.h
|
||||
CVCMIServer.h
|
||||
LobbyNetPackVisitors.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/VCMIDirs.h"
|
||||
#include "CGameHandler.h"
|
||||
#include "PlayerMessageProcessor.h"
|
||||
#include "processors/PlayerMessageProcessor.h"
|
||||
#include "../lib/mapping/CMapInfo.h"
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "../lib/logging/CBasicLogConfigurator.h"
|
||||
|
@ -11,8 +11,10 @@
|
||||
#include "ServerNetPackVisitors.h"
|
||||
|
||||
#include "CGameHandler.h"
|
||||
#include "HeroPoolProcessor.h"
|
||||
#include "PlayerMessageProcessor.h"
|
||||
#include "battles/BattleProcessor.h"
|
||||
#include "processors/HeroPoolProcessor.h"
|
||||
#include "processors/PlayerMessageProcessor.h"
|
||||
#include "queries/QueriesProcessor.h"
|
||||
|
||||
#include "../lib/IGameCallback.h"
|
||||
#include "../lib/mapObjects/CGTownInstance.h"
|
||||
@ -47,7 +49,7 @@ void ApplyGhNetPackVisitor::visitEndTurn(EndTurn & pack)
|
||||
}
|
||||
|
||||
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.states.setFlag(gs.currentPlayer, &PlayerStatus::makingTurn, false);
|
||||
@ -280,52 +282,10 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & 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;
|
||||
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);
|
||||
result = gh.battles->makePlayerBattleAction(pack.player, pack.ba);
|
||||
}
|
||||
|
||||
void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
|
||||
|
@ -55,8 +55,7 @@ public:
|
||||
virtual void visitBuildBoat(BuildBoat & pack) override;
|
||||
virtual void visitQueryReply(QueryReply & pack) override;
|
||||
virtual void visitMakeAction(MakeAction & pack) override;
|
||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) override;
|
||||
virtual void visitDigWithHero(DigWithHero & pack) override;
|
||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
||||
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
||||
};
|
||||
};
|
||||
|
@ -8,10 +8,15 @@
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "../lib/gameState/CGameState.h"
|
||||
#include "CGameHandler.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(CGameHandler * 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)
|
||||
{
|
||||
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;
|
||||
gh->queries.addQuery(query);
|
||||
gh->queries->addQuery(query);
|
||||
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 "HeroPoolProcessor.h"
|
||||
|
||||
#include "CGameHandler.h"
|
||||
#include "../CGameHandler.h"
|
||||
|
||||
#include "../lib/CHeroHandler.h"
|
||||
#include "../lib/CPlayerState.h"
|
||||
#include "../lib/GameSettings.h"
|
||||
#include "../lib/NetPacks.h"
|
||||
#include "../lib/StartInfo.h"
|
||||
#include "../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../lib/gameState/CGameState.h"
|
||||
#include "../lib/gameState/TavernHeroesPool.h"
|
||||
#include "../lib/gameState/TavernSlot.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/CPlayerState.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/gameState/TavernHeroesPool.h"
|
||||
#include "../../lib/gameState/TavernSlot.h"
|
||||
|
||||
HeroPoolProcessor::HeroPoolProcessor()
|
||||
: gameHandler(nullptr)
|
@ -10,18 +10,19 @@
|
||||
#include "StdInc.h"
|
||||
#include "PlayerMessageProcessor.h"
|
||||
|
||||
#include "CGameHandler.h"
|
||||
#include "CVCMIServer.h"
|
||||
#include "../CGameHandler.h"
|
||||
#include "../CVCMIServer.h"
|
||||
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/CGeneralTextHandler.h"
|
||||
#include "../lib/CHeroHandler.h"
|
||||
#include "../lib/CPlayerState.h"
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "../lib/NetPacks.h"
|
||||
#include "../lib/StartInfo.h"
|
||||
#include "../lib/gameState/CGameState.h"
|
||||
#include "../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/serializer/Connection.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/modding/IdentifierStorage.h"
|
||||
#include "../../lib/CPlayerState.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../lib/modding/IdentifierStorage.h"
|
||||
#include "../lib/modding/ModScope.h"
|
||||
|
||||
@ -212,15 +213,27 @@ void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroI
|
||||
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)
|
||||
return;
|
||||
|
||||
for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
|
||||
if (!words.empty())
|
||||
{
|
||||
if(VLC->arth->objects[g]->canBePutAt(hero))
|
||||
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
|
||||
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
|
||||
{
|
||||
if(VLC->arth->objects[g]->canBePutAt(hero))
|
||||
gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +446,7 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
|
||||
const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); };
|
||||
const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); };
|
||||
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 & doCheatExperience = [&]() { cheatExperience(player, hero, words); };
|
||||
const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); };
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CGHeroInstance;
|
||||
@ -31,7 +31,7 @@ class PlayerMessageProcessor
|
||||
void cheatBuildTown(PlayerColor player, const CGTownInstance * town);
|
||||
void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
|
||||
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 cheatExperience(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…
Reference in New Issue
Block a user