diff --git a/.gitignore b/.gitignore index eb820ec6b..da2f1d79a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ build-* CMakeLists.txt.user.* Doxyfile doc/* +VCMI_VS11.sdf +*.ipch +VCMI_VS11.opensdf diff --git a/AI/EmptyAI/CEmptyAI.cpp b/AI/EmptyAI/CEmptyAI.cpp index f46bc1e6e..58d8fa946 100644 --- a/AI/EmptyAI/CEmptyAI.cpp +++ b/AI/EmptyAI/CEmptyAI.cpp @@ -30,7 +30,7 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vectorselectionMade(0, askID); } -void CEmptyAI::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CEmptyAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { cb->selectionMade(0, askID); } diff --git a/AI/EmptyAI/CEmptyAI.h b/AI/EmptyAI/CEmptyAI.h index eb5e43a32..262511a5d 100644 --- a/AI/EmptyAI/CEmptyAI.h +++ b/AI/EmptyAI/CEmptyAI.h @@ -15,7 +15,7 @@ public: void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, QueryID queryID) override; void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; }; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 7fbda2d42..b87b14a8d 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -153,6 +153,12 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) } } + for ( auto & enemy : enemiesReachable ) + enemy.calcDmg( stack ); + + for ( auto & enemy : enemiesShootable ) + enemy.calcDmg( stack ); + if(enemiesShootable.size()) { const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable); diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index f05867d61..7cc22fe38 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -7,6 +7,10 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" #include "../../lib/mapObjects/CBank.h" +#include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/CQuest.h" +#include "../../lib/CPathfinder.h" +#include "../../lib/mapping/CMapDefines.h" /* * AIUtility.cpp, part of VCMI engine @@ -150,7 +154,7 @@ void foreach_neighbour(const int3 &pos, std::function foo { CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer - for(const int3 &dir : dirs) + for(const int3 &dir : int3::getDirs()) { const int3 n = pos + dir; if(cbp->isInTheMap(n)) @@ -160,7 +164,7 @@ void foreach_neighbour(const int3 &pos, std::function foo void foreach_neighbour(CCallback * cbp, const int3 &pos, std::function foo) { - for(const int3 &dir : dirs) + for(const int3 &dir : int3::getDirs()) { const int3 n = pos + dir; if(cbp->isInTheMap(n)) @@ -359,7 +363,7 @@ int3 whereToExplore(HeroPtr h) int radius = h->getSightRadious(); int3 hpos = h->visitablePos(); - SectorMap &sm = ai->getCachedSectorMap(h); + auto sm = ai->getCachedSectorMap(h); //look for nearby objs -> visit them if they're close enouh const int DIST_LIMIT = 3; @@ -372,9 +376,9 @@ int3 whereToExplore(HeroPtr h) { int3 op = obj->visitablePos(); CGPath p; - ai->myCb->getPathsInfo(h.get())->getPath(op, p); + ai->myCb->getPathsInfo(h.get())->getPath(p, op); if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) - if (ai->isGoodForVisit(obj, h, sm)) + if (ai->isGoodForVisit(obj, h, *sm)) nearbyVisitableObjs.push_back(obj); } } diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 34d299906..bb551e51b 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -6,9 +6,6 @@ #include "../../lib/CTownHandler.h" #include "../../lib/spells/CSpellHandler.h" #include "../../lib/Connection.h" -#include "../../lib/CGameState.h" -#include "../../lib/mapping/CMap.h" -#include "../../lib/NetPacks.h" #include "../../lib/CStopWatch.h" /* @@ -21,6 +18,8 @@ * */ +class CCallback; + typedef const int3& crint3; typedef const std::string& crstring; diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index f91b61963..098899b64 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -5,6 +5,8 @@ #include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapObjects/CommonConstructors.h" #include "../../lib/CCreatureHandler.h" +#include "../../lib/CPathfinder.h" +#include "../../lib/CGameStateFwd.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" #include "VCAI.h" @@ -421,7 +423,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) //assert(cb->isInTheMap(g.tile)); float turns = 0; - float distance = cb->getMovementCost(g.hero.h, g.tile); + float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile); if (!distance) //we stand on that tile turns = 0; else @@ -482,7 +484,7 @@ float FuzzyHelper::evaluate (Goals::ClearWayTo & g) if (!g.hero.h) throw cannotFulfillGoalException("ClearWayTo called without hero!"); - int3 t = ai->getCachedSectorMap(g.hero).firstTileToGet(g.hero, g.tile); + int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile); if (t.valid()) { @@ -530,4 +532,4 @@ float FuzzyHelper::evaluate (Goals::AbstractGoal & g) void FuzzyHelper::setPriority (Goals::TSubgoal & g) { g->setpriority(g->accept(this)); //this enforces returned value is set -} \ No newline at end of file +} diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 3fb441c5a..40406f214 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -3,6 +3,7 @@ #include "VCAI.h" #include "Fuzzy.h" #include "../../lib/mapping/CMap.h" //for victory conditions +#include "../../lib/CPathfinder.h" /* * Goals.cpp, part of VCMI engine @@ -483,9 +484,9 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals() //if our hero is trapped, make sure we request clearing the way from OUR perspective - SectorMap &sm = ai->getCachedSectorMap(h); + auto sm = ai->getCachedSectorMap(h); - int3 tileToHit = sm.firstTileToGet(h, tile); + int3 tileToHit = sm->firstTileToGet(h, tile); if (!tileToHit.valid()) continue; @@ -633,11 +634,11 @@ TGoalVec Explore::getAllPossibleSubgoals() for (auto h : heroes) { - SectorMap &sm = ai->getCachedSectorMap(h); + auto sm = ai->getCachedSectorMap(h); for (auto obj : objs) //double loop, performance risk? { - auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded + auto t = sm->firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded if (ai->isTileNotReserved(h, t)) ret.push_back (sptr(Goals::ClearWayTo(obj->visitablePos(), h).setisAbstract(true))); } @@ -963,7 +964,7 @@ TGoalVec Conquer::getAllPossibleSubgoals() for (auto h : cb->getHeroesInfo()) { - SectorMap &sm = ai->getCachedSectorMap(h); + auto sm = ai->getCachedSectorMap(h); std::vector ourObjs(objs); //copy common objects for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero @@ -974,7 +975,7 @@ TGoalVec Conquer::getAllPossibleSubgoals() for (auto obj : ourObjs) { int3 dest = obj->visitablePos(); - auto t = sm.firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded + auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded if (t.valid()) //we know any path at all { if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile @@ -1027,6 +1028,10 @@ TSubgoal GatherArmy::whatToDoToAchieve() return fh->chooseSolution (getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing. } + +static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, + BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7}; + TGoalVec GatherArmy::getAllPossibleSubgoals() { //get all possible towns, heroes and dwellings we may use @@ -1093,11 +1098,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() } for(auto h : cb->getHeroesInfo()) { - SectorMap &sm = ai->getCachedSectorMap(h); + auto sm = ai->getCachedSectorMap(h); for (auto obj : objs) { //find safe dwelling auto pos = obj->visitablePos(); - if (ai->isGoodForVisit(obj, h, sm)) + if (ai->isGoodForVisit(obj, h, *sm)) ret.push_back (sptr (Goals::VisitTile(pos).sethero(h))); } } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 2396fc8eb..31915aa57 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -8,6 +8,8 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" #include "../../lib/CModHandler.h" +#include "../../lib/CGameState.h" +#include "../../lib/NetPacks.h" /* @@ -96,6 +98,7 @@ VCAI::VCAI(void) LOG_TRACE(logAi); makingTurn = nullptr; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); } VCAI::~VCAI(void) @@ -614,33 +617,46 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector exits, bool impassable, QueryID askID) +void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { - LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); +// LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); NET_EVENT_HANDLER; status.addQuery(askID, boost::str(boost::format("Teleport dialog query with %d exits") % exits.size())); - ObjectInstanceID choosenExit; + int choosenExit = -1; if(impassable) knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE; - else + else if(destinationTeleport != ObjectInstanceID() && destinationTeleportPos.valid()) { - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); + } - if(!status.channelProbing()) + for(auto exit : exits) + { + if(status.channelProbing() && exit.first == destinationTeleport) { - vstd::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool + choosenExit = vstd::find_pos(exits, exit); + break; + } + else + { + // TODO: Implement checking if visiting that teleport will uncovert any FoW + // So far this is the best option to handle decision about probing + auto obj = cb->getObj(exit.first, false); + if(obj == nullptr && !vstd::contains(teleportChannelProbingList, exit.first) && + exit.first != destinationTeleport) { - return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit); - }); + teleportChannelProbingList.push_back(exit.first); + } } } requestActionASAP([=]() { - answerQuery(askID, choosenExit.getNum()); + answerQuery(askID, choosenExit); }); } @@ -843,9 +859,9 @@ void VCAI::makeTurnInternal() bool VCAI::goVisitObj(const CGObjectInstance * obj, HeroPtr h) { int3 dst = obj->visitablePos(); - SectorMap &sm = getCachedSectorMap(h); + auto sm = getCachedSectorMap(h); logAi->debugStream() << boost::format("%s will try to visit %s at (%s)") % h->name % obj->getObjectName() % strFromInt3(dst); - int3 pos = sm.firstTileToGet(h, dst); + int3 pos = sm->firstTileToGet(h, dst); if (!pos.valid()) //rare case when we are already standing on one of potential objects return false; return moveHeroToTile(pos, h); @@ -1301,6 +1317,20 @@ bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector VCAI::getPossibleDestinations(HeroPtr h) { validateVisitableObjs(); std::vector possibleDestinations; - SectorMap &sm = getCachedSectorMap(h); + auto sm = getCachedSectorMap(h); for(const CGObjectInstance *obj : visitableObjs) { - if (isGoodForVisit(obj, h, sm)) + if (isGoodForVisit(obj, h, *sm)) { possibleDestinations.push_back(obj); } @@ -1439,12 +1469,12 @@ void VCAI::wander(HeroPtr h) validateVisitableObjs(); std::vector dests, tmp; - SectorMap &sm = getCachedSectorMap(h); + auto sm = getCachedSectorMap(h); range::copy(reservedHeroesMap[h], std::back_inserter(tmp)); //also visit our reserved objects - but they are not prioritized to avoid running back and forth for (auto obj : tmp) { - int3 pos = sm.firstTileToGet(h, obj->visitablePos()); + int3 pos = sm->firstTileToGet(h, obj->visitablePos()); if (pos.valid()) if (isAccessibleForHero (pos, h)) //even nearby objects could be blocked by other heroes :( dests.push_back(obj); //can't use lambda for member function :( @@ -1453,7 +1483,7 @@ void VCAI::wander(HeroPtr h) range::copy(getPossibleDestinations(h), std::back_inserter(dests)); erase_if(dests, [&](ObjectIdRef obj) -> bool { - return !isSafeToVisit(h, sm.firstTileToGet(h, obj->visitablePos())); + return !isSafeToVisit(h, sm->firstTileToGet(h, obj->visitablePos())); }); if(!dests.size()) @@ -1840,7 +1870,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) else { CGPath path; - cb->getPathsInfo(h.get())->getPath(dst, path); + cb->getPathsInfo(h.get())->getPath(path, dst); if(path.nodes.empty()) { logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst; @@ -1860,25 +1890,29 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit); }; - auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId) + auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) { destinationTeleport = exitId; - cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true)); + if(exitPos.valid()) + destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); + cb->moveHero(*h, h->pos); destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); afterMovementCheck(); }; auto doChannelProbing = [&]() -> void { - auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false), false); - assert(currentExit); + auto currentPos = CGHeroInstance::convertPosition(h->pos,false); + auto currentExit = getObj(currentPos, true)->id; status.setChannelProbing(true); for(auto exit : teleportChannelProbingList) - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), exit); + doTeleportMovement(exit, int3(-1)); teleportChannelProbingList.clear(); - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), currentExit->id); status.setChannelProbing(false); + + doTeleportMovement(currentExit, currentPos); }; int i=path.nodes.size()-1; @@ -1891,7 +1925,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto nextObject = getObj(nextCoord, false); if(CGTeleport::isConnected(currentObject, nextObject)) { //we use special login if hero standing on teleporter it's mean we need - doTeleportMovement(currentCoord, nextObject->id); + doTeleportMovement(nextObject->id, nextCoord); if(teleportChannelProbingList.size()) doChannelProbing(); @@ -1915,6 +1949,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) { // Hero should be able to go through object if it's allow transit doMovement(endpos, true); } + else if(path.nodes[i-1].layer == EPathfindingLayer::AIR) + doMovement(endpos, true); else doMovement(endpos, false); @@ -2034,7 +2070,7 @@ void VCAI::tryRealize(Goals::BuildThis & g) void VCAI::tryRealize(Goals::DigAtTile & g) { assert(g.hero->visitablePos() == g.tile); //surely we want to crash here? - if (g.hero->diggingStatus() == CGHeroInstance::CAN_DIG) + if (g.hero->diggingStatus() == EDiggingStatus::CAN_DIG) { cb->dig(g.hero.get()); completeGoal(sptr(g)); // finished digging @@ -2463,7 +2499,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) { int3 ourPos = h->convertPosition(h->pos, false); std::map dstToRevealedTiles; - for(crint3 dir : dirs) + for(crint3 dir : int3::getDirs()) if(cb->isInTheMap(hpos+dir)) if (ourPos != dir) //don't stand in place if (isSafeToVisit(h, hpos + dir) && isAccessibleForHero (hpos + dir, h)) @@ -2519,7 +2555,7 @@ int3 VCAI::explorationNewPoint(HeroPtr h) continue; CGPath path; - cb->getPathsInfo(hero)->getPath(tile, path); + cb->getPathsInfo(hero)->getPath(path, tile); float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much @@ -2537,7 +2573,7 @@ int3 VCAI::explorationNewPoint(HeroPtr h) int3 VCAI::explorationDesperate(HeroPtr h) { - SectorMap &sm = getCachedSectorMap(h); + auto sm = getCachedSectorMap(h); int radius = h->getSightRadious(); std::vector > tiles; //tiles[distance_to_fow] @@ -2566,7 +2602,7 @@ int3 VCAI::explorationDesperate(HeroPtr h) if (!howManyTilesWillBeDiscovered(tile, radius, cbp)) //avoid costly checks of tiles that don't reveal much continue; - auto t = sm.firstTileToGet(h, tile); + auto t = sm->firstTileToGet(h, tile); if (t.valid()) { ui64 ourDanger = evaluateDanger(t, h.h); @@ -2669,13 +2705,24 @@ void VCAI::finish() void VCAI::requestActionASAP(std::function whatToDo) { - boost::thread newThread([this, whatToDo]() + boost::mutex mutex; + mutex.lock(); + + boost::thread newThread([&mutex,this,whatToDo]() { setThreadName("VCAI::requestActionASAP::whatToDo"); SET_GLOBAL_STATE(this); boost::shared_lock gsLock(cb->getGsMutex()); + // unlock mutex and allow parent function to exit + mutex.unlock(); whatToDo(); }); + + // wait for mutex to unlock and for thread to initialize properly + mutex.lock(); + + // unlock mutex - boost dislikes destruction of locked mutexes + mutex.unlock(); } void VCAI::lostHero(HeroPtr h) @@ -2749,14 +2796,14 @@ TResources VCAI::freeResources() const return myRes; } -SectorMap& VCAI::getCachedSectorMap(HeroPtr h) +std::shared_ptr VCAI::getCachedSectorMap(HeroPtr h) { auto it = cachedSectorMaps.find(h); if (it != cachedSectorMaps.end()) return it->second; else { - cachedSectorMaps.insert(std::make_pair(h, SectorMap(h))); + cachedSectorMaps[h] = std::make_shared(h); return cachedSectorMaps[h]; } } @@ -2905,7 +2952,7 @@ void AIStatus::setMove(bool ongoing) void AIStatus::setChannelProbing(bool ongoing) { boost::unique_lock lock(mx); - ongoingHeroMovement = ongoing; + ongoingChannelProbing = ongoing; cv.notify_all(); } @@ -3366,7 +3413,8 @@ int3 SectorMap::findFirstVisitableTile (HeroPtr h, crint3 dst) while(curtile != h->visitablePos()) { auto topObj = cb->getTopObj(curtile); - if (topObj && topObj->ID == Obj::HERO && h->tempOwner == topObj->tempOwner && topObj != h.h) + if(topObj && topObj->ID == Obj::HERO && topObj != h.h && + cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES) { logAi->warnStream() << ("Another allied hero stands in our way"); return ret; diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 23967df64..6963b8c79 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -12,11 +12,9 @@ #include "../../lib/CBuildingHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" +#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/spells/CSpellHandler.h" #include "../../lib/Connection.h" -#include "../../lib/CGameState.h" -#include "../../lib/mapping/CMap.h" -#include "../../lib/NetPacks.h" #include "../../lib/CondSh.h" struct QuestInfo; @@ -115,20 +113,6 @@ struct SectorMap int3 findFirstVisitableTile(HeroPtr h, crint3 dst); }; -//Set of buildings for different goals. Does not include any prerequisites. -const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL}; -const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}; -const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, - BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7}; -const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, - BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP}; -const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1, - BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR}; -const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, - BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; -const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3, - BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings - class VCAI : public CAdventureAI { public: @@ -148,6 +132,7 @@ public: std::map > knownTeleportChannels; std::map knownSubterraneanGates; ObjectInstanceID destinationTeleport; + int3 destinationTeleportPos; std::vector teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored //std::vector visitedThisWeek; //only OPWs std::map > townVisitsThisWeek; @@ -161,7 +146,7 @@ public: std::set alreadyVisited; std::set reservedObjs; //to be visited by specific hero - std::map cachedSectorMaps; //TODO: serialize? not necessary + std::map > cachedSectorMaps; //TODO: serialize? not necessary TResources saving; @@ -202,7 +187,7 @@ public: virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; //TODO virtual void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; virtual void saveGame(COSer & h, const int version) override; //saving virtual void loadGame(CISer & h, const int version) override; //loading virtual void finish() override; @@ -314,7 +299,7 @@ public: const CGObjectInstance *getUnvisitedObj(const std::function &predicate); bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const; //optimization - use one SM for every hero call - SectorMap& getCachedSectorMap(HeroPtr h); + std::shared_ptr getCachedSectorMap(HeroPtr h); const CGTownInstance *findTownWithTavern() const; bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; diff --git a/CCallback.cpp b/CCallback.cpp index 605dd2afa..6b8a28958 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -18,12 +18,7 @@ #include "lib/spells/CSpellHandler.h" #include "lib/CArtHandler.h" #include "lib/GameConstants.h" -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif +#include "lib/CPlayerState.h" #include "lib/UnlockGuard.h" /* @@ -290,11 +285,6 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b) return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a); } -int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest) -{ - return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->movement); -} - const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h) { return cl->getPathsInfo(h); diff --git a/CCallback.h b/CCallback.h index 73167a1b3..46afc2adf 100644 --- a/CCallback.h +++ b/CCallback.h @@ -104,7 +104,6 @@ public: //client-specific functionalities (pathfinding) virtual bool canMoveBetween(const int3 &a, const int3 &b); - virtual int getMovementCost(const CGHeroInstance * hero, int3 dest); virtual int3 getGuardingCreaturePosition(int3 tile); virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h); diff --git a/ChangeLog b/ChangeLog index 52d54291b..45262dad9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,19 @@ 0.98 -> 0.next +GENERAL: +* New Bonus NO_TERRAIN_PENALTY +* Nomads will remove Sand movement penalty from army +* Flying and water walking is now supported in pathfinder +* New artifacts supported +- Angel Wings +- Boots of Levitation +* Implemented rumors in tavern window + ADVETURE AI: * Fixed AI trying to go through underground rock * Fixed several cases causing AI wandering aimlessly * AI can again pick best artifacts and exchange artifacts between heroes +* AI heroes with patrol enabled won't leave patrol area anymore RANDOM MAP GENERATOR: * Changed fractalization algorithm so it can create cycles diff --git a/Global.h b/Global.h index 297d0ac76..56a35a9f4 100644 --- a/Global.h +++ b/Global.h @@ -95,6 +95,18 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); # define NOMINMAX // Exclude min/max macros from . Use std::[min/max] from instead. #endif +/* ---------------------------------------------------------------------------- */ +/* A macro to force inlining some of our functions */ +/* ---------------------------------------------------------------------------- */ +// Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower +#ifdef _MSC_VER +# define STRONG_INLINE __forceinline +#elif __GNUC__ +# define STRONG_INLINE inline __attribute__((always_inline)) +#else +# define STRONG_INLINE inline +#endif + #define _USE_MATH_DEFINES #include diff --git a/client/CMT.cpp b/client/CMT.cpp index 318c60c9d..f9e5a1a08 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -529,8 +529,9 @@ void processCommand(const std::string &message) std::string cn; //command name readed >> cn; - if(LOCPLINT && LOCPLINT->cingconsole) - LOCPLINT->cingconsole->print(message); +// Check mantis issue 2292 for details +// if(LOCPLINT && LOCPLINT->cingconsole) +// LOCPLINT->cingconsole->print(message); if(ermInteractiveMode) { @@ -789,11 +790,12 @@ void processCommand(const std::string &message) Settings session = settings.write["session"]; session["autoSkip"].Bool() = !session["autoSkip"].Bool(); } - else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server + // Check mantis issue 2292 for details +/* else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server { boost::unique_lock un(*LOCPLINT->pim); LOCPLINT->cb->sendMessage(message); - } + }*/ } //plays intro, ends when intro is over or button has been pressed (handles events) @@ -1071,6 +1073,15 @@ static void handleEvent(SDL_Event & ev) return; } + else if(ev.type == SDL_WINDOWEVENT) + { + switch (ev.window.event) { + case SDL_WINDOWEVENT_RESTORED: + fullScreenChanged(); + break; + } + return; + } { boost::unique_lock lock(eventsM); events.push(ev); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 1bfe4ae98..7eedb4431 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -37,6 +37,7 @@ #include "../lib/CStopWatch.h" #include "../lib/StartInfo.h" #include "../lib/CGameState.h" +#include "../lib/CPlayerState.h" #include "../lib/GameConstants.h" #include "gui/CGuiHandler.h" #include "windows/InfoWindows.h" @@ -97,6 +98,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) { logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed"; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); observerInDuelMode = false; howManyPeople++; GH.defActionsDef = 0; @@ -1147,14 +1149,15 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v } -void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { EVENT_HANDLER_CALLED_BY_CLIENT; - ObjectInstanceID choosenExit; - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + int choosenExit = -1; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); - cb->selectionMade(choosenExit.getNum(), askID); + cb->selectionMade(choosenExit, askID); } void CPlayerInterface::tileRevealed(const std::unordered_set &pos) @@ -1288,7 +1291,7 @@ template void CPlayerInterface::serializeTempl( Handler &h, c for(auto &p : pathsMap) { CGPath path; - cb->getPathsInfo(p.first)->getPath(p.second, path); + cb->getPathsInfo(p.first)->getPath(path, p.second); paths[p.first] = path; logGlobal->traceStream() << boost::format("Restored path for hero %s leading to %s with %d nodes") % p.first->nodeName() % p.second % path.nodes.size(); @@ -1414,6 +1417,7 @@ void CPlayerInterface::requestRealized( PackageApplied *pa ) && stillMoveHero.get() == DURING_MOVE) { // After teleportation via CGTeleport object is finished destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(-1); stillMoveHero.setn(CONTINUE_MOVE); } } @@ -2226,7 +2230,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h) { assert(h->getPosition(false) == path.startPos()); //update the hero path in case of something has changed on map - if(LOCPLINT->cb->getPathsInfo(h)->getPath(path.endPos(), path)) + if(LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos())) return &path; else paths.erase(h); @@ -2315,23 +2319,22 @@ void CPlayerInterface::tryDiggging(const CGHeroInstance *h) { std::string hlp; CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); + auto isDiggingPossible = h->diggingStatus(); + if(hlp.length()) + isDiggingPossible = EDiggingStatus::TILE_OCCUPIED; //TODO integrate with canDig int msgToShow = -1; - CGHeroInstance::ECanDig isDiggingPossible = h->diggingStatus(); - if(hlp.length()) - isDiggingPossible = CGHeroInstance::TILE_OCCUPIED; //TODO integrate with canDig - switch(isDiggingPossible) { - case CGHeroInstance::CAN_DIG: + case EDiggingStatus::CAN_DIG: break; - case CGHeroInstance::LACK_OF_MOVEMENT: + case EDiggingStatus::LACK_OF_MOVEMENT: msgToShow = 56; //"Digging for artifacts requires a whole day, try again tomorrow." break; - case CGHeroInstance::TILE_OCCUPIED: + case EDiggingStatus::TILE_OCCUPIED: msgToShow = 97; //Try searching on clear ground. break; - case CGHeroInstance::WRONG_TERRAIN: + case EDiggingStatus::WRONG_TERRAIN: msgToShow = 60; ////Try looking on land! break; default: @@ -2642,7 +2645,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) ETerrainType newTerrain; int sh = -1; - for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE); i--) + auto canStop = [&](CGPathNode * node) -> bool + { + if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) + return true; + + if(node->accessible == CGPathNode::ACCESSIBLE) + return true; + + return false; + }; + + for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--) { int3 currentCoord = path.nodes[i].coord; int3 nextCoord = path.nodes[i-1].coord; @@ -2652,6 +2666,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) { CCS->soundh->stopSound(sh); destinationTeleport = nextObject->id; + destinationTeleportPos = nextCoord; doMovement(h->pos, false); sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); continue; @@ -2683,18 +2698,21 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) int3 endpos(nextCoord.x, nextCoord.y, h->pos.z); logGlobal->traceStream() << "Requesting hero movement to " << endpos; + bool useTransit = false; if((i-2 >= 0) // Check there is node after next one; otherwise transit is pointless && (CGTeleport::isConnected(nextObject, getObj(path.nodes[i-2].coord, false)) || CGTeleport::isTeleport(nextObject))) { // Hero should be able to go through object if it's allow transit - doMovement(endpos, true); + useTransit = true; } - else - doMovement(endpos, false); + else if(path.nodes[i-1].layer == EPathfindingLayer::AIR) + useTransit = true; + + doMovement(endpos, useTransit); logGlobal->traceStream() << "Resuming " << __FUNCTION__; bool guarded = cb->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0))); - if(guarded || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136) + if((!useTransit && guarded) || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136) break; } diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 2e19aae30..1d0204d0c 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -5,7 +5,6 @@ #include "../lib/CGameInterface.h" #include "../lib/NetPacksBase.h" #include "gui/CIntObject.h" -//#include "../lib/CGameState.h" #ifdef __GNUC__ #define sprintf_s snprintf @@ -90,6 +89,7 @@ class CPlayerInterface : public CGameInterface, public IUpdateable public: bool observerInDuelMode; ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation + int3 destinationTeleportPos; //minor interfaces CondSh *showingDialog; //indicates if dialog box is displayed @@ -168,7 +168,7 @@ public: void showRecruitmentDialog(const CGDwelling *dwelling, const CArmedInstance *dst, int level) override; void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; void showPuzzleMap() override; void viewWorldMap() override; diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 51c1d1ea6..68b14df70 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -1875,9 +1875,9 @@ void CRandomMapTab::updateMapInfo() // Generate player information mapInfo->mapHeader->players.clear(); - int playersToGen = (mapGenOptions.getPlayerCount() == CMapGenOptions::RANDOM_SIZE - || mapGenOptions.getCompOnlyPlayerCount() == CMapGenOptions::RANDOM_SIZE) - ? 8 : mapGenOptions.getPlayerCount() + mapGenOptions.getCompOnlyPlayerCount(); + int playersToGen = PlayerColor::PLAYER_LIMIT_I; + if(mapGenOptions.getPlayerCount() != CMapGenOptions::RANDOM_SIZE) + playersToGen = mapGenOptions.getPlayerCount(); mapInfo->mapHeader->howManyTeams = playersToGen; for(int i = 0; i < playersToGen; ++i) @@ -1885,7 +1885,8 @@ void CRandomMapTab::updateMapInfo() PlayerInfo player; player.isFactionRandom = true; player.canComputerPlay = true; - if(i >= mapGenOptions.getPlayerCount() && mapGenOptions.getPlayerCount() != CMapGenOptions::RANDOM_SIZE) + if(mapGenOptions.getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE && + i >= mapGenOptions.getHumanOnlyPlayerCount()) { player.canHumanPlay = false; } diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index c33472517..2be6eed85 100644 --- a/client/CVideoHandler.cpp +++ b/client/CVideoHandler.cpp @@ -104,11 +104,7 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal return false; } // Retrieve stream information -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 17, 0) - if (av_find_stream_info(format) < 0) -#else if (avformat_find_stream_info(format, nullptr) < 0) -#endif return false; // Find the first video stream @@ -139,22 +135,16 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal } // Open codec -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 6, 0) - if ( avcodec_open(codecContext, codec) < 0 ) -#else if ( avcodec_open2(codecContext, codec, nullptr) < 0 ) -#endif { // Could not open codec codec = nullptr; return false; } - // Allocate video frame - frame = avcodec_alloc_frame(); + frame = av_frame_alloc(); //setup scaling - if(scale) { pos.w = screen->w; @@ -185,21 +175,21 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal if (texture) { // Convert the image into YUV format that SDL uses sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, - pos.w, pos.h, PIX_FMT_YUV420P, + pos.w, pos.h, + AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr); } else { - - PixelFormat screenFormat = PIX_FMT_NONE; + AVPixelFormat screenFormat = AV_PIX_FMT_NONE; if (screen->format->Bshift > screen->format->Rshift) { // this a BGR surface switch (screen->format->BytesPerPixel) { - case 2: screenFormat = PIX_FMT_BGR565; break; - case 3: screenFormat = PIX_FMT_BGR24; break; - case 4: screenFormat = PIX_FMT_BGR32; break; + case 2: screenFormat = AV_PIX_FMT_BGR565; break; + case 3: screenFormat = AV_PIX_FMT_BGR24; break; + case 4: screenFormat = AV_PIX_FMT_BGR32; break; default: return false; } } @@ -208,9 +198,9 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal // this a RGB surface switch (screen->format->BytesPerPixel) { - case 2: screenFormat = PIX_FMT_RGB565; break; - case 3: screenFormat = PIX_FMT_RGB24; break; - case 4: screenFormat = PIX_FMT_RGB32; break; + case 2: screenFormat = AV_PIX_FMT_RGB565; break; + case 3: screenFormat = AV_PIX_FMT_RGB24; break; + case 4: screenFormat = AV_PIX_FMT_RGB32; break; default: return false; } } @@ -367,8 +357,7 @@ void CVideoPlayer::close() if (frame) { - av_free(frame); - frame = nullptr; + av_frame_free(&frame);//will be set to null } if (codec) @@ -380,12 +369,7 @@ void CVideoPlayer::close() if (format) { -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 17, 0) - av_close_input_file(format); - format = nullptr; -#else avformat_close_input(&format); -#endif } if (context) diff --git a/client/CVideoHandler.h b/client/CVideoHandler.h index d7f67394c..a52600125 100644 --- a/client/CVideoHandler.h +++ b/client/CVideoHandler.h @@ -51,27 +51,42 @@ public: extern "C" { #include #include - -// compatibility with different versions od libavutil -#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 42, 0)) || \ - (LIBAVUTIL_VERSION_INT == AV_VERSION_INT(51, 73, 101)) - -#define AV_PIX_FMT_NONE PIX_FMT_NONE -#define AV_PIX_FMT_NV12 PIX_FMT_NV12 -#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P -#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 -#define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - -#endif } +//compatibility for libav 9.18 in ubuntu 14.04, 52.66.100 is ffmpeg 2.2.3 +#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 66, 100)) +inline AVFrame * av_frame_alloc() +{ + return avcodec_alloc_frame(); +} + +inline void av_frame_free(AVFrame ** frame) +{ + av_free(*frame); + *frame = nullptr; +} +#endif // VCMI_USE_OLD_AVUTIL + +//fix for travis-ci +#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 0, 0)) + #define AVPixelFormat PixelFormat + #define AV_PIX_FMT_NONE PIX_FMT_NONE + #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P + #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 + #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 + #define AV_PIX_FMT_BGR32 PIX_FMT_BGR32 + #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 + #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 + #define AV_PIX_FMT_RGB32 PIX_FMT_RGB32 +#endif + class CVideoPlayer : public IMainVideoPlayer { int stream; // stream index in video AVFormatContext *format; AVCodecContext *codecContext; // codec context for stream AVCodec *codec; - AVFrame *frame; + AVFrame *frame; struct SwsContext *sws; AVIOContext * context; @@ -102,7 +117,7 @@ public: void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true - + // Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played) bool openAndPlayVideo(std::string name, int x, int y, SDL_Surface *dst, bool stopOnKey = false, bool scale = false) override; diff --git a/client/Graphics.cpp b/client/Graphics.cpp index a6900056b..f71f238b5 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -22,6 +22,7 @@ #include "../lib/GameConstants.h" #include "../lib/CStopWatch.h" #include "../lib/mapObjects/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" using namespace CSDL_Ext; #ifdef min diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 5453fbc22..b8382868f 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -25,6 +25,7 @@ #include "../lib/CGameState.h" #include "../lib/BattleState.h" #include "../lib/GameConstants.h" +#include "../lib/CPlayerState.h" #include "gui/CGuiHandler.h" #include "widgets/MiscWidgets.h" #include "widgets/AdventureMapClasses.h" @@ -819,10 +820,10 @@ void SaveGame::applyCl(CClient *cl) void PlayerMessage::applyCl(CClient *cl) { - std::ostringstream str; - str << "Player "<< player <<" sends a message: " << text; + logNetwork->debugStream() << "Player "<< player <<" sends a message: " << text; - logNetwork->debugStream() << str.str(); + std::ostringstream str; + str << cl->getPlayer(player)->nodeName() <<": " << text; if(LOCPLINT) LOCPLINT->cingconsole->print(str.str()); } diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 37b9d722f..212bdc9e7 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -233,8 +233,9 @@ std::string CDefenceAnimation::getMySound() if(killed) return battle_sound(stack->getCreature(), killed); - if (stack->valOfBonuses(Bonus::UntilGetsTurn)) + if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) return battle_sound(stack->getCreature(), defend); + return battle_sound(stack->getCreature(), wince); } @@ -243,10 +244,9 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType() if(killed) return CCreatureAnim::DEATH; - auto selector = CSelector(Bonus::UntilGetsTurn).And(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)); - - if(stack->valOfBonuses(selector)) + if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) return CCreatureAnim::DEFENCE; + return CCreatureAnim::HITTED; } diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 4f2e3cdbd..f14f0a8c6 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -29,6 +29,7 @@ #include "../../lib/NetPacks.h" #include "../../lib/StartInfo.h" #include "../../lib/CondSh.h" +#include "../../lib/mapObjects/CGTownInstance.h" /* * CBattleInterfaceClasses.cpp, part of VCMI engine diff --git a/client/gui/Geometries.h b/client/gui/Geometries.h index af67813ec..c6ee4df7a 100644 --- a/client/gui/Geometries.h +++ b/client/gui/Geometries.h @@ -13,13 +13,6 @@ * */ -#ifdef max -#undef max -#endif -#ifdef min -#undef min -#endif - struct SDL_MouseMotionEvent; // A point with x/y coordinate, used mostly for graphic rendering diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 0aa691015..0f9a5e7d4 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -18,15 +18,6 @@ #include "../../lib/GameConstants.h" -//A macro to force inlining some of our functions. Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower -#ifdef _MSC_VER - #define STRONG_INLINE __forceinline -#elif __GNUC__ - #define STRONG_INLINE inline __attribute__((always_inline)) -#else - #define STRONG_INLINE inline -#endif - extern SDL_Window * mainWindow; extern SDL_Renderer * mainRenderer; extern SDL_Texture * screenTexture; diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index f78d65a17..e50d4a959 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -1579,7 +1579,13 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN if(t.hasFavourableWinds()) out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS); else if(terName) + { out = CGI->generaltexth->terrainNames[t.terType]; + if(t.getDiggingStatus(false) == EDiggingStatus::CAN_DIG) + { + out = boost::str(boost::format("%s %s") % out % CGI->generaltexth->allTexts[330]); /// digging ok + } + } } void CMapHandler::discardWorldViewCache() diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 92622c738..e488d86dc 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -114,7 +114,7 @@ void CTerrainRect::clickRight(tribool down, bool previousState) adventureInt->tileRClicked(mp); } -void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent) +void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent) { int3 tHovered = whichTileIsIt(sEvent.x,sEvent.y); int3 pom = adventureInt->verifyPos(tHovered); @@ -126,11 +126,11 @@ void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent) } if (pom != curHoveredTile) - curHoveredTile=pom; + curHoveredTile = pom; else return; - adventureInt->tileHovered(curHoveredTile); + adventureInt->tileHovered(pom); } void CTerrainRect::hover(bool on) { @@ -188,7 +188,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) * is id1=7, id2=5 (pns[7][5]) */ bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos); - if(pathContinuous && cv[i].land == cv[i+1].land) + if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK) { int id1=(curPos.x-nextPos.x+1)+3*(curPos.y-nextPos.y+1); //Direction of entering vector int id2=(cv[i-1].coord.x-curPos.x+1)+3*(cv[i-1].coord.y-curPos.y+1); //Direction of exiting vector @@ -881,7 +881,7 @@ void CAdvMapInt::showAll(SDL_Surface * to) statusbar.show(to); - LOCPLINT->cingconsole->showAll(to); + LOCPLINT->cingconsole->show(to); } bool CAdvMapInt::isHeroSleeping(const CGHeroInstance *hero) @@ -958,7 +958,7 @@ void CAdvMapInt::show(SDL_Surface * to) for(int i=0;i<4;i++) blitAt(gems[i]->ourImages[LOCPLINT->playerID.getNum()].bitmap,ADVOPT.gemX[i],ADVOPT.gemY[i],to); updateScreen=false; - LOCPLINT->cingconsole->showAll(to); + LOCPLINT->cingconsole->show(to); } else if (terrain.needsAnimUpdate()) { @@ -1190,7 +1190,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) CGPath &path = LOCPLINT->paths[h]; terrain.currentPath = &path; int3 dst = h->getPosition(false) + dir; - if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(dst, path)) + if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst)) { terrain.currentPath = nullptr; return; @@ -1445,7 +1445,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) { CGPath &path = LOCPLINT->paths[currentHero]; terrain.currentPath = &path; - bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(mapPos, path); //try getting path, erase if failed + bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(path, mapPos); //try getting path, erase if failed updateMoveHero(currentHero); if (!gotPath) LOCPLINT->eraseCurrentPathOf(currentHero); @@ -1467,7 +1467,8 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) void CAdvMapInt::tileHovered(const int3 &mapPos) { - if(mode != EAdvMapMode::NORMAL) + if(mode != EAdvMapMode::NORMAL //disable in world view + || !selection) //may occur just at the start of game (fake move before full intiialization) return; if(!LOCPLINT->cb->isVisible(mapPos)) { @@ -1475,10 +1476,11 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) statusbar.clear(); return; } + auto objRelations = PlayerRelations::ALLIES; const CGObjectInstance *objAtTile = getActiveObject(mapPos); - - if (objAtTile) + if(objAtTile) { + objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner); std::string text = curHero() ? objAtTile->getHoverText(curHero()) : objAtTile->getHoverText(LOCPLINT->playerID); boost::replace_all(text,"\n"," "); statusbar.setText(text); @@ -1490,9 +1492,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) statusbar.setText(hlp); } - if(!selection) //may occur just at the start of game (fake move before full intiialization) - return; - if(spellBeingCasted) { switch(spellBeingCasted->id) @@ -1505,9 +1504,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) return; case SpellID::DIMENSION_DOOR: { - const TerrainTile *t = LOCPLINT->cb->getTile(mapPos, false); + const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false); int3 hpos = selection->getSightCenter(); - if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos)) + if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos)) CCS->curh->changeGraphic(ECursor::ADVENTURE, 41); else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); @@ -1516,15 +1515,13 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) } } - const bool guardingCreature = CGI->mh->map->isInTheMap(LOCPLINT->cb->getGuardingCreaturePosition(mapPos)); - if(selection->ID == Obj::TOWN) { if(objAtTile) { - if(objAtTile->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner) != PlayerRelations::ENEMIES) + if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES) CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); - else if(objAtTile->ID == Obj::HERO && objAtTile->tempOwner == LOCPLINT->playerID) + else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER) CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); @@ -1532,129 +1529,61 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); } - else if(const CGHeroInstance *h = curHero()) + else if(const CGHeroInstance * h = curHero()) { int3 mapPosCopy = mapPos; - const CGPathNode *pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPosCopy); + const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPosCopy); assert(pnode); int turns = pnode->turns; vstd::amin(turns, 3); - bool accessible = pnode->turns < 255; - - if(objAtTile) + switch(pnode->action) { - if(objAtTile->ID == Obj::HERO) - { - if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy hero - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else //our or ally hero - { - if(selection == objAtTile) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); - else if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); - } - } - else if(objAtTile->ID == Obj::TOWN) - { - if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy town - { - if(accessible) - { - const CGTownInstance* townObj = dynamic_cast(objAtTile); - - // Show movement cursor for unguarded enemy towns, otherwise attack cursor. - if (townObj && !townObj->armedGarrison()) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - - } - else - { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - } - else //our or ally town - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); - } - } - else if(objAtTile->ID == Obj::BOAT) - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else if (objAtTile->ID == Obj::GARRISON || objAtTile->ID == Obj::GARRISON2) - { - if (accessible) - { - const CGGarrison* garrObj = dynamic_cast(objAtTile); //TODO evil evil cast! - - // Show battle cursor for guarded enemy garrisons or garrisons have guarding creature behind, otherwise movement cursor. - if (garrObj && ((garrObj->stacksCount() - && !LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, garrObj->tempOwner)) - || guardingCreature)) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - } - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else if (guardingCreature && accessible) //(objAtTile->ID == 54) //monster - { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - } + case CGPathNode::NORMAL: + if(pnode->layer == EPathfindingLayer::LAND) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6); else + CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); + break; + + case CGPathNode::VISIT: + case CGPathNode::BLOCKING_VISIT: + if(objAtTile && objAtTile->ID == Obj::HERO) { - if(accessible) - { - if(pnode->land) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); - } + if(selection == objAtTile) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); + CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6); } - } - else //no objs - { - if(accessible/* && pnode->accessible != CGPathNode::FLYABLE*/) + else if(pnode->layer == EPathfindingLayer::LAND) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); + else + CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); + break; + + case CGPathNode::BATTLE: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); + break; + + case CGPathNode::EMBARK: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); + break; + + case CGPathNode::DISEMBARK: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6); + break; + + default: + if(objAtTile && objRelations != PlayerRelations::ENEMIES) { - if (guardingCreature) - { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - } - else - { - if(pnode->land) - { - if(LOCPLINT->cb->getTile(h->getPosition(false))->terType != ETerrainType::WATER) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6); //anchor - } - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); - } + if(objAtTile->ID == Obj::TOWN) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); + else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); } else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); + break; } } diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index e9d5783f6..43f547386 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -19,7 +19,7 @@ #include "../../lib/CGameState.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/NetPacksBase.h" - +#include "../../lib/mapObjects/CQuest.h" /* * CQuestLog.cpp, part of VCMI engine * diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 756027ab6..79456e908 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -705,7 +705,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj): new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500"); - new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); + + auto rumorText = boost::str(boost::format(CGI->generaltexth->allTexts[216]) % LOCPLINT->cb->getTavernRumor(tavernObj)); + new CTextBox(rumorText, Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), SDLK_ESCAPE); @@ -1628,9 +1630,9 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): int counter = 0; for(auto & iter : tgi.colorToBestHero) { + new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); if(iter.second.portrait >= 0) { - new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360); //TODO: r-click info: // - r-click on hero diff --git a/config/resolutions.json b/config/resolutions.json index 1917a62da..e0e83be0b 100644 --- a/config/resolutions.json +++ b/config/resolutions.json @@ -3,7 +3,7 @@ [ { "resolution": { "x": 800, "y": 600 }, - "InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 39 }, + "InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 60 }, "AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1, "objectFading" : 1, "screenFading" : 1 }, "InfoBox": { "x": 605, "y": 389 }, "gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" }, diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 5b78fab89..fccc17f74 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -3,7 +3,7 @@ { "type" : "object", "$schema": "http://json-schema.org/draft-04/schema", - "required" : [ "general", "video", "adventure", "battle", "server", "logging", "launcher" ], + "required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ], "definitions" : { "logLevelEnum" : { "type" : "string", @@ -108,6 +108,74 @@ } } }, + "pathfinder" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "teleports", "layers", "oneTurnSpecialLayersLimit", "originalMovementRules", "lightweightFlyingMode" ], + "properties" : { + "layers" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "sailing", "waterWalking", "flying" ], + "properties" : { + "sailing" : { + "type" : "boolean", + "default" : true + }, + "waterWalking" : { + "type" : "boolean", + "default" : true + }, + "flying" : { + "type" : "boolean", + "default" : true + } + } + }, + "teleports" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "twoWay", "oneWay", "oneWayRandom", "whirlpool", "castleGate" ], + "properties" : { + "twoWay" : { + "type" : "boolean", + "default" : true + }, + "oneWay" : { + "type" : "boolean", + "default" : true + }, + "oneWayRandom" : { + "type" : "boolean", + "default" : false + }, + "whirlpool" : { + "type" : "boolean", + "default" : true + }, + "castleGate" : { + "type" : "boolean", + "default" : false + } + } + }, + "oneTurnSpecialLayersLimit" : { + "type" : "boolean", + "default" : true + }, + "originalMovementRules" : { + "type" : "boolean", + "default" : false + }, + "lightweightFlyingMode" : { + "type" : "boolean", + "default" : false + } + } + }, "battle" : { "type" : "object", "additionalProperties" : false, diff --git a/launcher/modManager/cmodlistmodel_moc.h b/launcher/modManager/cmodlistmodel_moc.h index 72430db69..dce474ded 100644 --- a/launcher/modManager/cmodlistmodel_moc.h +++ b/launcher/modManager/cmodlistmodel_moc.h @@ -52,7 +52,7 @@ public: /// CModListContainer overrides void resetRepositories() override; void addRepository(QVariantMap data) override; - void modChanged(QString modID); + void modChanged(QString modID) override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index cd6d2b0b5..e0cf02c99 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -1,4 +1,4 @@ -/* +/* * BattleState.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -22,6 +22,7 @@ #include "JsonNode.h" #include "filesystem/Filesystem.h" #include "CRandomGenerator.h" +#include "mapObjects/CGTownInstance.h" const CStack * BattleInfo::getNextStack() const { diff --git a/lib/BattleState.h b/lib/BattleState.h index 44260ad32..a06b517cd 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -11,12 +11,9 @@ #pragma once #include "BattleHex.h" -#include "HeroBonus.h" -#include "CCreatureSet.h" #include "mapObjects/CArmedInstance.h" // for army serialization #include "mapObjects/CGHeroInstance.h" // for commander serialization #include "CCreatureHandler.h" -#include "CObstacleInstance.h" #include "ConstTransitivePtr.h" #include "GameConstants.h" #include "CBattleCallback.h" diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 84abf0173..ac4a310a9 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -738,8 +738,8 @@ std::string CArtifactInstance::nodeName() const CArtifactInstance * CArtifactInstance::createScroll( const CSpell *s) { - auto ret = new CArtifactInstance(VLC->arth->artifacts[1]); - auto b = new Bonus(Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT_INSTANCE, -1, 1, s->id); + auto ret = new CArtifactInstance(VLC->arth->artifacts[ArtifactID::SPELL_SCROLL]); + auto b = new Bonus(Bonus::PERMANENT, Bonus::SPELL, Bonus::ARTIFACT_INSTANCE, -1, ArtifactID::SPELL_SCROLL, s->id); ret->addNewBonus(b); return ret; } diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 4a5c3193a..3116bf818 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -6,6 +6,7 @@ #include "spells/CSpellHandler.h" #include "VCMI_Lib.h" #include "CTownHandler.h" +#include "mapObjects/CGTownInstance.h" /* * CBattleCallback.cpp, part of VCMI engine @@ -2245,6 +2246,7 @@ BattleAttackInfo::BattleAttackInfo(const CStack *Attacker, const CStack *Defende chargedFields = 0; luckyHit = false; + unluckyHit = false; deathBlow = false; ballistaDoubleDamage = false; } diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 649df26aa..20a992f68 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -27,7 +27,7 @@ DLL_LINKAGE CConsoleHandler * console = nullptr; #define CONSOLE_GRAY "\x1b[1;30m" #define CONSOLE_TEAL "\x1b[1;36m" #else - #include + #include #include #ifndef __MINGW32__ #pragma comment(lib, "dbghelp.lib") diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 56401514b..249b6fe8b 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -12,12 +12,15 @@ #include "CGameInfoCallback.h" #include "CGameState.h" // PlayerState +#include "CGeneralTextHandler.h" #include "mapObjects/CObjectHandler.h" // for CGObjectInstance #include "StartInfo.h" // for StartInfo #include "BattleState.h" // for BattleInfo #include "NetPacks.h" // for InfoWindow #include "CModHandler.h" #include "spells/CSpellHandler.h" +#include "mapping/CMap.h" +#include "CPlayerState.h" //TODO make clean #define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0) @@ -196,7 +199,14 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj if(obj->ID == Obj::TOWN || obj->ID == Obj::TAVERN) { - gs->obtainPlayersStats(thi, gs->players[obj->tempOwner].towns.size()); + int taverns = 0; + for(auto town : gs->players[*player].towns) + { + if(town->hasBuilt(BuildingID::TAVERN)) + taverns++; + } + + gs->obtainPlayersStats(thi, taverns); } else if(obj->ID == Obj::DEN_OF_THIEVES) { @@ -564,9 +574,34 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo return ps->status; } -std::string CGameInfoCallback::getTavernGossip(const CGObjectInstance * townOrTavern) const +std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { - return "GOSSIP TEST"; + std::string text = "", extraText = ""; + if(gs->rumor.type == RumorState::TYPE_NONE) // (version < 755 backward compatability + return text; + + auto rumor = gs->rumor.last[gs->rumor.type]; + switch(gs->rumor.type) + { + case RumorState::TYPE_SPECIAL: + if(rumor.first == RumorState::RUMOR_GRAIL) + extraText = VLC->generaltexth->arraytxt[158 + rumor.second]; + else + extraText = VLC->generaltexth->capColors[rumor.second]; + + text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText); + + break; + case RumorState::TYPE_MAP: + text = gs->map->rumors[rumor.first].text; + break; + + case RumorState::TYPE_RAND: + text = VLC->generaltexth->tavernRumors[rumor.first]; + break; + } + + return text; } PlayerRelations::PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const @@ -853,7 +888,7 @@ std::vector CGameInfoCallback::getVisibleTeleportObjects(std:: { vstd::erase_if(ids, [&](ObjectInstanceID id) -> bool { - auto obj = getObj(id); + auto obj = getObj(id, false); return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); }); return ids; diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 7899acb10..20c94db30 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -99,7 +99,7 @@ public: int howManyTowns(PlayerColor Player) const; const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial) std::vector getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited - std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; + std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; const CTown *getNativeTown(PlayerColor color) const; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index c35d52985..a49b5894f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -7,6 +7,8 @@ #include "spells/ViewSpellInt.h" +#include "mapObjects/CObjectHandler.h" + /* * CGameInterface.h, part of VCMI engine * @@ -94,7 +96,7 @@ public: // all stacks operations between these objects become allowed, interface has to call onEnd when done virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0; - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) = 0; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) = 0; virtual void finish(){}; //if for some reason we want to end virtual void showWorldViewEx(const std::vector & objectPositions){}; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 49bd1d1a7..a0794ca3b 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -907,7 +907,7 @@ void CGameState::initDuel() if(!ss.spells.empty()) { - h->putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + h->putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(ArtifactID::SPELLBOOK)); boost::copy(ss.spells, std::inserter(h->spells, h->spells.begin())); } @@ -1383,7 +1383,8 @@ void CGameState::placeStartingHeroes() } int heroTypeId = pickNextHeroType(playerColor); - if(playerSettingPair.second.hero == -1) playerSettingPair.second.hero = heroTypeId; + if(playerSettingPair.second.hero == -1) + playerSettingPair.second.hero = heroTypeId; placeStartingHero(playerColor, HeroTypeID(heroTypeId), playerInfo.posOfMainTown); } @@ -2061,101 +2062,6 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col return PlayerRelations::ENEMIES; } -void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing) -{ - static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), - int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; - - //vec.reserve(8); //optimization - for (auto & dir : dirs) - { - const int3 hlp = tile + dir; - if(!map->isInTheMap(hlp)) - continue; - - const TerrainTile &hlpt = map->getTile(hlp); - -// //we cannot visit things from blocked tiles -// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) -// { -// continue; -// } - - if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water - { - int3 hlp1 = tile, - hlp2 = tile; - hlp1.x += dir.x; - hlp2.y += dir.y; - - if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) - continue; - } - - if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) - && hlpt.terType != ETerrainType::ROCK) - { - vec.push_back(hlp); - } - } -} - -int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast) -{ - if(src == dest) //same tile - return 0; - - TerrainTile &s = map->getTile(src), - &d = map->getTile(dest); - - //get basic cost - int ret = h->getTileCost(d,s); - - if(d.blocked && h->canFly()) - { - ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0; - } - else if(d.terType == ETerrainType::WATER) - { - if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds - ret *= 0.666; - else if(!h->boat && h->canWalkOnSea()) - { - ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; - } - } - - if(src.x != dest.x && src.y != dest.y) //it's diagonal move - { - int old = ret; - ret *= 1.414213; - //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points - if(ret > remainingMovePoints && remainingMovePoints >= old) - { - return remainingMovePoints; - } - } - - - int left = remainingMovePoints-ret; - if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points - { - std::vector vec; - vec.reserve(8); //optimization - getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); - for(auto & elem : vec) - { - int fcost = getMovementCost(h, dest, elem, left, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; - } - return ret; -} - void CGameState::apply(CPack *pack) { ui16 typ = typeList.getTypeID(pack); @@ -2228,6 +2134,76 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z]; } +void CGameState::updateRumor() +{ + static std::vector rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND}; + std::vector sRumorTypes = { + RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME}; + if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce + sRumorTypes.push_back(RumorState::RUMOR_GRAIL); + + int rumorId = -1, rumorExtra = -1; + auto & rand = getRandomGenerator(); + rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); + if(!map->rumors.size() && rumor.type == RumorState::TYPE_MAP) + rumor.type = RumorState::TYPE_RAND; + + do + { + switch(rumor.type) + { + case RumorState::TYPE_SPECIAL: + { + SThievesGuildInfo tgi; + obtainPlayersStats(tgi, 20); + rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); + if(rumorId == RumorState::RUMOR_GRAIL) + { + rumorExtra = getTile(map->grailPos)->terType; + break; + } + + std::vector players = {}; + switch(rumorId) + { + case RumorState::RUMOR_OBELISKS: + players = tgi.obelisks[0]; + break; + + case RumorState::RUMOR_ARTIFACTS: + players = tgi.artifacts[0]; + break; + + case RumorState::RUMOR_ARMY: + players = tgi.army[0]; + break; + + case RumorState::RUMOR_INCOME: + players = tgi.income[0]; + break; + } + rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum(); + + break; + } + case RumorState::TYPE_MAP: + rumorId = rand.nextInt(map->rumors.size() - 1); + + break; + + case RumorState::TYPE_RAND: + do + { + rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); + } + while(!VLC->generaltexth->tavernRumors[rumorId].length()); + + break; + } + } + while(!rumor.update(rumorId, rumorExtra)); +} + bool CGameState::isVisible(int3 pos, PlayerColor player) { if(player == PlayerColor::NEUTRAL) @@ -2549,6 +2525,58 @@ struct statsHLP } return str; } + + // get total gold income + static int getIncome(const PlayerState * ps) + { + int totalIncome = 0; + const CGObjectInstance * heroOrTown = nullptr; + + //Heroes can produce gold as well - skill, specialty or arts + for(auto & h : ps->heroes) + { + totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ESTATES)); + totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, Res::GOLD)); + + if(!heroOrTown) + heroOrTown = h; + } + + //Add town income of all towns + for(auto & t : ps->towns) + { + totalIncome += t->dailyIncome()[Res::GOLD]; + + if(!heroOrTown) + heroOrTown = t; + } + + /// FIXME: Dirty dirty hack + /// Stats helper need some access to gamestate. + std::vector ownedObjects; + for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) + { + if(obj && obj->tempOwner == ps->color) + ownedObjects.push_back(obj); + } + /// This is code from CPlayerSpecificInfoCallback::getMyObjects + /// I'm really need to find out about callback interface design... + + for(auto object : ownedObjects) + { + //Mines + if ( object->ID == Obj::MINE ) + { + const CGMine *mine = dynamic_cast(object); + assert(mine); + + if (mine->producedResource == Res::GOLD) + totalIncome += mine->producedQuantity; + } + } + + return totalIncome; + } }; void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) @@ -2579,20 +2607,22 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) tgi.playerColors.push_back(elem.second.color); } - if(level >= 1) //num of towns & num of heroes + if(level >= 0) //num of towns & num of heroes { //num of towns FILL_FIELD(numOfTowns, g->second.towns.size()) //num of heroes FILL_FIELD(numOfHeroes, g->second.heroes.size()) - //best hero's portrait + } + if(level >= 1) //best hero's portrait + { for(auto g = players.cbegin(); g != players.cend(); ++g) { if(playerInactive(g->second.color)) continue; const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color); InfoAboutHero iah; - iah.initFromHero(best, level >= 8); + iah.initFromHero(best, level >= 2); iah.army.clear(); tgi.colorToBestHero[g->second.color] = iah; } @@ -2609,27 +2639,27 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) { FILL_FIELD(mercSulfCrystGems, g->second.resources[Res::MERCURY] + g->second.resources[Res::SULFUR] + g->second.resources[Res::CRYSTAL] + g->second.resources[Res::GEMS]) } - if(level >= 4) //obelisks found + if(level >= 3) //obelisks found { FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id]) } - if(level >= 5) //artifacts + if(level >= 4) //artifacts { FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) } - if(level >= 6) //army strength + if(level >= 4) //army strength { FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) } - if(level >= 7) //income + if(level >= 5) //income { - //TODO:obtainPlayersStats - income + FILL_FIELD(income, statsHLP::getIncome(&g->second)) } - if(level >= 8) //best hero's stats + if(level >= 2) //best hero's stats { //already set in lvl 1 handling } - if(level >= 9) //personality + if(level >= 3) //personality { for(auto g = players.cbegin(); g != players.cend(); ++g) { @@ -2646,7 +2676,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } } - if(level >= 10) //best creature + if(level >= 4) //best creature { //best creatures belonging to player (highest AI value) for(auto g = players.cbegin(); g != players.cend(); ++g) @@ -2865,7 +2895,7 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const { for(auto hero : map->heroesOnMap) //heroes instances initialization { - if(hero->subID == hid.getNum()) + if(hero->type && hero->type->ID == hid) { return hero; } @@ -2873,9 +2903,12 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const for(auto obj : map->objects) //prisons { - if(obj && obj->ID == Obj::PRISON && obj->subID == hid.getNum()) + if(obj && obj->ID == Obj::PRISON ) { - return dynamic_cast(obj.get()); + auto hero = dynamic_cast(obj.get()); + assert(hero); + if ( hero->type && hero->type->ID == hid ) + return hero; } } @@ -2894,6 +2927,25 @@ std::string PlayerState::nodeName() const return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast(color)); } + +bool RumorState::update(int id, int extra) +{ + if(vstd::contains(last, type)) + { + if(last[type].first != id) + { + last[type].first = id; + last[type].second = extra; + } + else + return false; + } + else + last[type] = std::make_pair(id, extra); + + return true; +} + InfoAboutArmy::InfoAboutArmy(): owner(PlayerColor::NEUTRAL) {} diff --git a/lib/CGameState.h b/lib/CGameState.h index 6702616bf..b91f4e106 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -1,12 +1,7 @@ #pragma once - - -//#ifndef _MSC_VER #include "CCreatureHandler.h" #include "VCMI_Lib.h" -#include "mapping/CMap.h" -//#endif #include "HeroBonus.h" #include "CCreatureSet.h" @@ -67,79 +62,6 @@ namespace boost class shared_mutex; } -//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown) -struct ArmyDescriptor : public std::map -{ - bool isDetailed; - DLL_LINKAGE ArmyDescriptor(const CArmedInstance *army, bool detailed); //not detailed -> quantity ids as count - DLL_LINKAGE ArmyDescriptor(); - - DLL_LINKAGE int getStrength() const; -}; - -struct DLL_LINKAGE InfoAboutArmy -{ - PlayerColor owner; - std::string name; - - ArmyDescriptor army; - - InfoAboutArmy(); - InfoAboutArmy(const CArmedInstance *Army, bool detailed); - - void initFromArmy(const CArmedInstance *Army, bool detailed); -}; - -struct DLL_LINKAGE InfoAboutHero : public InfoAboutArmy -{ -private: - void assign(const InfoAboutHero & iah); -public: - struct DLL_LINKAGE Details - { - std::vector primskills; - si32 mana, luck, morale; - } *details; - - const CHeroClass *hclass; - int portrait; - - InfoAboutHero(); - InfoAboutHero(const InfoAboutHero & iah); - InfoAboutHero(const CGHeroInstance *h, bool detailed); - ~InfoAboutHero(); - - InfoAboutHero & operator=(const InfoAboutHero & iah); - - void initFromHero(const CGHeroInstance *h, bool detailed); -}; - -/// Struct which holds a int information about a town -struct DLL_LINKAGE InfoAboutTown : public InfoAboutArmy -{ - struct DLL_LINKAGE Details - { - si32 hallLevel, goldIncome; - bool customRes; - bool garrisonedHero; - - } *details; - - const CTown *tType; - - si32 built; - si32 fortLevel; //0 - none - - InfoAboutTown(); - InfoAboutTown(const CGTownInstance *t, bool detailed); - ~InfoAboutTown(); - void initFromTown(const CGTownInstance *t, bool detailed); -}; - -// typedef si32 TResourceUnit; -// typedef std::vector TResourceVector; -// typedef std::set TResourceSet; - struct DLL_LINKAGE SThievesGuildInfo { std::vector playerColors; //colors of players that are in-game @@ -159,55 +81,34 @@ struct DLL_LINKAGE SThievesGuildInfo }; -struct DLL_LINKAGE PlayerState : public CBonusSystemNode +struct DLL_LINKAGE RumorState { -public: - PlayerColor color; - bool human; //true if human controlled player, false for AI - TeamID team; - TResources resources; - std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks - std::vector > heroes; - std::vector > towns; - std::vector > availableHeroes; //heroes available in taverns - std::vector > dwellings; //used for town growth - std::vector quests; //store info about all received quests + enum ERumorType : ui8 + { + TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP + }; - bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory - EPlayerStatus::EStatus status; - boost::optional daysWithoutCastle; + enum ERumorTypeSpecial : ui8 + { + RUMOR_OBELISKS = 208, + RUMOR_ARTIFACTS = 209, + RUMOR_ARMY = 210, + RUMOR_INCOME = 211, + RUMOR_GRAIL = 212 + }; - PlayerState(); - std::string nodeName() const override; + ERumorType type; + std::map> last; + + RumorState(){type = TYPE_NONE; last = {};}; + bool update(int id, int extra); template void serialize(Handler &h, const int version) { - h & color & human & team & resources & status; - h & heroes & towns & availableHeroes & dwellings & quests & visitedObjects; - h & getBonusList(); //FIXME FIXME FIXME - h & status & daysWithoutCastle; - h & enteredLosingCheatCode & enteredWinningCheatCode; - h & static_cast(*this); + h & type & last; } }; -struct DLL_LINKAGE TeamState : public CBonusSystemNode -{ -public: - TeamID id; //position in gameState::teams - std::set players; // members of this team - std::vector > > fogOfWarMap; //true - visible, false - hidden - - TeamState(); - - template void serialize(Handler &h, const int version) - { - h & id & players & fogOfWarMap; - h & static_cast(*this); - } - -}; - struct UpgradeInfo { CreatureID oldID; //creature to be upgraded @@ -276,7 +177,6 @@ struct DLL_EXPORT DuelParameters } }; - struct BattleInfo; DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult); @@ -311,6 +211,7 @@ public: std::map players; std::map teams; CBonusSystemNode globalEffects; + RumorState rumor; boost::shared_mutex *mx; @@ -324,6 +225,7 @@ public: void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists int3 guardingCreaturePosition (int3 pos) const; std::vector guardingCreatures (int3 pos) const; + void updateRumor(); // ----- victory, loss condition checks ----- @@ -339,8 +241,6 @@ public: bool isVisible(int3 pos, PlayerColor player); bool isVisible(const CGObjectInstance *obj, boost::optional player); - void getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing); - int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints=-1, bool checkLast=true); int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month // ----- getters, setters ----- @@ -349,6 +249,13 @@ public: template void serialize(Handler &h, const int version) { h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects & rand; + if(version >= 755) + { + h & rumor; + } + else if(!h.saving) + rumor = RumorState(); + BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/CGameStateFwd.h b/lib/CGameStateFwd.h index d676e11c2..268300e02 100644 --- a/lib/CGameStateFwd.h +++ b/lib/CGameStateFwd.h @@ -10,8 +10,81 @@ * */ +#include "CCreatureSet.h" + class CQuest; class CGObjectInstance; +class CHeroClass; +class CTown; + +//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown) +struct ArmyDescriptor : public std::map +{ + bool isDetailed; + DLL_LINKAGE ArmyDescriptor(const CArmedInstance *army, bool detailed); //not detailed -> quantity ids as count + DLL_LINKAGE ArmyDescriptor(); + + DLL_LINKAGE int getStrength() const; +}; + +struct DLL_LINKAGE InfoAboutArmy +{ + PlayerColor owner; + std::string name; + + ArmyDescriptor army; + + InfoAboutArmy(); + InfoAboutArmy(const CArmedInstance *Army, bool detailed); + + void initFromArmy(const CArmedInstance *Army, bool detailed); +}; + +struct DLL_LINKAGE InfoAboutHero : public InfoAboutArmy +{ +private: + void assign(const InfoAboutHero & iah); +public: + struct DLL_LINKAGE Details + { + std::vector primskills; + si32 mana, luck, morale; + } *details; + + const CHeroClass *hclass; + int portrait; + + InfoAboutHero(); + InfoAboutHero(const InfoAboutHero & iah); + InfoAboutHero(const CGHeroInstance *h, bool detailed); + ~InfoAboutHero(); + + InfoAboutHero & operator=(const InfoAboutHero & iah); + + void initFromHero(const CGHeroInstance *h, bool detailed); +}; + +/// Struct which holds a int information about a town +struct DLL_LINKAGE InfoAboutTown : public InfoAboutArmy +{ + struct DLL_LINKAGE Details + { + si32 hallLevel, goldIncome; + bool customRes; + bool garrisonedHero; + + } *details; + + const CTown *tType; + + si32 built; + si32 fortLevel; //0 - none + + InfoAboutTown(); + InfoAboutTown(const CGTownInstance *t, bool detailed); + ~InfoAboutTown(); + void initFromTown(const CGTownInstance *t, bool detailed); +}; class DLL_LINKAGE EVictoryLossCheckResult { diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 2481fb669..03465d888 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -327,6 +327,7 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/PRISKILL.TXT", primarySkillNames); readToVector("DATA/JKTEXT.TXT", jktexts); readToVector("DATA/TVRNINFO.TXT", tavernInfo); + readToVector("DATA/RANDTVRN.TXT", tavernRumors); readToVector("DATA/TURNDUR.TXT", turnDurations); readToVector("DATA/HEROSCRN.TXT", heroscrn); readToVector("DATA/TENTCOLR.TXT", tentColors); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index a80531cfd..bc04c3afc 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -109,6 +109,7 @@ public: //towns std::vector tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen std::vector tavernInfo; + std::vector tavernRumors; std::vector > zelp; std::vector lossCondtions; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 521a24668..6657e0b91 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -117,6 +117,7 @@ set(lib_HEADERS filesystem/ISimpleResourceLoader.h mapObjects/MapObjects.h + mapping/CMapDefines.h CSoundBase.h AI_Base.h @@ -131,6 +132,7 @@ set(lib_HEADERS IGameEventsReceiver.h int3.h CGameStateFwd.h + CPlayerState.h Interprocess.h NetPacks.h NetPacksBase.h diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 31a953292..571d5c10b 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -467,7 +467,7 @@ static JsonNode loadModSettings(std::string path) JsonNode addMeta(JsonNode config, std::string meta) { config.setMeta(meta); - return std::move(config); + return config; } CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode & config): diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index df7fadb53..2af70b48a 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -7,6 +7,8 @@ #include "mapObjects/CGHeroInstance.h" #include "GameConstants.h" #include "CStopWatch.h" +#include "CConfigHandler.h" +#include "../lib/CPlayerState.h" /* * CPathfinder.cpp, part of VCMI engine @@ -20,48 +22,61 @@ CPathfinder::PathfinderOptions::PathfinderOptions() { - useFlying = false; - useWaterWalking = false; - useEmbarkAndDisembark = true; - useTeleportTwoWay = true; - useTeleportOneWay = true; - useTeleportOneWayRandom = false; - useTeleportWhirlpool = false; + useFlying = settings["pathfinder"]["layers"]["flying"].Bool(); + useWaterWalking = settings["pathfinder"]["layers"]["waterWalking"].Bool(); + useEmbarkAndDisembark = settings["pathfinder"]["layers"]["sailing"].Bool(); + useTeleportTwoWay = settings["pathfinder"]["teleports"]["twoWay"].Bool(); + useTeleportOneWay = settings["pathfinder"]["teleports"]["oneWay"].Bool(); + useTeleportOneWayRandom = settings["pathfinder"]["teleports"]["oneWayRandom"].Bool(); + useTeleportWhirlpool = settings["pathfinder"]["teleports"]["whirlpool"].Bool(); + + useCastleGate = settings["pathfinder"]["teleports"]["castleGate"].Bool(); + + lightweightFlyingMode = settings["pathfinder"]["lightweightFlyingMode"].Bool(); + oneTurnSpecialLayersLimit = settings["pathfinder"]["oneTurnSpecialLayersLimit"].Bool(); + originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); } -CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) +CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) + : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({}) { assert(hero); assert(hero == getHero(hero->id)); out.hero = hero; out.hpos = hero->getPosition(false); - if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input + if(!isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input { logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you..."; throw std::runtime_error("Wrong checksum"); } + hlp = make_unique(hero, options); + + initializePatrol(); initializeGraph(); - - if(hero->canFly()) - options.useFlying = true; - if(hero->canWalkOnSea()) - options.useWaterWalking = true; - if(CGWhirlpool::isProtected(hero)) - options.useTeleportWhirlpool = true; - + neighbourTiles.reserve(8); neighbours.reserve(16); } void CPathfinder::calculatePaths() { - int maxMovePointsLand = hero->maxMovePoints(true); - int maxMovePointsWater = hero->maxMovePoints(false); - - auto maxMovePoints = [&](CGPathNode *cp) -> int + auto passOneTurnLimitCheck = [&]() -> bool { - return cp->land ? maxMovePointsLand : maxMovePointsWater; + if(!options.oneTurnSpecialLayersLimit) + return true; + + if(cp->layer == ELayer::WATER) + return false; + if(cp->layer == ELayer::AIR) + { + if(options.originalMovementRules && cp->accessible == CGPathNode::ACCESSIBLE) + return true; + else + return false; + } + + return true; }; auto isBetterWay = [&](int remains, int turn) -> bool @@ -70,7 +85,7 @@ void CPathfinder::calculatePaths() return true; else if(dp->turns > turn) return true; - else if(dp->turns >= turn && dp->moveRemains < remains) //this route is faster + else if(dp->turns >= turn && dp->moveRemains < remains) //this route is faster return true; return false; @@ -79,222 +94,471 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *getNode(out.hpos); - initialNode.turns = 0; - initialNode.moveRemains = hero->movement; - mq.push_back(&initialNode); + CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); + initialNode->turns = 0; + initialNode->moveRemains = hero->movement; + if(isHeroPatrolLocked()) + return; - while(!mq.empty()) + pq.push(initialNode); + while(!pq.empty()) { - cp = mq.front(); - mq.pop_front(); + cp = pq.top(); + pq.pop(); + cp->locked = true; int movement = cp->moveRemains, turn = cp->turns; + hlp->updateTurnInfo(turn); if(!movement) { - movement = maxMovePoints(cp); - turn++; + hlp->updateTurnInfo(++turn); + movement = hlp->getMaxMovePoints(cp->layer); + if(!passOneTurnLimitCheck()) + continue; } + ct = &gs->map->getTile(cp->coord); + ctObj = ct->topVisitableObj(isSourceInitialPosition()); //add accessible neighbouring nodes to the queue - addNeighbours(cp->coord); + addNeighbours(); for(auto & neighbour : neighbours) { - dp = getNode(neighbour); - dt = &gs->map->getTile(neighbour); - useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark - - if(!isMovementPossible()) + if(!isPatrolMovementAllowed(neighbour)) continue; - int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); - int remains = movement - cost; - if(useEmbarkCost) + dt = &gs->map->getTile(neighbour); + dtObj = dt->topVisitableObj(); + for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { - remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); - cost = movement - remains; - } + if(!hlp->isLayerAvailable(i)) + continue; - int turnAtNextTile = turn; - if(remains < 0) - { - //occurs rarely, when hero with low movepoints tries to leave the road - turnAtNextTile++; - int moveAtNextTile = maxMovePoints(cp); - cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( - remains = moveAtNextTile - cost; - } + if(cp->layer != i && !isLayerTransitionPossible(i)) + continue; - if(isBetterWay(remains, turnAtNextTile)) - { - assert(dp != cp->theNodeBefore); //two tiles can't point to each other - dp->moveRemains = remains; - dp->turns = turnAtNextTile; - dp->theNodeBefore = cp; + dp = out.getNode(neighbour, i); + if(dp->locked) + continue; - if(checkDestinationTile()) - mq.push_back(dp); + if(dp->accessible == CGPathNode::NOT_SET) + continue; + + if(cp->layer != i && !isLayerTransitionPossible()) + continue; + + if(!isMovementToDestPossible()) + continue; + + destAction = getDestAction(); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, movement, hlp->getTurnInfo()); + int remains = movement - cost; + if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) + { + remains = hero->movementPointsAfterEmbark(movement, cost, destAction - 1, hlp->getTurnInfo()); + cost = movement - remains; + } + int turnAtNextTile = turn; + if(remains < 0) + { + //occurs rarely, when hero with low movepoints tries to leave the road + hlp->updateTurnInfo(++turnAtNextTile); + int moveAtNextTile = hlp->getMaxMovePoints(i); + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( + remains = moveAtNextTile - cost; + } + + if(isBetterWay(remains, turnAtNextTile) && + ((cp->turns == turnAtNextTile && remains) || passOneTurnLimitCheck())) + { + assert(dp != cp->theNodeBefore); //two tiles can't point to each other + dp->moveRemains = remains; + dp->turns = turnAtNextTile; + dp->theNodeBefore = cp; + dp->action = destAction; + + if(isMovementAfterDestPossible()) + pq.push(dp); + } } } //neighbours loop //just add all passable teleport exits - if(sTileObj) + addTeleportExits(); + for(auto & neighbour : neighbours) { - addTeleportExits(); - for(auto & neighbour : neighbours) + dp = out.getNode(neighbour, cp->layer); + if(dp->locked) + continue; + + if(isBetterWay(movement, turn)) { - dp = getNode(neighbour); - if(isBetterWay(movement, turn)) - { - dp->moveRemains = movement; - dp->turns = turn; - dp->theNodeBefore = cp; - mq.push_back(dp); - } + dp->moveRemains = movement; + dp->turns = turn; + dp->theNodeBefore = cp; + dp->action = CGPathNode::NORMAL; + pq.push(dp); } } } //queue loop } -void CPathfinder::addNeighbours(const int3 &coord) +void CPathfinder::addNeighbours() { neighbours.clear(); - ct = &gs->map->getTile(coord); - - std::vector tiles; - gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, !cp->land); - sTileObj = ct->topVisitableObj(coord == CGHeroInstance::convertPosition(hero->pos, false)); - if(sTileObj) + neighbourTiles.clear(); + CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, neighbourTiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); + if(isSourceVisitableObj()) { - for(int3 tile: tiles) + for(int3 tile: neighbourTiles) { - if(canMoveBetween(tile, sTileObj->visitablePos())) + if(canMoveBetween(tile, ctObj->visitablePos())) neighbours.push_back(tile); } } else - vstd::concatenate(neighbours, tiles); + vstd::concatenate(neighbours, neighbourTiles); } -void CPathfinder::addTeleportExits(bool noTeleportExcludes) +void CPathfinder::addTeleportExits() { - assert(sTileObj); - neighbours.clear(); - auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool + /// For now we disable teleports usage for patrol movement + /// VCAI not aware about patrol and may stuck while attempt to use teleport + if(!isSourceVisitableObj() || patrolState == PATROL_RADIUS) + return; + + const CGTeleport * objTeleport = dynamic_cast(ctObj); + if(isAllowedTeleportEntrance(objTeleport)) { - if(!gs->isTeleportEntrancePassable(obj, hero->tempOwner)) - return false; - - if(noTeleportExcludes) - return true; - - auto whirlpool = dynamic_cast(obj); - if(whirlpool) - { - if(addTeleportWhirlpool(whirlpool)) - return true; - } - else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj)) - return true; - - return false; - }; - - const CGTeleport *sTileTeleport = dynamic_cast(sTileObj); - if(isAllowedTeleportEntrance(sTileTeleport)) - { - for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) + for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) { auto obj = getObj(objId); - if(CGTeleport::isExitPassable(gs, hero, obj)) + if(dynamic_cast(obj)) + { + auto pos = obj->getBlockedPos(); + for(auto p : pos) + { + if(gs->map->getTile(p).topVisitableId() == obj->ID) + neighbours.push_back(p); + } + } + else if(CGTeleport::isExitPassable(gs, hero, obj)) neighbours.push_back(obj->visitablePos()); } } -} -bool CPathfinder::isMovementPossible() -{ - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) - return false; - - Obj destTopVisObjID = dt->topVisitableId(); - if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances + if(options.useCastleGate + && (ctObj->ID == Obj::TOWN && ctObj->subID == ETownType::INFERNO + && getPlayerRelations(hero->tempOwner, ctObj->tempOwner) != PlayerRelations::ENEMIES)) { - if(cp->land) //from land to sea -> embark or assault hero on boat + /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo + /// This may be handy if we allow to use teleportation to friendly towns + auto towns = getPlayer(hero->tempOwner)->towns; + for(const auto & town : towns) { - if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable - return false; - if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land - return false; - if(destTopVisObjID == Obj::BOAT) - useEmbarkCost = 1; - } - else //disembark - { - //can disembark only on coastal tiles - if(!dt->isCoastal()) - return false; - - //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast - if((dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) - || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - return false;; - - useEmbarkCost = 2; + if(town->id != ctObj->id && town->visitingHero == nullptr + && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) + { + neighbours.push_back(town->visitablePos()); + } } } +} - if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard - return false; +bool CPathfinder::isHeroPatrolLocked() const +{ + return patrolState == PATROL_LOCKED; +} + +bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const +{ + if(patrolState == PATROL_RADIUS) + { + if(!vstd::contains(patrolTiles, dst)) + return false; + } return true; } -bool CPathfinder::checkDestinationTile() +bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const { - if(dp->accessible == CGPathNode::ACCESSIBLE) - return true; - if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false)) - return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation - if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) - return true; // For now we'll always allow transit for teleporters - if(useEmbarkCost && options.useEmbarkAndDisembark) - return true; - if(isDestinationGuarded() && !isSourceGuarded()) - return true; // Can step into a hostile tile once + /// No layer transition allowed when previous node action is BATTLE + if(cp->action == CGPathNode::BATTLE) + return false; + + switch(cp->layer) + { + case ELayer::LAND: + if(destLayer != ELayer::AIR) + return true; + + if(!options.lightweightFlyingMode || isSourceInitialPosition()) + return true; + + break; + + case ELayer::SAIL: + if(destLayer == ELayer::LAND && dt->isCoastal()) + return true; + + break; + + case ELayer::AIR: + if(destLayer == ELayer::LAND) + return true; + + break; + + case ELayer::WATER: + if(destLayer == ELayer::LAND) + return true; + + break; + } return false; } -int3 CPathfinder::getSourceGuardPosition() +bool CPathfinder::isLayerTransitionPossible() const { - return gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; + switch(cp->layer) + { + case ELayer::LAND: + if(dp->layer == ELayer::SAIL) + { + /// Cannot enter empty water tile from land -> it has to be visitable + if(dp->accessible == CGPathNode::ACCESSIBLE) + return false; + } + + break; + + case ELayer::SAIL: + //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast + if((dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) + || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + { + return false; + } + + break; + + case ELayer::AIR: + if(options.originalMovementRules) + { + if((cp->accessible != CGPathNode::ACCESSIBLE && + cp->accessible != CGPathNode::VISITABLE) && + (dp->accessible != CGPathNode::VISITABLE && + dp->accessible != CGPathNode::ACCESSIBLE)) + { + return false; + } + } + else if(cp->accessible != CGPathNode::ACCESSIBLE && dp->accessible != CGPathNode::ACCESSIBLE) + { + /// Hero that fly can only land on accessible tiles + return false; + } + + break; + } + + return true; } -bool CPathfinder::isSourceGuarded() +bool CPathfinder::isMovementToDestPossible() const { - //map can start with hero on guarded tile or teleport there using dimension door - //so threat tile hero standing on like it's not guarded because it's should be possible to move out of here - if(getSourceGuardPosition() != int3(-1, -1, -1) - && cp->coord != hero->getPosition(false)) + if(dp->accessible == CGPathNode::BLOCKED) + return false; + + switch(dp->layer) { - //special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile - if(cp->accessible != CGPathNode::VISITABLE - || !cp->theNodeBefore->land - || ct->topVisitableId() != Obj::BOAT) + case ELayer::LAND: + if(!canMoveBetween(cp->coord, dp->coord)) + return false; + if(isSourceGuarded()) { + if(!(options.originalMovementRules && cp->layer == ELayer::AIR) && + !isDestinationGuardian()) // Can step into tile of guard + { + return false; + } + } + + break; + + case ELayer::SAIL: + if(!canMoveBetween(cp->coord, dp->coord)) + return false; + if(isSourceGuarded()) + { + // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile + if(cp->action != CGPathNode::EMBARK && !isDestinationGuardian()) + return false; + } + + if(cp->layer == ELayer::LAND) + { + if(!isDestVisitableObj()) + return false; + + if(dtObj->ID != Obj::BOAT && dtObj->ID != Obj::HERO) + return false; + } + else if(isDestVisitableObj() && dtObj->ID == Obj::BOAT) + { + /// Hero in boat can't visit empty boats + return false; + } + + break; + + case ELayer::WATER: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) + return false; + if(isDestinationGuarded()) + return false; + + break; + } + + return true; +} + +bool CPathfinder::isMovementAfterDestPossible() const +{ + switch(destAction) + { + /// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles + /// Likely in many cases we don't need to add visitable tile to queue when hero don't fly + case CGPathNode::VISIT: + { + /// For now we only add visitable tile into queue when it's teleporter that allow transit + /// Movement from visitable tile when hero is standing on it is possible into any layer + const CGTeleport * objTeleport = dynamic_cast(dtObj); + if(isAllowedTeleportEntrance(objTeleport)) + { + /// For now we'll always allow transit over teleporters + /// Transit over whirlpools only allowed when hero protected return true; } + else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2) + { + /// Transit via unguarded garrisons is always possible + return true; + } + + break; + } + + case CGPathNode::NORMAL: + return true; + + case CGPathNode::EMBARK: + if(options.useEmbarkAndDisembark) + return true; + + break; + + case CGPathNode::DISEMBARK: + if(options.useEmbarkAndDisembark && !isDestinationGuarded()) + return true; + + break; + + case CGPathNode::BATTLE: + /// Movement after BATTLE action only possible from guarded tile to guardian tile + if(isDestinationGuarded()) + return true; + + break; } return false; } -bool CPathfinder::isDestinationGuarded() +CGPathNode::ENodeAction CPathfinder::getDestAction() const { - if(gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() - && dp->accessible == CGPathNode::BLOCKVIS) + CGPathNode::ENodeAction action = CGPathNode::NORMAL; + switch(dp->layer) + { + case ELayer::LAND: + if(cp->layer == ELayer::SAIL) + { + // TODO: Handle dismebark into guarded areaa + action = CGPathNode::DISEMBARK; + break; + } + + /// don't break - next case shared for both land and sail layers + + case ELayer::SAIL: + if(isDestVisitableObj()) + { + auto objRel = getPlayerRelations(dtObj->tempOwner, hero->tempOwner); + + if(dtObj->ID == Obj::BOAT) + action = CGPathNode::EMBARK; + else if(dtObj->ID == Obj::HERO) + { + if(objRel == PlayerRelations::ENEMIES) + action = CGPathNode::BATTLE; + else + action = CGPathNode::BLOCKING_VISIT; + } + else if(dtObj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) + { + const CGTownInstance * townObj = dynamic_cast(dtObj); + if(townObj->armedGarrison()) + action = CGPathNode::BATTLE; + } + else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2) + { + const CGGarrison * garrisonObj = dynamic_cast(dtObj); + if((garrisonObj->stacksCount() && objRel == PlayerRelations::ENEMIES) || isDestinationGuarded(true)) + action = CGPathNode::BATTLE; + } + else if(isDestinationGuardian()) + action = CGPathNode::BATTLE; + else if(dtObj->blockVisit && !(options.useCastleGate && dtObj->ID == Obj::TOWN)) + action = CGPathNode::BLOCKING_VISIT; + + if(action == CGPathNode::NORMAL) + { + if(options.originalMovementRules && isDestinationGuarded()) + action = CGPathNode::BATTLE; + else + action = CGPathNode::VISIT; + } + } + else if(isDestinationGuarded()) + action = CGPathNode::BATTLE; + + break; + } + + return action; +} + +bool CPathfinder::isSourceInitialPosition() const +{ + return cp->coord == out.hpos; +} + +bool CPathfinder::isSourceVisitableObj() const +{ + return isVisitableObj(ctObj, cp->layer); +} + +bool CPathfinder::isSourceGuarded() const +{ + /// Hero can move from guarded tile if movement started on that tile + /// It's possible at least in these cases: + /// - Map start with hero on guarded tile + /// - Dimention door used + /// TODO: check what happen when there is several guards + if(gs->guardingCreaturePosition(cp->coord).valid() && !isSourceInitialPosition()) { return true; } @@ -302,97 +566,193 @@ bool CPathfinder::isDestinationGuarded() return false; } -bool CPathfinder::isDestinationGuardian() +bool CPathfinder::isDestVisitableObj() const { - return getSourceGuardPosition() == dp->coord; + return isVisitableObj(dtObj, dp->layer); +} + +bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const +{ + /// isDestinationGuarded is exception needed for garrisons. + /// When monster standing behind garrison it's visitable and guarded at the same time. + if(gs->guardingCreaturePosition(dp->coord).valid() + && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) + { + return true; + } + + return false; +} + +bool CPathfinder::isDestinationGuardian() const +{ + return gs->guardingCreaturePosition(cp->coord) == dp->coord; +} + +void CPathfinder::initializePatrol() +{ + auto state = PATROL_NONE; + if(hero->patrol.patrolling && !getPlayer(hero->tempOwner)->human) + { + if(hero->patrol.patrolRadious) + { + state = PATROL_RADIUS; + gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, boost::optional(), 0, true); + } + else + state = PATROL_LOCKED; + } + + patrolState = state; } void CPathfinder::initializeGraph() { + auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) + { + auto node = out.getNode(pos, layer); + auto accessibility = evaluateAccessibility(pos, tinfo, layer); + node->update(pos, layer, accessibility); + }; + int3 pos; - CGPathNode ***graph = out.nodes; for(pos.x=0; pos.x < out.sizes.x; ++pos.x) { for(pos.y=0; pos.y < out.sizes.y; ++pos.y) { for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { - const TerrainTile *tinfo = &gs->map->getTile(pos); - CGPathNode &node = graph[pos.x][pos.y][pos.z]; - node.accessible = evaluateAccessibility(pos, tinfo); - node.turns = 0xff; - node.moveRemains = 0; - node.coord = pos; - node.land = tinfo->terType != ETerrainType::WATER; - node.theNodeBefore = nullptr; + const TerrainTile * tinfo = &gs->map->getTile(pos); + switch(tinfo->terType) + { + case ETerrainType::ROCK: + break; + + case ETerrainType::WATER: + updateNode(pos, ELayer::SAIL, tinfo); + if(options.useFlying) + updateNode(pos, ELayer::AIR, tinfo); + if(options.useWaterWalking) + updateNode(pos, ELayer::WATER, tinfo); + break; + + default: + updateNode(pos, ELayer::LAND, tinfo); + if(options.useFlying) + updateNode(pos, ELayer::AIR, tinfo); + break; + } } } } } -CGPathNode *CPathfinder::getNode(const int3 &coord) +CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const { - return &out.nodes[coord.x][coord.y][coord.z]; -} - -CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const -{ - CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); - - if(tinfo->terType == ETerrainType::ROCK || !FoW[pos.x][pos.y][pos.z]) return CGPathNode::BLOCKED; - if(tinfo->visitable) + switch(layer) { - if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary + case ELayer::LAND: + case ELayer::SAIL: + if(tinfo->visitable) { - return CGPathNode::BLOCKED; - } - else - { - for(const CGObjectInstance *obj : tinfo->visitableObjects) + if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary { - if(obj->passableFor(hero->tempOwner)) + return CGPathNode::BLOCKED; + } + else + { + for(const CGObjectInstance * obj : tinfo->visitableObjects) { - ret = CGPathNode::ACCESSIBLE; - } - else if(obj->blockVisit) - { - return CGPathNode::BLOCKVIS; - } - else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events - { - ret = CGPathNode::VISITABLE; + if(obj->blockVisit) + { + return CGPathNode::BLOCKVIS; + } + else if(obj->passableFor(hero->tempOwner)) + { + return CGPathNode::ACCESSIBLE; + } + else if(canSeeObj(obj)) + { + return CGPathNode::VISITABLE; + } } } } - } - else if(gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z].valid() - && !tinfo->blocked) - { - // Monster close by; blocked visit for battle. - return CGPathNode::BLOCKVIS; + else if(tinfo->blocked) + { + return CGPathNode::BLOCKED; + } + else if(gs->guardingCreaturePosition(pos).valid()) + { + // Monster close by; blocked visit for battle + return CGPathNode::BLOCKVIS; + } + + break; + + case ELayer::WATER: + if(tinfo->blocked || tinfo->terType != ETerrainType::WATER) + return CGPathNode::BLOCKED; + + break; + + case ELayer::AIR: + if(tinfo->blocked || tinfo->terType == ETerrainType::WATER) + return CGPathNode::FLYABLE; + + break; } - return ret; + return CGPathNode::ACCESSIBLE; } -bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const +bool CPathfinder::isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const +{ + /// Hero can't visit objects while walking on water or flying + return canSeeObj(obj) && (layer == ELayer::LAND || layer == ELayer::SAIL); +} + +bool CPathfinder::canSeeObj(const CGObjectInstance * obj) const +{ + /// Pathfinder should ignore placed events + return obj != nullptr && obj->ID != Obj::EVENT; +} + +bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const { return gs->checkForVisitableDir(a, b); } +bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const +{ + if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner)) + return false; + + auto whirlpool = dynamic_cast(obj); + if(whirlpool) + { + if(addTeleportWhirlpool(whirlpool)) + return true; + } + else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj)) + return true; + + return false; +} + bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const { - return options.useTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner); + return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner); } bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const { if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { - auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner)); + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); if(passableExits.size() == 1) return true; } @@ -403,7 +763,7 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const { if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { - auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner)); + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); if(passableExits.size() > 1) return true; } @@ -412,17 +772,289 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const { - return options.useTeleportWhirlpool && obj; + return options.useTeleportWhirlpool && hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; +} + +TurnInfo::BonusCache::BonusCache(TBonusListPtr bl) +{ + noTerrainPenalty.reserve(ETerrainType::ROCK); + for(int i = 0; i < ETerrainType::ROCK; i++) + { + noTerrainPenalty.push_back(bl->getFirst(Selector::type(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype(i)))); + } + + freeShipBoarding = bl->getFirst(Selector::type(Bonus::FREE_SHIP_BOARDING)); + flyingMovement = bl->getFirst(Selector::type(Bonus::FLYING_MOVEMENT)); + flyingMovementVal = bl->valOfBonuses(Selector::type(Bonus::FLYING_MOVEMENT)); + waterWalking = bl->getFirst(Selector::type(Bonus::WATER_WALKING)); + waterWalkingVal = bl->valOfBonuses(Selector::type(Bonus::WATER_WALKING)); +} + +TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) + : hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1) +{ + std::stringstream cachingStr; + cachingStr << "days_" << turn; + + bonuses = hero->getAllBonuses(Selector::days(turn), nullptr, nullptr, cachingStr.str()); + bonusCache = make_unique(bonuses); + nativeTerrain = hero->getNativeTerrain(); +} + +bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const +{ + switch(layer) + { + case EPathfindingLayer::AIR: + if(!hasBonusOfType(Bonus::FLYING_MOVEMENT)) + return false; + + break; + + case EPathfindingLayer::WATER: + if(!hasBonusOfType(Bonus::WATER_WALKING)) + return false; + + break; + } + + return true; +} + +bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const +{ + switch(type) + { + case Bonus::FREE_SHIP_BOARDING: + return bonusCache->freeShipBoarding; + case Bonus::FLYING_MOVEMENT: + return bonusCache->flyingMovement; + case Bonus::WATER_WALKING: + return bonusCache->waterWalking; + case Bonus::NO_TERRAIN_PENALTY: + return bonusCache->noTerrainPenalty[subtype]; + } + + return bonuses->getFirst(Selector::type(type).And(Selector::subtype(subtype))); +} + +int TurnInfo::valOfBonuses(Bonus::BonusType type, int subtype) const +{ + switch(type) + { + case Bonus::FLYING_MOVEMENT: + return bonusCache->flyingMovementVal; + case Bonus::WATER_WALKING: + return bonusCache->waterWalkingVal; + } + + return bonuses->valOfBonuses(Selector::type(type).And(Selector::subtype(subtype))); +} + +int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const +{ + if(maxMovePointsLand == -1) + maxMovePointsLand = hero->maxMovePoints(true, this); + if(maxMovePointsWater == -1) + maxMovePointsWater = hero->maxMovePoints(false, this); + + return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; +} + +CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options) + : turn(-1), hero(Hero), options(Options) +{ + turnsInfo.reserve(16); + updateTurnInfo(); +} + +void CPathfinderHelper::updateTurnInfo(const int Turn) +{ + if(turn != Turn) + { + turn = Turn; + if(turn >= turnsInfo.size()) + { + auto ti = new TurnInfo(hero, turn); + turnsInfo.push_back(ti); + } + } +} + +bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer layer) const +{ + switch(layer) + { + case EPathfindingLayer::AIR: + if(!options.useFlying) + return false; + + break; + + case EPathfindingLayer::WATER: + if(!options.useWaterWalking) + return false; + + break; + } + + return turnsInfo[turn]->isLayerAvailable(layer); +} + +const TurnInfo * CPathfinderHelper::getTurnInfo() const +{ + return turnsInfo[turn]; +} + +bool CPathfinderHelper::hasBonusOfType(const Bonus::BonusType type, const int subtype) const +{ + return turnsInfo[turn]->hasBonusOfType(type, subtype); +} + +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const +{ + return turnsInfo[turn]->getMaxMovePoints(layer); +} + +void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) +{ + static const int3 dirs[] = { + int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0), + int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0), + int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) + }; + + for(auto & dir : dirs) + { + const int3 hlp = tile + dir; + if(!map->isInTheMap(hlp)) + continue; + + const TerrainTile & hlpt = map->getTile(hlp); + if(hlpt.terType == ETerrainType::ROCK) + continue; + +// //we cannot visit things from blocked tiles +// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) +// { +// continue; +// } + + /// Following condition let us avoid diagonal movement over coast when sailing + if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water + { + int3 hlp1 = tile, + hlp2 = tile; + hlp1.x += dir.x; + hlp2.y += dir.y; + + if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) + continue; + } + + if(indeterminate(onLand) || onLand == (hlpt.terType != ETerrainType::WATER)) + { + vec.push_back(hlp); + } + } +} + +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const TurnInfo * ti, const bool checkLast) +{ + if(src == dst) //same tile + return 0; + + if(!ti) + ti = new TurnInfo(h); + + if(ct == nullptr || dt == nullptr) + { + ct = h->cb->getTile(src); + dt = h->cb->getTile(dst); + } + + /// TODO: by the original game rules hero shouldn't be affected by terrain penalty while flying. + /// Also flying movement only has penalty when player moving over blocked tiles. + /// So if you only have base flying with 40% penalty you can still ignore terrain penalty while having zero flying penalty. + int ret = h->getTileCost(*dt, *ct, ti); + /// Unfortunately this can't be implemented yet as server don't know when player flying and when he's not. + /// Difference in cost calculation on client and server is much worse than incorrect cost. + /// So this one is waiting till server going to use pathfinder rules for path validation. + + if(dt->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) + { + ret *= (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0; + } + else if(dt->terType == ETerrainType::WATER) + { + if(h->boat && ct->hasFavourableWinds() && dt->hasFavourableWinds()) //Favourable Winds + ret *= 0.666; + else if(!h->boat && ti->hasBonusOfType(Bonus::WATER_WALKING)) + { + ret *= (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; + } + } + + if(src.x != dst.x && src.y != dst.y) //it's diagonal move + { + int old = ret; + ret *= 1.414213; + //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points + if(ret > remainingMovePoints && remainingMovePoints >= old) + return remainingMovePoints; + } + + /// TODO: This part need rework in order to work properly with flying and water walking + /// Currently it's only work properly for normal movement or sailing + int left = remainingMovePoints-ret; + if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points + { + std::vector vec; + vec.reserve(8); //optimization + getNeighbours(h->cb->gameState()->map, *dt, dst, vec, ct->terType != ETerrainType::WATER, true); + for(auto & elem : vec) + { + int fcost = getMovementCost(h, dst, elem, nullptr, nullptr, left, ti, false); + if(fcost <= left) + return ret; + } + ret = remainingMovePoints; + } + return ret; +} + +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & dst) +{ + return getMovementCost(h, h->visitablePos(), dst, nullptr, nullptr, h->movement); } CGPathNode::CGPathNode() -:coord(-1,-1,-1) + : coord(int3(-1, -1, -1)), layer(ELayer::WRONG) { + reset(); +} + +void CGPathNode::reset() +{ + locked = false; accessible = NOT_SET; - land = 0; moveRemains = 0; turns = 255; theNodeBefore = nullptr; + action = UNKNOWN; +} + +void CGPathNode::update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible) +{ + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + reset(); + + accessible = Accessible; } bool CGPathNode::reachable() const @@ -440,7 +1072,7 @@ int3 CGPath::endPos() const return nodes[0].coord; } -void CGPath::convert( ui8 mode ) +void CGPath::convert(ui8 mode) { if(mode==0) { @@ -451,70 +1083,66 @@ void CGPath::convert( ui8 mode ) } } -CPathsInfo::CPathsInfo( const int3 &Sizes ) -:sizes(Sizes) +CPathsInfo::CPathsInfo(const int3 & Sizes) + : sizes(Sizes) { hero = nullptr; - nodes = new CGPathNode**[sizes.x]; - for(int i = 0; i < sizes.x; i++) - { - nodes[i] = new CGPathNode*[sizes.y]; - for(int j = 0; j < sizes.y; j++) - { - nodes[i][j] = new CGPathNode[sizes.z]; - } - } + nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][ELayer::NUM_LAYERS]); } CPathsInfo::~CPathsInfo() { - for(int i = 0; i < sizes.x; i++) - { - for(int j = 0; j < sizes.y; j++) - { - delete [] nodes[i][j]; - } - delete [] nodes[i]; - } - delete [] nodes; } -const CGPathNode * CPathsInfo::getPathInfo(const int3& tile) const +const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const { - boost::unique_lock pathLock(pathMx); + assert(vstd::iswithin(tile.x, 0, sizes.x)); + assert(vstd::iswithin(tile.y, 0, sizes.y)); + assert(vstd::iswithin(tile.z, 0, sizes.z)); - if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z - || tile.x < 0 || tile.y < 0 || tile.z < 0) - return nullptr; - return &nodes[tile.x][tile.y][tile.z]; + boost::unique_lock pathLock(pathMx); + return getNode(tile); } -bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const +bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const { boost::unique_lock pathLock(pathMx); out.nodes.clear(); - const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z]; + const CGPathNode * curnode = getNode(dst); if(!curnode->theNodeBefore) return false; - while(curnode) { - CGPathNode cpn = *curnode; + const CGPathNode cpn = * curnode; curnode = curnode->theNodeBefore; out.nodes.push_back(cpn); } return true; } -int CPathsInfo::getDistance( int3 tile ) const +int CPathsInfo::getDistance(const int3 & tile) const { boost::unique_lock pathLock(pathMx); CGPath ret; - if(getPath(tile, ret)) + if(getPath(ret, tile)) return ret.nodes.size(); else return 255; } + +const CGPathNode * CPathsInfo::getNode(const int3 & coord) const +{ + auto landNode = &nodes[coord.x][coord.y][coord.z][ELayer::LAND]; + if(landNode->reachable()) + return landNode; + else + return &nodes[coord.x][coord.y][coord.z][ELayer::SAIL]; +} + +CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer) +{ + return &nodes[coord.x][coord.y][coord.z][layer]; +} diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 43615456d..e2f55dc37 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -1,10 +1,12 @@ #pragma once #include "VCMI_Lib.h" -#include "mapping/CMap.h" #include "IGameCallback.h" +#include "HeroBonus.h" #include "int3.h" +#include + /* * CPathfinder.h, part of VCMI engine * @@ -18,26 +20,47 @@ class CGHeroInstance; class CGObjectInstance; struct TerrainTile; +class CPathfinderHelper; +class CMap; +class CGWhirlpool; struct DLL_LINKAGE CGPathNode { - enum EAccessibility + typedef EPathfindingLayer ELayer; + + enum ENodeAction : ui8 + { + UNKNOWN = 0, + EMBARK = 1, + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT + }; + + enum EAccessibility : ui8 { NOT_SET = 0, ACCESSIBLE = 1, //tile can be entered and passed VISITABLE, //tile can be entered as the last tile in path BLOCKVIS, //visitable from neighbouring tile but not passable + FLYABLE, //can only be accessed in air layer BLOCKED //tile can't be entered nor visited }; - EAccessibility accessible; - ui8 land; - ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn - ui32 moveRemains; //remaining tiles after hero reaches the tile CGPathNode * theNodeBefore; int3 coord; //coordinates + ui32 moveRemains; //remaining tiles after hero reaches the tile + ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn + ELayer layer; + EAccessibility accessible; + ENodeAction action; + bool locked; CGPathNode(); + void reset(); + void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible); bool reachable() const; }; @@ -52,27 +75,36 @@ struct DLL_LINKAGE CGPath struct DLL_LINKAGE CPathsInfo { + typedef EPathfindingLayer ELayer; + mutable boost::mutex pathMx; - const CGHeroInstance *hero; + const CGHeroInstance * hero; int3 hpos; int3 sizes; - CGPathNode ***nodes; //[w][h][level] + boost::multi_array nodes; //[w][h][level][layer] - CPathsInfo(const int3 &Sizes); + CPathsInfo(const int3 & Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3& tile) const; - bool getPath(const int3 &dst, CGPath &out) const; - int getDistance( int3 tile ) const; + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + int getDistance(const int3 & tile) const; + const CGPathNode * getNode(const int3 & coord) const; + + CGPathNode * getNode(const int3 & coord, const ELayer layer); }; class CPathfinder : private CGameInfoCallback { public: - CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); + friend class CPathfinderHelper; + + CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: + typedef EPathfindingLayer ELayer; + struct PathfinderOptions { bool useFlying; @@ -83,42 +115,164 @@ private: bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) + /// TODO: Find out with client and server code, merge with normal teleporters. + /// Likely proper implementation would require some refactoring of CGTeleport. + /// So for now this is unfinished and disabled by default. + bool useCastleGate; + + /// If true transition into air layer only possible from initial node. + /// This is drastically decrease path calculation complexity (and time). + /// Downside is less MP effective paths calculation. + /// + /// TODO: If this option end up useful for slow devices it's can be improved: + /// - Allow transition into air layer not only from initial position, but also from teleporters. + /// Movement into air can be also allowed when hero disembarked. + /// - Other idea is to allow transition into air within certain radius of N tiles around hero. + /// Patrol support need similar functionality so it's won't be ton of useless code. + /// Such limitation could be useful as it's can be scaled depend on device performance. + bool lightweightFlyingMode; + + /// This option enable one turn limitation for flying and water walking. + /// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue. + /// + /// Following imitation is default H3 mechanics, but someone may want to disable it in mods. + /// After all this limit should benefit performance on maps with tons of water or blocked tiles. + /// + /// TODO: + /// - Behavior when option is disabled not implemented and will lead to crashes. + bool oneTurnSpecialLayersLimit; + + /// VCMI have different movement rules to solve flaws original engine has. + /// If this option enabled you'll able to do following things in fly: + /// - Move from blocked tiles to visitable one + /// - Move from guarded tiles to blockvis tiles without being attacked + /// - Move from guarded tiles to guarded visitable tiles with being attacked after + /// TODO: + /// - Option should also allow same tile land <-> air layer transitions. + /// Current implementation only allow go into (from) air layer only to neighbour tiles. + /// I find it's reasonable limitation, but it's will make some movements more expensive than in H3. + bool originalMovementRules; + PathfinderOptions(); } options; - CPathsInfo &out; - const CGHeroInstance *hero; + CPathsInfo & out; + const CGHeroInstance * hero; const std::vector > > &FoW; + unique_ptr hlp; - std::list mq; //BFS queue -> nodes to be checked + enum EPatrolState { + PATROL_NONE = 0, + PATROL_LOCKED = 1, + PATROL_RADIUS + } patrolState; + std::unordered_set patrolTiles; + struct NodeComparer + { + bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const + { + if(rhs->turns > lhs->turns) + return false; + else if(rhs->turns == lhs->turns && rhs->moveRemains < lhs->moveRemains) + return false; + + return true; + } + }; + boost::heap::priority_queue > pq; + + std::vector neighbourTiles; std::vector neighbours; - CGPathNode *cp; //current (source) path node -> we took it from the queue - CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider - const TerrainTile *ct, *dt; //tile info for both nodes - const CGObjectInstance *sTileObj; - ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark + CGPathNode * cp; //current (source) path node -> we took it from the queue + CGPathNode * dp; //destination node -> it's a neighbour of cp that we consider + const TerrainTile * ct, * dt; //tile info for both nodes + const CGObjectInstance * ctObj, * dtObj; + CGPathNode::ENodeAction destAction; - void addNeighbours(const int3 &coord); - void addTeleportExits(bool noTeleportExcludes = false); + void addNeighbours(); + void addTeleportExits(); - bool isMovementPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost - bool checkDestinationTile(); + bool isHeroPatrolLocked() const; + bool isPatrolMovementAllowed(const int3 & dst) const; - int3 getSourceGuardPosition(); - bool isSourceGuarded(); - bool isDestinationGuarded(); - bool isDestinationGuardian(); + bool isLayerTransitionPossible(const ELayer dstLayer) const; + bool isLayerTransitionPossible() const; + bool isMovementToDestPossible() const; + bool isMovementAfterDestPossible() const; + CGPathNode::ENodeAction getDestAction() const; + bool isSourceInitialPosition() const; + bool isSourceVisitableObj() const; + bool isSourceGuarded() const; + bool isDestVisitableObj() const; + bool isDestinationGuarded(const bool ignoreAccessibility = true) const; + bool isDestinationGuardian() const; + + void initializePatrol(); void initializeGraph(); - CGPathNode *getNode(const int3 &coord); - CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const; - bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) + CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; + bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const; + bool canSeeObj(const CGObjectInstance * obj) const; + bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) + bool isAllowedTeleportEntrance(const CGTeleport * obj) const; bool addTeleportTwoWay(const CGTeleport * obj) const; bool addTeleportOneWay(const CGTeleport * obj) const; bool addTeleportOneWayRandom(const CGTeleport * obj) const; bool addTeleportWhirlpool(const CGWhirlpool * obj) const; + +}; + +struct DLL_LINKAGE TurnInfo +{ + /// This is certainly not the best design ever and certainly can be improved + /// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead + struct BonusCache { + std::vector noTerrainPenalty; + bool freeShipBoarding; + bool flyingMovement; + int flyingMovementVal; + bool waterWalking; + int waterWalkingVal; + + BonusCache(TBonusListPtr bonusList); + }; + unique_ptr bonusCache; + + const CGHeroInstance * hero; + TBonusListPtr bonuses; + mutable int maxMovePointsLand; + mutable int maxMovePointsWater; + int nativeTerrain; + + TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + bool isLayerAvailable(const EPathfindingLayer layer) const; + bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; + int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const; + int getMaxMovePoints(const EPathfindingLayer layer) const; +}; + +class DLL_LINKAGE CPathfinderHelper +{ +public: + CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options); + void updateTurnInfo(const int turn = 0); + bool isLayerAvailable(const EPathfindingLayer layer) const; + const TurnInfo * getTurnInfo() const; + bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; + int getMaxMovePoints(const EPathfindingLayer layer) const; + + static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); + + static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); + static int getMovementCost(const CGHeroInstance * h, const int3 & dst); + +private: + int turn; + const CGHeroInstance * hero; + std::vector turnsInfo; + const CPathfinder::PathfinderOptions & options; }; diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h new file mode 100644 index 000000000..f676379a3 --- /dev/null +++ b/lib/CPlayerState.h @@ -0,0 +1,66 @@ +#pragma once + +/* + * CPlayerState.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "HeroBonus.h" + +class CGHeroInstance; +class CGTownInstance; +class CGDwelling; + +struct DLL_LINKAGE PlayerState : public CBonusSystemNode +{ +public: + PlayerColor color; + bool human; //true if human controlled player, false for AI + TeamID team; + TResources resources; + std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks + std::vector > heroes; + std::vector > towns; + std::vector > availableHeroes; //heroes available in taverns + std::vector > dwellings; //used for town growth + std::vector quests; //store info about all received quests + + bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory + EPlayerStatus::EStatus status; + boost::optional daysWithoutCastle; + + PlayerState(); + std::string nodeName() const override; + + template void serialize(Handler &h, const int version) + { + h & color & human & team & resources & status; + h & heroes & towns & availableHeroes & dwellings & quests & visitedObjects; + h & getBonusList(); //FIXME FIXME FIXME + h & status & daysWithoutCastle; + h & enteredLosingCheatCode & enteredWinningCheatCode; + h & static_cast(*this); + } +}; + +struct DLL_LINKAGE TeamState : public CBonusSystemNode +{ +public: + TeamID id; //position in gameState::teams + std::set players; // members of this team + std::vector > > fogOfWarMap; //true - visible, false - hidden + + TeamState(); + + template void serialize(Handler &h, const int version) + { + h & id & players & fogOfWarMap; + h & static_cast(*this); + } + +}; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index fb76a2523..340deab4c 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -774,12 +774,8 @@ std::set CTownHandler::getAllowedFactions(bool withTown /*=true*/) con if (withTown) allowed = getDefaultAllowed(); else - { - for (auto town : factions) - { - allowed.push_back (true); - } - } + allowed.resize( factions.size(), true); + for (size_t i=0; i diff --git a/lib/Connection.h b/lib/Connection.h index 73f9a43e7..c7552ca33 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -11,7 +11,7 @@ #pragma once -#include //XXX this is in namespace std if you want w/o use typeinfo.h? +#include #include #include @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 754; +const ui32 version = 755; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 9a48b476f..864b1aba2 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -25,56 +25,6 @@ const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); const PlayerColor PlayerColor::PLAYER_LIMIT = PlayerColor(PLAYER_LIMIT_I); const TeamID TeamID::NO_TEAM = TeamID(255); -#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \ -bool operator==(const A & a, const B & b) \ -{ \ - return AN == BN ; \ -} \ -bool operator!=(const A & a, const B & b) \ -{ \ - return AN != BN ; \ -} \ -bool operator<(const A & a, const B & b) \ -{ \ - return AN < BN ; \ -} \ -bool operator<=(const A & a, const B & b) \ -{ \ - return AN <= BN ; \ -} \ -bool operator>(const A & a, const B & b) \ -{ \ - return AN > BN ; \ -} \ -bool operator>=(const A & a, const B & b) \ -{ \ - return AN >= BN ; \ -} - -#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \ - ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \ - ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) - - -ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) - -ID_LIKE_OPERATORS(Obj, Obj::EObj) - -ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) - -ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) - -ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) - -ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) - -ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) - -ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) - -ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) - CArtifact * ArtifactID::toArtifact() const { return VLC->arth->artifacts[*this]; @@ -130,7 +80,7 @@ std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType else return os << it->second; } -std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) +std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType) { static const std::map terrainTypeToString = { @@ -147,9 +97,10 @@ std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) DEFINE_ELEMENT(LAVA), DEFINE_ELEMENT(WATER), DEFINE_ELEMENT(ROCK) + #undef DEFINE_ELEMENT }; - auto it = terrainTypeToString.find(actionType.num); + auto it = terrainTypeToString.find(terrainType.num); if (it == terrainTypeToString.end()) return os << ""; else return os << it->second; } @@ -160,3 +111,23 @@ std::string ETerrainType::toString() const ss << *this; return ss.str(); } + +std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer) +{ + static const std::map pathfinderLayerToString + { + #define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element} + DEFINE_ELEMENT(WRONG), + DEFINE_ELEMENT(AUTO), + DEFINE_ELEMENT(LAND), + DEFINE_ELEMENT(SAIL), + DEFINE_ELEMENT(WATER), + DEFINE_ELEMENT(AIR), + DEFINE_ELEMENT(NUM_LAYERS) + #undef DEFINE_ELEMENT + }; + + auto it = pathfinderLayerToString.find(pathfindingLayer.num); + if (it == pathfinderLayerToString.end()) return os << ""; + else return os << it->second; +} diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 0a1f74945..ad13b9a7c 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -14,7 +14,7 @@ namespace GameConstants { - const std::string VCMI_VERSION = "VCMI 0.98e"; + const std::string VCMI_VERSION = "VCMI 0.98f"; const int BFIELD_WIDTH = 17; const int BFIELD_HEIGHT = 11; @@ -93,18 +93,37 @@ CLASS_NAME & advance(int i) \ } -#define ID_LIKE_OPERATORS_INTERNAL_DECLS(A, B) \ -bool DLL_LINKAGE operator==(const A & a, const B & b); \ -bool DLL_LINKAGE operator!=(const A & a, const B & b); \ -bool DLL_LINKAGE operator<(const A & a, const B & b); \ -bool DLL_LINKAGE operator<=(const A & a, const B & b); \ -bool DLL_LINKAGE operator>(const A & a, const B & b); \ -bool DLL_LINKAGE operator>=(const A & a, const B & b); +// Operators are performance-critical and to be inlined they must be in header +#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \ +STRONG_INLINE bool operator==(const A & a, const B & b) \ +{ \ + return AN == BN ; \ +} \ +STRONG_INLINE bool operator!=(const A & a, const B & b) \ +{ \ + return AN != BN ; \ +} \ +STRONG_INLINE bool operator<(const A & a, const B & b) \ +{ \ + return AN < BN ; \ +} \ +STRONG_INLINE bool operator<=(const A & a, const B & b) \ +{ \ + return AN <= BN ; \ +} \ +STRONG_INLINE bool operator>(const A & a, const B & b) \ +{ \ + return AN > BN ; \ +} \ +STRONG_INLINE bool operator>=(const A & a, const B & b) \ +{ \ + return AN >= BN ; \ +} -#define ID_LIKE_OPERATORS_DECLS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, CLASS_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(ENUM_NAME, CLASS_NAME) +#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \ + ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \ + ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \ + ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) #define OP_DECL_INT(CLASS_NAME, OP) \ @@ -296,7 +315,7 @@ public: ESecondarySkill num; }; -ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill) +ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) namespace EAlignment { @@ -384,7 +403,19 @@ public: EBuildingID num; }; -ID_LIKE_OPERATORS_DECLS(BuildingID, BuildingID::EBuildingID) +ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) + +namespace EAiTactic +{ +enum EAiTactic +{ + NONE = -1, + RANDOM, + WARRIOR, + BUILDER, + EXPLORER +}; +} namespace EBuildingState { @@ -432,8 +463,21 @@ namespace EMarketMode namespace EBattleStackState { - enum EBattleStackState{ALIVE = 180, SUMMONED, CLONED, DEAD_CLONE, HAD_MORALE, WAITING, MOVED, DEFENDING, FEAR, - DRAINED_MANA /*remember to drain mana only once per turn*/}; + enum EBattleStackState + { + ALIVE = 180, + SUMMONED, CLONED, + DEAD_CLONE, + HAD_MORALE, + WAITING, + MOVED, + DEFENDING, + FEAR, + //remember to drain mana only once per turn + DRAINED_MANA, + //only for defending animation + DEFENDING_ANIM + }; } namespace ECommander @@ -651,7 +695,7 @@ public: EObj num; }; -ID_LIKE_OPERATORS_DECLS(Obj, Obj::EObj) +ID_LIKE_OPERATORS(Obj, Obj::EObj) namespace SecSkillLevel { @@ -741,10 +785,51 @@ public: std::string toString() const; }; -DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType actionType); +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType); -ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) +ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) +class DLL_LINKAGE EDiggingStatus +{ +public: + enum EEDiggingStatus + { + UNKNOWN = -1, + CAN_DIG = 0, + LACK_OF_MOVEMENT, + WRONG_TERRAIN, + TILE_OCCUPIED + }; + + EDiggingStatus(EEDiggingStatus _num = UNKNOWN) : num(_num) + {} + + ID_LIKE_CLASS_COMMON(EDiggingStatus, EEDiggingStatus) + + EEDiggingStatus num; +}; + +ID_LIKE_OPERATORS(EDiggingStatus, EDiggingStatus::EEDiggingStatus) + +class DLL_LINKAGE EPathfindingLayer +{ +public: + enum EEPathfindingLayer : ui8 + { + LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS, WRONG, AUTO + }; + + EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num) + {} + + ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer) + + EEPathfindingLayer num; +}; + +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer); + +ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) class BFieldType { @@ -767,7 +852,7 @@ public: EBFieldType num; }; -ID_LIKE_OPERATORS_DECLS(BFieldType, BFieldType::EBFieldType) +ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) namespace EPlayerStatus { @@ -807,7 +892,7 @@ public: EArtifactPosition num; }; -ID_LIKE_OPERATORS_DECLS(ArtifactPosition, ArtifactPosition::EArtifactPosition) +ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) class ArtifactID { @@ -824,6 +909,7 @@ public: FIRST_AID_TENT = 6, //CENTAUR_AXE = 7, //BLACKSHARD_OF_THE_DEAD_KNIGHT = 8, + ARMAGEDDONS_BLADE = 128, TITANS_THUNDER = 135, //CORNUCOPIA = 140, //FIXME: the following is only true if WoG is enabled. Otherwise other mod artifacts will take these slots. @@ -851,7 +937,7 @@ public: EArtifactID num; }; -ID_LIKE_OPERATORS_DECLS(ArtifactID, ArtifactID::EArtifactID) +ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) class CreatureID { @@ -895,7 +981,7 @@ public: ECreatureID num; }; -ID_LIKE_OPERATORS_DECLS(CreatureID, CreatureID::ECreatureID) +ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) class SpellID { @@ -939,7 +1025,7 @@ public: ESpellID num; }; -ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) +ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) enum class ESpellSchool: ui8 { @@ -949,8 +1035,6 @@ enum class ESpellSchool: ui8 EARTH = 3 }; -ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) - // Typedef declarations typedef ui8 TFaction; typedef si64 TExpType; @@ -961,8 +1045,8 @@ typedef si32 TQuantity; typedef int TRmgTemplateZoneId; #undef ID_LIKE_CLASS_COMMON -#undef ID_LIKE_OPERATORS_DECLS -#undef ID_LIKE_OPERATORS_INTERNAL_DECLS +#undef ID_LIKE_OPERATORS +#undef ID_LIKE_OPERATORS_INTERNAL #undef INSTID_LIKE_CLASS_COMMON #undef OP_DECL_INT diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 0380d0144..c054d586a 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -763,8 +763,29 @@ void CBonusSystemNode::popBonuses(const CSelector &s) child->popBonuses(s); } +void CBonusSystemNode::updateBonuses(const CSelector &s) +{ + BonusList bl; + exportedBonuses.getBonuses(bl, s); + for(Bonus *b : bl) + { + b->turnsRemain--; + if(b->turnsRemain <= 0) + removeBonus(b); + } + + for(CBonusSystemNode *child : children) + child->updateBonuses(s); +} + void CBonusSystemNode::addNewBonus(Bonus *b) { + //turnsRemain shouldn't be zero for following durations + if(Bonus::NTurns(b) || Bonus::NDays(b) || Bonus::OneWeek(b)) + { + assert(b->turnsRemain); + } + assert(!vstd::contains(exportedBonuses,b)); exportedBonuses.push_back(b); exportBonus(b); @@ -950,18 +971,7 @@ void CBonusSystemNode::getRedDescendants(TNodes &out) void CBonusSystemNode::battleTurnPassed() { - BonusList bonusesCpy = exportedBonuses; //copy, because removing bonuses invalidates iters - for (auto & elem : bonusesCpy) - { - Bonus *b = elem; - - if(b->duration & Bonus::N_TURNS) - { - b->turnsRemain--; - if(b->turnsRemain <= 0) - removeBonus(b); - } - } + updateBonuses(Bonus::NTurns); } void CBonusSystemNode::exportBonus(Bonus * b) @@ -1183,6 +1193,7 @@ namespace Selector DLL_LINKAGE CSelectFieldEqual sourceType(&Bonus::source); DLL_LINKAGE CSelectFieldEqual effectRange(&Bonus::effectRange); DLL_LINKAGE CWillLastTurns turns; + DLL_LINKAGE CWillLastDays days; DLL_LINKAGE CSelectFieldAny anyRange(&Bonus::effectRange); CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 75f1b956c..2121263de 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -294,7 +294,7 @@ struct DLL_LINKAGE Bonus }; ui16 duration; //uses BonusDuration values - si16 turnsRemain; //used if duration is N_TURNS or N_DAYS + si16 turnsRemain; //used if duration is N_TURNS, N_DAYS or ONE_WEEK BonusType type; //uses BonusType values - says to what is this bonus - 1 byte TBonusSubtype subtype; //-1 if not applicable - 4 bytes @@ -686,6 +686,7 @@ public: //bool isLimitedOnUs(Bonus *b) const; //if bonus should be removed from list acquired from this node void popBonuses(const CSelector &s); + void updateBonuses(const CSelector &s); virtual std::string bonusToString(const Bonus *bonus, bool description) const {return "";}; //description or bonus name virtual std::string nodeName() const; @@ -810,7 +811,7 @@ public: bool operator()(const Bonus *bonus) const { return turnsRequested <= 0 //every present effect will last zero (or "less") turns - || !(bonus->duration & Bonus::N_TURNS) //so do every not expriing after N-turns effect + || !Bonus::NTurns(bonus) //so do every not expriing after N-turns effect || bonus->turnsRemain > turnsRequested; } CWillLastTurns& operator()(const int &setVal) @@ -820,6 +821,31 @@ public: } }; +class DLL_LINKAGE CWillLastDays +{ +public: + int daysRequested; + + bool operator()(const Bonus *bonus) const + { + if(daysRequested <= 0 || Bonus::Permanent(bonus) || Bonus::OneBattle(bonus)) + return true; + else if(Bonus::OneDay(bonus)) + return false; + else if(Bonus::NDays(bonus) || Bonus::OneWeek(bonus)) + { + return bonus->turnsRemain > daysRequested; + } + + return false; // TODO: ONE_WEEK need support for turnsRemain, but for now we'll exclude all unhandled durations + } + CWillLastDays& operator()(const int &setVal) + { + daysRequested = setVal; + return *this; + } +}; + //Stores multiple limiters. If any of them fails -> bonus is dropped. class DLL_LINKAGE LimiterList : public ILimiter { @@ -958,6 +984,7 @@ namespace Selector extern DLL_LINKAGE CSelectFieldEqual sourceType; extern DLL_LINKAGE CSelectFieldEqual effectRange; extern DLL_LINKAGE CWillLastTurns turns; + extern DLL_LINKAGE CWillLastDays days; extern DLL_LINKAGE CSelectFieldAny anyRange; CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype); diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index f71d2fa15..8f9c5bd44 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -21,6 +21,8 @@ #include "mapObjects/CObjectClassesHandler.h" #include "StartInfo.h" #include "CGameState.h" +#include "mapping/CMap.h" +#include "CPlayerState.h" void CPrivilagedInfoCallback::getFreeTiles (std::vector &tiles) const { @@ -38,14 +40,14 @@ void CPrivilagedInfoCallback::getFreeTiles (std::vector &tiles) const for (int yd = 0; yd < gs->map->height; yd++) { tinfo = getTile(int3 (xd,yd,zd)); - if (tinfo->terType != ETerrainType::WATER && !tinfo->blocked) //land and free + if (tinfo->terType != ETerrainType::WATER && tinfo->terType != ETerrainType::ROCK && !tinfo->blocked) //land and free tiles.push_back (int3 (xd,yd,zd)); } } } } -void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set &tiles, int3 pos, int radious, boost::optional player/*=uninit*/, int mode/*=0*/ ) const +void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set &tiles, int3 pos, int radious, boost::optional player/*=uninit*/, int mode/*=0*/, bool patrolDistance/*=false*/) const { if(!!player && *player >= PlayerColor::PLAYER_LIMIT) { @@ -61,7 +63,13 @@ void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set(pos.y - radious, 0); yd <= std::min(pos.y + radious, gs->map->height - 1); yd++) { - double distance = pos.dist2d(int3(xd,yd,pos.z)) - 0.5; + int3 tilePos(xd,yd,pos.z); + double distance; + if(patrolDistance) + distance = pos.mandist2d(tilePos); + else + distance = pos.dist2d(tilePos) - 0.5; + if(distance <= radious) { if(!player diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index b2ca42030..43c24d05c 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -30,7 +30,7 @@ class DLL_LINKAGE CPrivilagedInfoCallback : public CGameInfoCallback public: CGameState * gameState(); void getFreeTiles (std::vector &tiles) const; //used for random spawns - void getTilesInRange(std::unordered_set &tiles, int3 pos, int radious, boost::optional player = boost::optional(), int mode=0) const; //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 - only unrevealed + void getTilesInRange(std::unordered_set &tiles, int3 pos, int radious, boost::optional player = boost::optional(), int mode = 0, bool patrolDistance = false) const; //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 - only unrevealed void getAllTiles (std::unordered_set &tiles, boost::optional player = boost::optional(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water void pickAllowedArtsSet(std::vector &out); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant void getAllowedSpells(std::vector &out, ui16 level); diff --git a/lib/NetPacks.h b/lib/NetPacks.h index e788fd9b2..06bdbf014 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -13,7 +13,7 @@ #include "ResourceSet.h" //#include "CObstacleInstance.h" #include "CGameStateFwd.h" -#include "mapping/CMap.h" +#include "mapping/CMapDefines.h" #include "CObstacleInstance.h" #include "spells/ViewSpellInt.h" @@ -363,6 +363,7 @@ struct GiveBonus : public CPackForClient //115 template void serialize(Handler &h, const int version) { h & bonus & id & bdescr & who; + assert( id != -1); } }; @@ -1218,7 +1219,7 @@ struct TeleportDialog : public Query//2006 const CGHeroInstance *hero; TeleportChannelID channel; - std::vector exits; + TTeleportExitsList exits; bool impassable; template void serialize(Handler &h, const int version) diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index ede41bd8b..3c76e3333 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -16,6 +16,7 @@ #include "CTownHandler.h" #include "mapping/CMapInfo.h" #include "StartInfo.h" +#include "CPlayerState.h" /* * NetPacksLib.cpp, part of VCMI engine @@ -263,6 +264,9 @@ DLL_LINKAGE void GiveBonus::applyGs( CGameState *gs ) assert(cbsn); + if(Bonus::OneWeek(&bonus)) + bonus.turnsRemain = 8 - gs->getDate(Date::DAY_OF_WEEK); // set correct number of days before adding bonus + auto b = new Bonus(bonus); cbsn->addNewBonus(b); @@ -1023,13 +1027,15 @@ DLL_LINKAGE void NewTurn::applyGs( CGameState *gs ) creatureSet.second.applyGs(gs); gs->globalEffects.popBonuses(Bonus::OneDay); //works for children -> all game objs - if(gs->getDate(Date::DAY_OF_WEEK) == 1) //new week - gs->globalEffects.popBonuses(Bonus::OneWeek); //works for children -> all game objs - + gs->globalEffects.updateBonuses(Bonus::NDays); + gs->globalEffects.updateBonuses(Bonus::OneWeek); //TODO not really a single root hierarchy, what about bonuses placed elsewhere? [not an issue with H3 mechanics but in the future...] for(CGTownInstance* t : gs->map->towns) t->builded = 0; + + if(gs->getDate(Date::DAY_OF_WEEK) == 1) + gs->updateRumor(); } DLL_LINKAGE void SetObjectProperty::applyGs( CGameState *gs ) @@ -1329,14 +1335,18 @@ DLL_LINKAGE void StartAction::applyGs( CGameState *gs ) switch(ba.actionType) { case Battle::DEFEND: + st->state -= EBattleStackState::DEFENDING_ANIM; st->state.insert(EBattleStackState::DEFENDING); + st->state.insert(EBattleStackState::DEFENDING_ANIM); break; case Battle::WAIT: + st->state -= EBattleStackState::DEFENDING_ANIM; st->state.insert(EBattleStackState::WAITING); return; case Battle::HERO_SPELL: //no change in current stack state return; default: //any active stack action - attack, catapult, heal, spell... + st->state -= EBattleStackState::DEFENDING_ANIM; st->state.insert(EBattleStackState::MOVED); break; } diff --git a/lib/StartInfo.h b/lib/StartInfo.h index e254a1053..8d54faa70 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -31,7 +31,7 @@ struct PlayerSettings Ebonus bonus; si16 castle; si32 hero, - heroPortrait; //-1 if default, else ID + heroPortrait; //-1 if default, else ID std::string heroName; PlayerColor color; //from 0 - @@ -70,13 +70,13 @@ struct StartInfo { enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, DUEL, INVALID = 255}; - EMode mode; - ui8 difficulty; //0=easy; 4=impossible - - typedef std::map TPlayerInfos; - TPlayerInfos playerInfos; //color indexed - - ui32 seedToBeUsed; //0 if not sure (client requests server to decide, will be send in reply pack) + EMode mode; + ui8 difficulty; //0=easy; 4=impossible + + typedef std::map TPlayerInfos; + TPlayerInfos playerInfos; //color indexed + + ui32 seedToBeUsed; //0 if not sure (client requests server to decide, will be send in reply pack) ui32 seedPostInit; //so we know that game is correctly synced at the start; 0 if not known yet ui32 mapfileChecksum; //0 if not relevant ui8 turnTime; //in minutes, 0=unlimited diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 679b68fd0..7c2110afa 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -34,9 +34,9 @@ void IVCMIDirs::init() #endif #endif // __MINGW32__ -#include -#include -#include +#include +#include +#include // Generates script file named _temp.bat in 'to' directory and runs it // Script will: diff --git a/lib/int3.h b/lib/int3.h index 7ac595183..389b9241c 100644 --- a/lib/int3.h +++ b/lib/int3.h @@ -105,6 +105,11 @@ public: { return std::sqrt((double)dist2dSQ(o)); } + //manhattan distance used for patrol radius (z coord is not used) + double mandist2d(const int3 & o) const + { + return abs(o.x - x) + abs(o.y - y); + } bool areNeighbours(const int3 & o) const { @@ -131,6 +136,12 @@ public: { h & x & y & z; } + + static std::array getDirs() + { + return { { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), + int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) } }; + } }; inline std::ostream & operator<<(std::ostream & str, const int3 & sth) @@ -154,9 +165,6 @@ struct ShashInt3 } }; -static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), - int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; - template int3 findClosestTile (Container & container, int3 dest) { @@ -175,4 +183,4 @@ int3 findClosestTile (Container & container, int3 dest) } } return result; -} \ No newline at end of file +} diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index d6d879b7e..adc47a7a5 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -1,4 +1,4 @@ -/* +/* * CArmedInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -15,6 +15,7 @@ #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" #include "../CGameState.h" +#include "../CPlayerState.h" void CArmedInstance::randomizeArmy(int type) { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 3042e35df..cf29848ae 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1,4 +1,4 @@ -/* +/* * CGHeroInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -22,6 +22,8 @@ #include "../CGameState.h" #include "../CCreatureHandler.h" #include "../BattleState.h" +#include "../CTownHandler.h" +#include "CGTownInstance.h" ///helpers static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) @@ -56,7 +58,7 @@ static int lowestSpeed(const CGHeroInstance * chi) return ret; } -ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const +ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const { unsigned ret = GameConstants::BASE_MOVEMENT_COST; @@ -80,30 +82,40 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro break; } } - else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) + else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) { - // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. - // This is clearly bug in H3 however intended behaviour is not clear. - // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI - // will always have best penalty without any influence from player-defined stacks order - - for(auto stack : stacks) - { - int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; - if(nativeTerrain != -1 && nativeTerrain != from.terType) - { - ret = VLC->heroh->terrCosts[from.terType]; - ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; - if(ret < GameConstants::BASE_MOVEMENT_COST) - ret = GameConstants::BASE_MOVEMENT_COST; - - break; - } - } + ret = VLC->heroh->terrCosts[from.terType]; + ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; + if(ret < GameConstants::BASE_MOVEMENT_COST) + ret = GameConstants::BASE_MOVEMENT_COST; } return ret; } +int CGHeroInstance::getNativeTerrain() const +{ + // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. + // This is clearly bug in H3 however intended behaviour is not clear. + // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI + // will always have best penalty without any influence from player-defined stacks order + + // TODO: What should we do if all hero stacks are neutral creatures? + int nativeTerrain = -1; + for(auto stack : stacks) + { + int stackNativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; + if(stackNativeTerrain == -1) + continue; + + if(nativeTerrain == -1) + nativeTerrain = stackNativeTerrain; + else if(nativeTerrain != stackNativeTerrain) + return -1; + } + + return nativeTerrain; +} + int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest { if (toh3m) @@ -129,16 +141,6 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o } } -bool CGHeroInstance::canFly() const -{ - return hasBonusOfType(Bonus::FLYING_MOVEMENT); -} - -bool CGHeroInstance::canWalkOnSea() const -{ - return hasBonusOfType(Bonus::WATER_WALKING); -} - ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const { for(auto & elem : secSkills) @@ -181,8 +183,11 @@ bool CGHeroInstance::canLearnSkill() const return secSkills.size() < GameConstants::SKILL_PER_HERO; } -int CGHeroInstance::maxMovePoints(bool onLand) const +int CGHeroInstance::maxMovePoints(bool onLand, const TurnInfo * ti) const { + if(!ti) + ti = new TurnInfo(this); + int base; if(onLand) @@ -201,10 +206,10 @@ int CGHeroInstance::maxMovePoints(bool onLand) const } const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; - const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); + const int bonus = ti->valOfBonuses(Bonus::MOVEMENT) + ti->valOfBonuses(bt); const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; - const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; + const double modifier = ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; return int(base* (1+modifier)) + bonus; } @@ -261,10 +266,10 @@ void CGHeroInstance::initHero() spells -= SpellID::PRESET; if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook - putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(ArtifactID::SPELLBOOK)); if(!getArt(ArtifactPosition::MACH4)) - putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult + putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(ArtifactID::CATAPULT)); //everyone has a catapult if(portrait < 0 || portrait == 255) portrait = type->imageIndex; @@ -1099,9 +1104,11 @@ int CGHeroInstance::getBoatType() const void CGHeroInstance::getOutOffsets(std::vector &offsets) const { + // FIXME: Offsets need to be fixed once we get rid of convertPosition + // Check issue 515 for details offsets = { - int3(0,1,0), int3(0,-1,0), int3(-1,0,0), int3(+1,0,0), int3(1,1,0), int3(-1,1,0), int3(1,-1,0), int3(-1,-1,0) + int3(-1,1,0), int3(-1,-1,0), int3(-2,0,0), int3(0,0,0), int3(0,1,0), int3(-2,1,0), int3(0,-1,0), int3(-2,-1,0) }; } @@ -1171,30 +1178,25 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) return CArmedInstance::whereShouldBeAttached(gs); } -int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const +int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/, const TurnInfo * ti) const { - if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) - return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); + if(!ti) + ti = new TurnInfo(this); + + int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); + int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); + if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) + return (MPsBefore - basicCost) * static_cast(mp1) / mp2; return 0; //take all MPs otherwise } -CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const +EDiggingStatus CGHeroInstance::diggingStatus() const { if(movement < maxMovePoints(true)) - return LACK_OF_MOVEMENT; - else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER) - return WRONG_TERRAIN; - else - { - const TerrainTile *t = cb->getTile(getPosition()); - //TODO look for hole - //CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); - if(/*hlp.length() || */t->blockingObjects.size() > 1) - return TILE_OCCUPIED; - else - return CAN_DIG; - } + return EDiggingStatus::LACK_OF_MOVEMENT; + + return cb->getTile(getPosition(false))->getDiggingStatus(); } ArtBearer::ArtBearer CGHeroInstance::bearerType() const diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index c8338fb79..16d0ae633 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -21,6 +21,7 @@ class CHero; class CGBoat; class CGTownInstance; struct TerrainTile; +struct TurnInfo; class CGHeroPlaceholder : public CGObjectInstance { @@ -39,10 +40,6 @@ public: class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public ISpellCaster { public: - enum ECanDig - { - CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED - }; ////////////////////////////////////////////////////////////////////////// ui8 moveDir; //format: 123 @@ -75,12 +72,23 @@ public: struct DLL_LINKAGE Patrol { - Patrol(){patrolling=false;patrolRadious=-1;}; + Patrol(){patrolling=false;initialPos=int3();patrolRadious=-1;}; bool patrolling; + int3 initialPos; ui32 patrolRadious; template void serialize(Handler &h, const int version) { - h & patrolling & patrolRadious; + h & patrolling; + if(version >= 755) + { + h & initialPos; + } + else if(!h.saving) + { + patrolling = false; + initialPos = int3(); + } + h & patrolRadious; } } patrol; @@ -129,12 +137,11 @@ public: EAlignment::EAlignment getAlignment() const; const std::string &getBiography() const; bool needsLastStack()const override; - ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + int getNativeTerrain() const; ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day - bool canFly() const; - bool canWalkOnSea() const; int getCurrentLuck(int stack=-1, bool town=false) const; int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored @@ -161,8 +168,8 @@ public: void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value void levelUp(std::vector skills); - int maxMovePoints(bool onLand) const; - int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; + int maxMovePoints(bool onLand, const TurnInfo * ti = nullptr) const; + int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest double getFightingStrength() const; // takes attack / defense skill into account @@ -174,7 +181,7 @@ public: bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; - ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - + EDiggingStatus diggingStatus() const; ////////////////////////////////////////////////////////////////////////// diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 4add987fd..addd493d1 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -1,4 +1,4 @@ -/* +/* * * CGMarket.cpp, part of VCMI engine * @@ -17,6 +17,7 @@ #include "../IGameCallback.h" #include "../CCreatureHandler.h" #include "../CGameState.h" +#include "CGTownInstance.h" ///helpers static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index e9532ae46..2d0b4d321 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -51,7 +51,7 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const { - cb->removeAfterVisit(this); + afterSuccessfulVisit(); InfoWindow iw; iw.player = h->getOwner(); @@ -331,6 +331,11 @@ void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const giveContentsAfterExp(hero); } +void CGPandoraBox::afterSuccessfulVisit() const +{ + cb->removeAfterVisit(this); +} + void CGEvent::onHeroVisit( const CGHeroInstance * h ) const { if(!(availableFor & (1 << h->tempOwner.getNum()))) @@ -362,3 +367,13 @@ void CGEvent::activated( const CGHeroInstance * h ) const giveContentsUpToExp(h); } } + +void CGEvent::afterSuccessfulVisit() const +{ + if(removeAfterVisit) + { + cb->removeAfterVisit(this); + } + else if(hasGuardians) + hasGuardians = false; +} diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 79ab0a417..13be53add 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -20,7 +20,7 @@ class DLL_LINKAGE CGPandoraBox : public CArmedInstance { public: std::string message; - bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle + mutable bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle //gained things: ui32 gainedExp; @@ -54,6 +54,7 @@ protected: private: 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; + virtual void afterSuccessfulVisit() const; }; class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects @@ -74,4 +75,5 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; private: void activated(const CGHeroInstance * h) const; + void afterSuccessfulVisit() const override; }; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 5dead32a0..ba1b8baca 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1,4 +1,4 @@ -/* +/* * CGTownInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -17,6 +17,8 @@ #include "../CModHandler.h" #include "../IGameCallback.h" #include "../CGameState.h" +#include "../mapping/CMapDefines.h" +#include "../CPlayerState.h" std::vector CGTownInstance::merchantArtifacts; std::vector CGTownInstance::universitySkills; diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index c283000d7..b636b16a5 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "CObjectHandler.h" #include "CGMarket.h" // For IMarket interface @@ -52,11 +52,7 @@ public: CSpecObjInfo * info; //h3m info about dewlling TCreaturesSet creatures; //creatures[level] -> - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & creatures; - } - +private: void initObj() override; void onHeroVisit(const CGHeroInstance * h) const override; void newTurn() const override; @@ -64,9 +60,14 @@ public: void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; -private: void updateGuards() const; void heroAcceptsCreatures(const CGHeroInstance *h) const; + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & creatures; + } }; class DLL_LINKAGE CGTownBuilding : public IObjectInterface diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 9b491d3b1..88c7e6bd0 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -1,4 +1,4 @@ -/* +/* * CObjectHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -18,8 +18,10 @@ #include "../filesystem/ResourceID.h" #include "../IGameCallback.h" #include "../CGameState.h" +#include "../mapping/CMap.h" #include "CObjectClassesHandler.h" +#include "CGTownInstance.h" IGameCallback * IObjectInterface::cb = nullptr; diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index adc71b3d1..b159482c8 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -22,6 +22,10 @@ class CGObjectInstance; struct MetaString; struct BattleResult; +// This one teleport-specific, but has to be available everywhere in callbacks and netpacks +// For now it's will be there till teleports code refactored and moved into own file +typedef std::vector> TTeleportExitsList; + class DLL_LINKAGE IObjectInterface { public: diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index d610a588a..b8964a342 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -17,6 +17,7 @@ #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "CObjectClassesHandler.h" +#include "MiscObjects.h" #include "../IGameCallback.h" #include "../CGameState.h" diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 5c6d80cbd..bb454007c 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -17,6 +17,7 @@ #include "../NetPacks.h" #include "../IGameCallback.h" #include "../CGameState.h" +#include "../CPlayerState.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index ba56179cd..a4a925c5e 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -20,6 +20,8 @@ #include "../spells/CSpellHandler.h" #include "../IGameCallback.h" #include "../CGameState.h" +#include "../mapping/CMap.h" +#include "../CPlayerState.h" std::map > CGMagi::eyelist; ui8 CGObelisk::obeliskCount; //how many obelisks are on map @@ -164,7 +166,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const case FIGHT: fight(h); break; - case FLEE: //flee + case FLEE: { flee(h); break; @@ -322,13 +324,13 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy; - if(charisma < character) //creatures will fight - return -2; + if(charisma < character) + return FIGHT; if (allowJoin) { if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character) - return 0; //join for free + return JOIN_FOR_FREE; else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2 + sympathy + 1 >= character) return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold @@ -336,10 +338,10 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const //we are still here - creatures have not joined hero, flee or fight - if (charisma > character) - return -1; //flee + if (charisma > character && !neverFlees) + return FLEE; else - return -2; //fight + return FIGHT; } void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const @@ -361,7 +363,7 @@ void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) co { if(!accept) { - if(takenAction(h,false) == -1) //they flee + if(takenAction(h,false) == FLEE) { cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); flee(h); @@ -834,7 +836,7 @@ bool CGTeleport::isTeleport(const CGObjectInstance * obj) bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst) { - return src && dst && src != dst && src->isChannelExit(dst->id); + return src && dst && src->isChannelExit(dst->id); } bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstance * dst) @@ -913,7 +915,13 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const if(isEntrance()) { if(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size()) - td.exits = cb->getTeleportChannelExits(channel); + { + auto exits = cb->getTeleportChannelExits(channel); + for(auto exit : exits) + { + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } + } if(cb->isTeleportChannelImpassable(channel)) { @@ -929,9 +937,9 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const cb->showTeleportDialog(&td); } -void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; auto realExits = getAllExits(true); if(!isEntrance() // Do nothing if hero visited exit only object || (!exits.size() && !realExits.size()) // Do nothing if there no exits on this channel @@ -939,14 +947,12 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, { return; } - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; else - assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list + dPos = CGHeroInstance::convertPosition(cb->getObj(getRandomExit(hero))->visitablePos(), true); - auto obj = cb->getObj(objId); - if(obj) - cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); + cb->moveHero(hero->id, dPos, true); } void CGMonolith::initObj() @@ -986,7 +992,10 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const td.impassable = true; } else - td.exits.push_back(getRandomExit(h)); + { + auto exit = getRandomExit(h); + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } cb->showTeleportDialog(&td); } @@ -1085,31 +1094,35 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const cb->changeStackCount(StackLocation(h, targetstack), -countToTake); } else - td.exits = getAllExits(true); + { + auto exits = getAllExits(); + for(auto exit : exits) + { + auto blockedPosList = cb->getObj(exit)->getBlockedPos(); + for(auto bPos : blockedPosList) + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(bPos, true))); + } + } cb->showTeleportDialog(&td); } -void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; auto realExits = getAllExits(); if(!exits.size() && !realExits.size()) return; - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; else - assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list - - auto obj = cb->getObj(objId); - if(obj) { + auto obj = cb->getObj(getRandomExit(hero)); std::set tiles = obj->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(hero->id, tile + int3(1,0,0), true); - - cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); + dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true); } + + cb->moveHero(hero->id, dPos, true); } bool CGWhirlpool::isProtected( const CGHeroInstance * h ) diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 21990b688..6abf94bc7 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -227,7 +227,8 @@ class DLL_LINKAGE CGMine : public CArmedInstance public: Res::ERes producedResource; ui32 producedQuantity; - + +private: 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; @@ -239,6 +240,7 @@ public: std::string getObjectName() const override; std::string getHoverText(PlayerColor player) const override; +public: template void serialize(Handler &h, const int version) { h & static_cast(*this); @@ -265,29 +267,33 @@ struct DLL_LINKAGE TeleportChannel class DLL_LINKAGE CGTeleport : public CGObjectInstance { -public: - enum EType {UNKNOWN, ENTRANCE, EXIT, BOTH}; - - EType type; - TeleportChannelID channel; - - CGTeleport(); - bool isEntrance() const; - bool isExit() const; bool isChannelEntrance(ObjectInstanceID id) const; bool isChannelExit(ObjectInstanceID id) const; - std::vector getAllEntrances(bool excludeCurrent = false) const; - std::vector getAllExits(bool excludeCurrent = false) const; - ObjectInstanceID getRandomExit(const CGHeroInstance * h) const; - virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const = 0; + std::vector getAllEntrances(bool excludeCurrent = false) const; + +protected: + enum EType {UNKNOWN, ENTRANCE, EXIT, BOTH}; + EType type; + + CGTeleport(); + ObjectInstanceID getRandomExit(const CGHeroInstance * h) const; + std::vector getAllExits(bool excludeCurrent = false) const; + +public: + TeleportChannelID channel; + + bool isEntrance() const; + bool isExit() const; + + virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const = 0; static bool isTeleport(const CGObjectInstance * dst); static bool isConnected(const CGTeleport * src, const CGTeleport * dst); static bool isConnected(const CGObjectInstance * src, const CGObjectInstance * dst); - static bool isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj); - static std::vector getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector exits); static void addToChannel(std::map > &channelsList, const CGTeleport * obj); + static std::vector getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector exits); + static bool isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj); template void serialize(Handler &h, const int version) { @@ -299,11 +305,12 @@ class DLL_LINKAGE CGMonolith : public CGTeleport { TeleportChannelID findMeChannel(std::vector IDs, int SubID) const; -public: +protected: void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; void initObj() override; +public: template void serialize(Handler &h, const int version) { h & static_cast(*this); @@ -312,9 +319,10 @@ public: class DLL_LINKAGE CGSubterraneanGate : public CGMonolith { -public: void onHeroVisit(const CGHeroInstance * h) const override; void initObj() override; + +public: static void postInit(); template void serialize(Handler &h, const int version) @@ -325,11 +333,11 @@ public: class DLL_LINKAGE CGWhirlpool : public CGMonolith { -public: void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; static bool isProtected( const CGHeroInstance * h ); +public: template void serialize(Handler &h, const int version) { h & static_cast(*this); diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 9935ed153..93f40e524 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -292,7 +292,7 @@ CScenarioTravel CCampaignHandler::readScenarioTravelFromMemory(CBinaryReader & r std::vector< std::vector > CCampaignHandler::getFile(const std::string & name, bool headerOnly) { - CCompressedStream stream(std::move(CResourceHandler::get()->load(ResourceID(name, EResType::CAMPAIGN))), true); + CCompressedStream stream(CResourceHandler::get()->load(ResourceID(name, EResType::CAMPAIGN)), true); std::vector< std::vector > ret; do @@ -363,7 +363,7 @@ std::vector CCampaignScenario::getLostCrossoverHeroes() const } } } - return std::move(lostCrossoverHeroes); + return lostCrossoverHeroes; } bool CScenarioTravel::STravelBonus::isBonusForHero() const diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index ca1f4cd05..c8fc0cbe1 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CDrawRoadsOperation.h" +#include "CMap.h" const std::vector CDrawRoadsOperation::patterns = { diff --git a/lib/mapping/CDrawRoadsOperation.h b/lib/mapping/CDrawRoadsOperation.h index 8f5bede85..de74a0c23 100644 --- a/lib/mapping/CDrawRoadsOperation.h +++ b/lib/mapping/CDrawRoadsOperation.h @@ -11,9 +11,9 @@ #pragma once #include "../CRandomGenerator.h" -#include "CMap.h" #include "CMapEditManager.h" +struct TerrainTile; class CDrawRoadsOperation : public CMapOperation { diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 644589df1..ddc620332 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -132,11 +132,13 @@ Obj TerrainTile::topVisitableId(bool excludeTop) const CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const { - auto visitableObj = visitableObjects; - if(excludeTop && visitableObj.size()) - visitableObj.pop_back(); + if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1)) + return nullptr; - return visitableObj.size() ? visitableObj.back() : nullptr; + if(excludeTop) + return visitableObjects[visitableObjects.size()-2]; + + return visitableObjects.back(); } bool TerrainTile::isCoastal() const @@ -144,6 +146,18 @@ bool TerrainTile::isCoastal() const return extTileFlags & 64; } +EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const +{ + if(terType == ETerrainType::WATER || terType == ETerrainType::ROCK) + return EDiggingStatus::WRONG_TERRAIN; + + int allowedBlocked = excludeTop ? 1 : 0; + if(blockingObjects.size() > allowedBlocked || topVisitableObj(excludeTop)) + return EDiggingStatus::TILE_OCCUPIED; + else + return EDiggingStatus::CAN_DIG; +} + bool TerrainTile::hasFavourableWinds() const { return extTileFlags & 128; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index df15775df..8fea38cf3 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -19,6 +19,7 @@ #include "../int3.h" #include "../GameConstants.h" #include "../LogicalExpression.h" +#include "CMapDefines.h" class CArtifactInstance; class CGObjectInstance; @@ -47,18 +48,6 @@ struct DLL_LINKAGE SHeroName } }; -namespace EAiTactic -{ -enum EAiTactic -{ - NONE = -1, - RANDOM, - WARRIOR, - BUILDER, - EXPLORER -}; -} - /// The player info constains data about which factions are allowed, AI tactical settings, /// the main hero name, where to generate the hero, whether the faction should be selected randomly,... struct DLL_LINKAGE PlayerInfo @@ -216,93 +205,6 @@ struct DLL_LINKAGE DisposedHero } }; -/// The map event is an event which e.g. gives or takes resources of a specific -/// amount to/from players and can appear regularly or once a time. -class DLL_LINKAGE CMapEvent -{ -public: - CMapEvent(); - - bool earlierThan(const CMapEvent & other) const; - bool earlierThanOrEqual(const CMapEvent & other) const; - - std::string name; - std::string message; - TResources resources; - ui8 players; // affected players, bit field? - ui8 humanAffected; - ui8 computerAffected; - ui32 firstOccurence; - ui32 nextOccurence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time - - template - void serialize(Handler & h, const int version) - { - h & name & message & resources - & players & humanAffected & computerAffected & firstOccurence & nextOccurence; - } -}; - -/// The castle event builds/adds buildings/creatures for a specific town. -class DLL_LINKAGE CCastleEvent: public CMapEvent -{ -public: - CCastleEvent(); - - std::set buildings; - std::vector creatures; - CGTownInstance * town; - - template - void serialize(Handler & h, const int version) - { - h & static_cast(*this); - h & buildings & creatures; - } -}; - -/// The terrain tile describes the terrain type and the visual representation of the terrain. -/// Furthermore the struct defines whether the tile is visitable or/and blocked and which objects reside in it. -struct DLL_LINKAGE TerrainTile -{ - TerrainTile(); - - /// Gets true if the terrain is not a rock. If from is water/land, same type is also required. - bool entrableTerrain(const TerrainTile * from = nullptr) const; - bool entrableTerrain(bool allowLand, bool allowSea) const; - /// Checks for blocking objects and terraint type (water / land). - bool isClear(const TerrainTile * from = nullptr) const; - /// Gets the ID of the top visitable object or -1 if there is none. - Obj topVisitableId(bool excludeTop = false) const; - CGObjectInstance * topVisitableObj(bool excludeTop = false) const; - bool isWater() const; - bool isCoastal() const; - bool hasFavourableWinds() const; - - ETerrainType terType; - ui8 terView; - ERiverType::ERiverType riverType; - ui8 riverDir; - ERoadType::ERoadType roadType; - ui8 roadDir; - /// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road); - /// 7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favourable Winds effect - ui8 extTileFlags; - bool visitable; - bool blocked; - - std::vector visitableObjects; - std::vector blockingObjects; - - template - void serialize(Handler & h, const int version) - { - h & terType & terView & riverType & riverDir & roadType &roadDir & extTileFlags; - h & visitable & blocked; - h & visitableObjects & blockingObjects; - } -}; - namespace EMapFormat { enum EMapFormat diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h new file mode 100644 index 000000000..77f9bf0be --- /dev/null +++ b/lib/mapping/CMapDefines.h @@ -0,0 +1,99 @@ +/* + * CMapDefines.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +/// The map event is an event which e.g. gives or takes resources of a specific +/// amount to/from players and can appear regularly or once a time. +class DLL_LINKAGE CMapEvent +{ +public: + CMapEvent(); + + bool earlierThan(const CMapEvent & other) const; + bool earlierThanOrEqual(const CMapEvent & other) const; + + std::string name; + std::string message; + TResources resources; + ui8 players; // affected players, bit field? + ui8 humanAffected; + ui8 computerAffected; + ui32 firstOccurence; + ui32 nextOccurence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time + + template + void serialize(Handler & h, const int version) + { + h & name & message & resources + & players & humanAffected & computerAffected & firstOccurence & nextOccurence; + } +}; + +/// The castle event builds/adds buildings/creatures for a specific town. +class DLL_LINKAGE CCastleEvent: public CMapEvent +{ +public: + CCastleEvent(); + + std::set buildings; + std::vector creatures; + CGTownInstance * town; + + template + void serialize(Handler & h, const int version) + { + h & static_cast(*this); + h & buildings & creatures; + } +}; + +/// The terrain tile describes the terrain type and the visual representation of the terrain. +/// Furthermore the struct defines whether the tile is visitable or/and blocked and which objects reside in it. +struct DLL_LINKAGE TerrainTile +{ + TerrainTile(); + + /// Gets true if the terrain is not a rock. If from is water/land, same type is also required. + bool entrableTerrain(const TerrainTile * from = nullptr) const; + bool entrableTerrain(bool allowLand, bool allowSea) const; + /// Checks for blocking objects and terraint type (water / land). + bool isClear(const TerrainTile * from = nullptr) const; + /// Gets the ID of the top visitable object or -1 if there is none. + Obj topVisitableId(bool excludeTop = false) const; + CGObjectInstance * topVisitableObj(bool excludeTop = false) const; + bool isWater() const; + bool isCoastal() const; + EDiggingStatus getDiggingStatus(const bool excludeTop = true) const; + bool hasFavourableWinds() const; + + ETerrainType terType; + ui8 terView; + ERiverType::ERiverType riverType; + ui8 riverDir; + ERoadType::ERoadType roadType; + ui8 roadDir; + /// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road); + /// 7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favourable Winds effect + ui8 extTileFlags; + bool visitable; + bool blocked; + + std::vector visitableObjects; + std::vector blockingObjects; + + template + void serialize(Handler & h, const int version) + { + h & terType & terView & riverType & riverDir & roadType &roadDir & extTileFlags; + h & visitable & blocked; + h & visitableObjects & blockingObjects; + } +}; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index f48a63197..8f5648c83 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -7,6 +7,7 @@ #include "../mapObjects/CGHeroInstance.h" #include "../VCMI_Lib.h" #include "CDrawRoadsOperation.h" +#include "../mapping/CMap.h" MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) { diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index fa8f16706..97e5ecd8f 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -12,11 +12,13 @@ #pragma once #include "../CRandomGenerator.h" -#include "CMap.h" +#include "../int3.h" +#include "../GameConstants.h" class CGObjectInstance; class CTerrainViewPatternConfig; struct TerrainViewPattern; +class CMap; /// Represents a map rectangle. struct DLL_LINKAGE MapRect diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 4810ac492..fc4efb2e3 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -20,7 +20,7 @@ std::unique_ptr CMapService::loadMap(const std::string & name) getMapPatcher(name)->patchMapHeader(header); header.release(); - return std::move(map); + return map; } std::unique_ptr CMapService::loadMapHeader(const std::string & name) @@ -28,7 +28,7 @@ std::unique_ptr CMapService::loadMapHeader(const std::string & name) auto stream = getStreamFromFS(name); std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); getMapPatcher(name)->patchMapHeader(header); - return std::move(header); + return header; } std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size, const std::string & name) @@ -40,7 +40,7 @@ std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size, const s getMapPatcher(name)->patchMapHeader(header); header.release(); - return std::move(map); + return map; } std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int size, const std::string & name) @@ -48,7 +48,7 @@ std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int s auto stream = getStreamFromMem(buffer, size); std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); getMapPatcher(name)->patchMapHeader(header); - return std::move(header); + return header; } std::unique_ptr CMapService::getStreamFromFS(const std::string & name) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index eba380123..859abc110 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -668,8 +668,7 @@ void CMapLoaderH3M::readAllowedArtifacts() } if (map->version == EMapFormat::ROE) { - // Armageddon's Blade - map->allowedArtifact[128] = false; + map->allowedArtifact[ArtifactID::ARMAGEDDONS_BLADE] = false; } } @@ -1061,7 +1060,7 @@ void CMapLoaderH3M::readObjects() case Obj::RANDOM_HERO: case Obj::PRISON: { - nobj = readHero(idToBeGiven); + nobj = readHero(idToBeGiven, objPos); break; } case Obj::MONSTER: //Monster @@ -1550,7 +1549,7 @@ void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number) out->validTypes(true); } -CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) +CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos) { auto nhi = new CGHeroInstance(); @@ -1659,6 +1658,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) else { nhi->patrol.patrolling = true; + nhi->patrol.initialPos = CGHeroInstance::convertPosition(initialPos, false); } if(map->version > EMapFormat::ROE) diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index b37fd5640..5d306bc06 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -172,7 +172,7 @@ private: * @param idToBeGiven the object id which should be set for the hero * @return a object instance */ - CGObjectInstance * readHero(ObjectInstanceID idToBeGiven); + CGObjectInstance * readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos); /** * Reads a seer hut. diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 173b08116..13ab1b028 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -4,7 +4,7 @@ #include "../NetPacks.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" -#include "../CGameState.h" +#include "../CPlayerState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../CModHandler.h" //needed? @@ -24,8 +24,6 @@ * */ - - template void registerTypesMapObjects1(Serializer &s) { diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 2ef064f45..657865df3 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -78,6 +78,11 @@ void CMapGenOptions::setPlayerCount(si8 value) resetPlayersMap(); } +si8 CMapGenOptions::getHumanOnlyPlayerCount() const +{ + return humanPlayersCount; +} + si8 CMapGenOptions::getTeamCount() const { return teamCount; @@ -96,7 +101,7 @@ si8 CMapGenOptions::getCompOnlyPlayerCount() const void CMapGenOptions::setCompOnlyPlayerCount(si8 value) { - assert(value == RANDOM_SIZE || (value >= 0 && value <= getPlayerCount())); + assert(value == RANDOM_SIZE || (getPlayerCount() == RANDOM_SIZE || (value >= 0 && value <= getPlayerCount()))); compOnlyPlayerCount = value; if (getPlayerCount() != RANDOM_SIZE && getCompOnlyPlayerCount() != RANDOM_SIZE) diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 7975c54f9..7846148cb 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -108,6 +108,8 @@ public: si8 getPlayerCount() const; void setPlayerCount(si8 value); + si8 getHumanOnlyPlayerCount() const; + /// The count of the teams ranging from 0 to or RANDOM_SIZE for random. si8 getTeamCount() const; void setTeamCount(si8 value); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 48621d32f..0c7f6c963 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -17,7 +17,7 @@ static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)} void CMapGenerator::foreach_neighbour(const int3 &pos, std::function foo) { - for(const int3 &dir : dirs) + for(const int3 &dir : int3::getDirs()) { int3 n = pos + dir; if(map->isInTheMap(n)) diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index 97f299967..9da6366be 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -735,7 +735,7 @@ bool CRmgTemplateZone::createRoad(CMapGenerator* gen, const int3& src, const int std::map cameFrom; // The map of navigated nodes. std::map distances; - int3 currentNode = src; + //int3 currentNode = src; gen->setRoad (src, ERoadType::NO_ROAD); //just in case zone guard already has road under it. Road under nodes will be added at very end cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition @@ -824,7 +824,7 @@ bool CRmgTemplateZone::connectPath(CMapGenerator* gen, const int3& src, bool onl std::map cameFrom; // The map of navigated nodes. std::map distances; - int3 currentNode = src; + //int3 currentNode = src; cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition distances[src] = 0; @@ -866,7 +866,7 @@ bool CRmgTemplateZone::connectPath(CMapGenerator* gen, const int3& src, bool onl return; if (distance < bestDistanceSoFar || !vstd::contains(closed, pos)) { - auto obj = gen->map->getTile(pos).topVisitableObj(); + //auto obj = gen->map->getTile(pos).topVisitableObj(); if (vstd::contains(this->tileinfo, pos)) { cameFrom[pos] = currentNode; @@ -902,7 +902,7 @@ bool CRmgTemplateZone::connectWithCenter(CMapGenerator* gen, const int3& src, bo std::map cameFrom; // The map of navigated nodes. std::map distances; - int3 currentNode = src; + //int3 currentNode = src; cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition distances[src] = 0; @@ -950,7 +950,7 @@ bool CRmgTemplateZone::connectWithCenter(CMapGenerator* gen, const int3& src, bo if (distance < bestDistanceSoFar || !vstd::contains(closed, pos)) { - auto obj = gen->map->getTile(pos).topVisitableObj(); + //auto obj = gen->map->getTile(pos).topVisitableObj(); if (vstd::contains(this->tileinfo, pos)) { cameFrom[pos] = currentNode; @@ -2336,7 +2336,8 @@ ObjectInfo CRmgTemplateZone::getRandomObject(CMapGenerator* gen, CTreasurePileIn } assert (0); //we should never be here } - //FIXME: control reaches end of non-void function. Missing return? + + return ObjectInfo(); // unreachable } void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index d89887494..118826fb2 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -13,6 +13,7 @@ #include "../CRandomGenerator.h" #include "CZonePlacer.h" #include "CRmgTemplateZone.h" +#include "../mapping/CMap.h" #include "CZoneGraphGenerator.h" diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 092307269..2bd35d407 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -12,8 +12,6 @@ #pragma once #include "CMapGenerator.h" -#include "../mapping/CMap.h" - #include "float3.h" #include "../int3.h" diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index 85ec3df99..c8ac2f9d0 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -18,6 +18,8 @@ #include "../BattleState.h" #include "../CGameState.h" #include "../CGameInfoCallback.h" +#include "../mapping/CMap.h" +#include "../CPlayerState.h" ///SummonBoatMechanics ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 065ad4513..14b946c56 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -14,6 +14,7 @@ #include "../NetPacks.h" #include "../BattleState.h" #include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/CGTownInstance.h" ///HealingSpellMechanics void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 4b4a220ed..1beaac6e3 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -640,7 +640,7 @@ std::string CSpell::AnimationInfo::selectProjectile(const double angle) const } } - return std::move(res); + return res; } ///CSpell::TargetInfo diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ca5312b4f..72eade6d6 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1665,7 +1665,7 @@ std::list CGameHandler::generatePlayerTurnOrder() const if(!player.second.human) playerTurnOrder.push_back(player.first); } - return std::move(playerTurnOrder); + return playerTurnOrder; } void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town ) @@ -1755,7 +1755,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst; - const int3 hmpos = dst + int3(-1,0,0); + const int3 hmpos = CGHeroInstance::convertPosition(dst, false); if(!gs->map->isInTheMap(hmpos)) { @@ -1764,7 +1764,6 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } const TerrainTile t = *gs->getTile(hmpos); - const int cost = gs->getMovementCost(h, h->getPosition(), hmpos, h->movement); const int3 guardPos = gs->guardingCreaturePosition(hmpos); const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; @@ -1779,12 +1778,16 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo tmh.movePoints = h->movement; //check if destination tile is available + auto ti = new TurnInfo(h); + const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT); + const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING); + const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) - if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !h->hasBonusOfType(Bonus::FLYING_MOVEMENT) )) + if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !canFly)) && complain("Cannot move hero, destination tile is blocked!")) - || ((!h->boat && !h->canWalkOnSea() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) + || ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) && complain("Cannot move hero, destination tile is on water!")) || ((h->boat && t.terType != ETerrainType::WATER && t.blocked) && complain("Cannot disembark hero, tile is blocked!")) @@ -1794,6 +1797,8 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo && complain("Can not move garrisoned hero!")) || ((h->movement < cost && dst != h->pos && !teleporting) && complain("Hero doesn't have any movement points left!")) + || ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj())) + && complain("Hero cannot transit over this tile!")) /*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) && complain("Cannot move hero during the battle"))*/) { @@ -1843,8 +1848,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } else if(visitDest == VISIT_DEST) { - if(!transit || !CGTeleport::isTeleport(t.topVisitableObj())) - visitObjectOnTile(t, h); + visitObjectOnTile(t, h); } queries.popIfTop(moveQuery); @@ -1867,16 +1871,16 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo }; - if(embarking) + if(!transit && embarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti); 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 } if(disembarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti); return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); } @@ -1905,10 +1909,23 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo ? h->movement - cost : 0; - if(blockingVisit()) + EGuardLook lookForGuards = CHECK_FOR_GUARDS; + EVisitDest visitDest = VISIT_DEST; + if(transit) + { + if(CGTeleport::isTeleport(t.topVisitableObj())) + visitDest = DONT_VISIT_DEST; + + if(canFly) + { + lookForGuards = IGNORE_GUARDS; + visitDest = DONT_VISIT_DEST; + } + } + else if(blockingVisit()) return true; - doMove(TryMoveHero::SUCCESS, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); + doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); return true; } } @@ -1961,7 +1978,8 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner) { InfoWindow iw; iw.player = oldOwner; - iw.text.addTxt (MetaString::GENERAL_TXT, 6); //%s, you have lost your last town. If you do not conquer another town in the next week, you will be eliminated. + iw.text.addTxt(MetaString::GENERAL_TXT, 6); //%s, you have lost your last town. If you do not conquer another town in the next week, you will be eliminated. + iw.text.addReplacement(MetaString::COLOR, oldOwner.getNum()); sendAndApply(&iw); } } @@ -2772,14 +2790,14 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst switch(crid) { - case 146: - giveHeroNewArtifact(h, VLC->arth->artifacts[4], ArtifactPosition::MACH1); + case CreatureID::BALLISTA: + giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1); break; - case 147: - giveHeroNewArtifact(h, VLC->arth->artifacts[6], ArtifactPosition::MACH3); + case CreatureID::FIRST_AID_TENT: + giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); break; - case 148: - giveHeroNewArtifact(h, VLC->arth->artifacts[5], ArtifactPosition::MACH2); + case CreatureID::AMMO_CART: + giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2); break; default: complain("This war machine cannot be recruited!"); @@ -2998,7 +3016,7 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition if(assemble) { - CArtifact *combinedArt = VLC->arth->artifacts.at(assembleTo); + CArtifact *combinedArt = VLC->arth->artifacts[assembleTo]; if(!combinedArt->constituents) COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!"); if(!vstd::contains(destArtifact->assemblyPossibilities(hero), combinedArt)) @@ -3042,7 +3060,7 @@ bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid ) } else if(aid < 7 && aid > 3) //war machine { - int price = VLC->arth->artifacts.at(aid)->price; + int price = VLC->arth->artifacts[aid]->price; if(( hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!")) || (gs->getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!"))) @@ -3053,7 +3071,7 @@ bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid ) || ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA)) { giveResource(hero->getOwner(),Res::GOLD,-price); - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(aid), ArtifactPosition(9+aid)); + giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], ArtifactPosition(9+aid)); return true; } else @@ -3110,7 +3128,7 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::E sendAndApply(&saa); - giveHeroNewArtifact(h, VLC->arth->artifacts.at(aid), ArtifactPosition::FIRST_AVAILABLE); + giveHeroNewArtifact(h, VLC->arth->artifacts[aid], ArtifactPosition::FIRST_AVAILABLE); return true; } @@ -3961,7 +3979,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message sm.absolute = true; if(!h->hasSpellbook()) //hero doesn't have spellbook - giveHeroNewArtifact(h, VLC->arth->artifacts.at(0), ArtifactPosition::SPELLBOOK); //give spellbook + giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); //give spellbook sendAndApply(&sm); } @@ -4014,18 +4032,18 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message if(!hero) return; if(!hero->getArt(ArtifactPosition::MACH1)) - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(4), ArtifactPosition::MACH1); + giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1); if(!hero->getArt(ArtifactPosition::MACH2)) - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(5), ArtifactPosition::MACH2); + giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2); if(!hero->getArt(ArtifactPosition::MACH3)) - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(6), ArtifactPosition::MACH3); + giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); } else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book { CGHeroInstance *hero = gs->getHero(currObj); if(!hero) return; for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(g), ArtifactPosition::PRE_FIRST); + giveHeroNewArtifact(hero, VLC->arth->artifacts[g], ArtifactPosition::PRE_FIRST); } else if(message == "vcmiglorfindel") //selected hero gains a new level { @@ -4878,7 +4896,7 @@ bool CGameHandler::dig( const CGHeroInstance *h ) } } - if(h->diggingStatus() != CGHeroInstance::CAN_DIG) //checks for terrain and movement + if(h->diggingStatus() != EDiggingStatus::CAN_DIG) //checks for terrain and movement COMPLAIN_RETF("Hero cannot dig (error code %d)!", h->diggingStatus()); //create a hole @@ -4899,14 +4917,14 @@ bool CGameHandler::dig( const CGHeroInstance *h ) if(gs->map->grailPos == h->getPosition()) { iw.text.addTxt(MetaString::GENERAL_TXT, 58); //"Congratulations! After spending many hours digging here, your hero has uncovered the " - iw.text.addTxt(MetaString::ART_NAMES, 2); + iw.text.addTxt(MetaString::ART_NAMES, ArtifactID::GRAIL); iw.soundID = soundBase::ULTIMATEARTIFACT; - giveHeroNewArtifact(h, VLC->arth->artifacts.at(2), ArtifactPosition::PRE_FIRST); //give grail + giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::GRAIL], ArtifactPosition::PRE_FIRST); //give grail sendAndApply(&iw); iw.soundID = soundBase::invalid; iw.text.clear(); - iw.text.addTxt(MetaString::ART_DESCR, 2); + iw.text.addTxt(MetaString::ART_DESCR, ArtifactID::GRAIL); sendAndApply(&iw); } else diff --git a/server/CGameHandler.h b/server/CGameHandler.h index acf1698a2..4370e6aec 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -2,11 +2,9 @@ #include "../lib/FunctionList.h" -#include "../lib/CGameState.h" #include "../lib/Connection.h" #include "../lib/IGameCallback.h" #include "../lib/BattleAction.h" -#include "../lib/NetPacks.h" #include "CQuery.h" diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 7e1a53433..0c69bd671 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -2,6 +2,7 @@ #include "CQuery.h" #include "CGameHandler.h" #include "../lib/BattleState.h" +#include "../lib/mapObjects/MiscObjects.h" boost::mutex Queries::mx;