diff --git a/AI/EmptyAI/EmptyAI.vcxproj b/AI/EmptyAI/EmptyAI.vcxproj index 19c4f8e62..e80f7a21f 100644 --- a/AI/EmptyAI/EmptyAI.vcxproj +++ b/AI/EmptyAI/EmptyAI.vcxproj @@ -68,40 +68,38 @@ + + + + - ..\..\..\AI + $(VCMI_Out)\AI\ $(IncludePath) $(LibraryPath) - $(SolutionDir)\AI\ - $(IncludePath) - $(LibraryPath) + $(VCMI_Out)\AI\ - $(SolutionDir)$(Configuration)\bin\AI\ - $(IncludePath) - $(LibraryPath) + $(VCMI_Out)\AI\ - $(SolutionDir)$(Configuration)\bin\AI\ - $(IncludePath) - $(LibraryPath) + $(VCMI_Out)\AI\ @@ -116,7 +114,6 @@ true VCMI_lib.lib;%(AdditionalDependencies) - ../..;../../../libs;../../..;%(AdditionalLibraryDirectories) $(OutDir)EmptyAI.dll @@ -132,7 +129,6 @@ true VCMI_lib.lib;%(AdditionalDependencies) - $(OutDir)..;%(AdditionalLibraryDirectories) $(OutDir)EmptyAI.dll @@ -146,13 +142,13 @@ Use StdInc.h _WINDLL;%(PreprocessorDefinitions) + /Zm130 %(AdditionalOptions) true true true VCMI_lib.lib;%(AdditionalDependencies) - G:\Programowanie\VCMI\RD;../../../libs;../../;E:\vcmi\rep - assembla\trunk;E:\C++\lua bin;E:\C++\boost_1_43_0\lib;E:\C++\SDL_mixer-1.2.7\lib;E:\C++\SDL_ttf-2.0.8\lib;E:\C++\zlib 1.2.3 binaries\lib;E:\C++\SDL-1.2.11\devlibs - visual\SDL-1.2.11\lib;F:\C++\SDL_Image 1.2.5\SDL_image-1.2.5\lib;%(AdditionalLibraryDirectories) $(OutDir)EmptyAI.dll @@ -166,13 +162,13 @@ Use StdInc.h _WINDLL;%(PreprocessorDefinitions) + /Zm130 %(AdditionalOptions) true true true VCMI_lib.lib;%(AdditionalDependencies) - $(OutDir)..;%(AdditionalLibraryDirectories) $(OutDir)EmptyAI.dll diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 99ca58145..6bfe0bbf3 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -493,12 +493,15 @@ void VCAI::showThievesGuildWindow (const CGObjectInstance * obj) NET_EVENT_HANDLER; } -void VCAI::playerBlocked(int reason) +void VCAI::playerBlocked(int reason, bool start) { - LOG_TRACE_PARAMS(logAi, "reason '%i'", reason); + LOG_TRACE_PARAMS(logAi, "reason '%i', start '%i'", reason % start); NET_EVENT_HANDLER; - if (reason == PlayerBlocked::UPCOMING_BATTLE) + if (start && reason == PlayerBlocked::UPCOMING_BATTLE) status.setBattle(UPCOMING_BATTLE); + + if(reason == PlayerBlocked::ONGOING_MOVEMENT) + status.setMove(start); } void VCAI::showPuzzleMap() @@ -571,15 +574,17 @@ void VCAI::artifactDisassembled(const ArtifactLocation &al) void VCAI::heroVisit(const CGHeroInstance *visitor, const CGObjectInstance *visitedObj, bool start) { - LOG_TRACE_PARAMS(logAi, "start '%i'", start); + LOG_TRACE_PARAMS(logAi, "start '%i'; obj '%s'", start % (visitedObj ? visitedObj->hoverName : std::string("n/a"))); NET_EVENT_HANDLER; - if (start) + if(start) { markObjectVisited (visitedObj); erase_if_present(reservedObjs, visitedObj); //unreserve objects erase_if_present(reservedHeroesMap[visitor], visitedObj); completeGoal (CGoal(GET_OBJ).sethero(visitor)); //we don't need to visit in anymore } + + status.heroVisit(visitedObj, start); } void VCAI::availableArtifactsChanged(const CGBlackMarket *bm /*= nullptr*/) @@ -2629,6 +2634,7 @@ AIStatus::AIStatus() { battle = NO_BATTLE; havingTurn = false; + ongoingHeroMovement = false; } AIStatus::~AIStatus() @@ -2701,8 +2707,8 @@ void AIStatus::madeTurn() void AIStatus::waitTillFree() { boost::unique_lock lock(mx); - while(battle != NO_BATTLE || remainingQueries.size()) - cv.wait(lock); + while(battle != NO_BATTLE || remainingQueries.size() || objectsBeingVisited.size() || ongoingHeroMovement) + cv.timed_wait(lock, boost::posix_time::milliseconds(100)); } bool AIStatus::haveTurn() @@ -2738,6 +2744,26 @@ void AIStatus::receivedAnswerConfirmation(int answerRequestID, int result) } } +void AIStatus::heroVisit(const CGObjectInstance *obj, bool started) +{ + boost::unique_lock lock(mx); + if(started) + objectsBeingVisited.push_back(obj); + else + { + assert(objectsBeingVisited.size() == 1); + objectsBeingVisited.clear(); + } + cv.notify_all(); +} + +void AIStatus::setMove(bool ongoing) +{ + boost::unique_lock lock(mx); + ongoingHeroMovement = ongoing; + cv.notify_all(); +} + int3 whereToExplore(HeroPtr h) { //TODO it's stupid and ineffective, write sth better diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 6f6ed9c4f..38c14b81a 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -75,6 +75,8 @@ class AIStatus BattleState battle; std::map remainingQueries; std::map requestToQueryID; //IDs of answer-requests sent to server => query ids (so we can match answer confirmation from server to the query) + std::vector objectsBeingVisited; + bool ongoingHeroMovement; bool havingTurn; @@ -82,6 +84,7 @@ public: AIStatus(); ~AIStatus(); void setBattle(BattleState BS); + void setMove(bool ongoing); BattleState getBattle(); void addQuery(QueryID ID, std::string description); void removeQuery(QueryID ID); @@ -92,6 +95,7 @@ public: bool haveTurn(); void attemptedAnsweringQuery(QueryID queryID, int answerRequestID); void receivedAnswerConfirmation(int answerRequestID, int result); + void heroVisit(const CGObjectInstance *obj, bool started); template void serialize(Handler &h, const int version) @@ -327,7 +331,7 @@ public: virtual void artifactAssembled(const ArtifactLocation &al) override; virtual void showTavernWindow(const CGObjectInstance *townOrTavern) override; virtual void showThievesGuildWindow (const CGObjectInstance * obj) override; - virtual void playerBlocked(int reason) override; + virtual void playerBlocked(int reason, bool start) override; virtual void showPuzzleMap() override; virtual void showShipyardDialog(const IShipyard *obj) override; virtual void gameOver(PlayerColor player, bool victory) override; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 880985a95..b8ce4d916 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -601,6 +601,13 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID build castleInt->townlist->update(town); } +void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) +{ + //Don't wait for dialogs when we are non-active hot-seat player + if(LOCPLINT == this) + waitForAllDialogs(); +} + void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) { EVENT_HANDLER_CALLED_BY_CLIENT; @@ -990,6 +997,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, CComponent * comp void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector & components, int soundID, bool delComps) { + LOG_TRACE_PARAMS(logGlobal, "player=%s, text=%s, is LOCPLINT=%d", playerID % text % (this==LOCPLINT)); waitWhileDialog(); if (settings["session"]["autoSkip"].Bool() && !LOCPLINT->shiftPressed()) diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index d7661f9ca..5449f36db 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -203,6 +203,7 @@ public: void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect void battleStacksAttacked(const std::vector & bsa) override; + void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 74b32f07f..8bb53211a 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -246,7 +246,7 @@ void DisassembledArtifact::applyCl( CClient *cl ) void HeroVisit::applyCl( CClient *cl ) { assert(hero); - INTERFACE_CALL_IF_PRESENT(hero->tempOwner, heroVisit, hero, obj, starting); + INTERFACE_CALL_IF_PRESENT(player, heroVisit, hero, obj, starting); } void NewTurn::applyCl( CClient *cl ) @@ -600,6 +600,17 @@ void ExchangeDialog::applyCl(CClient *cl) INTERFACE_CALL_IF_PRESENT(heroes[0]->tempOwner, heroExchangeStarted, heroes[0]->id, heroes[1]->id, queryID); } +void BattleStart::applyFirstCl( CClient *cl ) +{ + //Cannot use the usual macro because curB is not set yet + CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[0].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, + info->tile, info->sides[0].hero, info->sides[1].hero); + CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, + info->tile, info->sides[0].hero, info->sides[1].hero); + BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, + info->tile, info->sides[0].hero, info->sides[1].hero); +} + void BattleStart::applyCl( CClient *cl ) { cl->battleStarted(info); @@ -780,7 +791,7 @@ void SystemMessage::applyCl( CClient *cl ) void PlayerBlocked::applyCl( CClient *cl ) { - INTERFACE_CALL_IF_PRESENT(player,playerBlocked,reason); + INTERFACE_CALL_IF_PRESENT(player,playerBlocked,reason, startOrEnd==BLOCKADE_STARTED); } void YourTurn::applyCl( CClient *cl ) diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 5cefa3f4e..d14d6c442 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -85,7 +85,9 @@ CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CSt : CBattleAnimation(owner), myAnim(owner->creAnims[stack->ID]), stack(stack) -{} +{ + assert(myAnim); +} void CAttackAnimation::nextFrame() { diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index e4d413468..99968101a 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -153,7 +153,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe //initializing armies this->army1 = army1; this->army2 = army2; - std::vector stacks = curInt->cb->battleGetAllStacks(); + std::vector stacks = curInt->cb->battleGetAllStacks(true); for(const CStack *s : stacks) { newStack(s); @@ -3652,7 +3652,7 @@ void CBattleInterface::showPiecesOfWall(SDL_Surface * to, std::vector piece const CStack *turret = nullptr; - for(auto & stack : curInt->cb->battleGetAllStacks()) + for(auto & stack : curInt->cb->battleGetAllStacks(true)) { if(stack->position == stackPos) { diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 3a47a42d0..90c1a7c92 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -177,11 +177,14 @@ bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const return false; } -TStacks CBattleInfoEssentials::battleGetAllStacks() const /*returns all stacks, alive or dead or undead or mechanical :) */ +TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const /*returns all stacks, alive or dead or undead or mechanical :) */ { TStacks ret; RETURN_IF_NOT_BATTLE(ret); boost::copy(getBattle()->stacks, std::back_inserter(ret)); + if(!includeTurrets) + vstd::erase_if(ret, [](const CStack *stack) { return stack->type->idNumber == CreatureID::ARROW_TOWERS; }); + return ret; } @@ -245,7 +248,7 @@ const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive) { RETURN_IF_NOT_BATTLE(nullptr); - for(auto s : battleGetAllStacks()) + for(auto s : battleGetAllStacks(true)) if(s->ID == ID && (!onlyAlive || s->alive())) return s; @@ -512,7 +515,7 @@ SpellID CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERa const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyAlive) const { RETURN_IF_NOT_BATTLE(nullptr); - for(auto s : battleGetAllStacks()) + for(auto s : battleGetAllStacks(true)) if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive())) return s; @@ -594,7 +597,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector &out, return; } - auto allStacks = battleGetAllStacks(); + auto allStacks = battleGetAllStacks(true); if(!vstd::contains_if(allStacks, [](const CStack *stack) { return stack->willMove(100000); })) //little evil, but 100000 should be enough for all effects to disappear { //No stack will be able to move, battle is over. @@ -602,7 +605,7 @@ void CBattleInfoCallback::battleGetStackQueue(std::vector &out, return; } - for(auto s : battleGetAllStacks()) + for(auto s : battleGetAllStacks(true)) { if((turn <= 0 && !s->willMove()) //we are considering current round and stack won't move || (turn > 0 && !s->canMove(turn)) //stack won't be able to move in later rounds diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 8eb55d115..c0d084850 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -168,7 +168,7 @@ public: ETerrainType battleTerrainType() const; BFieldType battleGetBattlefieldType() const; std::vector > battleGetAllObstacles(boost::optional perspective = boost::none) const; //returns all obstacles on the battlefield - TStacks battleGetAllStacks() const; //returns all stacks, alive or dead or undead or mechanical :) + TStacks battleGetAllStacks(bool includeTurrets = false) const; //returns all stacks, alive or dead or undead or mechanical :) bool battleHasNativeStack(ui8 side) const; si8 battleGetWallState(int partOfWall) const; //for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 004200bcf..2a585e051 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1369,6 +1369,10 @@ void CGameState::init(StartInfo * si) } } + //Early check for #1444-like problems + for(auto building : vti->builtBuildings) + assert(vti->town->buildings[building]); + //town events for(CCastleEvent &ev : vti->events) { diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 0f809b026..af518130f 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -2749,7 +2749,6 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu } } - bool CGVisitableOPH::wasVisited (const CGHeroInstance * h) const { return vstd::contains(visitors, h->id); diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index 068f40638..79448c5da 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -634,6 +634,17 @@ public: h & town & townAndVis; BONUS_TREE_DESERIALIZATION_FIX + + vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool + { + if(!town->buildings.count(building) || !town->buildings.at(building)) + { + logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s") + % name % pos % building; + return true; + } + return false; + }); } ////////////////////////////////////////////////////////////////////////// diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 693651e09..734987935 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -205,7 +205,7 @@ public: //Fix #1444 corrupted save while(auto badElem = vstd::tryFindIf(buildings, findNull)) { - std::cout << "#1444-like bug encountered, fixing buildings list by removing bogus entry " << badElem->first << " from " << faction->name << std::endl; + logGlobal->errorStream() << "#1444-like bug encountered in CTown::serialize, fixing buildings list by removing bogus entry " << badElem->first << " from " << faction->name; buildings.erase(badElem->first); } } diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index c2b5165eb..290ffa7b8 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -60,6 +60,7 @@ public: virtual void battleSpellCast(const BattleSpellCast *sc){}; virtual void battleStacksEffectsSet(const SetStackEffect & sse){};//called when a specific effect is set to stacks virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects + virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right virtual void battleStacksHealedRes(const std::vector > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom){}; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp virtual void battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned @@ -126,7 +127,7 @@ public: virtual void requestRealized(PackageApplied *pa){}; virtual void objectPropertyChanged(const SetObjectProperty * sop){}; //eg. mine has been flagged virtual void objectRemoved(const CGObjectInstance *obj){}; //eg. collected resource, picked artifact, beaten hero - virtual void playerBlocked(int reason){}; //reason: 0 - upcoming battle + virtual void playerBlocked(int reason, bool start){}; //reason: 0 - upcoming battle virtual void gameOver(PlayerColor player, bool victory){}; //player lost or won the game virtual void playerStartsTurn(PlayerColor player){}; virtual void showComp(const Component &comp, std::string message) {}; //display component in the advmapint infobox diff --git a/lib/NetPacks.h b/lib/NetPacks.h index ec2bcee53..6b8191a07 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -230,14 +230,16 @@ struct PlayerBlocked : public CPackForClient //96 PlayerBlocked(){type = 96;}; void applyCl(CClient *cl); - enum EReason { UPCOMING_BATTLE }; - + enum EReason { UPCOMING_BATTLE, ONGOING_MOVEMENT }; + enum EMode { BLOCKADE_STARTED, BLOCKADE_ENDED }; + EReason reason; + EMode startOrEnd; PlayerColor player; template void serialize(Handler &h, const int version) { - h & reason & player; + h & reason & startOrEnd & player; } }; @@ -1090,6 +1092,7 @@ struct HeroVisit : CPackForClient //531 { const CGHeroInstance *hero; const CGObjectInstance *obj; + PlayerColor player; //if hero was killed during the visit, its color is already reset bool starting; //false -> ending void applyCl(CClient *cl); @@ -1097,7 +1100,7 @@ struct HeroVisit : CPackForClient //531 template void serialize(Handler &h, const int version) { - h & hero & obj & starting; + h & hero & obj & player & starting; } }; @@ -1360,6 +1363,8 @@ struct BattleInfo; struct BattleStart : public CPackForClient//3000 { BattleStart(){type = 3000;}; + + void applyFirstCl(CClient *cl); void applyCl(CClient *cl); DLL_LINKAGE void applyGs(CGameState *gs); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5409c20f0..e87e82347 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -13,6 +13,7 @@ #include "CCreatureHandler.h" #include "CGameState.h" #include "BattleState.h" +#include "CTownHandler.h" #undef min #undef max @@ -448,6 +449,7 @@ DLL_LINKAGE void NewStructures::applyGs( CGameState *gs ) CGTownInstance *t = gs->getTown(tid); for(const auto & id : bid) { + assert(t->town->buildings[id]); t->builtBuildings.insert(id); } t->builded = builded; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 723fa18c2..1700ad645 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3253,13 +3253,18 @@ static EndAction end_action; bool CGameHandler::makeBattleAction( BattleAction &ba ) { - logGlobal->errorStream() << "\tMaking action of type " << ba.actionType; bool ok = true; - - + const CStack *stack = battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack + const CStack *destinationStack = ba.actionType == Battle::WALK_AND_ATTACK ? gs->curB->battleGetStackByPos(ba.additionalInfo) + : ba.actionType == Battle::SHOOT ? gs->curB->battleGetStackByPos(ba.destinationTile) + : nullptr; const bool isAboutActiveStack = stack && (stack == battleActiveStack()); + logGlobal->traceStream() << boost::format( + "Making action: type=%d; side=%d; stack=%s; dst=%s; additionalInfo=%d; stackAtDst=%s") + % ba.actionType % (int)ba.side % (stack ? stack->getName() : std::string("none")) + % ba.destinationTile % ba.additionalInfo % (destinationStack ? destinationStack->getName() : std::string("none")); switch(ba.actionType) { @@ -3370,8 +3375,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) StartAction start_action(ba); sendAndApply(&start_action); //start movement and attack - const CStack *stackAtEnd = gs->curB->battleGetStackByPos(ba.additionalInfo); - if(!stack || !stackAtEnd) + if(!stack || !destinationStack) { sendAndApply(&end_action); break; @@ -3380,7 +3384,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) BattleHex startingPos = stack->position; int distance = moveStack(ba.stackNumber, ba.destinationTile); - logGlobal->traceStream() << stack->nodeName() << " will attack " << stackAtEnd->nodeName(); + logGlobal->traceStream() << stack->nodeName() << " will attack " << destinationStack->nodeName(); if(stack->position != ba.destinationTile //we wasn't able to reach destination tile && !(stack->doubleWide() @@ -3396,12 +3400,12 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) break; } - if(stackAtEnd && stack->ID == stackAtEnd->ID) //we should just move, it will be handled by following check + if(destinationStack && stack->ID == destinationStack->ID) //we should just move, it will be handled by following check { - stackAtEnd = nullptr; + destinationStack = nullptr; } - if(!stackAtEnd) + if(!destinationStack) { complain(boost::str(boost::format("walk and attack error: no stack at additionalInfo tile (%d)!\n") % ba.additionalInfo)); ok = false; @@ -3409,7 +3413,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) break; } - if( !CStack::isMeleeAttackPossible(stack, stackAtEnd) ) + if( !CStack::isMeleeAttackPossible(stack, destinationStack) ) { complain("Attack cannot be performed!"); sendAndApply(&end_action); @@ -3426,10 +3430,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { if (stack && stack->alive() && //move can cause death, eg. by walking into the moat - stackAtEnd->alive()) + destinationStack->alive()) { BattleAttack bat; - prepareAttack(bat, stack, stackAtEnd, (i ? 0 : distance), ba.additionalInfo); //no distance travelled on second attack + prepareAttack(bat, stack, destinationStack, (i ? 0 : distance), ba.additionalInfo); //no distance travelled on second attack //prepareAttack(bat, stack, stackAtEnd, 0, ba.additionalInfo); handleAttackBeforeCasting(bat); //only before first attack sendAndApply(&bat); @@ -3437,13 +3441,13 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } //counterattack - if (stackAtEnd + if (destinationStack && !stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION) - && stackAtEnd->ableToRetaliate() + && destinationStack->ableToRetaliate() && stack->alive()) //attacker may have died (fire shield) { BattleAttack bat; - prepareAttack(bat, stackAtEnd, stack, 0, stack->position); + prepareAttack(bat, destinationStack, stack, 0, stack->position); bat.flags |= BattleAttack::COUNTER; sendAndApply(&bat); handleAfterAttackCasting(bat); @@ -3462,7 +3466,6 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } case Battle::SHOOT: { - const CStack *destStack= gs->curB->battleGetStackByPos(ba.destinationTile); if( !gs->curB->battleCanShoot(stack, ba.destinationTile) ) { complain("Cannot shoot!"); @@ -3475,7 +3478,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { BattleAttack bat; bat.flags |= BattleAttack::SHOT; - prepareAttack(bat, stack, destStack, 0, ba.destinationTile); + prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile); handleAttackBeforeCasting(bat); sendAndApply(&bat); handleAfterAttackCasting(bat); @@ -3485,14 +3488,14 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); - if( destStack->alive() + if( destinationStack->alive() && (stack->getCreature()->idNumber == CreatureID::BALLISTA) && (attackingHero->getSecSkillLevel(SecondarySkill::ARTILLERY) >= SecSkillLevel::ADVANCED) ) { BattleAttack bat2; bat2.flags |= BattleAttack::SHOT; - prepareAttack(bat2, stack, destStack, 0, ba.destinationTile); + prepareAttack(bat2, stack, destinationStack, 0, ba.destinationTile); sendAndApply(&bat2); } //allow more than one additional attack @@ -3503,13 +3506,13 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) { if( stack->alive() - && destStack->alive() + && destinationStack->alive() && stack->shots ) { BattleAttack bat; bat.flags |= BattleAttack::SHOT; - prepareAttack(bat, stack, destStack, 0, ba.destinationTile); + prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile); sendAndApply(&bat); handleAfterAttackCasting(bat); } @@ -4983,6 +4986,7 @@ void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInst HeroVisit hv; hv.obj = obj; hv.hero = h; + hv.player = h->tempOwner; hv.starting = true; sendAndApply(&hv); @@ -4996,6 +5000,7 @@ void CGameHandler::objectVisitEnded(const CObjectVisitQuery &query) logGlobal->traceStream() << query.visitingHero->nodeName() << " visit ends.\n"; HeroVisit hv; + hv.player = query.players.front(); hv.obj = nullptr; //not necessary, moreover may have been deleted in the meantime hv.hero = query.visitingHero; assert(hv.hero); @@ -5059,6 +5064,7 @@ void CGameHandler::engageIntoBattle( PlayerColor player ) PlayerBlocked pb; pb.player = player; pb.reason = PlayerBlocked::UPCOMING_BATTLE; + pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED; sendAndApply(&pb); } diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 1f530d39e..748635efc 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -86,6 +86,11 @@ void CQuery::onExposure(CGameHandler *gh, QueryPtr topQuery) gh->queries.popQuery(*this); } +void CQuery::onAdding(CGameHandler *gh, PlayerColor color) +{ + +} + CObjectVisitQuery::CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile) : visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false) { @@ -169,6 +174,7 @@ void Queries::addQuery(QueryPtr query) void Queries::addQuery(PlayerColor player, QueryPtr query) { LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query); + query->onAdding(gh, player); queries[player].push_back(query); } @@ -372,3 +378,21 @@ void CHeroMovementQuery::onExposure(CGameHandler *gh, QueryPtr topQuery) gh->queries.popIfTop(*this); } + +void CHeroMovementQuery::onRemoval(CGameHandler *gh, PlayerColor color) +{ + PlayerBlocked pb; + pb.player = color; + pb.reason = PlayerBlocked::ONGOING_MOVEMENT; + pb.startOrEnd = PlayerBlocked::BLOCKADE_ENDED; + gh->sendAndApply(&pb); +} + +void CHeroMovementQuery::onAdding(CGameHandler *gh, PlayerColor color) +{ + PlayerBlocked pb; + pb.player = color; + pb.reason = PlayerBlocked::ONGOING_MOVEMENT; + pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED; + gh->sendAndApply(&pb); +} diff --git a/server/CQuery.h b/server/CQuery.h index e2301052c..f04dacf9e 100644 --- a/server/CQuery.h +++ b/server/CQuery.h @@ -36,6 +36,7 @@ public: 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(CGameHandler *gh, PlayerColor color); //called just before query is pushed on stack virtual void onRemoval(CGameHandler *gh, PlayerColor color); //called after query is removed from stack virtual void onExposure(CGameHandler *gh, QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top) virtual std::string toString() const; @@ -98,6 +99,8 @@ public: virtual void onExposure(CGameHandler *gh, QueryPtr topQuery); CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory = false); + virtual void onAdding(CGameHandler *gh, PlayerColor color) override; + virtual void onRemoval(CGameHandler *gh, PlayerColor color) override; }; class CDialogQuery : public CQuery