diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 8957b226e..ab51569b1 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -907,6 +907,7 @@ void VCAI::saveGame(COSer &h, const int version) { LOG_TRACE_PARAMS(logAi, "version '%i'", version); NET_EVENT_HANDLER; + validateVisitableObjs(); CAdventureAI::saveGame(h, version); serializeInternal(h, version); } @@ -1018,6 +1019,11 @@ void VCAI::makeTurnInternal() boost::sort (hero.second, isCloser); for (auto obj : hero.second) { + if(!obj || !obj->defInfo || !cb->getObj(obj->id)) + { + logAi->errorStream() << "Error: there is wrong object on list for hero " << hero.first->name; + continue; + } striveToGoal (CGoal(VISIT_TILE).sethero(hero.first).settile(obj->visitablePos())); } } @@ -1102,7 +1108,7 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) void VCAI::moveCreaturesToHero(const CGTownInstance * t) { - if(t->visitingHero && t->armedGarrison()) + if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner) { pickBestCreatures (t->visitingHero, t); } @@ -1110,6 +1116,13 @@ void VCAI::moveCreaturesToHero(const CGTownInstance * t) bool VCAI::canGetArmy (const CGHeroInstance * army, const CGHeroInstance * source) { //TODO: merge with pickBestCreatures + if(army->tempOwner != source->tempOwner) + { + logAi->errorStream() << "Why are we even considering exchange between heroes from different players?"; + return false; + } + + const CArmedInstance *armies[] = {army, source}; int armySize = 0; //we calculate total strength for each creature type available in armies @@ -1438,6 +1451,7 @@ void VCAI::wander(HeroPtr h) { while(1) { + validateVisitableObjs(); std::vector dests; range::copy(reservedHeroesMap[h], std::back_inserter(dests)); if (!dests.size()) @@ -1598,21 +1612,41 @@ void VCAI::reserveObject(HeroPtr h, const CGObjectInstance *obj) { reservedObjs.push_back(obj); reservedHeroesMap[h].push_back(obj); + logAi->debugStream() << "reserved object id=" << obj->id << "; address=" << (int)obj << "; name=" << obj->getHoverText(); } void VCAI::validateVisitableObjs() { std::vector hlp; retreiveVisitableObjs(hlp, true); - erase_if(visitableObjs, [&](const CGObjectInstance *obj) -> bool + + std::string errorMsg; + auto shouldBeErased = [&](const CGObjectInstance *obj) -> bool { if(!vstd::contains(hlp, obj)) { - logAi->errorStream() << helperObjInfo[obj].name << " at " << helperObjInfo[obj].pos << " shouldn't be on list!"; + logAi->errorStream() << helperObjInfo[obj].name << " at " << helperObjInfo[obj].pos << errorMsg; return true; } return false; - }); + }; + + //errorMsg is captured by ref so lambda will take the new text + errorMsg = " shouldn't be on the visitable objects list!"; + erase_if(visitableObjs, shouldBeErased); + + for(auto &p : reservedHeroesMap) + { + errorMsg = " shouldn't be on list for hero " + p.first->name + "!"; + erase_if(p.second, shouldBeErased); + } + + errorMsg = " shouldn't be on the reserved objs list!"; + erase_if(reservedObjs, shouldBeErased); + + //TODO overkill, hidden object should not be removed. However, we can't know if hidden object is erased from game. + errorMsg = " shouldn't be on the already visited objs list!"; + erase_if(alreadyVisited, shouldBeErased); } void VCAI::retreiveVisitableObjs(std::vector &out, bool includeOwned /*= false*/) const @@ -2575,6 +2609,8 @@ void VCAI::validateObject(ObjectIdRef obj) for(auto &p : reservedHeroesMap) erase_if(p.second, matchesId); + + erase_if(reservedObjs, matchesId); } } diff --git a/Global.h b/Global.h index 83c8ba812..60ccf7c9e 100644 --- a/Global.h +++ b/Global.h @@ -268,6 +268,16 @@ public: }; +template +std::ostream &operator<<(std::ostream &out, const boost::optional &opt) +{ + if(opt) + return out << *opt; + else + return out<< "empty"; +} + + namespace vstd { diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 51b83d96f..6a4d7223a 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1183,7 +1183,12 @@ template void CPlayerInterface::serializeTempl( Handler &h, c if(h.saving) { for(auto &p : paths) - pathsMap[p.first] = p.second.endPos(); + { + if(p.second.nodes.size()) + pathsMap[p.first] = p.second.endPos(); + else + logGlobal->errorStream() << p.first->name << " has assigned an empty path! Ignoring it..."; + } h & pathsMap; } else diff --git a/client/Client.cpp b/client/Client.cpp index 3e44b93f6..fc5896ea3 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -290,6 +290,15 @@ void CClient::loadGame( const std::string & fname ) serv->enableStackSendingByID(); serv->disableSmartPointerSerialization(); +// logGlobal->traceStream() << "Objects:"; +// for(int i = 0; i < gs->map->objects.size(); i++) +// { +// auto o = gs->map->objects[i]; +// if(o) +// logGlobal->traceStream() << boost::format("\tindex=%5d, id=%5d; address=%5d, pos=%s, name=%s") % i % o->id % (int)o.get() % o->pos % o->getHoverText(); +// else +// logGlobal->traceStream() << boost::format("\tindex=%5d --- nullptr") % i; +// } } void CClient::newGame( CConnection *con, StartInfo *si ) diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 58daf743b..a9a925795 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -300,9 +300,9 @@ DLL_LINKAGE void RemoveBonus::applyGs( CGameState *gs ) DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) { - logGlobal->debugStream() << "removing object oid=" << id; CGObjectInstance *obj = gs->getObjInstance(id); + logGlobal->debugStream() << "removing object id=" << id << "; address=" << (int)obj << "; name=" << obj->getHoverText(); //unblock tiles if(obj->defInfo) { @@ -594,7 +594,10 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs ) gs->map->addBlockVisTiles(o); o->initObj(); assert(o->defInfo); + + logGlobal->debugStream() << "added object id=" << id << "; address=" << (int)o << "; name=" << o->getHoverText(); } + DLL_LINKAGE void NewArtifact::applyGs( CGameState *gs ) { assert(!vstd::contains(gs->map->artInstances, art));