From e8354908c3ebb66a50bf01516086f72da7ea1f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Sat, 20 Apr 2013 11:34:01 +0000 Subject: [PATCH] Big change: Introduced new mechanism to handle queries. It should not cause any visible changes ATM apart from fixing several long-standing bugs realted to handling post-visit/battle/levelup callback, including infamous creature bank issues: #955, #1053, #1063, #1191. Needs testing. Minor changes: * default log level set to trace * LOG_TRACE raii guardian lifetime will last till the end of block * compile fixes * minor refactorings --- AI/VCAI/VCAI.cpp | 21 +- Global.h | 18 + VCMI_global.props | 2 +- client/CMT.cpp | 4 +- client/CPlayerInterface.cpp | 48 +- client/Client.h | 13 +- client/NetPacksClient.cpp | 9 +- config/schemas/settings.json | 2 +- lib/CCreatureSet.cpp | 5 + lib/CCreatureSet.h | 1 + lib/CGameState.cpp | 2 +- lib/CObjectHandler.cpp | 616 ++++++++++++++---------- lib/CObjectHandler.h | 81 ++-- lib/GameConstants.cpp | 5 + lib/GameConstants.h | 2 + lib/IGameCallback.h | 12 +- lib/NetPacks.h | 21 +- lib/NetPacksLib.cpp | 4 +- lib/ResourceSet.cpp | 6 +- lib/ResourceSet.h | 11 +- lib/logging/CLogger.h | 20 +- lib/mapping/CMapEditManager.h | 4 +- lib/rmg/CMapGenerator.h | 2 +- server/CGameHandler.cpp | 853 +++++++++++++++------------------- server/CGameHandler.h | 68 +-- server/CMakeLists.txt | 1 + server/CQuery.cpp | 342 ++++++++++++++ server/CQuery.h | 177 +++++++ server/NetPacksServer.cpp | 4 +- server/VCMI_server.vcxproj | 2 + 30 files changed, 1481 insertions(+), 875 deletions(-) create mode 100644 server/CQuery.cpp create mode 100644 server/CQuery.h diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 53a871df7..75d5907b5 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -43,24 +43,6 @@ struct SetGlobalState } }; -template -typename Container::value_type backOrNull(const Container &c) //returns last element of container or NULL if it is empty (to be used with containers of pointers) -{ - if(c.size()) - return c.back(); - else - return NULL; -} - -template -typename Container::value_type frontOrNull(const Container &c) //returns first element of container or NULL if it is empty (to be used with containers of pointers) -{ - if(c.size()) - return c.front(); - else - return NULL; -} - #define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai); @@ -793,7 +775,7 @@ void VCAI::heroManaPointsChanged(const CGHeroInstance * hero) void VCAI::heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) { - LOG_TRACE_PARAMS(logAi, "which '%', val '%'", which % val); + LOG_TRACE_PARAMS(logAi, "which '%d', val '%d'", which % val); NET_EVENT_HANDLER; } @@ -2579,6 +2561,7 @@ AIStatus::~AIStatus() void AIStatus::setBattle(BattleState BS) { boost::unique_lock lock(mx); + LOG_TRACE_PARAMS(logAi, "battle state=%d", (int)BS); battle = BS; cv.notify_all(); } diff --git a/Global.h b/Global.h index 27f479e61..7c4f68afe 100644 --- a/Global.h +++ b/Global.h @@ -568,6 +568,24 @@ namespace vstd { obj = (T)(((int)obj) + change); } + + template + typename Container::value_type backOrNull(const Container &c) //returns last element of container or NULL if it is empty (to be used with containers of pointers) + { + if(c.size()) + return c.back(); + else + return NULL; + } + + template + typename Container::value_type frontOrNull(const Container &c) //returns first element of container or NULL if it is empty (to be used with containers of pointers) + { + if(c.size()) + return c.front(); + else + return NULL; + } } using vstd::operator-=; using vstd::make_unique; diff --git a/VCMI_global.props b/VCMI_global.props index 40df06ece..273d77434 100644 --- a/VCMI_global.props +++ b/VCMI_global.props @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/client/CMT.cpp b/client/CMT.cpp index 8c84cc365..32274c346 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -176,7 +176,9 @@ static void prog_help(const po::options_description &opts) void OSX_checkForUpdates(); #endif -#if defined(__APPLE__) +#ifdef _WIN32 +int wmain(int argc, wchar_t* argv[]) +#elif defined(__APPLE__) int SDL_main(int argc, char *argv[]) #else int main(int argc, char** argv) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index cc3e88ee2..d9e42aa1b 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -230,10 +230,10 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) if(LOCPLINT != this) return; - const CGHeroInstance * ho = cb->getHero(details.id); //object representing this hero + const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero int3 hp = details.start; - if(!ho) + if(!hero) { //AI hero left the visible area (we can't obtain info) //TODO very evil workaround -> retreive pointer to hero so we could animate it @@ -241,25 +241,25 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) const TerrainTile2 &tile = CGI->mh->ttiles[hp.x-1][hp.y][hp.z]; for(int i = 0; i < tile.objects.size(); i++) if(tile.objects[i].first->id == details.id) - ho = dynamic_cast(tile.objects[i].first); + hero = dynamic_cast(tile.objects[i].first); - if(!ho) //still nothing... + if(!hero) //still nothing... return; } - adventureInt->centerOn(ho); //actualizing screen pos + adventureInt->centerOn(hero); //actualizing screen pos adventureInt->minimap.redraw(); adventureInt->heroList.redraw(); bool directlyAttackingCreature = - CGI->mh->map->isInTheMap(details.attackedFrom) + details.attackedFrom && adventureInt->terrain.currentPath //in case if movement has been canceled in the meantime and path was already erased && adventureInt->terrain.currentPath->nodes.size() == 3;//FIXME should be 2 but works nevertheless... - if(makingTurn && ho->tempOwner == playerID) //we are moving our hero - we may need to update assigned path + if(makingTurn && hero->tempOwner == playerID) //we are moving our hero - we may need to update assigned path { //We may need to change music - select new track, music handler will change it if needed - CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(ho->visitablePos())->terType, true); + CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType, true); if(details.result == TryMoveHero::TELEPORTATION) { @@ -272,42 +272,42 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) && (nodesIt-1)->coord == CGHeroInstance::convertPosition(details.end, false)) { //path was between entrance and exit of teleport -> OK, erase node as usual - removeLastNodeFromPath(ho); + removeLastNodeFromPath(hero); } else { //teleport was not along current path, it'll now be invalid (hero is somewhere else) - eraseCurrentPathOf(ho); + eraseCurrentPathOf(hero); } } - adventureInt->heroList.update(ho); + adventureInt->heroList.update(hero); return; //teleport - no fancy moving animation //TODO: smooth disappear / appear effect } - if (ho->pos != details.end //hero didn't change tile but visit succeeded + if (hero->pos != details.end //hero didn't change tile but visit succeeded || directlyAttackingCreature) // or creature was attacked from endangering tile. { - eraseCurrentPathOf(ho, false); + eraseCurrentPathOf(hero, false); } - else if(adventureInt->terrain.currentPath && ho->pos == details.end) //&& hero is moving + else if(adventureInt->terrain.currentPath && hero->pos == details.end) //&& hero is moving { if(details.start != details.end) //so we don't touch path when revisiting with spacebar - removeLastNodeFromPath(ho); + removeLastNodeFromPath(hero); } } if (details.result != TryMoveHero::SUCCESS) //hero failed to move { - ho->isStanding = true; + hero->isStanding = true; stillMoveHero.setn(STOP_MOVE); GH.totalRedraw(); - adventureInt->heroList.update(ho); + adventureInt->heroList.update(hero); return; } - initMovement(details, ho, hp); + initMovement(details, hero, hp); //first initializing done GH.mainFPSmng->framerateDelay(); // after first move @@ -316,7 +316,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) //main moving for(int i=1; i<32; i+=2*speed) { - movementPxStep(details, i, hp, ho); + movementPxStep(details, i, hp, hero); adventureInt->updateScreen = true; adventureInt->show(screen); CSDL_Ext::update(screen); @@ -325,12 +325,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) //main moving done //finishing move - finishMovement(details, hp, ho); - ho->isStanding = true; + finishMovement(details, hp, hero); + hero->isStanding = true; //move finished adventureInt->minimap.redraw(); - adventureInt->heroList.update(ho); + adventureInt->heroList.update(hero); //check if user cancelled movement { @@ -358,14 +358,14 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) // Hero attacked creature directly, set direction to face it. if (directlyAttackingCreature) { // Get direction to attacker. - int3 posOffset = details.attackedFrom - details.end + int3(2, 1, 0); + int3 posOffset = *details.attackedFrom - details.end + int3(2, 1, 0); static const ui8 dirLookup[3][3] = { { 1, 2, 3 }, { 8, 0, 4 }, { 7, 6, 5 } }; // FIXME: Avoid const_cast, make moveDir mutable in some other way? - const_cast(ho)->moveDir = dirLookup[posOffset.y][posOffset.x]; + const_cast(hero)->moveDir = dirLookup[posOffset.y][posOffset.x]; } } void CPlayerInterface::heroKilled(const CGHeroInstance* hero) diff --git a/client/Client.h b/client/Client.h index 1a199907a..8f9486466 100644 --- a/client/Client.h +++ b/client/Client.h @@ -170,11 +170,12 @@ public: void setHoverName(const CGObjectInstance * obj, MetaString * name) OVERRIDE {}; void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs=false) OVERRIDE {}; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) OVERRIDE {}; - void showBlockingDialog(BlockingDialog *iw, const CFunctionList &callback) OVERRIDE {}; - ui32 showBlockingDialog(BlockingDialog *iw) OVERRIDE {return 0;}; //synchronous version of above + + void showBlockingDialog(BlockingDialog *iw) OVERRIDE {}; void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits, const boost::function &cb) OVERRIDE {}; void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) OVERRIDE {}; void giveResource(PlayerColor player, Res::ERes which, int val) OVERRIDE {}; + virtual void giveResources(PlayerColor player, TResources resources) OVERRIDE {}; void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) OVERRIDE {}; void takeCreatures(ObjectInstanceID objid, const std::vector &creatures) OVERRIDE {}; @@ -199,11 +200,11 @@ public: void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) OVERRIDE {}; //void giveHeroArtifact(int artid, int hid, int position){}; //void giveNewArtifact(int hid, int position){}; - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, boost::function cb = 0, const CGTownInstance *town = NULL) OVERRIDE {}; //use hero=NULL for no hero - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, boost::function cb = 0, bool creatureBank = false) OVERRIDE {}; //if any of armies is hero, hero will be used - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, boost::function cb = 0, bool creatureBank = false) OVERRIDE {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = NULL) OVERRIDE {}; //use hero=NULL for no hero + void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) OVERRIDE {}; //if any of armies is hero, hero will be used + void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) OVERRIDE {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle void setAmount(ObjectInstanceID objid, ui32 val) OVERRIDE {}; - bool moveHero(ObjectInstanceID hid, int3 dst, ui8 instant, PlayerColor asker = PlayerColor::NEUTRAL) OVERRIDE {return false;}; + bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) OVERRIDE {return false;}; void giveHeroBonus(GiveBonus * bonus) OVERRIDE {}; void setMovePoints(SetMovePoints * smp) OVERRIDE {}; void setManaPoints(ObjectInstanceID hid, int val) OVERRIDE {}; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index e425c6f9c..50cba975f 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -545,11 +545,10 @@ void SetObjectProperty::applyCl( CClient *cl ) void HeroLevelUp::applyCl( CClient *cl ) { - const CGHeroInstance *h = cl->getHero(heroid); //INTERFACE_CALL_IF_PRESENT(h->tempOwner, heroGotLevel, h, primskill, skills, id); - if(vstd::contains(cl->playerint,h->tempOwner)) + if(vstd::contains(cl->playerint,hero->tempOwner)) { - cl->playerint[h->tempOwner]->heroGotLevel(h, primskill, skills, queryID); + cl->playerint[hero->tempOwner]->heroGotLevel(hero, primskill, skills, queryID); } //else // cb->selectionMade(0, queryID); @@ -557,9 +556,9 @@ void HeroLevelUp::applyCl( CClient *cl ) void CommanderLevelUp::applyCl( CClient *cl ) { - CCommanderInstance * commander = GS(cl)->getHero(heroid)->commander; + const CCommanderInstance * commander = hero->commander; assert (commander); - PlayerColor player = commander->armyObj->tempOwner; + PlayerColor player = hero->tempOwner; if (commander->armyObj && vstd::contains(cl->playerint, player)) //is it possible for Commander to exist beyond armed instance? { cl->playerint[player]->commanderGotLevel(commander, skills, queryID); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 73b878b44..2fdcfc4f8 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -207,7 +207,7 @@ }, "loggers" : { "type" : "array", - "default" : [ { "domain" : "global", "level" : "info" } ], + "default" : [ { "domain" : "global", "level" : "trace" } ], "items" : { "type" : "object", "additionalProperties" : false, diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index be46bf171..5d147766d 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -741,6 +741,11 @@ ArtBearer::ArtBearer CCommanderInstance::bearerType() const return ArtBearer::COMMANDER; } +bool CCommanderInstance::gainsLevel() const +{ + return experience >= VLC->heroh->reqExp(level+1); +} + CStackBasicDescriptor::CStackBasicDescriptor() { type = NULL; diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index b953e5c76..d5966f7aa 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -103,6 +103,7 @@ public: void giveStackExp (TExpType exp); void levelUp (); + bool gainsLevel() const; //true if commander has lower level than should upon his experience ui64 getPower() const {return 0;}; int getExpRank() const; int getLevel() const OVERRIDE; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 5d5940c58..2d463dc2a 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -3196,7 +3196,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile { BOOST_FOREACH(const CGObjectInstance *obj, tinfo->visitableObjects) { - if(obj->getPassableness() & 1<tempOwner.getNum()) //special object instance specific passableness flag - overwrites other accessibility flags + if(obj->passableFor(hero->tempOwner)) //special object instance specific passableness flag - overwrites other accessibility flags { ret = CGPathNode::ACCESSIBLE; } diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index a61eb15ef..193731bcd 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -125,6 +125,21 @@ void IObjectInterface::postInit() void IObjectInterface::preInit() {} +void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + +} + +void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + +} + +void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const +{ + +} + void CPlayersVisited::setPropertyDer( ui8 what, ui32 val ) { if(what == 10) @@ -540,6 +555,11 @@ bool CGObjectInstance::isVisitable() const return false; } +bool CGObjectInstance::passableFor(PlayerColor color) const +{ + return getPassableness() & 1<Slots().size()) @@ -1590,6 +1610,55 @@ ArtBearer::ArtBearer CGHeroInstance::bearerType() const return ArtBearer::HERO; } +std::vector CGHeroInstance::levelUpProposedSkills() const +{ + std::vector skills; + //picking sec. skills for choice + std::set basicAndAdv, expert, none; + for(int i=0;iisAllowed(2,i)) + none.insert(SecondarySkill(i)); + + for(unsigned i=0; iheroClass->chooseSecSkill(basicAndAdv);//upgrade existing + skills.push_back(s); + basicAndAdv.erase(s); + } + else if(none.size() && canLearnSkill()) + { + skills.push_back(type->heroClass->chooseSecSkill(none)); //give new skill + none.erase(skills.back()); + } + + //second offered skill + if(none.size() && canLearnSkill()) //hero have free skill slot + { + skills.push_back(type->heroClass->chooseSecSkill(none)); //new skill + } + else if(basicAndAdv.size()) + { + skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv)); //upgrade existing + } + + return skills; +} + +bool CGHeroInstance::gainsLevel() const +{ + return exp >= VLC->heroh->reqExp(level+1); +} + void CGDwelling::initObj() { switch(ID) @@ -1710,7 +1779,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3); bd.text.addReplacement(*Slots().begin()->second); - cb->showBlockingDialog(&bd, boost::bind(&CGDwelling::wantsFight, this, h, _1)); + cb->showBlockingDialog(&bd); return; } @@ -1740,7 +1809,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const else throw std::runtime_error("Illegal dwelling!"); - cb->showBlockingDialog(&bd, boost::bind(&CGDwelling::heroAcceptsCreatures, this, h, _1)); + cb->showBlockingDialog(&bd); } void CGDwelling::newTurn() const @@ -1780,11 +1849,8 @@ void CGDwelling::newTurn() const cb->sendAndApply(&sac); } -void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h, ui32 answer ) const +void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const { - if(!answer) - return; - CreatureID crid = creatures[0].second[0]; CCreature *crs = VLC->creh->creatures[crid]; TQuantity count = creatures[0].first; @@ -1854,17 +1920,25 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h, ui32 answer ) co } } -void CGDwelling::wantsFight( const CGHeroInstance *h, ui32 answer ) const +void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { - if(answer) - cb->startBattleI(h, this, boost::bind(&CGDwelling::fightOver, this, h, _1)); + if (result.winner == 0) + { + onHeroVisit(hero); + } } -void CGDwelling::fightOver(const CGHeroInstance *h, BattleResult *result) const +void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if (result->winner == 0) + auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); + if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present { - onHeroVisit(h); + if(answer) + cb->startBattleI(hero, this); + } + else if(relations == PlayerRelations::SAME_PLAYER && answer) + { + heroAcceptsCreatures(hero); } } @@ -2091,7 +2165,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const //TODO //"borrowing" army from garrison to visiting hero - cb->startBattleI(h, defendingArmy, getSightCenter(), h, defendingHero, false, boost::bind(&CGTownInstance::fightOver, this, h, _1), (outsideTown ? NULL : this)); + cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? NULL : this)); } else { @@ -2255,20 +2329,6 @@ void CGTownInstance::getOutOffsets( std::vector &offsets ) const offsets += int3(-1,2,0), int3(-3,2,0); } -void CGTownInstance::fightOver( const CGHeroInstance *h, BattleResult *result ) const -{ - if(result->winner == 0) - { - removeCapitols (h->getOwner()); - cb->setOwner (this, h->tempOwner); //give control after checkout is done - FoWChange fw; - fw.player = h->tempOwner; - fw.mode = 1; - getSightTiles (fw.tiles); //update visibility for castle structures - cb->sendAndApply (&fw); - } -} - void CGTownInstance::removeCapitols (PlayerColor owner) const { if (hasCapitol()) // search if there's an older capitol @@ -2552,6 +2612,20 @@ void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 s } } +void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) + { + removeCapitols(hero->getOwner()); + cb->setOwner (this, hero->tempOwner); //give control after checkout is done + FoWChange fw; + fw.player = hero->tempOwner; + fw.mode = 1; + getSightTiles (fw.tiles); //update visibility for castle structures + cb->sendAndApply (&fw); + } +} + bool CGVisitableOPH::wasVisited (const CGHeroInstance * h) const { @@ -2585,17 +2659,29 @@ void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const void CGVisitableOPH::initObj() { if(ID==Obj::TREE_OF_KNOWLEDGE) - ttype = ran()%3; - else - ttype = -1; + { + switch (ran() % 3) + { + case 1: + treePrice[Res::GOLD] = 2000; + break; + case 2: + treePrice[Res::GEMS] = 10; + break; + default: + break; + } + } } -void CGVisitableOPH::treeSelected( ObjectInstanceID heroID, int resType, int resVal, TExpType expVal, ui32 result ) const +void CGVisitableOPH::treeSelected( ObjectInstanceID heroID, ui32 result) const { if(result) //player agreed to give res for exp { - cb->giveResource(cb->getOwner(heroID), static_cast(resType), -resVal); //take resource - cb->changePrimSkill(cb->getHero(heroID), PrimarySkill::EXPERIENCE, expVal); + auto h = cb->getHero(heroID); + si64 expToGive = VLC->heroh->reqExp(h->level+1) - VLC->heroh->reqExp(h->level);; + cb->giveResources(cb->getOwner(heroID), -treePrice); + cb->changePrimSkill(cb->getHero(heroID), PrimarySkill::EXPERIENCE, expToGive); cb->setObjProperty(id, ObjProperty::VISITORS, heroID.getNum()); //add to the visitors } } @@ -2669,7 +2755,7 @@ void CGVisitableOPH::onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) sd.components.push_back(Component(c_id, PrimarySkill::ATTACK, 2, 0)); sd.components.push_back(Component(c_id, PrimarySkill::DEFENSE, 2, 0)); sd.player = cb->getOwner(heroID); - cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::arenaSelected,this,heroID,_1)); + cb->showBlockingDialog(&sd); return; } case Obj::MERCENARY_CAMP: @@ -2703,7 +2789,7 @@ void CGVisitableOPH::onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) { const CGHeroInstance *h = cb->getHero(heroID); val = VLC->heroh->reqExp(h->level+val) - VLC->heroh->reqExp(h->level); - if(!ttype) + if(!treePrice.nonZero()) { cb->setObjProperty(id, ObjProperty::VISITORS, heroID.getNum()); //add to the visitors InfoWindow iw; @@ -2717,22 +2803,12 @@ void CGVisitableOPH::onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) } else { - Res::ERes res; - si32 resval; - if(ttype==1) - { - res = Res::GOLD; - resval = 2000; + if(treePrice[Res::GOLD] > 0) ot = 149; - } else - { - res = Res::GEMS; - resval = 10; ot = 151; - } - if(cb->getResource(h->tempOwner,res) < resval) //not enough resources + if(!cb->getPlayer(h->tempOwner)->resources.canAfford(treePrice)) //not enough resources { ot++; showInfoDialog(h,ot,sound); @@ -2743,8 +2819,8 @@ void CGVisitableOPH::onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) sd.soundID = sound; sd.player = cb->getOwner(heroID); sd.text.addTxt(MetaString::ADVOB_TXT,ot); - sd.components.push_back (Component (Component::RESOURCE, res, resval, 0)); - cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::treeSelected,this,heroID,res,resval,val,_1)); + sd.addResourceComponents(treePrice); + cb->showBlockingDialog(&sd); } break; } @@ -2783,7 +2859,7 @@ void CGVisitableOPH::onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) sd.text.addTxt(MetaString::ADVOB_TXT,ot); sd.components.push_back(Component(c_id, skill, +1, 0)); sd.components.push_back(Component(c_id, skill+1, +1, 0)); - cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::schoolSelected,this,heroID,_1)); + cb->showBlockingDialog(&sd); } } break; @@ -2869,6 +2945,28 @@ void CGVisitableOPH::schoolSelected(ObjectInstanceID heroID, ui32 which) const cb->changePrimSkill(cb->getHero(heroID), static_cast(base + which-1), +1); //give appropriate skill } +void CGVisitableOPH::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + switch (ID) + { + case Obj::ARENA: + arenaSelected(hero->id, answer); + break; + + case Obj::TREE_OF_KNOWLEDGE: + treeSelected(hero->id, answer); + break; + + case Obj::SCHOOL_OF_MAGIC: + case Obj::SCHOOL_OF_WAR: + schoolSelected(id, answer); + + default: + assert(0); + break; + } +} + COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) { ID = index; @@ -2957,7 +3055,7 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const mid = 582; iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); break; - case ETownType::STRONGHOLD://hall of valhalla + case ETownType::STRONGHOLD://hall of Valhalla what = PrimarySkill::ATTACK; val = 1; mid = 584; @@ -2993,6 +3091,13 @@ void CTownBonus::onHeroVisit (const CGHeroInstance * h) const } const std::string & CGCreature::getHoverText() const { + if(stacks.empty()) + { + //should not happen... + logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id; + return "!!!INVALID_STACK!!!"; + } + MetaString ms; int pom = stacks.begin()->second->getQuantityID(); pom = 172 + 3*pom; @@ -3029,21 +3134,21 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const int action = takenAction(h); switch( action ) //decide what we do... { - case -2: //fight + case FIGHT: fight(h); break; - case -1: //flee + case FLEE: //flee { flee(h); break; } - case 0: //join for free + case JOIN_FOR_FREE: //join for free { BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.addTxt(MetaString::ADVOB_TXT, 86); ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::joinDecision,this,h,0,_1)); + cb->showBlockingDialog(&ynd); break; } default: //join for gold @@ -3058,67 +3163,13 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl); ynd.text << tmp; - cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::joinDecision,this,h,action,_1)); + cb->showBlockingDialog(&ynd); break; } } } -void CGCreature::endBattle( BattleResult *result ) const -{ - if(result->winner==0) - { - cb->removeObject(this); - } - else - { - //int killedAmount=0; - //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) - // if(i->first == subID) - // killedAmount += i->second; - //cb->setAmount(id, slots.find(0)->second.second - killedAmount); - - /* - MetaString ms; - int pom = slots.find(0)->second.getQuantityID(); - pom = 174 + 3*pom + 1; - ms << std::pair(6,pom) << " " << std::pair(7,subID); - cb->setHoverName(id,&ms); - cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); - */ - - //merge stacks into one - TSlots::const_iterator i; - CCreature * cre = VLC->creh->creatures[restore.basicType]; - for (i = stacks.begin(); i != stacks.end(); i++) - { - if (cre->isMyUpgrade(i->second->type)) - { - cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures - } - } - - //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack - if(!hasStackAtSlot(SlotID(0))) - cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); - - while (stacks.size() > 1) //hopefully that's enough - { - // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) - i = stacks.end(); - i--; - SlotID slot = getSlotFor(i->second->type); - if (slot == i->first) //no reason to move stack to its own slot - break; - else - cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); - } - - cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties - } -} - void CGCreature::initObj() { blockVisit = true; @@ -3159,7 +3210,9 @@ void CGCreature::initObj() } temppower = stacks[SlotID(0)]->count * 1000; + refusedJoining = false; } + void CGCreature::newTurn() const {//Works only for stacks of single type of size up to 2 millions if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1) @@ -3187,6 +3240,9 @@ void CGCreature::setPropertyDer(ui8 what, ui32 val) case ObjProperty::MONSTER_RESTORE_TYPE: restore.basicType = val; break; + case ObjProperty::MONSTER_REFUSED_JOIN: + refusedJoining = val; + break; } } @@ -3261,6 +3317,9 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const { + if(refusedJoining) + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false); + if(pursue) { fight(h); @@ -3277,6 +3336,7 @@ void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) co { if(takenAction(h,false) == -1) //they flee { + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); flee(h); } else //they fight @@ -3373,7 +3433,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const } } - cb->startBattleI(h, this, boost::bind(&CGCreature::endBattle,this,_1)); + cb->startBattleI(h, this); } @@ -3383,7 +3443,73 @@ void CGCreature::flee( const CGHeroInstance * h ) const ynd.player = h->tempOwner; ynd.text.addTxt(MetaString::ADVOB_TXT,91); ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::fleeDecision,this,h,_1)); + cb->showBlockingDialog(&ynd); +} + +void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + + if(result.winner==0) + { + cb->removeObject(this); + } + else + { + //int killedAmount=0; + //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) + // if(i->first == subID) + // killedAmount += i->second; + //cb->setAmount(id, slots.find(0)->second.second - killedAmount); + + /* + MetaString ms; + int pom = slots.find(0)->second.getQuantityID(); + pom = 174 + 3*pom + 1; + ms << std::pair(6,pom) << " " << std::pair(7,subID); + cb->setHoverName(id,&ms); + cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); + */ + + //merge stacks into one + TSlots::const_iterator i; + CCreature * cre = VLC->creh->creatures[restore.basicType]; + for (i = stacks.begin(); i != stacks.end(); i++) + { + if (cre->isMyUpgrade(i->second->type)) + { + cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures + } + } + + //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack + if(!hasStackAtSlot(SlotID(0))) + cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); + + while (stacks.size() > 1) //hopefully that's enough + { + // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) + i = stacks.end(); + i--; + SlotID slot = getSlotFor(i->second->type); + if (slot == i->first) //no reason to move stack to its own slot + break; + else + cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); + } + + cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties + } +} + +void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto action = takenAction(hero); + if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price + joinDecision(hero, action, answer); + else if(action != FIGHT) + fleeDecision(hero, answer); + else + assert(0); } void CGMine::onHeroVisit( const CGHeroInstance * h ) const @@ -3403,7 +3529,7 @@ void CGMine::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187); - cb->showBlockingDialog(&ynd,boost::bind(&CGMine::fight, this, _1, h)); + cb->showBlockingDialog(&ynd); return; } @@ -3458,23 +3584,6 @@ void CGMine::initObj() producedQuantity = defaultResProduction(); } -void CGMine::fight(ui32 agreed, const CGHeroInstance *h) const -{ - cb->startBattleI(h, this, boost::bind(&CGMine::endBattle, this, _1, h->tempOwner)); -} - -void CGMine::endBattle(BattleResult *result, PlayerColor attackingPlayer) const -{ - if(result->winner == 0) //attacker won - { - if(subID == 7) - { - showInfoDialog(attackingPlayer,85,0); - } - flagMine(attackingPlayer); - } -} - void CGMine::flagMine(PlayerColor player) const { assert(tempOwner != player); @@ -3511,6 +3620,24 @@ ui32 CGMine::defaultResProduction() } } +void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + { + if(subID == 7) + { + showInfoDialog(hero->tempOwner, 85, 0); + } + flagMine(hero->tempOwner); + } +} + +void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + void CGResource::initObj() { blockVisit = true; @@ -3542,11 +3669,11 @@ void CGResource::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->getOwner(); ynd.text << message; - cb->showBlockingDialog(&ynd,boost::bind(&CGResource::fightForRes,this,_1,h)); + cb->showBlockingDialog(&ynd); } else { - fightForRes(1,h); + blockingDialogAnswered(h, true); //behave as if player accepted battle } } else @@ -3574,16 +3701,16 @@ void CGResource::collectRes( PlayerColor player ) const cb->removeObject(this); } -void CGResource::fightForRes(ui32 agreed, const CGHeroInstance *h) const +void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { - if(agreed) - cb->startBattleI(h, this, boost::bind(&CGResource::endBattle,this,_1,h)); + if(result.winner == 0) //attacker won + collectRes(hero->getOwner()); } -void CGResource::endBattle( BattleResult *result, const CGHeroInstance *h ) const +void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if(result->winner == 0) //attacker won - collectRes(h->getOwner()); + if(answer) + cb->startBattleI(hero, this); } void CGVisitableOPW::newTurn() const @@ -3895,11 +4022,11 @@ void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->getOwner(); ynd.text << message; - cb->showBlockingDialog(&ynd,boost::bind(&CGArtifact::fightForArt,this,_1,h)); + cb->showBlockingDialog(&ynd); } else { - fightForArt(0,h); + blockingDialogAnswered(h, true); } } } @@ -3910,17 +4037,18 @@ void CGArtifact::pick(const CGHeroInstance * h) const cb->removeObject(this); } -void CGArtifact::fightForArt( ui32 agreed, const CGHeroInstance *h ) const +void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { - if(agreed) - cb->startBattleI(h, this, boost::bind(&CGArtifact::endBattle,this,_1,h)); + if(result.winner == 0) //attacker won + pick(hero); } -void CGArtifact::endBattle( BattleResult *result, const CGHeroInstance *h ) const +void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if(result->winner == 0) //attacker won - pick(h); + if(answer) + cb->startBattleI(hero, this); } + void CGPickable::initObj() { blockVisit = true; @@ -4110,8 +4238,7 @@ void CGPickable::onHeroVisit( const CGHeroInstance * h ) const TExpType expVal = h->calculateXp(val2); sd.components.push_back(Component(Component::EXPERIENCE,0,expVal, 0)); sd.soundID = soundBase::chest; - boost::function fun = boost::bind(&CGPickable::chosen,this,_1,h->id); - cb->showBlockingDialog(&sd,fun); + cb->showBlockingDialog(&sd); return; } } @@ -4119,16 +4246,15 @@ void CGPickable::onHeroVisit( const CGHeroInstance * h ) const cb->removeObject(this); } -void CGPickable::chosen( int which, ObjectInstanceID heroID ) const +void CGPickable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - const CGHeroInstance *h = cb->getHero(heroID); - switch(which) + switch(answer) { case 1: //player pick gold - cb->giveResource(cb->getOwner(heroID), Res::GOLD, val1); + cb->giveResource(hero->tempOwner, Res::GOLD, val1); break; case 2: //player pick exp - cb->changePrimSkill(cb->getHero(heroID), PrimarySkill::EXPERIENCE, h->calculateXp(val2)); + cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(val2)); break; default: throw std::runtime_error("Unhandled treasure choice"); @@ -4655,7 +4781,7 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const getCompletionText (bd.text, bd.components, isCustom, h); - cb->showBlockingDialog (&bd, boost::bind (&CGSeerHut::finishQuest, this, h, _1)); + cb->showBlockingDialog (&bd); return; } } @@ -4801,6 +4927,11 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const return static_cast(o); } +void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + finishQuest(hero, answer); +} + void CGQuestGuard::initObj() { blockVisit = true; @@ -5146,40 +5277,7 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const bd.player = h->getOwner(); bd.soundID = soundBase::QUEST; bd.text.addTxt (MetaString::ADVOB_TXT, 14); - cb->showBlockingDialog (&bd, boost::bind (&CGPandoraBox::open, this, h, _1)); -} - -void CGPandoraBox::open( const CGHeroInstance * h, ui32 accept ) const -{ - if (accept) - { - if (stacksCount() > 0) //if pandora's box is protected by army - { - showInfoDialog(h,16,0); - cb->startBattleI(h, this, boost::bind(&CGPandoraBox::endBattle, this, h, _1)); //grants things after battle - } - else if (message.size() == 0 && resources.size() == 0 - && primskills.size() == 0 && abilities.size() == 0 - && abilityLevels.size() == 0 && artifacts.size() == 0 - && spells.size() == 0 && creatures.Slots().size() > 0 - && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle - { - showInfoDialog(h,15,0); - cb->removeObject(this); - } - else //if it gives something without battle - { - giveContents (h, false); - } - } -} - -void CGPandoraBox::endBattle( const CGHeroInstance *h, BattleResult *result ) const -{ - if(result->winner) - return; - - giveContents(h,true); + cb->showBlockingDialog (&bd); } void CGPandoraBox::giveContents( const CGHeroInstance *h, bool afterBattle ) const @@ -5412,6 +5510,39 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int nega } } +void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner) + return; + + giveContents(hero, true); +} + +void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + if (stacksCount() > 0) //if pandora's box is protected by army + { + showInfoDialog(hero,16,0); + cb->startBattleI(hero, this); //grants things after battle + } + else if (message.size() == 0 && resources.size() == 0 + && primskills.size() == 0 && abilities.size() == 0 + && abilityLevels.size() == 0 && artifacts.size() == 0 + && spells.size() == 0 && creatures.Slots().size() > 0 + && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle + { + showInfoDialog(hero,15,0); + cb->removeObject(this); + } + else //if it gives something without battle + { + giveContents(hero, false); + } + } +} + void CGEvent::onHeroVisit( const CGHeroInstance * h ) const { if(!(availableFor & (1 << h->tempOwner.getNum()))) @@ -5436,7 +5567,7 @@ void CGEvent::activated( const CGHeroInstance * h ) const else iw.text.addTxt(MetaString::ADVOB_TXT, 16); cb->showInfoDialog(&iw); - cb->startBattleI(h, this, boost::bind(&CGEvent::endBattle,this,h,_1)); + cb->startBattleI(h, this); } else { @@ -5619,7 +5750,7 @@ void CGScholar::onHeroVisit( const CGHeroInstance * h ) const } break; default: - logGlobal->errorStream() << "Error: wrong bonustype (" << (int)type << ") for Scholar!"; + logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n"; return; } @@ -5656,7 +5787,7 @@ void CGGarrison::onHeroVisit (const CGHeroInstance *h) const int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); if (!ally && stacksCount() > 0) { //TODO: Find a way to apply magic garrison effects in battle. - cb->startBattleI(h, this, boost::bind(&CGGarrison::fightOver, this, h, _1)); + cb->startBattleI(h, this); return; } @@ -5667,12 +5798,6 @@ void CGGarrison::onHeroVisit (const CGHeroInstance *h) const cb->showGarrisonDialog(id, h->id, removableUnits, 0); } -void CGGarrison::fightOver (const CGHeroInstance *h, BattleResult *result) const -{ - if (result->winner == 0) - onHeroVisit(h); -} - ui8 CGGarrison::getPassableness() const { if ( !stacksCount() )//empty - anyone can visit @@ -5688,6 +5813,12 @@ ui8 CGGarrison::getPassableness() const return mask; } +void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + onHeroVisit(hero); +} + void CGOnceVisitable::onHeroVisit( const CGHeroInstance * h ) const { int sound = soundBase::sound_todo; @@ -5714,7 +5845,7 @@ void CGOnceVisitable::onHeroVisit( const CGHeroInstance * h ) const bd.soundID = soundBase::GRAVEYARD; bd.player = h->getOwner(); bd.text.addTxt(MetaString::ADVOB_TXT,161); - cb->showBlockingDialog(&bd,boost::bind(&CGOnceVisitable::searchTomb,this,h,_1)); + cb->showBlockingDialog(&bd); return; } break; @@ -5847,12 +5978,13 @@ void CGOnceVisitable::initObj() } } -void CGOnceVisitable::searchTomb(const CGHeroInstance *h, ui32 accept) const +void CGOnceVisitable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if(accept) + //must have been Tomb + if(answer) { InfoWindow iw; - iw.player = h->getOwner(); + iw.player = hero->getOwner(); iw.components.push_back(Component(Component::MORALE,0,-3,0)); if(players.size()) //we've been already visited, player found nothing @@ -5865,20 +5997,20 @@ void CGOnceVisitable::searchTomb(const CGHeroInstance *h, ui32 accept) const iw.components.push_back(Component(Component::ARTIFACT,bonusType,0,0)); iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); + cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); } - if(!h->hasBonusFrom(Bonus::OBJECT,ID)) //we don't have modifier from this object yet + if(!hero->hasBonusFrom(Bonus::OBJECT,ID)) //we don't have modifier from this object yet { //ruin morale GiveBonus gb; - gb.id = h->id.getNum(); + gb.id = hero->id.getNum(); gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,-3,id.getNum(),""); gb.bdescr.addTxt(MetaString::ARRAY_TXT,104); //Warrior Tomb Visited -3 cb->giveHeroBonus(&gb); } cb->showInfoDialog(&iw); - cb->setObjProperty(id, 10, h->getOwner().getNum()); + cb->setObjProperty(id, 10, hero->getOwner().getNum()); } } @@ -6067,7 +6199,7 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const bd.text.addTxt(MetaString::ADVOB_TXT,banktext); if (ID == Obj::CREATURE_BANK) bd.text.addReplacement(VLC->objh->creBanksNames[index]); - cb->showBlockingDialog (&bd, boost::bind (&CBank::fightGuards, this, h, _1)); + cb->showBlockingDialog (&bd); } else { @@ -6096,20 +6228,14 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const cb->showInfoDialog(&iw); } } -void CBank::fightGuards (const CGHeroInstance * h, ui32 accept) const + +void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { - if (accept) - { - cb->startBattleI (h, this, boost::bind (&CBank::endBattle, this, h, _1), true); - } -} -void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) const -{ - if (result->winner == 0) + if (result.winner == 0) { int textID = -1; InfoWindow iw; - iw.player = h->getOwner(); + iw.player = hero->getOwner(); MetaString loot; switch (ID) @@ -6123,7 +6249,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons else { GiveBonus gbonus; - gbonus.id = h->id.getNum(); + gbonus.id = hero->id.getNum(); gbonus.bonus.duration = Bonus::ONE_BATTLE; gbonus.bonus.source = Bonus::OBJECT; gbonus.bonus.sid = ID; @@ -6142,7 +6268,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons { iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); GiveBonus gbonus; - gbonus.id = h->id.getNum(); + gbonus.id = hero->id.getNum(); gbonus.bonus.duration = Bonus::ONE_BATTLE; gbonus.bonus.source = Bonus::OBJECT; gbonus.bonus.sid = ID; @@ -6173,7 +6299,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons loot << "%d %s"; loot.addReplacement(iw.components.back().val); loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); - cb->giveResource (h->getOwner(), static_cast(it), bc->resources[it]); + cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); } } } @@ -6183,7 +6309,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons iw.components.push_back (Component (Component::ARTIFACT, *it, 0, 0)); loot << "%s"; loot.addReplacement(MetaString::ART_NAMES, *it); - cb->giveHeroNewArtifact (h, VLC->arth->artifacts[*it], ArtifactPosition::FIRST_AVAILABLE); + cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[*it], ArtifactPosition::FIRST_AVAILABLE); } //display loot if (!iw.components.empty()) @@ -6191,7 +6317,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons iw.text.addTxt (MetaString::ADVOB_TXT, textID); if (textID == 34) { - iw.text.addReplacement(MetaString::CRE_PL_NAMES, result->casualties[1].begin()->first); + iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first); iw.text.addReplacement(loot.buildList()); } cb->showInfoDialog(&iw); @@ -6222,13 +6348,20 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons iw.text.addTxt (MetaString::ADVOB_TXT, 186); iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(h->name); + iw.text.addReplacement(hero->name); cb->showInfoDialog(&iw); - cb->giveCreatures(this, h, ourArmy, false); + cb->giveCreatures(this, hero, ourArmy, false); } cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = NULL } +} +void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + cb->startBattleI(hero, this, true); + } } void CGPyramid::initObj() @@ -6259,7 +6392,7 @@ void CGPyramid::onHeroVisit (const CGHeroInstance * h) const bd.player = h->getOwner(); bd.soundID = soundBase::MYSTERY; bd.text << VLC->generaltexth->advobtxt[105]; - cb->showBlockingDialog (&bd, boost::bind (&CBank::fightGuards, this, h, _1)); + cb->showBlockingDialog(&bd); } else { @@ -6275,29 +6408,30 @@ void CGPyramid::onHeroVisit (const CGHeroInstance * h) const } } -void CGPyramid::endBattle (const CGHeroInstance *h, const BattleResult *result) const +void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { - if (result->winner == 0) + if (result.winner == 0) { InfoWindow iw; - iw.player = h->getOwner(); + iw.player = hero->getOwner(); iw.text.addTxt (MetaString::ADVOB_TXT, 106); iw.text.addTxt (MetaString::SPELL_NAME, spell); - if (!h->getArt(ArtifactPosition::SPELLBOOK)) + if (!hero->getArt(ArtifactPosition::SPELLBOOK)) iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook - else if (h->getSecSkillLevel(SecondarySkill::WISDOM) < 3) + else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3) iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom else { std::set spells; spells.insert (SpellID(spell)); - cb->changeSpells (h, true, spells); - iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); + cb->changeSpells (hero, true, spells); + iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); } cb->showInfoDialog(&iw); cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); } } + void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8 { if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I)) @@ -6375,7 +6509,7 @@ void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const bd.player = h->getOwner(); bd.soundID = soundBase::QUEST; bd.text.addTxt (MetaString::ADVOB_TXT, 17); - cb->showBlockingDialog (&bd, boost::bind (&CGBorderGuard::openGate, this, h, _1)); + cb->showBlockingDialog (&bd); } else { @@ -6389,9 +6523,9 @@ void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const } } -void CGBorderGuard::openGate(const CGHeroInstance *h, ui32 accept) const +void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if (accept) + if (answer) cb->removeObject(this); } @@ -6689,7 +6823,7 @@ void CCartographer::onHeroVisit( const CGHeroInstance * h ) const bd.player = h->getOwner(); bd.soundID = soundBase::LIGHTHOUSE; bd.text.addTxt (MetaString::ADVOB_TXT, text); - cb->showBlockingDialog (&bd, boost::bind (&CCartographer::buyMap, this, h, _1)); + cb->showBlockingDialog (&bd); } else //if he cannot afford { @@ -6702,20 +6836,20 @@ void CCartographer::onHeroVisit( const CGHeroInstance * h ) const } } -void CCartographer::buyMap (const CGHeroInstance *h, ui32 accept) const +void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - if (accept) //if hero wants to buy map + if (answer) //if hero wants to buy map { - cb->giveResource (h->tempOwner, Res::GOLD, -1000); + cb->giveResource (hero->tempOwner, Res::GOLD, -1000); FoWChange fw; fw.mode = 1; - fw.player = h->tempOwner; + fw.player = hero->tempOwner; //subIDs of different types of cartographers: //water = 0; land = 1; underground = 2; - cb->getAllTiles (fw.tiles, h->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles + cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles cb->sendAndApply (&fw); - cb->setObjProperty (id, 10, h->tempOwner.getNum()); + cb->setObjProperty (id, 10, hero->tempOwner.getNum()); } } diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index c49b8adf3..df3d4e90f 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -113,6 +113,13 @@ public: virtual void newTurn() const; virtual void initObj(); //synchr virtual void setProperty(ui8 what, ui32 val);//synchr + + //Called when queries created DURING HERO VISIT are resolved + //First parameter is always hero that visited object and triggered the query + virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const; + virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; + virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; + //unified interface, AI helpers virtual bool wasVisited (PlayerColor player) const; virtual bool wasVisited (const CGHeroInstance * h) const; @@ -179,6 +186,7 @@ public: virtual ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used virtual int3 getSightCenter() const; //"center" tile from which the sight distance is calculated virtual int getSightRadious() const; //sight distance (should be used if player-owned structure) + bool passableFor(PlayerColor color) const; void getSightTiles(boost::unordered_set &tiles) const; //returns reference to the set PlayerColor getOwner() const; void setOwner(PlayerColor ow); @@ -396,6 +404,8 @@ public: CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - + std::vector levelUpProposedSkills() const; + bool gainsLevel() const; //true if hero has lower level than should upon his experience ////////////////////////////////////////////////////////////////////////// @@ -471,10 +481,11 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void newTurn() const override; void setProperty(ui8 what, ui32 val) override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; + private: - void heroAcceptsCreatures(const CGHeroInstance *h, ui32 answer) const; - void fightOver(const CGHeroInstance *h, BattleResult *result) const; - void wantsFight(const CGHeroInstance *h, ui32 answer) const; + void heroAcceptsCreatures(const CGHeroInstance *h) const; }; @@ -482,25 +493,26 @@ class DLL_LINKAGE CGVisitableOPH : public CGObjectInstance //objects visitable o { public: std::set visitors; //ids of heroes who have visited this obj - si8 ttype; //tree type - used only by trees of knowledge: 0 - give level for free; 1 - take 2000 gold; 2 - take 10 gems + TResources treePrice; //used only by trees of knowledge: empty, 2000 gold, 10 gems const std::string & getHoverText() const override; void onHeroVisit(const CGHeroInstance * h) const override; void initObj() override; bool wasVisited (const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & visitors & ttype; + h & visitors & treePrice; } protected: void setPropertyDer(ui8 what, ui32 val) override;//synchr private: void onNAHeroVisit(ObjectInstanceID heroID, bool alreadyVisited) const; ///dialog callbacks - void treeSelected(ObjectInstanceID heroID, int resType, int resVal, TExpType expVal, ui32 result) const; + void treeSelected(ObjectInstanceID heroID, ui32 result) const; void schoolSelected(ObjectInstanceID heroID, ui32 which) const; void arenaSelected(ObjectInstanceID heroID, int primSkill) const; }; @@ -662,11 +674,9 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void onHeroLeave(const CGHeroInstance * h) const override; void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; protected: void setPropertyDer(ui8 what, ui32 val) override; -private: - ///dialog callbacks - void fightOver(const CGHeroInstance *h, BattleResult *result) const; }; class DLL_LINKAGE CGPandoraBox : public CArmedInstance { @@ -688,6 +698,8 @@ public: void initObj() override; void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -696,10 +708,8 @@ public: & abilities & abilityLevels & artifacts & spells & creatures; } protected: - void endBattle(const CGHeroInstance *h, BattleResult *result) const; void giveContents(const CGHeroInstance *h, bool afterBattle) const; private: - void open (const CGHeroInstance * h, ui32 accept) const; void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; }; @@ -725,6 +735,10 @@ private: class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map { + enum Action { + FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price + }; + public: ui32 identifier; //unique code for this monster (used in missions) si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) @@ -735,11 +749,14 @@ public: bool notGrowingTeam; //if true, number of units won't grow ui64 temppower; //used to handle fractional stack growth for tiny stacks + bool refusedJoining; void onHeroVisit(const CGHeroInstance * h) const override; const std::string & getHoverText() const override; void initObj() override; void newTurn() const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; struct DLL_LINKAGE RestoredCreature // info about merging stacks after battle back into one @@ -754,7 +771,8 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower & restore; + h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower; + h & refusedJoining & restore; } protected: void setPropertyDer(ui8 what, ui32 val) override; @@ -762,7 +780,6 @@ private: void fight(const CGHeroInstance *h) const; void flee( const CGHeroInstance * h ) const; - void endBattle(BattleResult *result) const; void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const; @@ -815,6 +832,7 @@ public: const std::string & getHoverText() const override; void newTurn() const override; void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; int checkDirection() const; //calculates the region of map where monster is placed void setObjToKill(); //remember creatures / heroes to kill after they are initialized @@ -888,7 +906,7 @@ public: ui8 getPassableness() const; void onHeroVisit(const CGHeroInstance * h) const override; - void fightOver (const CGHeroInstance *h, BattleResult *result) const; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -904,8 +922,9 @@ public: std::string message; void onHeroVisit(const CGHeroInstance * h) const override; - void fightForArt(ui32 agreed, const CGHeroInstance *h) const; - void endBattle(BattleResult *result, const CGHeroInstance *h) const; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; + void pick( const CGHeroInstance * h ) const; void initObj() override; @@ -924,10 +943,10 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; void collectRes(PlayerColor player) const; - void fightForRes(ui32 agreed, const CGHeroInstance *h) const; - void endBattle(BattleResult *result, const CGHeroInstance *h) const; template void serialize(Handler &h, const int version) { @@ -943,7 +962,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void initObj() override; - void chosen(int which, ObjectInstanceID heroID) const; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -972,12 +991,10 @@ class DLL_LINKAGE CGMine : public CArmedInstance public: Res::ERes producedResource; ui32 producedQuantity; - - void offerLeavingGuards(const CGHeroInstance *h) const; - void endBattle(BattleResult *result, PlayerColor attackingPlayer) const; - void fight(ui32 agreed, const CGHeroInstance *h) const; - + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; void flagMine(PlayerColor player) const; void newTurn() const override; @@ -1125,13 +1142,12 @@ public: CGBorderGuard() : IQuestObject(){}; void initObj() override; void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = NULL) const; void getRolloverText (MetaString &text, bool onHover) const; bool checkQuest (const CGHeroInstance * h) const; - void openGate(const CGHeroInstance *h, ui32 accept) const; - template void serialize(Handler &h, const int version) { h & static_cast(*this); @@ -1184,8 +1200,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; const std::string & getHoverText() const override; void initObj() override; - - void searchTomb(const CGHeroInstance *h, ui32 accept) const; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -1210,9 +1225,8 @@ class DLL_LINKAGE CBank : public CArmedInstance void newTurn() const override; bool wasVisited (PlayerColor player) const override; void onHeroVisit(const CGHeroInstance * h) const override; - - virtual void fightGuards (const CGHeroInstance *h, ui32 accept) const; - virtual void endBattle (const CGHeroInstance *h, const BattleResult *result) const; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -1231,8 +1245,7 @@ public: const std::string & getHoverText() const override; void newTurn() const override {}; //empty, no reset void onHeroVisit(const CGHeroInstance * h) const override; - - void endBattle (const CGHeroInstance *h, const BattleResult *result) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const OVERRIDE; template void serialize(Handler &h, const int version) { @@ -1270,7 +1283,7 @@ class DLL_LINKAGE CCartographer : public CPlayersVisited ///behaviour varies depending on surface and floor public: void onHeroVisit(const CGHeroInstance * h) const override; - void buyMap (const CGHeroInstance *h, ui32 accept) const; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const OVERRIDE; template void serialize(Handler &h, const int version) { diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 1373cc57c..61d836a67 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -98,3 +98,8 @@ CSpell * SpellID::toSpell() const //template std::ostream & operator << (std::ostream & os, BaseForID id); //template std::ostream & operator << (std::ostream & os, BaseForID id); + +bool PlayerColor::isValidPlayer() const +{ + return num < PLAYER_LIMIT_I; +} diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 1a4188f7d..3bb9f6c69 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -215,6 +215,8 @@ class PlayerColor : public BaseForID DLL_LINKAGE static const int PLAYER_LIMIT_I = 8; //player limit per map DLL_LINKAGE static const PlayerColor PLAYER_LIMIT; //player limit per map + DLL_LINKAGE bool isValidPlayer() const; //valid means < PLAYER_LIMIT (especially non-neutral) + friend class CGameInfoCallback; friend class CNonConstInfoCallback; }; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 84ce79bc6..96e2e4565 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -220,11 +220,11 @@ public: virtual void setHoverName(const CGObjectInstance * obj, MetaString * name)=0; virtual void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs=false)=0; virtual void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false)=0; - virtual void showBlockingDialog(BlockingDialog *iw, const CFunctionList &callback)=0; - virtual ui32 showBlockingDialog(BlockingDialog *iw) =0; //synchronous version of above //TODO: + virtual void showBlockingDialog(BlockingDialog *iw) =0; virtual void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits, const boost::function &cb) =0; //cb will be called when player closes garrison window virtual void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) =0; virtual void giveResource(PlayerColor player, Res::ERes which, int val)=0; + virtual void giveResources(PlayerColor player, TResources resources)=0; virtual void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) =0; virtual void takeCreatures(ObjectInstanceID objid, const std::vector &creatures) =0; @@ -247,11 +247,11 @@ public: virtual void showCompInfo(ShowInInfobox * comp)=0; virtual void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0; virtual void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0; - virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, boost::function cb = 0, const CGTownInstance *town = NULL)=0; //use hero=NULL for no hero - virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, boost::function cb = 0, bool creatureBank = false)=0; //if any of armies is hero, hero will be used - virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, boost::function cb = 0, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + virtual void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = NULL)=0; //use hero=NULL for no hero + virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used + virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle virtual void setAmount(ObjectInstanceID objid, ui32 val)=0; - virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 instant, PlayerColor asker = PlayerColor::NEUTRAL)=0; + virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL)=0; virtual void giveHeroBonus(GiveBonus * bonus)=0; virtual void setMovePoints(SetMovePoints * smp)=0; virtual void setManaPoints(ObjectInstanceID hid, int val)=0; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index ca61168e8..41a0c92bf 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -633,7 +633,7 @@ struct RemoveObject : public CPackForClient //500 }; struct TryMoveHero : public CPackForClient //501 { - TryMoveHero(){type = 501;humanKnows=false; attackedFrom = int3(-1, -1, -1);}; + TryMoveHero(){type = 501;humanKnows=false;}; void applyFirstCl(CClient *cl); void applyCl(CClient *cl); void applyGs(CGameState *gs); @@ -648,7 +648,7 @@ struct TryMoveHero : public CPackForClient //501 EResult result; //uses EResult int3 start, end; //h3m format boost::unordered_set fowRevealed; //revealed tiles - int3 attackedFrom; // Set when stepping into endangered tile. + boost::optional attackedFrom; // Set when stepping into endangered tile. bool humanKnows; //used locally during applying to client @@ -1168,7 +1168,7 @@ struct InfoWindow : public CPackForClient //103 - displays simple info window namespace ObjProperty { enum {OWNER = 1, BLOCKVIS = 2, PRIMARY_STACK_COUNT = 3, VISITORS = 4, VISITED = 5, ID = 6, AVAILABLE_CREATURE = 7, SUBID = 8, - MONSTER_COUNT = 10, MONSTER_POWER = 11, MONSTER_EXP = 12, MONSTER_RESTORE_TYPE = 13, + MONSTER_COUNT = 10, MONSTER_POWER = 11, MONSTER_EXP = 12, MONSTER_RESTORE_TYPE = 13, MONSTER_REFUSED_JOIN, //town-specific STRUCTURE_ADD_VISITING_HERO, STRUCTURE_CLEAR_VISITORS, STRUCTURE_ADD_GARRISONED_HERO, //changing buildings state @@ -1216,7 +1216,7 @@ struct HeroLevelUp : public Query//2000 void applyCl(CClient *cl); DLL_LINKAGE void applyGs(CGameState *gs); - ObjectInstanceID heroid; + const CGHeroInstance *hero; PrimarySkill::PrimarySkill primskill; ui8 level; std::vector skills; @@ -1225,7 +1225,7 @@ struct HeroLevelUp : public Query//2000 template void serialize(Handler &h, const int version) { - h & queryID & heroid & primskill & level & skills; + h & queryID & hero & primskill & level & skills; } }; @@ -1234,8 +1234,7 @@ struct CommanderLevelUp : public Query void applyCl(CClient *cl); DLL_LINKAGE void applyGs(CGameState *gs); - ObjectInstanceID heroid; //for commander attached to hero - StackLocation sl; //for commander not on the hero? + const CGHeroInstance *hero; std::vector skills; //0-5 - secondary skills, val-100 - special skill @@ -1243,7 +1242,7 @@ struct CommanderLevelUp : public Query template void serialize(Handler &h, const int version) { - h & queryID & heroid & sl & skills; + h & queryID & hero & skills; } }; @@ -1302,6 +1301,12 @@ struct BlockingDialog : public Query//2003 soundID = 0; }; + void addResourceComponents(TResources resources) + { + for(TResources::nziterator i(resources); i.valid(); i++) + components.push_back(Component(Component::RESOURCE, i->resType, i->resVal, 0)); + } + template void serialize(Handler &h, const int version) { h & queryID & text & components & player & flags & soundID; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index e7a7414fe..42f468c38 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -994,7 +994,7 @@ DLL_LINKAGE void SetHoverName::applyGs( CGameState *gs ) DLL_LINKAGE void HeroLevelUp::applyGs( CGameState *gs ) { - CGHeroInstance* h = gs->getHero(heroid); + CGHeroInstance* h = gs->getHero(hero->id); h->level = level; //specialty h->Updatespecialty(); @@ -1002,7 +1002,7 @@ DLL_LINKAGE void HeroLevelUp::applyGs( CGameState *gs ) DLL_LINKAGE void CommanderLevelUp::applyGs (CGameState *gs) { - CCommanderInstance * commander = gs->getHero(heroid)->commander; + CCommanderInstance * commander = gs->getHero(hero->id)->commander; assert (commander); commander->levelUp(); } diff --git a/lib/ResourceSet.cpp b/lib/ResourceSet.cpp index ac9295deb..e2cfe1cdd 100644 --- a/lib/ResourceSet.cpp +++ b/lib/ResourceSet.cpp @@ -92,7 +92,7 @@ void Res::ResourceSet::nziterator::advance() { do { - cur.resType++; + vstd::advance(cur.resType, +1); } while(cur.resType < GameConstants::RESOURCE_QUANTITY && !(cur.resVal=rs[cur.resType])); if(cur.resType >= GameConstants::RESOURCE_QUANTITY) @@ -102,8 +102,8 @@ void Res::ResourceSet::nziterator::advance() Res::ResourceSet::nziterator::nziterator(const ResourceSet &RS) : rs(RS) { - cur.resType = 0; - cur.resVal = rs[0]; + cur.resType = WOOD; + cur.resVal = rs[WOOD]; if(!valid()) advance(); diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index c3f3c3abf..f81c38f29 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -98,6 +98,14 @@ namespace Res return *this; } + ResourceSet operator-() const + { + ResourceSet ret; + for(int i = 0; i < size(); i++) + ret[i] = -at(i); + return ret; + } + // WARNING: comparison operators are used for "can afford" relation: a <= b means that foreach i a[i] <= b[i] // that doesn't work the other way: a > b doesn't mean that a cannot be afforded with b, it's still b can afford a // bool operator<(const ResourceSet &rhs) @@ -124,7 +132,8 @@ namespace Res { struct ResEntry { - TResourceCap resType, resVal; + Res::ERes resType; + TResourceCap resVal; } cur; const ResourceSet &rs; void advance(); diff --git a/lib/logging/CLogger.h b/lib/logging/CLogger.h index d06a56f0c..72f4b0cea 100644 --- a/lib/logging/CLogger.h +++ b/lib/logging/CLogger.h @@ -128,7 +128,7 @@ extern DLL_LINKAGE CLogger * logAi; /// RAII class for tracing the program execution. /// It prints "Leaving function XYZ" automatically when the object gets destructed. -class DLL_LINKAGE CTraceLogger +class DLL_LINKAGE CTraceLogger : boost::noncopyable { public: CTraceLogger(const CLogger * logger, const std::string & beginMessage, const std::string & endMessage); @@ -141,10 +141,20 @@ private: /// Macros for tracing the control flow of the application conveniently. If the LOG_TRACE macro is used it should be /// the first statement in the function. Logging traces via this macro have almost no impact when the trace is disabled. -#define LOG_TRACE(logger) if(logger->isTraceEnabled()) CTraceLogger ctl00(logger, boost::str(boost::format("Entering %s.") % BOOST_CURRENT_FUNCTION), \ - boost::str(boost::format("Leaving %s.") % BOOST_CURRENT_FUNCTION)) -#define LOG_TRACE_PARAMS(logger, formatStr, params) if(logger->isTraceEnabled()) CTraceLogger ctl00(logger, boost::str(boost::format("Entering %s: " + \ - std::string(formatStr) + ".") % BOOST_CURRENT_FUNCTION % params), boost::str(boost::format("Leaving %s.") % BOOST_CURRENT_FUNCTION)) +/// +#define RAII_TRACE(logger, onEntry, onLeave) \ + unique_ptr ctl00; \ + if(logger->isTraceEnabled()) \ + ctl00 = make_unique(logger, onEntry, onLeave); + +#define LOG_TRACE(logger) RAII_TRACE(logger, \ + boost::str(boost::format("Entering %s.") % BOOST_CURRENT_FUNCTION), \ + boost::str(boost::format("Leaving %s.") % BOOST_CURRENT_FUNCTION)) + + +#define LOG_TRACE_PARAMS(logger, formatStr, params) RAII_TRACE(logger, \ + boost::str(boost::format("Entering %s: " + std::string(formatStr) + ".") % BOOST_CURRENT_FUNCTION % params), \ + boost::str(boost::format("Leaving %s.") % BOOST_CURRENT_FUNCTION)) /* ---------------------------------------------------------------------------- */ /* Implementation/Detail classes, Private API */ diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index a2698e227..fd05b29c9 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -55,7 +55,7 @@ protected: }; /// The CMapUndoManager provides the functionality to save operations and undo/redo them. -class CMapUndoManager +class CMapUndoManager : boost::noncopyable { public: CMapUndoManager(); @@ -87,7 +87,7 @@ private: /// The map edit manager provides functionality for drawing terrain and placing /// objects on the map. -class DLL_LINKAGE CMapEditManager +class DLL_LINKAGE CMapEditManager : boost::noncopyable { public: CMapEditManager(CMap * map); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index 3a832b3f0..7222f9f24 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -56,7 +56,7 @@ class DLL_LINKAGE CMapGenOptions { public: /// The player settings class maps the player color, starting town and human player flag. - class CPlayerSettings + class DLL_LINKAGE CPlayerSettings { public: CPlayerSettings(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index d43e62e20..5b1cc20d4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -54,6 +54,7 @@ extern bool end2; #endif #define COMPLAIN_RET_IF(cond, txt) do {if(cond){complain(txt); return;}} while(0) +#define COMPLAIN_RET_FALSE_IF(cond, txt) do {if(cond){complain(txt); return false;}} while(0) #define COMPLAIN_RET(txt) {complain(txt); return false;} #define COMPLAIN_RETF(txt, FORMAT) {complain(boost::str(boost::format(txt) % FORMAT)); return false;} #define NEW_ROUND BattleNextRound bnr;\ @@ -123,19 +124,6 @@ void PlayerStatuses::addPlayer(PlayerColor player) players[player]; } -int PlayerStatuses::getQueriesCount(PlayerColor player) -{ - boost::unique_lock l(mx); - if(players.find(player) != players.end()) - { - return players[player].queries.size(); - } - else - { - return 0; - } -} - bool PlayerStatuses::checkFlag(PlayerColor player, bool PlayerStatus::*flag) { boost::unique_lock l(mx); @@ -148,6 +136,7 @@ bool PlayerStatuses::checkFlag(PlayerColor player, bool PlayerStatus::*flag) throw std::runtime_error("No such player!"); } } + void PlayerStatuses::setFlag(PlayerColor player, bool PlayerStatus::*flag, bool val) { boost::unique_lock l(mx); @@ -161,33 +150,6 @@ void PlayerStatuses::setFlag(PlayerColor player, bool PlayerStatus::*flag, bool } cv.notify_all(); } -void PlayerStatuses::addQuery(PlayerColor player, ui32 id) -{ - boost::unique_lock l(mx); - if(players.find(player) != players.end()) - { - players[player].queries.insert(id); - } - else - { - throw std::runtime_error("No such player!"); - } - cv.notify_all(); -} -void PlayerStatuses::removeQuery(PlayerColor player, ui32 id) -{ - boost::unique_lock l(mx); - if(players.find(player) != players.end()) - { - assert(vstd::contains(players[player].queries, id)); - players[player].queries.erase(id); - } - else - { - throw std::runtime_error("No such player!"); - } - cv.notify_all(); -} template void callWith(std::vector args, boost::function fun, ui32 which) @@ -198,15 +160,14 @@ void callWith(std::vector args, boost::function fun, ui32 which) void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill) { changeSecSkill(hero, skill, 1, 0); - levelUpHero(hero); + expGiven(hero); } void CGameHandler::levelUpHero(const CGHeroInstance * hero) { // required exp for at least 1 lvl-up hasn't been reached - if (hero->exp < VLC->heroh->reqExp(hero->level+1)) + if(!hero->gainsLevel()) { - afterBattleCallback(); return; } @@ -231,80 +192,28 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) sendAndApply(&sps); HeroLevelUp hlu; - hlu.heroid = hero->id; + hlu.hero = hero; hlu.primskill = static_cast(x); hlu.level = hero->level+1; + hlu.skills = hero->levelUpProposedSkills(); - //picking sec. skills for choice - std::set basicAndAdv, expert, none; - for(int i=0;isecSkills.size();i++) + if(hlu.skills.size() == 0) { - if(hero->secSkills[i].second < 3) - basicAndAdv.insert(hero->secSkills[i].first); - else - expert.insert(hero->secSkills[i].first); - none.erase(hero->secSkills[i].first); + sendAndApply(&hlu); + levelUpHero(hero); } - - //first offered skill - if(basicAndAdv.size()) + else if(hlu.skills.size() == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically { - SecondarySkill s = hero->type->heroClass->chooseSecSkill(basicAndAdv);//upgrade existing - hlu.skills.push_back(s); - basicAndAdv.erase(s); + sendAndApply(&hlu); + levelUpHero(hero, vstd::pickRandomElementOf (hlu.skills, rand)); } - else if(none.size() && hero->canLearnSkill()) + else if(hlu.skills.size() > 1) { - hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(none)); //give new skill - none.erase(hlu.skills.back()); - } - - //second offered skill - if(none.size() && hero->canLearnSkill()) //hero have free skill slot - { - hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(none)); //new skill - } - else if(basicAndAdv.size()) - { - hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(basicAndAdv)); //upgrade existing - } - - if (hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically - { - sendAndApply (&hlu); - if (hlu.skills.size()) - { - levelUpHero (hero, vstd::pickRandomElementOf (hlu.skills, rand)); - } - else //apply and send info - { - levelUpHero(hero); - } - } - else - { - if(hlu.skills.size() > 1) //apply and ask for secondary skill - { - boost::function callback = boost::function(boost::bind - (callWith, hlu.skills, - boost::function(boost::bind - (&CGameHandler::levelUpHero, this, hero, _1) ), _1)); - applyAndAsk(&hlu,hero->tempOwner,callback); //call levelUpHero when client responds - } - else if(hlu.skills.size() == 1) //apply, give only possible skill and send info - { - sendAndApply(&hlu); - levelUpHero(hero, hlu.skills.back()); - } - else //apply and send info - { - sendAndApply(&hlu); - levelUpHero(hero); - } + auto levelUpQuery = make_shared(hlu); + hlu.queryID = levelUpQuery->queryID; + queries.addQuery(levelUpQuery); + sendAndApply(&hlu); + //level up will be called on query reply } } @@ -388,12 +297,12 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill) scp.additionalInfo = skill; //unnormalized sendAndApply (&scp); } - levelUpCommander (c); + expGiven(hero); } void CGameHandler::levelUpCommander(const CCommanderInstance * c) { - if (c->experience < VLC->heroh->reqExp (c->level + 1)) + if (!c->gainsLevel()) { return; } @@ -401,7 +310,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c) auto hero = dynamic_cast(c->armyObj); if (hero) - clu.heroid = hero->id; + clu.hero = hero; else { complain ("Commander is not led by hero!"); @@ -418,44 +327,44 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c) int i = 100; BOOST_FOREACH (auto specialSkill, VLC->creh->skillRequirements) { - if (c->secondarySkills[specialSkill.second.first] == ECommander::MAX_SKILL_LEVEL && - c->secondarySkills[specialSkill.second.second] == ECommander::MAX_SKILL_LEVEL && - !vstd::contains (c->specialSKills, i)) + if (c->secondarySkills[specialSkill.second.first] == ECommander::MAX_SKILL_LEVEL + && c->secondarySkills[specialSkill.second.second] == ECommander::MAX_SKILL_LEVEL + && !vstd::contains (c->specialSKills, i)) clu.skills.push_back (i); ++i; } int skillAmount = clu.skills.size(); - if (hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically + if(!skillAmount) { sendAndApply(&clu); - if (clu.skills.size()) - { - levelUpCommander(c, vstd::pickRandomElementOf (clu.skills, rand)); - } - else //apply and send info - { - levelUpCommander(c); - } + levelUpCommander(c); } - else + else if(skillAmount == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically { - if (skillAmount > 1) //apply and ask for secondary skill - { - auto callback = boost::function(boost::bind(callWith, clu.skills, boost::function(boost::bind(&CGameHandler::levelUpCommander, this, c, _1)), _1)); - applyAndAsk (&clu, c->armyObj->tempOwner, callback); //call levelUpCommander when client responds - } - else if (skillAmount == 1) //apply, give only possible skill and send info - { - sendAndApply(&clu); - levelUpCommander(c, clu.skills.back()); - } - else //apply and send info - { - sendAndApply(&clu); - levelUpCommander(c); - } + sendAndApply(&clu); + levelUpCommander(c, vstd::pickRandomElementOf (clu.skills, rand)); } + else if(skillAmount > 1) //apply and ask for secondary skill + { + auto commanderLevelUp = make_shared(clu); + clu.queryID = commanderLevelUp->queryID; + queries.addQuery(commanderLevelUp); + sendAndApply(&clu); + } +} + +void CGameHandler::expGiven(const CGHeroInstance *hero) +{ + if(hero->gainsLevel()) + levelUpHero(hero); + else if(hero->commander && hero->commander->gainsLevel()) + levelUpCommander(hero->commander); + + //if(hero->commander && hero->level > hero->commander->level && hero->commander->gainsLevel()) +// levelUpCommander(hero->commander); +// else +// levelUpHero(hero); } void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs) @@ -470,17 +379,17 @@ void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill::Pr //only for exp - hero may level up if (which == PrimarySkill::EXPERIENCE) { - levelUpHero(hero); - if (hero->commander && hero->commander->alive) + if(hero->commander && hero->commander->alive) { SetCommanderProperty scp; scp.heroid = hero->id; scp.which = SetCommanderProperty::EXPERIENCE; scp.amount = val; sendAndApply (&scp); - levelUpCommander (hero->commander); CBonusSystemNode::treeHasChanged(); } + + expGiven(hero); } } @@ -500,111 +409,104 @@ void CGameHandler::changeSecSkill( const CGHeroInstance * hero, SecondarySkill w } } -void CGameHandler::startBattle( const CArmedInstance *armies[2], int3 tile, const CGHeroInstance *heroes[2], bool creatureBank, boost::function cb, const CGTownInstance *town /*= NULL*/ ) -{ - battleEndCallback = new boost::function(cb); - { - setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces - } - - runBattle(); -} - void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) { - bool duel = gs->initialOpts->mode == StartInfo::DUEL; - BattleResultsApplied resultsApplied; + LOG_TRACE(logGlobal); - const CArmedInstance *bEndArmy1 = gs->curB->belligerents[0]; - const CArmedInstance *bEndArmy2 = gs->curB->belligerents[1]; - resultsApplied.player1 = bEndArmy1->tempOwner; - resultsApplied.player2 = bEndArmy2->tempOwner; - const CGHeroInstance *victoriousHero = gs->curB->heroes[battleResult.data->winner]; - - int result = battleResult.get()->result; - - if(!duel) - { - //unblock engaged players - if(bEndArmy1->tempOwnertempOwner, &PlayerStatus::engagedIntoBattle, false); - if(bEndArmy2 && bEndArmy2->tempOwnertempOwner, &PlayerStatus::engagedIntoBattle, false); - } - - //end battle, remove all info, free memory + //Fill BattleResult structure with exp info giveExp(*battleResult.data); if (hero1) battleResult.data->exp[0] = hero1->calculateXp(battleResult.data->exp[0]);//scholar skill if (hero2) battleResult.data->exp[1] = hero2->calculateXp(battleResult.data->exp[1]); - PlayerColor sides[2]; - for(int i=0; i<2; ++i) + const CArmedInstance *bEndArmy1 = gs->curB->belligerents[0]; + const CArmedInstance *bEndArmy2 = gs->curB->belligerents[1]; + const BattleResult::EResult result = battleResult.get()->result; + + auto findBattleQuery = [this] () -> shared_ptr { - sides[i] = gs->curB->sides[i]; - } - PlayerColor loser = sides[!battleResult.data->winner]; + BOOST_FOREACH(auto &q, queries.allQueries()) + { + if(auto bq = std::dynamic_pointer_cast(q)) + if(bq->bi == gs->curB) + return bq; + } + return nullptr; + }; + + const auto battleQuery = findBattleQuery(); + if(!battleQuery) + logGlobal->errorStream() << "Cannot find battle query!"; + if(battleQuery != queries.topQuery(gs->curB->sides[0])) + complain("Player " + boost::lexical_cast(gs->curB->sides[0]) + " although in battle has no battle query at the top!"); + + battleQuery->result = *battleResult.data; + + //Check how many battle queries were created (number of players blocked by battle) + const int queriedPlayers = boost::count(queries.allQueries(), battleQuery); + + finishingBattle = make_unique(battleQuery, gs->initialOpts->mode == StartInfo::DUEL, queriedPlayers); + CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle ChangeSpells cs; //for Eagle Eye - if(victoriousHero) + if(finishingBattle->winnerHero) { - if(int eagleEyeLevel = victoriousHero->getSecSkillLevel(SecondarySkill::EAGLE_EYE)) + if(int eagleEyeLevel = finishingBattle->winnerHero->getSecSkillLevel(SecondarySkill::EAGLE_EYE)) { int maxLevel = eagleEyeLevel + 1; - double eagleEyeChance = victoriousHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE); + double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE); BOOST_FOREACH(const CSpell *sp, gs->curB->usedSpellsHistory[!battleResult.data->winner]) - if(sp->level <= maxLevel && !vstd::contains(victoriousHero->spells, sp->id) && rand() % 100 < eagleEyeChance) + if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && rand() % 100 < eagleEyeChance) cs.spells.insert(sp->id); } } - ConstTransitivePtr winnerHero = battleResult.data->winner != 0 ? hero2 : hero1; - ConstTransitivePtr loserHero = battleResult.data->winner != 0 ? hero1 : hero2; + std::vector arts; //display them in window - if (result == BattleResult::NORMAL && winnerHero) + if (result == BattleResult::NORMAL && finishingBattle->winnerHero) { - if (loserHero) + if (finishingBattle->loserHero) { - auto artifactsWorn = loserHero->artifactsWorn; //TODO: wrap it into a function, somehow (boost::variant -_-) + auto artifactsWorn = finishingBattle->loserHero->artifactsWorn; //TODO: wrap it into a function, somehow (boost::variant -_-) BOOST_FOREACH (auto artSlot, artifactsWorn) { MoveArtifact ma; - ma.src = ArtifactLocation (loserHero, artSlot.first); + ma.src = ArtifactLocation (finishingBattle->loserHero, artSlot.first); const CArtifactInstance * art = ma.src.getArt(); if (art && !art->artType->isBig() && art->artType->id != 0) // don't move war machines or locked arts (spellbook) { arts.push_back (art->artType->id); - ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero)); + ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero)); sendAndApply(&ma); } } - while (!loserHero->artifactsInBackpack.empty()) + while (!finishingBattle->loserHero->artifactsInBackpack.empty()) { //we assume that no big artifacts can be found MoveArtifact ma; - ma.src = ArtifactLocation (loserHero, + ma.src = ArtifactLocation (finishingBattle->loserHero, ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning const CArtifactInstance * art = ma.src.getArt(); arts.push_back (art->artType->id); - ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero)); + ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero)); sendAndApply(&ma); } - if (loserHero->commander) //TODO: what if commanders belong to no hero? + if (finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero? { - artifactsWorn = loserHero->commander->artifactsWorn; + artifactsWorn = finishingBattle->loserHero->commander->artifactsWorn; BOOST_FOREACH (auto artSlot, artifactsWorn) { MoveArtifact ma; - ma.src = ArtifactLocation (loserHero->commander.get(), artSlot.first); + ma.src = ArtifactLocation (finishingBattle->loserHero->commander.get(), artSlot.first); const CArtifactInstance * art = ma.src.getArt(); if (art && !art->artType->isBig()) { arts.push_back (art->artType->id); - ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero)); + ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero)); sendAndApply(&ma); } } @@ -621,7 +523,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer if (art && !art->artType->isBig()) { arts.push_back (art->artType->id); - ma.dst = ArtifactLocation (winnerHero, art->firstAvailableSlot(winnerHero)); + ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero)); sendAndApply(&ma); } } @@ -633,7 +535,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer if (arts.size()) //display loot { InfoWindow iw; - iw.player = winnerHero->tempOwner; + iw.player = finishingBattle->winnerHero->tempOwner; iw.text.addTxt (MetaString::GENERAL_TXT, 30); //You have captured enemy artifact @@ -656,12 +558,12 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer if(cs.spells.size()) { cs.learn = 1; - cs.hid = victoriousHero->id; + cs.hid = finishingBattle->winnerHero->id; InfoWindow iw; - iw.player = victoriousHero->tempOwner; + iw.player = finishingBattle->winnerHero->tempOwner; iw.text.addTxt(MetaString::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s - iw.text.addReplacement(victoriousHero->name); + iw.text.addReplacement(finishingBattle->winnerHero->name); std::ostringstream names; for(int i = 0; i < cs.spells.size(); i++) @@ -689,72 +591,97 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer sendAndApply(&cs); } + if(finishingBattle->duel) + return; + + cab1.takeFromArmy(this); cab2.takeFromArmy(this); //take casualties after battle is deleted + + //if one hero has lost we will erase him + if(battleResult.data->winner!=0 && hero1) + { + RemoveObject ro(hero1->id); + sendAndApply(&ro); + } + if(battleResult.data->winner!=1 && hero2) + { + RemoveObject ro(hero2->id); + sendAndApply(&ro); + } + + //give exp + if (battleResult.data->exp[0] && hero1) + changePrimSkill(hero1, PrimarySkill::EXPERIENCE, battleResult.data->exp[0]); + else if (battleResult.data->exp[1] && hero2) + changePrimSkill(hero2, PrimarySkill::EXPERIENCE, battleResult.data->exp[1]); + + queries.popIfTop(battleQuery); + + //--> continuation (battleAfterLevelUp) occurs after level-up queries are handled or on removing query (above) +} + +void CGameHandler::battleAfterLevelUp( const BattleResult &result ) +{ + LOG_TRACE(logGlobal); + + + finishingBattle->remainingBattleQueriesCount--; + logGlobal->traceStream() << "Decremented queries count to " << finishingBattle->remainingBattleQueriesCount; + + if(finishingBattle->remainingBattleQueriesCount > 0) + //Battle results will be hndled when all battle 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 = winnerHero ? winnerHero->calculateNecromancy(*battleResult.data) : CStackBasicDescriptor(); + const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(*battleResult.data) : CStackBasicDescriptor(); // Give raised units to winner and show dialog, if any were raised, // units will be given after casualities are taken - const SlotID necroSlot = raisedStack.type ? winnerHero->getSlotFor(raisedStack.type) : SlotID(); - - if(!duel) - { - cab1.takeFromArmy(this); cab2.takeFromArmy(this); //take casualties after battle is deleted - - //if one hero has lost we will erase him - if(battleResult.data->winner!=0 && hero1) - { - RemoveObject ro(hero1->id); - sendAndApply(&ro); - } - if(battleResult.data->winner!=1 && hero2) - { - RemoveObject ro(hero2->id); - sendAndApply(&ro); - } - - //give exp - if (battleResult.data->exp[0] && hero1) - changePrimSkill(hero1, PrimarySkill::EXPERIENCE, battleResult.data->exp[0]); - else if (battleResult.data->exp[1] && hero2) - changePrimSkill(hero2, PrimarySkill::EXPERIENCE, battleResult.data->exp[1]); - else - afterBattleCallback(); - } + const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID(); if (necroSlot != SlotID()) { - winnerHero->showNecromancyDialog(raisedStack); - addToSlot(StackLocation(winnerHero, necroSlot), raisedStack.type, raisedStack.count); - } + finishingBattle->winnerHero->showNecromancyDialog(raisedStack); + addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.type, raisedStack.count); + } + + BattleResultsApplied resultsApplied; + resultsApplied.player1 = finishingBattle->victor; + resultsApplied.player2 = finishingBattle->loser; sendAndApply(&resultsApplied); + setBattle(nullptr); - if(duel) + if(finishingBattle->duel) { CSaveFile resultFile("result.vdrst"); resultFile << *battleResult.data; return; } - if(visitObjectAfterVictory && winnerHero == hero1) + if(visitObjectAfterVictory && result.winner==0) { - visitObjectOnTile(*getTile(winnerHero->getPosition()), winnerHero); + logGlobal->traceStream() << "post-victory visit"; + visitObjectOnTile(*getTile(finishingBattle->winnerHero->getPosition()), finishingBattle->winnerHero); } visitObjectAfterVictory = false; - winLoseHandle(1<loser.getNum() | 1<victor.getNum()); //handle victory/loss of engaged players - if(result == BattleResult::SURRENDER || result == BattleResult::ESCAPE) //loser has escaped or surrendered + if(result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered { SetAvailableHeroes sah; - sah.player = loser; - sah.hid[0] = loserHero->subID; - if(result == 1) //retreat + sah.player = finishingBattle->loser; + sah.hid[0] = finishingBattle->loserHero->subID; + if(result.result == BattleResult::ESCAPE) //retreat { sah.army[0].clear(); - sah.army[0].setCreature(SlotID(0), loserHero->type->initialArmy[0].creature, 1); + sah.army[0].setCreature(SlotID(0), finishingBattle->loserHero->type->initialArmy[0].creature, 1); } - if(const CGHeroInstance *another = getPlayer(loser)->availableHeroes[1]) + if(const CGHeroInstance *another = getPlayer(finishingBattle->loser)->availableHeroes[1]) sah.hid[1] = another->subID; else sah.hid[1] = -1; @@ -762,16 +689,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer sendAndApply(&sah); } } -void CGameHandler::afterBattleCallback() //object interaction after leveling up is done -{ - if(battleEndCallback && *battleEndCallback) - { - boost::function *backup = battleEndCallback; - battleEndCallback = NULL; //we need to set it to NULL or else we have infinite recursion when after battle callback gives exp (eg Pandora Box with exp) - (*backup)(battleResult.data); - delete backup; - } -} + void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex) { bat.bsa.clear(); @@ -909,36 +827,34 @@ void CGameHandler::handleConnection(std::set players, CConnection & } //prepare struct informing that action was applied - PackageApplied applied; - applied.player = player; - applied.result = false; - applied.packType = packType; - applied.requestID = requestID; + auto sendPackageResponse = [&](bool succesfullyApplied) + { + PackageApplied applied; + applied.player = player; + applied.result = succesfullyApplied; + applied.packType = packType; + applied.requestID = requestID; + boost::unique_lock lock(*c.wmx); + c << &applied; + }; CBaseForGHApply *apply = applier->apps[packType]; //and appropriae applier object - if(isBlockedByQueries(pack, packType, player)) + if(isBlockedByQueries(pack, player)) { - complain(boost::str(boost::format("Player %d has to answer queries before attempting any further actions (count=%d)!") % player.getNum() % states.getQueriesCount(player))); - { - boost::unique_lock lock(*c.wmx); - c << &applied; - } + sendPackageResponse(false); } else if(apply) { - bool result = apply->applyOnGH(this,&c,pack, player); + const bool result = apply->applyOnGH(this,&c,pack, player); + if(!result) + complain("Got false in applying... that request must have been fishy!"); logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!"; - - //send confirmation that we've applied the package - applied.result = result; - { - boost::unique_lock lock(*c.wmx); - c << &applied; - } + sendPackageResponse(true); } else { logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!"; + sendPackageResponse(false); } vstd::clear_pointer(pack); @@ -1086,7 +1002,7 @@ CGameHandler::CGameHandler(void) applier = new CApplier; registerTypes3(*applier); visitObjectAfterVictory = false; - battleEndCallback = NULL; + queries.gh = this; } CGameHandler::~CGameHandler(void) @@ -1679,11 +1595,11 @@ void CGameHandler::setAmount(ObjectInstanceID objid, ui32 val) sendAndApply(&sop); } -bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, PlayerColor asker /*= 255*/ ) +bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker /*= 255*/ ) { const CGHeroInstance *h = getHero(hid); - if(!h || (asker != PlayerColor::NEUTRAL && (instant || h->getOwner() != gs->currentPlayer)) //not turn of that hero or player can't simply teleport hero (at least not with this function) + if(!h || (asker != PlayerColor::NEUTRAL && (teleporting || h->getOwner() != gs->currentPlayer)) //not turn of that hero or player can't simply teleport hero (at least not with this function) ) { logGlobal->errorStream() << "Illegal call to move hero!"; @@ -1691,7 +1607,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player } logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst; - int3 hmpos = dst + int3(-1,0,0); + const int3 hmpos = dst + int3(-1,0,0); if(!gs->map->isInTheMap(hmpos)) { @@ -1699,9 +1615,12 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player return false; } - TerrainTile t = gs->map->getTile(hmpos); - int cost = gs->getMovementCost(h, h->getPosition(false), CGHeroInstance::convertPosition(dst,false),h->movement); - int3 guardPos = gs->guardingCreaturePosition(hmpos); + const TerrainTile t = *gs->getTile(hmpos); + const int cost = gs->getMovementCost(h, h->getPosition(false), hmpos, h->movement); + const int3 guardPos = gs->guardingCreaturePosition(hmpos); + + const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; + const bool disembarking = h->boat && t.terType != ETerrainType::WATER && !t.blocked; //result structure for start - movement failed, no move points used TryMoveHero tmh; @@ -1721,14 +1640,14 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player && complain("Cannot move hero, destination tile is on water!")) || ((h->boat && t.terType != ETerrainType::WATER && t.blocked) && complain("Cannot disembark hero, tile is blocked!")) - || ( (distance(h->pos, dst) >= 1.5 && !instant) + || ( (distance(h->pos, dst) >= 1.5 && !teleporting) && complain("Tiles are not neighboring!")) || ( (h->inTownGarrison) && complain("Can not move garrisoned hero!")) - || ((h->movement < cost && dst != h->pos && !instant) + || ((h->movement < cost && dst != h->pos && !teleporting) && complain("Hero doesn't have any movement points left!")) - || (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) - && complain("Cannot move hero during the battle"))) + /*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) + && complain("Cannot move hero during the battle"))*/) { //send info about movement failure sendAndApply(&tmh); @@ -1747,10 +1666,41 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadious(), h->tempOwner, 1); }; - auto applyWithResult = [&](TryMoveHero::EResult result) -> bool + //use enums as parameters, because doMove(sth, true, false, true) is not readable + enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST}; + enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE}; + enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS}; + + auto doMove = [&](TryMoveHero::EResult result, EGuardLook lookForGuards, + EVisitDest visitDest, ELEaveTile leavingTile) -> bool { + LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->name % tmh.start % tmh.end); + + auto moveQuery = make_shared(tmh, h); + queries.addQuery(moveQuery); + + if(leavingTile == LEAVING_TILE) + leaveTile(); + tmh.result = result; sendAndApply(&tmh); + + if(lookForGuards == CHECK_FOR_GUARDS && isInTheMap(guardPos)) + { + tmh.attackedFrom = guardPos; + + const TerrainTile &guardTile = *gs->getTile(guardPos); + objectVisited(guardTile.visitableObjects.back(), h); + + moveQuery->visitDestAfterVictory = visitDest==VISIT_DEST; + } + else if(visitDest == VISIT_DEST) + { + visitObjectOnTile(t, h); + } + + queries.popIfTop(moveQuery); + logGlobal->traceStream() << "Hero " << h->name << " ends movement"; return result != TryMoveHero::FAILED; }; @@ -1759,73 +1709,34 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player { BOOST_FOREACH(CGObjectInstance *obj, t.visitableObjects) { - if(obj != h && obj->blockVisit && !(obj->getPassableness() & 1<tempOwner.getNum())) + if(obj != h && obj->blockVisit && !obj->passableFor(h->tempOwner)) { - - applyWithResult(TryMoveHero::BLOCKING_VISIT); - - //can't move to that tile but we visit object - objectVisited(t.visitableObjects.back(), h); - - logGlobal->traceStream() << "Blocking visit at " << hmpos; - return true; + return doMove(TryMoveHero::BLOCKING_VISIT, IGNORE_GUARDS, VISIT_DEST, REMAINING_ON_TILE); } } return false; }; - //hero enters the boat - if(!h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT) + + if(embarking) { - // blockingVisit test? - leaveTile(); tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false); - return applyWithResult(TryMoveHero::EMBARK); + return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE); //attack guards on embarking? In H3 creatures on water had no zone of control at all } - //hero leaves the boat - if(h->boat && t.terType != ETerrainType::WATER && !t.blocked) + if(disembarking) { - // blockingVisit test? - leaveTile(); - tmh.attackedFrom = guardPos; tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true); - - applyWithResult(TryMoveHero::DISEMBARK); - if (!tryAttackingGuard(guardPos, h)) - visitObjectOnTile(t, h); - return true; + return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); } - //standard movement - if(!instant) - { - tmh.movePoints = std::max(ui32(0), h->movement - cost); - - if(blockingVisit()) - return true; - - leaveTile(); - - tmh.attackedFrom = guardPos; - applyWithResult(TryMoveHero::SUCCESS); - - logGlobal->traceStream() << "Moved to " <traceStream() << "Movement end!"; - return true; - } - else //instant move - teleportation + if(teleporting) { if(blockingVisit()) // e.g. hero on the other side of teleporter return true; - leaveTile(); - applyWithResult(TryMoveHero::TELEPORTATION); + doMove(TryMoveHero::TELEPORTATION, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE); // visit town for town portal \ castle gates // do not use generic visitObjectOnTile to avoid double-teleporting @@ -1838,6 +1749,19 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player return true; } + + //still here? it is standard movement! + { + tmh.movePoints = h->movement >= cost + ? h->movement - cost + : 0; + + if(blockingVisit()) + return true; + + doMove(TryMoveHero::SUCCESS, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); + return true; + } } bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker/* = 255*/) @@ -1902,25 +1826,12 @@ void CGameHandler::setHoverName(const CGObjectInstance * obj, MetaString* name) sendAndApply(&shn); } -void CGameHandler::showBlockingDialog( BlockingDialog *iw, const CFunctionList &callback ) +void CGameHandler::showBlockingDialog( BlockingDialog *iw ) { - ask(iw,iw->player,callback); -} - -ui32 CGameHandler::showBlockingDialog( BlockingDialog *iw ) -{ - //TODO - - //gsm.lock(); - //int query = QID++; - //states.addQuery(player,query); - //sendToAllClients(iw); - //gsm.unlock(); - //ui32 ret = getQueryResult(iw->player, query); - //gsm.lock(); - //states.removeQuery(player, query); - //gsm.unlock(); - return 0; + auto dialogQuery = make_shared(*iw); + queries.addQuery(dialogQuery); + iw->queryID = dialogQuery->queryID; + sendToAllClients(iw); } void CGameHandler::giveResource(PlayerColor player, Res::ERes which, int val) //TODO: cap according to Bersy's suggestion @@ -1933,6 +1844,12 @@ void CGameHandler::giveResource(PlayerColor player, Res::ERes which, int val) // sendAndApply(&sr); } +void CGameHandler::giveResources(PlayerColor player, TResources resources) +{ + for(TResources::nziterator i(resources); i.valid(); i++) + giveResource(player, i->resType, i->resVal); +} + void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) { boost::function removeOrNot = 0; @@ -2026,13 +1943,12 @@ void CGameHandler::removeArtifact(const ArtifactLocation &al) ea.al = al; sendAndApply(&ea); } -void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, boost::function cb, const CGTownInstance *town) //use hero=NULL for no hero +void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, + const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, + const CGTownInstance *town) //use hero=NULL for no hero { engageIntoBattle(army1->tempOwner); engageIntoBattle(army2->tempOwner); - //block engaged players - if(army2->tempOwner < PlayerColor::PLAYER_LIMIT) - states.setFlag(army2->tempOwner,&PlayerStatus::engagedIntoBattle,true); static const CArmedInstance *armies[2]; armies[0] = army1; @@ -2041,20 +1957,26 @@ void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstanc heroes[0] = hero1; heroes[1] = hero2; - boost::thread(boost::bind(&CGameHandler::startBattle, this, armies, tile, heroes, creatureBank, cb, town)); + + setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces + + auto battleQuery = make_shared(gs->curB); + queries.addQuery(battleQuery); + + boost::thread(&CGameHandler::runBattle, this); } -void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, boost::function cb, bool creatureBank ) +void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank ) { - startBattleI(army1, army2, tile, + startBattlePrimary(army1, army2, tile, army1->ID == Obj::HERO ? static_cast(army1) : NULL, army2->ID == Obj::HERO ? static_cast(army2) : NULL, - creatureBank, cb); + creatureBank); } -void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, boost::function cb, bool creatureBank) +void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank) { - startBattleI(army1, army2, army2->visitablePos(), cb, creatureBank); + startBattleI(army1, army2, army2->visitablePos(), creatureBank); } void CGameHandler::changeSpells( const CGHeroInstance * hero, bool give, const std::set &spells ) @@ -2214,30 +2136,6 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) } } -void CGameHandler::prepareNewQuery(Query * queryPack, PlayerColor player, const boost::function &callback) -{ - boost::unique_lock lock(gsm); - logGlobal->debugStream() << "Creating a query for player " << player << " with ID=" << QID; - callbacks[QID] = callback; - states.addQuery(player, QID); - queryPack->queryID = QID; - QID++; -} - -void CGameHandler::applyAndAsk( Query * sel, PlayerColor player, boost::function &callback ) -{ - boost::unique_lock lock(gsm); - prepareNewQuery(sel, player, callback); - sendAndApply(sel); -} - -void CGameHandler::ask( Query * sel, PlayerColor player, const CFunctionList &callback ) -{ - boost::unique_lock lock(gsm); - prepareNewQuery(sel, player, callback); - sendToAllClients(sel); -} - void CGameHandler::sendToAllClients( CPackForClient * info ) { logGlobal->traceStream() << "Sending to all clients a package of type " << typeid(*info).name(); @@ -2267,14 +2165,6 @@ void CGameHandler::sendAndApply(CGarrisonOperationPack * info) winLoseHandle(); } -// void CGameHandler::sendAndApply( SetGarrisons * info ) -// { -// sendAndApply((CPackForClient*)info); -// if(gs->map->victoryCondition.condition == gatherTroop) -// for(std::map::const_iterator i = info->garrs.begin(); i != info->garrs.end(); i++) -// checkLossVictory(getObj(i->first)->tempOwner); -// } - void CGameHandler::sendAndApply( SetResource * info ) { sendAndApply((CPackForClient*)info); @@ -3275,19 +3165,18 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl bool CGameHandler::queryReply(ui32 qid, ui32 answer, PlayerColor player) { boost::unique_lock lock(gsm); - states.removeQuery(player, qid); - if(vstd::contains(callbacks,qid)) - { - CFunctionList callb = callbacks[qid]; - callbacks.erase(qid); - if(callb) - callb(answer); - } - else - { - complain("Unknown query reply!"); - return false; - } + + logGlobal->traceStream() << boost::format("Player %s attempts answering query %d with answer %d") % player % qid % answer; + + auto topQuery = queries.topQuery(player); + COMPLAIN_RET_FALSE_IF(!topQuery, "This player doesn't have any queries!"); + COMPLAIN_RET_FALSE_IF(topQuery->queryID != qid, "This player top query has different ID!"); + COMPLAIN_RET_FALSE_IF(!topQuery->endsByPlayerAnswer(), "This query cannot be ended by player's answer!"); + + if(auto dialogQuery = std::dynamic_pointer_cast(topQuery)) + dialogQuery->answer = answer; + + queries.popQuery(topQuery); return true; } @@ -4852,37 +4741,23 @@ bool CGameHandler::complain( const std::string &problem ) return true; } -ui32 CGameHandler::getQueryResult( PlayerColor player, int queryID ) -{ - //TODO: write - return 0; -} - void CGameHandler::showGarrisonDialog( ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits, const boost::function &cb ) { PlayerColor player = getOwner(hid); + auto upperArmy = dynamic_cast(getObj(upobj)); + auto lowerArmy = dynamic_cast(getObj(hid)); + + assert(upperArmy, lowerArmy); + + auto garrisonQuery = make_shared(upperArmy, lowerArmy); + queries.addQuery(garrisonQuery); + GarrisonDialog gd; gd.hid = hid; gd.objid = upobj; gd.removableUnits = removableUnits; - - { - boost::unique_lock lock(gsm); - prepareNewQuery(&gd, player); - - //register callback manually since we need to use query ID that's given in result of prepareNewQuery call - callbacks[gd.queryID] = [=](ui32 answer) - { - // Garrison callback calls the "original callback" and closes the exchange between objs. - if (cb) - cb(); - boost::unique_lock lockGsm(this->gsm); - allowedExchanges.erase(gd.queryID); - }; - - allowedExchanges[gd.queryID] = std::make_pair(upobj,hid); - sendAndApply(&gd); - } + gd.queryID = garrisonQuery->queryID; + sendAndApply(&gd); } void CGameHandler::showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) @@ -4894,30 +4769,15 @@ void CGameHandler::showThievesGuildWindow(PlayerColor player, ObjectInstanceID r sendAndApply(&ow); } -bool CGameHandler::isAllowedArrangePack(const ArrangeStacks *pack) -{ - return isAllowedExchangeForQuery(pack->id1, pack->id2); -} - -bool CGameHandler::isAllowedExchangeForQuery(ObjectInstanceID id1, ObjectInstanceID id2) { - boost::unique_lock lock(gsm); - for(auto i = allowedExchanges.cbegin(); i!=allowedExchanges.cend(); i++) - if((id1 == i->second.first && id2 == i->second.second) || - (id2 == i->second.first && id1 == i->second.second)) - return true; - - return false; -} - bool CGameHandler::isAllowedExchange( ObjectInstanceID id1, ObjectInstanceID id2 ) { if(id1 == id2) return true; - if (isAllowedExchangeForQuery(id1, id2)) - return true; - const CGObjectInstance *o1 = getObj(id1), *o2 = getObj(id2); + if(!o1 || !o2) + return true; //arranging stacks within an object should be always allowed + if (o1 && o2) { if(o1->ID == Obj::TOWN) @@ -4932,23 +4792,27 @@ bool CGameHandler::isAllowedExchange( ObjectInstanceID id1, ObjectInstanceID id2 if(t->visitingHero == o1 || t->garrisonHero == o1) return true; } - if(o1->ID == Obj::HERO && o2->ID == Obj::HERO - && distance(o1->pos, o2->pos) < 2) //hero stands on the same tile or on the neighbouring tiles + + //Ongoing garrison exchange + if(auto dialog = std::dynamic_pointer_cast(queries.topQuery(o1->tempOwner))) { - //TODO: it's workaround, we should check if first hero visited second and player hasn't closed exchange window - //(to block moving stacks for free [without visiting] between heroes) - return true; + if(dialog->exchangingArmies[0] == o1 && dialog->exchangingArmies[1] == o2) + return true; + + if(dialog->exchangingArmies[1] == o1 && dialog->exchangingArmies[0] == o2) + return true; } } - else //not exchanging between heroes, TODO: more sophisticated logic - { - return true; - } + return false; } void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h ) { + logGlobal->traceStream() << h->nodeName() << " visits " << obj->getHoverText(); + auto visitQuery = make_shared(obj, h, obj->visitablePos()); + queries.addQuery(visitQuery); //TODO real visit pos + HeroVisit hv; hv.obj = obj; hv.hero = h; @@ -4957,7 +4821,16 @@ void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInst obj->onHeroVisit(h); - hv.obj = NULL; //not necessary, moreover may have been deleted in the meantime + queries.popIfTop(visitQuery); //visit ends here if no queries were created +} + +void CGameHandler::objectVisitEnded(const CObjectVisitQuery &query) +{ + logGlobal->traceStream() << query.visitingHero->nodeName() << " visit ends.\n"; + + HeroVisit hv; + hv.obj = nullptr; //not necessary, moreover may have been deleted in the meantime + hv.hero = query.visitingHero; hv.starting = false; sendAndApply(&hv); } @@ -5014,9 +4887,6 @@ bool CGameHandler::buildBoat( ObjectInstanceID objid ) void CGameHandler::engageIntoBattle( PlayerColor player ) { - if(vstd::contains(states.players, player)) - states.setFlag(player,&PlayerStatus::engagedIntoBattle,true); - //notify interfaces PlayerBlocked pb; pb.player = player; @@ -5632,17 +5502,6 @@ void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance } } -bool CGameHandler::tryAttackingGuard(const int3 &guardPos, const CGHeroInstance * h) -{ - if(!gs->map->isInTheMap(guardPos)) - return false; - - const TerrainTile &guardTile = gs->map->getTile(guardPos); - objectVisited(guardTile.visitableObjects.back(), h); - visitObjectAfterVictory = true; - return true; -} - bool CGameHandler::sacrificeCreatures(const IMarket *market, const CGHeroInstance *hero, SlotID slot, ui32 count) { int oldCount = hero->getStackCount(slot); @@ -6185,22 +6044,6 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID) } } -bool CGameHandler::isBlockedByQueries(const CPack *pack, int packType, PlayerColor player) -{ - //it's always legal to send query reply (we'll check later if it makes sense) - if(packType == typeList.getTypeID()) - return false; - - if(packType == typeList.getTypeID() && isAllowedArrangePack((const ArrangeStacks*)pack)) - return false; - - //if there are no queries, nothing is blocking - if(states.getQueriesCount(player) == 0) - return false; - - return true; //block package -} - void CGameHandler::removeObstacle(const CObstacleInstance &obstacle) { ObstaclesRemoved obsRem; @@ -6218,6 +6061,26 @@ void CGameHandler::synchronizeArtifactHandlerLists() sendAndApply(&uahl); } +bool CGameHandler::isValidObject(const CGObjectInstance *obj) const +{ + return vstd::contains(gs->map->objects, obj); +} + +bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player) +{ + if(dynamic_cast(pack)) + return false; + + auto query = queries.topQuery(player); + if(query && query->blocksPack(pack)) + { + complain(boost::str(boost::format("Player %s has to answer queries before attempting any further actions. Top query is %s!") % player % query->toString())); + return true; + } + + return false; +} + CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat) { heroWithDeadCommander = ObjectInstanceID(); @@ -6270,3 +6133,23 @@ void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh) gh->sendAndApply (&scp); } } + +CGameHandler::FinishingBattleHelper::FinishingBattleHelper(shared_ptr Query, bool Duel, int RemainingBattleQueriesCount) +{ + assert(Query->result); + assert(Query->bi); + auto &result = *Query->result; + auto &info = *Query->bi; + + winnerHero = result.winner != 0 ? info.heroes[1] : info.heroes[0]; + loserHero = result.winner != 0 ? info.heroes[0] : info.heroes[1]; + victor = info.sides[result.winner]; + loser = info.sides[!result.winner]; + duel = Duel; + remainingBattleQueriesCount = RemainingBattleQueriesCount; +} + +CGameHandler::FinishingBattleHelper::FinishingBattleHelper() +{ + winnerHero = loserHero = nullptr; +} diff --git a/server/CGameHandler.h b/server/CGameHandler.h index b14509d3b..e64ba94f3 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -7,6 +7,8 @@ #include "../lib/IGameCallback.h" #include "../lib/BattleAction.h" #include "../lib/NetPacks.h" +#include "CQuery.h" + /* * CGameHandler.h, part of VCMI engine @@ -41,13 +43,12 @@ extern boost::mutex gsm; struct PlayerStatus { - bool makingTurn, engagedIntoBattle; - std::set queries; + bool makingTurn; - PlayerStatus():makingTurn(false),engagedIntoBattle(false){}; + PlayerStatus():makingTurn(false){}; template void serialize(Handler &h, const int version) { - h & makingTurn & engagedIntoBattle & queries; + h & makingTurn; } }; class PlayerStatuses @@ -59,11 +60,8 @@ public: void addPlayer(PlayerColor player); PlayerStatus operator[](PlayerColor player); - int getQueriesCount(PlayerColor player); //returns 0 if there is no such player bool checkFlag(PlayerColor player, bool PlayerStatus::*flag); void setFlag(PlayerColor player, bool PlayerStatus::*flag, bool val); - void addQuery(PlayerColor player, ui32 id); - void removeQuery(PlayerColor player, ui32 id); template void serialize(Handler &h, const int version) { h & players; @@ -85,7 +83,6 @@ class CGameHandler : public IGameCallback, CBattleInfoCallback { private: void makeStackDoNothing(const CStack * next); - bool isAllowedExchangeForQuery(ObjectInstanceID id1, ObjectInstanceID id2); public: CVCMIServer *s; std::map connections; //player color -> connection to client with interface of that player @@ -95,25 +92,22 @@ public: //queries stuff boost::recursive_mutex gsm; ui32 QID; + Queries queries; //TODO get rid of cfunctionlist (or similar) and use serialziable callback structure std::map > callbacks; //query id => callback function - for selection and yes/no dialogs - std::map > allowedExchanges; - bool isBlockedByQueries(const CPack *pack, int packType, PlayerColor player); + bool isValidObject(const CGObjectInstance *obj) const; + bool isBlockedByQueries(const CPack *pack, PlayerColor player); bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2); - bool isAllowedArrangePack(const ArrangeStacks *pack); void giveSpells(const CGTownInstance *t, const CGHeroInstance *h); int moveStack(int stack, BattleHex dest); //returned value - travelled distance - void startBattle(const CArmedInstance *armies[2], int3 tile, const CGHeroInstance *heroes[2], bool creatureBank, boost::function cb, const CGTownInstance *town = NULL); //use hero=NULL for no hero void runBattle(); void checkLossVictory(PlayerColor player); void winLoseHandle(ui8 players=255); //players: bit field - colours of players to be checked; default: all void getLossVicMessage(PlayerColor player, si8 standard, bool victory, InfoWindow &out) const; ////used only in endBattle - don't touch elsewhere - boost::function * battleEndCallback; - //const CArmedInstance * bEndArmy1, * bEndArmy2; bool visitObjectAfterVictory; // void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle @@ -137,11 +131,12 @@ public: void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs=false) OVERRIDE; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) OVERRIDE; //void showInfoDialog(InfoWindow *iw) OVERRIDE; - void showBlockingDialog(BlockingDialog *iw, const CFunctionList &callback) OVERRIDE; - ui32 showBlockingDialog(BlockingDialog *iw) OVERRIDE; //synchronous version of above + + void showBlockingDialog(BlockingDialog *iw) OVERRIDE; void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits, const boost::function &cb) OVERRIDE; void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) OVERRIDE; void giveResource(PlayerColor player, Res::ERes which, int val) OVERRIDE; + void giveResources(PlayerColor player, TResources resources) OVERRIDE; void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) OVERRIDE; void takeCreatures(ObjectInstanceID objid, const std::vector &creatures) OVERRIDE; @@ -165,11 +160,11 @@ public: void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) OVERRIDE; void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) OVERRIDE; //bool removeArtifact(const CArtifact* art, int hid) OVERRIDE; - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, boost::function cb = 0, const CGTownInstance *town = NULL) OVERRIDE; //use hero=NULL for no hero - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, boost::function cb = 0, bool creatureBank = false) OVERRIDE; //if any of armies is hero, hero will be used - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, boost::function cb = 0, bool creatureBank = false) OVERRIDE; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle//void startBattleI(int heroID, CCreatureSet army, int3 tile, boost::function cb) OVERRIDE; //for hero<=>neutral army + void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = NULL) OVERRIDE; //use hero=NULL for no hero + void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) OVERRIDE; //if any of armies is hero, hero will be used + void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) OVERRIDE; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle//void startBattleI(int heroID, CCreatureSet army, int3 tile, boost::function cb) OVERRIDE; //for hero<=>neutral army void setAmount(ObjectInstanceID objid, ui32 val) OVERRIDE; - bool moveHero(ObjectInstanceID hid, int3 dst, ui8 instant, PlayerColor asker = PlayerColor::NEUTRAL) OVERRIDE; + bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) OVERRIDE; void giveHeroBonus(GiveBonus * bonus) OVERRIDE; void setMovePoints(SetMovePoints * smp) OVERRIDE; void setManaPoints(ObjectInstanceID hid, int val) OVERRIDE; @@ -179,7 +174,6 @@ public: ////////////////////////////////////////////////////////////////////////// void useScholarSkill(ObjectInstanceID hero1, ObjectInstanceID hero2); void setPortalDwelling(const CGTownInstance * town, bool forced, bool clear); - bool tryAttackingGuard(const int3 &guardPos, const CGHeroInstance * h); void visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h); bool teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker = PlayerColor::NEUTRAL); void vistiCastleObjects (const CGTownInstance *t, const CGHeroInstance *h); @@ -187,7 +181,8 @@ public: void levelUpHero(const CGHeroInstance * hero);//initial call - check if hero have remaining levelups & handle them void levelUpCommander (const CCommanderInstance * c, int skill); //secondary skill 1 to 6, special skill : skill - 100 void levelUpCommander (const CCommanderInstance * c); - void afterBattleCallback(); // called after level-ups are finished + + void expGiven(const CGHeroInstance *hero); //triggers needed level-ups, handles also commander of this hero ////////////////////////////////////////////////////////////////////////// void commitPackage(CPackForClient *pack) OVERRIDE; @@ -233,6 +228,7 @@ public: void handleTownEvents(CGTownInstance *town, NewTurn &n); 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); bool castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos); @@ -240,15 +236,11 @@ public: template void serialize(Handler &h, const int version) { - h & QID & states; + h & QID & states & finishingBattle; } - ui32 getQueryResult(PlayerColor player, int queryID); void sendMessageToAll(const std::string &message); void sendMessageTo(CConnection &c, const std::string &message); - void applyAndAsk(Query * sel, PlayerColor player, boost::function &callback); - void prepareNewQuery(Query * queryPack, PlayerColor player, const boost::function &callback = 0); //generates unique query id and writes it to the pack; blocks the player till query is answered (then callback is called) - void ask(Query * sel, PlayerColor player, const CFunctionList &callback); void sendToAllClients(CPackForClient * info); void sendAndApply(CPackForClient * info); void applyAndSend(CPackForClient * info); @@ -257,6 +249,28 @@ public: void sendAndApply(SetResources * info); void sendAndApply(NewStructures * info); + struct FinishingBattleHelper + { + FinishingBattleHelper(); + FinishingBattleHelper(shared_ptr Query, bool Duel, int RemainingBattleQueriesCount); + + shared_ptr query; + const CGHeroInstance *winnerHero, *loserHero; + PlayerColor victor, loser; + bool duel; + + int remainingBattleQueriesCount; + + template void serialize(Handler &h, const int version) + { + h & query & winnerHero & loserHero & victor & loser & duel & remainingBattleQueriesCount; + } + }; + + unique_ptr finishingBattle; + + void battleAfterLevelUp(const BattleResult &result); + void run(bool resume); void newTurn(); void handleAttackBeforeCasting (const BattleAttack & bat); diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 0aae925fc..211087923 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories(${Boost_INCLUDE_DIRS}) set(server_SRCS CGameHandler.cpp + CQuery.cpp CVCMIServer.cpp NetPacksServer.cpp ) diff --git a/server/CQuery.cpp b/server/CQuery.cpp new file mode 100644 index 000000000..ee82a64da --- /dev/null +++ b/server/CQuery.cpp @@ -0,0 +1,342 @@ +#include "StdInc.h" +#include "CQuery.h" +#include "CGameHandler.h" +#include "..\lib\BattleState.h" + +boost::mutex Queries::mx; + +template +std::string formatContainer(const Container &c, std::string delimeter=", ", std::string opener="(", std::string closer=")") +{ + std::string ret = opener; + auto itr = boost::begin(c); + if(itr != boost::end(c)) + { + ret += boost::lexical_cast(*itr); + while(++itr != boost::end(c)) + { + ret += delimeter; + ret += boost::lexical_cast(*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(void) +{ + boost::unique_lock l(Queries::mx); + + static TQueryID QID = 1; + + queryID = QID++; + logGlobal->traceStream() << "Created a new query with id " << queryID; +} + + +CQuery::~CQuery(void) +{ + logGlobal->traceStream() << "Destructed the query with id " << queryID; +} + +void CQuery::addPlayer(PlayerColor color) +{ + if(color.isValidPlayer()) + { + players.push_back(color); + } +} + +std::string CQuery::toString() const +{ + std::string ret = boost::str(boost::format("A query of type %s and qid=%d affecting players %s") % typeid(*this).name() % queryID % formatContainer(players)); + return ret; +} + +bool CQuery::endsByPlayerAnswer() const +{ + return false; +} + +void CQuery::onRemoval(CGameHandler *gh, PlayerColor color) +{ +} + +bool CQuery::blocksPack(const CPack *pack) const +{ + return false; +} + +void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const +{ +} + +void CQuery::onExposure(CGameHandler *gh, QueryPtr topQuery) +{ + gh->queries.popQuery(*this); +} + +CObjectVisitQuery::CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile) + : visitedObject(Obj), visitingHero(Hero), tile(Tile) +{ + 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(CGameHandler *gh, PlayerColor color) +{ + gh->objectVisitEnded(*this); +} + +void CObjectVisitQuery::onExposure(CGameHandler *gh, QueryPtr topQuery) +{ + //Object may have been removed and deleted. + if(gh->isValidObject(visitedObject)) + topQuery->notifyObjectAboutRemoval(*this); + + gh->queries.popQuery(*this); +} + +void Queries::popQuery(PlayerColor player, QueryPtr query) +{ + LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query); + if(topQuery(player) != query) + { + logGlobal->traceStream() << "Cannot remove, not a top!"; + return; + } + + queries[player] -= query; + auto nextQuery = topQuery(player); + + query->onRemoval(gh, player); + + //Exposure on query below happens only if removal didnt trigger any new query + if(nextQuery && nextQuery == topQuery(player)) + { + nextQuery->onExposure(gh, query); + } +} + +void Queries::popQuery(const CQuery &query) +{ + LOG_TRACE_PARAMS(logGlobal, "query='%s'", query); + + assert(query.players.size()); + BOOST_FOREACH(auto player, query.players) + { + auto top = topQuery(player); + if(top.get() == &query) + popQuery(top); + else + logGlobal->traceStream() << "Cannot remove query " << query; + } +} + +void Queries::popQuery(QueryPtr query) +{ + BOOST_FOREACH(auto player, query->players) + popQuery(player, query); +} + +void Queries::addQuery(QueryPtr query) +{ + BOOST_FOREACH(auto player, query->players) + addQuery(player, query); +} + +void Queries::addQuery(PlayerColor player, QueryPtr query) +{ + LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query); + queries[player].push_back(query); +} + +QueryPtr Queries::topQuery(PlayerColor player) +{ + return vstd::backOrNull(queries[player]); +} + +void Queries::popIfTop(QueryPtr query) +{ + popIfTop(*query); +} + +void Queries::popIfTop(const CQuery &query) +{ + BOOST_FOREACH(PlayerColor color, query.players) + if(topQuery(color).get() == &query) + popQuery(color, topQuery(color)); +} + +std::vector> Queries::allQueries() const +{ + std::vector> ret; + BOOST_FOREACH(auto &playerQueries, queries) + BOOST_FOREACH(auto &query, playerQueries.second) + ret.push_back(query); + + return ret; +} + +std::vector> Queries::allQueries() +{ + //TODO code duplication with const function :( + std::vector> ret; + BOOST_FOREACH(auto &playerQueries, queries) + BOOST_FOREACH(auto &query, playerQueries.second) + ret.push_back(query); + + return ret; +} + +void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const +{ + assert(result); + objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result); +} + +CBattleQuery::CBattleQuery(const BattleInfo *Bi) +{ + belligerents[0] = Bi->belligerents[0]; + belligerents[1] = Bi->belligerents[1]; + + bi = Bi; + + BOOST_FOREACH(PlayerColor side, bi->sides) + addPlayer(side); +} + +CBattleQuery::CBattleQuery() +{ + +} + +bool CBattleQuery::blocksPack(const CPack *pack) const +{ + return !dynamic_cast(pack) && !dynamic_cast(pack); +} + +void CBattleQuery::onRemoval(CGameHandler *gh, PlayerColor color) +{ + gh->battleAfterLevelUp(*result); +} + +void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const +{ + objectVisit.visitedObject->garrisonDialogClosed(objectVisit.visitingHero); +} + +CGarrisonDialogQuery::CGarrisonDialogQuery(const CArmedInstance *up, const CArmedInstance *down) +{ + exchangingArmies[0] = up; + exchangingArmies[1] = down; + + addPlayer(up->tempOwner); + addPlayer(down->tempOwner); +} + +bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const +{ + if(auto stacks = dynamic_cast(pack)) + { + std::set ourIds; + ourIds.insert(this->exchangingArmies[0]->id); + ourIds.insert(this->exchangingArmies[1]->id); + + return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2); + } + + return CDialogQuery::blocksPack(pack); +} + +void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const +{ + assert(answer); + objectVisit.visitedObject->blockingDialogAnswered(objectVisit.visitingHero, *answer); +} + +CBlockingDialogQuery::CBlockingDialogQuery(const BlockingDialog &bd) +{ + this->bd = bd; + addPlayer(bd.player); +} + +CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(const HeroLevelUp &Hlu) +{ + hlu = Hlu; + addPlayer(hlu.hero->tempOwner); +} + +void CHeroLevelUpDialogQuery::onRemoval(CGameHandler *gh, PlayerColor color) +{ + assert(answer); + logGlobal->traceStream() << "Completing hero level-up query. " << hlu.hero->getHoverText() << " gains skill " << answer; + gh->levelUpHero(hlu.hero, hlu.skills[*answer]); +} + +CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(const CommanderLevelUp &Clu) +{ + clu = Clu; + addPlayer(clu.hero->tempOwner); +} + +void CCommanderLevelUpDialogQuery::onRemoval(CGameHandler *gh, PlayerColor color) +{ + assert(answer); + logGlobal->traceStream() << "Completing commander level-up query. Commander of hero " << clu.hero->getHoverText() << " gains skill " << answer; + gh->levelUpCommander(clu.hero->commander, clu.skills[*answer]); +} + +bool CDialogQuery::endsByPlayerAnswer() const +{ + return true; +} + +bool CDialogQuery::blocksPack(const CPack *pack) const +{ + //We accept only query replies from correct player + if(auto reply = dynamic_cast(pack)) + { + return !vstd::contains(players, reply->player); + } + + return true; +} + +CHeroMovementQuery::CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory) + : tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero) +{ + players.push_back(hero->tempOwner); +} + +void CHeroMovementQuery::onExposure(CGameHandler *gh, 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->traceStream() << "Hero " << hero->name << " after victory over guard finishes visit to " << tmh.end; + //finish movement + visitDestAfterVictory = false; + gh->visitObjectOnTile(*gh->getTile(CGHeroInstance::convertPosition(tmh.end, false)), hero); + } + + gh->queries.popIfTop(*this); +} diff --git a/server/CQuery.h b/server/CQuery.h new file mode 100644 index 000000000..ef3197624 --- /dev/null +++ b/server/CQuery.h @@ -0,0 +1,177 @@ +#pragma once +#include "..\lib\GameConstants.h" +#include "..\lib\int3.h" +#include "..\lib\NetPacks.h" + +class CGObjectInstance; +class CGHeroInstance; +class CArmedInstance; +struct CPack; +class CGameHandler; +class CObjectVisitQuery; +struct TryMoveHero; +class CQuery; + +typedef si32 TQueryID; +typedef shared_ptr QueryPtr; + +// 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 +{ +protected: + void addPlayer(PlayerColor color); +public: + std::vector players; //players that are affected (often "blocked") by query + TQueryID queryID; + + CQuery(void); + + + 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 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; + + virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const; + + virtual ~CQuery(void); + + + template void serialize(Handler &h, const int version) + { + h & players & queryID; + } +}; + +std::ostream &operator<<(std::ostream &out, const CQuery &query); +std::ostream &operator<<(std::ostream &out, QueryPtr query); + +//Created when hero visits object. +//Removed when query above is resolved (or immediately after visit if no queries were created) +class CObjectVisitQuery : public CQuery +{ +public: + const CGObjectInstance *visitedObject; + const CGHeroInstance *visitingHero; + int3 tile; //may be different than hero pos -> eg. visit via teleport + + CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile); + + virtual bool blocksPack(const CPack *pack) const OVERRIDE; + virtual void onRemoval(CGameHandler *gh, PlayerColor color) OVERRIDE; + virtual void onExposure(CGameHandler *gh, QueryPtr topQuery) OVERRIDE; +}; + +class CBattleQuery : public CQuery +{ +public: + std::array belligerents; + + const BattleInfo *bi; + boost::optional result; + + CBattleQuery(); + CBattleQuery(const BattleInfo *Bi); //TODO + virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const OVERRIDE; + virtual bool blocksPack(const CPack *pack) const OVERRIDE; + virtual void onRemoval(CGameHandler *gh, PlayerColor color) OVERRIDE; +}; + +//Created when hero attempts move and something happens +//(not necessarily position change, could be just an object interaction). +class CHeroMovementQuery : public CQuery +{ +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(CGameHandler *gh, QueryPtr topQuery); + + CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory = false); +}; + +class CDialogQuery : public CQuery +{ +public: + boost::optional answer; + virtual bool endsByPlayerAnswer() const OVERRIDE; + virtual bool blocksPack(const CPack *pack) const OVERRIDE; +}; + +class CGarrisonDialogQuery : public CDialogQuery +{ +public: + std::array exchangingArmies; + + CGarrisonDialogQuery(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(const BlockingDialog &bd); + + virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const OVERRIDE; +}; + +class CHeroLevelUpDialogQuery : public CDialogQuery +{ +public: + CHeroLevelUpDialogQuery(const HeroLevelUp &Hlu); + + virtual void onRemoval(CGameHandler *gh, PlayerColor color) OVERRIDE; + + HeroLevelUp hlu; +}; + + +class CCommanderLevelUpDialogQuery : public CDialogQuery +{ +public: + CCommanderLevelUpDialogQuery(const CommanderLevelUp &Clu); + + virtual void onRemoval(CGameHandler *gh, PlayerColor color) OVERRIDE; + + CommanderLevelUp clu; +}; + +struct Queries +{ +private: + void addQuery(PlayerColor player, QueryPtr query); + void popQuery(PlayerColor player, QueryPtr query); + + std::map> queries; //player => stack of queries + +public: + CGameHandler *gh; + 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> allQueries() const; + std::vector> allQueries(); + //void removeQuery + +}; \ No newline at end of file diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index ca94b32f2..18f33592a 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -69,8 +69,8 @@ bool EndTurn::applyGh( CGameHandler *gh ) { PlayerColor player = GS(gh)->currentPlayer; ERROR_IF_NOT(player); - if(gh->states.checkFlag(player, &PlayerStatus::engagedIntoBattle)) - COMPLAIN_AND_RETURN("Cannot end turn when in battle!"); + if(gh->queries.topQuery(player)) + COMPLAIN_AND_RETURN("Cannot end turn before resolving queries!"); gh->states.setFlag(GS(gh)->currentPlayer,&PlayerStatus::makingTurn,false); return true; diff --git a/server/VCMI_server.vcxproj b/server/VCMI_server.vcxproj index ff7d71891..26b3c1424 100644 --- a/server/VCMI_server.vcxproj +++ b/server/VCMI_server.vcxproj @@ -145,6 +145,7 @@ + @@ -158,6 +159,7 @@ +