From 3fa7e0976f120bb83065494fd6e25da4df60389a Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 20:53:11 +0300 Subject: [PATCH] Nullkiller: update / fix build, core changes required for Nullkiller AI --- AI/BattleAI/BattleAI.cpp | 13 +- AI/Nullkiller/Analyzers/ArmyManager.cpp | 9 +- AI/Nullkiller/Analyzers/ArmyManager.h | 4 +- .../Behaviors/GatherArmyBehavior.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 16 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/VCAI/Pathfinding/AINodeStorage.cpp | 6 +- AI/VCAI/Pathfinding/AINodeStorage.h | 10 +- AI/VCAI/Pathfinding/AIPathfinder.cpp | 4 +- AI/VCAI/Pathfinding/AIPathfinderConfig.cpp | 12 +- AI/VCAI/Pathfinding/AIPathfinderConfig.h | 6 + AI/VCAI/VCAI.cpp | 14 +- CI/msvc/install.sh | 2 +- CI/mxe/before_install.sh | 3 +- client/CMakeLists.txt | 2 +- lib/CGameInfoCallback.cpp | 4 +- lib/CGameInfoCallback.h | 2 +- lib/CGameState.cpp | 4 +- lib/CGameState.h | 2 +- lib/CPathfinder.cpp | 214 ++++++++++-------- lib/CPathfinder.h | 61 +++-- lib/HeroBonus.cpp | 13 +- lib/HeroBonus.h | 1 + lib/mapObjects/CArmedInstance.cpp | 15 +- lib/mapObjects/CommonConstructors.cpp | 158 ++++++++----- lib/mapObjects/CommonConstructors.h | 29 ++- 28 files changed, 379 insertions(+), 233 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index bfd8d010f..820988a18 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -167,11 +167,22 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff()) return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id); else if(bestAttack.attack.shooting) + { + auto &target = bestAttack; + logAi->debug("BattleAI: %s -> %s x %d, shot, from %d curpos %d dist %d speed %d: %lld %lld %lld", + target.attackerState->unitType()->identifier, + target.affectedUnits[0]->unitType()->identifier, + (int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex, + bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true), + target.damageDealt, target.damageReceived, target.attackValue() + ); + return BattleAction::makeShotAttack(stack, bestAttack.attack.defender); + } else { auto &target = bestAttack; - logAi->debug("BattleAI: %s -> %s %d from, %d curpos %d dist %d speed %d: %lld %lld %lld", + logAi->debug("BattleAI: %s -> %s x %d, mellee, from %d curpos %d dist %d speed %d: %lld %lld %lld", target.attackerState->unitType()->identifier, target.affectedUnits[0]->unitType()->identifier, (int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex, diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index 877acb130..0aaffce4f 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -146,13 +146,16 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, auto morale = slot.second->MoraleVal(); auto multiplier = 1.0f; + const float BadMoraleChance = 0.083f; + const float HighMoraleChance = 0.04f; + if(morale < 0) { - multiplier += morale * 0.083f; + multiplier += morale * BadMoraleChance; } else if(morale > 0) { - multiplier += morale * 0.04f; + multiplier += morale * HighMoraleChance; } newValue += multiplier * slot.second->getPower(); @@ -439,7 +442,7 @@ std::vector ArmyManager::getPossibleUpgrades(const CCreatureSe return upgrades; } -ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade( +ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade( const CCreatureSet * army, const CGObjectInstance * upgrader, const TResources & availableResources) const diff --git a/AI/Nullkiller/Analyzers/ArmyManager.h b/AI/Nullkiller/Analyzers/ArmyManager.h index 0078bc0d5..2ddb1ae3d 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.h +++ b/AI/Nullkiller/Analyzers/ArmyManager.h @@ -55,7 +55,7 @@ public: virtual std::vector getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const = 0; virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0; virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0; - virtual ArmyUpgradeInfo calculateCreateresUpgrade( + virtual ArmyUpgradeInfo calculateCreaturesUpgrade( const CCreatureSet * army, const CGObjectInstance * upgrader, const TResources & availableResources) const = 0; @@ -90,7 +90,7 @@ public: std::shared_ptr getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override; uint64_t evaluateStackPower(const CCreature * creature, int count) const override; SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; - ArmyUpgradeInfo calculateCreateresUpgrade( + ArmyUpgradeInfo calculateCreaturesUpgrade( const CCreatureSet * army, const CGObjectInstance * upgrader, const TResources & availableResources) const override; diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 9397d2ed7..f16f93d8d 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -224,7 +224,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) continue; } - auto upgrade = ai->nullkiller->armyManager->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources); + auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources); auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength(); if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index de5789e3e..2ae47c37e 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -95,8 +95,16 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance); CBankInfo * bankInfo = dynamic_cast(objectInfo.get()); auto resources = bankInfo->getPossibleResourcesReward(); + TResources result = TResources(); + int sum = 0; - return resources; + for(auto & reward : resources) + { + result += reward.data * reward.chance; + sum += reward.chance; + } + + return sum > 1 ? result / sum : result; } uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero) @@ -108,7 +116,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero for(auto c : creatures) { - result += c.type->AIValue * c.count; + result += c.data.type->AIValue * c.data.count * c.chance / 100; } return result; @@ -205,7 +213,7 @@ uint64_t RewardEvaluator::getArmyReward( case Obj::TOWN: return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; case Obj::HILL_FORT: - return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue; + return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue; case Obj::CREATURE_BANK: return getCreatureBankArmyReward(target, hero); case Obj::CREATURE_GENERATOR1: @@ -239,7 +247,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn switch(target->ID) { case Obj::HILL_FORT: - return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD]; + return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD]; case Obj::SCHOOL_OF_MAGIC: case Obj::SCHOOL_OF_WAR: return 1000; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 138add1c1..d07725fca 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1267,7 +1267,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) path.targetHero = node.actor->hero; path.heroArmy = node.actor->creatureSet; path.armyLoss = node.armyLoss; - path.targetObjectDanger = evaluateDanger(pos, path.targetHero, false); + path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle); path.targetObjectArmyLoss = evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger); path.chainMask = node.actor->chainMask; path.exchangeCount = node.actor->actorExchangeCount; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 368e6681a..cf9e910cc 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,7 +11,7 @@ #pragma once #define PATHFINDER_TRACE_LEVEL 0 -#define AI_TRACE_LEVEL 1 +#define AI_TRACE_LEVEL 0 #define SCOUT_TURN_DISTANCE_LIMIT 3 #define MAIN_TURN_DISTANCE_LIMIT 5 diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 5178606e3..bd26bff0d 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -343,7 +343,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade( TResources resources) const { HeroExchangeArmy * target = new HeroExchangeArmy(); - auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(army, upgrader, resources); + auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources); if(upgradeInfo.upgradeValue) { diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 5ffac967c..30bfc93a9 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -26,7 +26,7 @@ AINodeStorage::AINodeStorage(const int3 & Sizes) AINodeStorage::~AINodeStorage() = default; -void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) +void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs) { //TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline @@ -109,7 +109,7 @@ boost::optional AINodeStorage::getOrCreateNode(const int3 & pos, c return boost::none; } -CGPathNode * AINodeStorage::getInitialNode() +std::vector AINodeStorage::getInitialNodes() { auto hpos = hero->getPosition(false); auto initialNode = @@ -121,7 +121,7 @@ CGPathNode * AINodeStorage::getInitialNode() initialNode->danger = 0; initialNode->setCost(0.0); - return initialNode; + return {initialNode}; } void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 31c1c8c3a..0989809ca 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -80,9 +80,9 @@ public: AINodeStorage(const int3 & sizes); ~AINodeStorage(); - void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override; + void initialize(const PathfinderOptions & options, const CGameState * gs) override; - virtual CGPathNode * getInitialNode() override; + virtual std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, @@ -106,11 +106,7 @@ public: bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const; void setHero(HeroPtr heroPtr, const VCAI * ai); - - const CGHeroInstance * getHero() const - { - return hero; - } + const CGHeroInstance * getHero() const { return hero; } uint64_t evaluateDanger(const int3 & tile) const { diff --git a/AI/VCAI/Pathfinding/AIPathfinder.cpp b/AI/VCAI/Pathfinding/AIPathfinder.cpp index 545535111..bec15a66f 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinder.cpp @@ -56,8 +56,8 @@ void AIPathfinder::updatePaths(std::vector heroes) auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr config) { logAi->debug("Recalculate paths for %s", hero->name); - - cb->calculatePaths(config, hero); + + cb->calculatePaths(config); }; std::vector calculationTasks; diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp index d1e4f727f..165f41e7a 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp @@ -37,7 +37,17 @@ namespace AIPathfinding CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage) - :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)) + :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero()) { } + + CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) + { + if(!helper) + { + helper.reset(new CPathfinderHelper(gs, hero, options)); + } + + return helper.get(); + } } diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.h b/AI/VCAI/Pathfinding/AIPathfinderConfig.h index e57fc9954..b7c0664ed 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.h +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.h @@ -17,10 +17,16 @@ namespace AIPathfinding { class AIPathfinderConfig : public PathfinderConfig { + private: + const CGHeroInstance * hero; + std::unique_ptr helper; + public: AIPathfinderConfig( CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage); + + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; }; } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 99abaa366..2ec4d459f 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2670,10 +2670,16 @@ void AIStatus::attemptedAnsweringQuery(QueryID queryID, int answerRequestID) void AIStatus::receivedAnswerConfirmation(int answerRequestID, int result) { - assert(vstd::contains(requestToQueryID, answerRequestID)); - QueryID query = requestToQueryID[answerRequestID]; - assert(vstd::contains(remainingQueries, query)); - requestToQueryID.erase(answerRequestID); + QueryID query; + + { + boost::unique_lock lock(mx); + + assert(vstd::contains(requestToQueryID, answerRequestID)); + query = requestToQueryID[answerRequestID]; + assert(vstd::contains(remainingQueries, query)); + requestToQueryID.erase(answerRequestID); + } if(result) { diff --git a/CI/msvc/install.sh b/CI/msvc/install.sh index 3e5c71f66..bbca585a4 100644 --- a/CI/msvc/install.sh +++ b/CI/msvc/install.sh @@ -4,7 +4,7 @@ git submodule update --init --recursive cd .. curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \ - "https://github.com/nullkiller/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" + "https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" 7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" rmdir vcpkg\installed\${VCMI_BUILD_PLATFORM}-windows\debug /S/Q diff --git a/CI/mxe/before_install.sh b/CI/mxe/before_install.sh index dbef4671d..63127bbc7 100644 --- a/CI/mxe/before_install.sh +++ b/CI/mxe/before_install.sh @@ -15,7 +15,7 @@ sudo apt-get install -f --yes if false; then # Add MXE repository and key - echo "deb http://pkg.mxe.cc/repos/apt trusty main" \ + echo "deb http://pkg.mxe.cc/repos/apt/debian wheezy main" \ | sudo tee /etc/apt/sources.list.d/mxeapt.list sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D43A795B73B16ABE9643FE1AFD8FFF16DB45C6AB @@ -35,6 +35,7 @@ if false; then mxe-$MXE_TARGET-ffmpeg \ mxe-$MXE_TARGET-qt \ mxe-$MXE_TARGET-qtbase \ + mxe-$MXE_TARGET-intel-tbb \ mxe-i686-w64-mingw32.static-luajit fi # Disable diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 40729791d..919a9d48d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -159,7 +159,7 @@ if(ENABLE_DEBUG_CONSOLE) else() add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON}) endif(ENABLE_DEBUG_CONSOLE) -add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI) +add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller) if(WIN32) set_target_properties(vcmiclient diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 3aa31a6f1..90df9e60e 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -923,9 +923,9 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_setgetTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula); } -void CGameInfoCallback::calculatePaths(std::shared_ptr config, const CGHeroInstance * hero) +void CGameInfoCallback::calculatePaths(std::shared_ptr config) { - gs->calculatePaths(config, hero); + gs->calculatePaths(config); } const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index b30b14a57..99ca789c9 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -188,7 +188,7 @@ public: virtual std::shared_ptr> getAllVisibleTiles() const; virtual bool isInTheMap(const int3 &pos) const; virtual void getVisibleTilesInRange(std::unordered_set &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const; - virtual void calculatePaths(std::shared_ptr config, const CGHeroInstance * hero); + virtual void calculatePaths(std::shared_ptr config); //town virtual const CGTownInstance* getTown(ObjectInstanceID objid) const; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index b9f82afbe..95bfb9d5b 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2056,9 +2056,9 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) pathfinder.calculatePaths(); } -void CGameState::calculatePaths(std::shared_ptr config, const CGHeroInstance * hero) +void CGameState::calculatePaths(std::shared_ptr config) { - CPathfinder pathfinder(this, hero, config); + CPathfinder pathfinder(this, config); pathfinder.calculatePaths(); } diff --git a/lib/CGameState.h b/lib/CGameState.h index 36de7a442..baf8998a4 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -184,7 +184,7 @@ public: PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2); bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile 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 - void calculatePaths(std::shared_ptr config, const CGHeroInstance * hero) override; + void calculatePaths(std::shared_ptr config) override; int3 guardingCreaturePosition (int3 pos) const; std::vector guardingCreatures (int3 pos) const; void updateRumor(); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 74d06eb88..8e2660574 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -26,14 +26,14 @@ bool canSeeObj(const CGObjectInstance * obj) return obj != nullptr && obj->ID != Obj::EVENT; } -void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) +void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs) { //TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline int3 pos; + const PlayerColor player = out.hero->tempOwner; const int3 sizes = gs->getMapSize(); - const auto & fow = static_cast(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap; - const PlayerColor player = hero->tempOwner; + const auto & fow = static_cast(gs)->getPlayerTeam(player)->fogOfWarMap; //make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) const bool useFlying = options.useFlying; @@ -155,15 +155,20 @@ void NodeStorage::resetTile( getNode(tile, layer)->update(tile, layer, accessibility); } -CGPathNode * NodeStorage::getInitialNode() +std::vector NodeStorage::getInitialNodes() { - auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); + auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); initialNode->turns = 0; initialNode->moveRemains = out.hero->movement; initialNode->setCost(0.0); - return initialNode; + if(!initialNode->coord.valid()) + { + initialNode->coord = out.hpos; + } + + return std::vector { initialNode }; } void NodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) @@ -256,36 +261,40 @@ CPathfinder::CPathfinder( const CGHeroInstance * _hero) : CPathfinder( _gs, - _hero, - std::make_shared( - std::make_shared(_out, _hero), - std::vector>{ - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared() - })) + std::make_shared(_out, _gs, _hero)) { } +std::vector> SingleHeroPathfinderConfig::buildRuleSet() +{ + return std::vector>{ + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared() + }; +} + +SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero) + : PathfinderConfig(std::make_shared(out, hero), buildRuleSet()) +{ + pathfinderHelper.reset(new CPathfinderHelper(gs, hero, options)); +} + +CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) +{ + return pathfinderHelper.get(); +} + CPathfinder::CPathfinder( CGameState * _gs, - const CGHeroInstance * _hero, std::shared_ptr config) : CGameInfoCallback(_gs, boost::optional()) - , hero(_hero) - , patrolTiles({}) , config(config) , source() , destination() { - assert(hero); - assert(hero == getHero(hero->id)); - - hlp = make_unique(_gs, hero, config->options); - - initializePatrol(); initializeGraph(); } @@ -316,31 +325,40 @@ void CPathfinder::calculatePaths() //logGlobal->info("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 = config->nodeStorage->getInitialNode(); + std::vector initialNodes = config->nodeStorage->getInitialNodes(); + int counter = 0; - if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input + for(auto initialNode : initialNodes) { - logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you..."); - throw std::runtime_error("Wrong checksum"); + if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input + { + logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you..."); + throw std::runtime_error("Wrong checksum"); + } + + source.setNode(gs, initialNode); + auto hlp = config->getOrCreatePathfinderHelper(source, gs); + + if(hlp->isHeroPatrolLocked()) + break; + + pq.push(initialNode); } - if(isHeroPatrolLocked()) - return; - - push(initialNode); - while(!pq.empty()) { + counter++; auto node = topAndPop(); - auto excludeOurHero = node->coord == initialNode->coord; - source.setNode(gs, node, excludeOurHero); + source.setNode(gs, node); source.node->locked = true; int movement = source.node->moveRemains; uint8_t turn = source.node->turns; float cost = source.node->getCost(); + auto hlp = config->getOrCreatePathfinderHelper(source, gs); + hlp->updateTurnInfo(turn); if(!movement) { @@ -350,24 +368,24 @@ void CPathfinder::calculatePaths() continue; } - source.guarded = isSourceGuarded(); - if(source.nodeObject) - source.objectRelations = gs->getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner); + source.isInitialPosition = source.nodeHero == hlp->hero; + source.updateInfo(hlp, gs); //add accessible neighbouring nodes to the queue - auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp.get()); + auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp); for(CGPathNode * neighbour : neighbourNodes) { if(neighbour->locked) continue; - if(!isPatrolMovementAllowed(neighbour->coord)) - continue; - if(!hlp->isLayerAvailable(neighbour->layer)) continue; destination.setNode(gs, neighbour); + hlp = config->getOrCreatePathfinderHelper(destination, gs); + + if(!hlp->isPatrolMovementAllowed(neighbour->coord)) + continue; /// Check transition without tile accessability rules if(source.node->layer != neighbour->layer && !isLayerTransitionPossible()) @@ -376,14 +394,12 @@ void CPathfinder::calculatePaths() destination.turn = turn; destination.movementLeft = movement; destination.cost = cost; - destination.guarded = isDestinationGuarded(); + destination.updateInfo(hlp, gs); destination.isGuardianTile = destination.guarded && isDestinationGuardian(); - if(destination.nodeObject) - destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner); for(auto rule : config->rules) { - rule->process(source, destination, config.get(), hlp.get()); + rule->process(source, destination, config.get(), hlp); if(destination.blocked) break; @@ -395,13 +411,14 @@ void CPathfinder::calculatePaths() } //neighbours loop //just add all passable teleport exits + hlp = config->getOrCreatePathfinderHelper(source, gs); /// For now we disable teleports usage for patrol movement /// VCAI not aware about patrol and may stuck while attempt to use teleport - if(patrolState == PATROL_RADIUS) + if(hlp->patrolState == CPathfinderHelper::PATROL_RADIUS) continue; - auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get()); + auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp); for(CGPathNode * teleportNode : teleportationNodes) { if(teleportNode->locked) @@ -429,6 +446,8 @@ void CPathfinder::calculatePaths() } } } //queue loop + + logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter)); } std::vector CPathfinderHelper::getAllowedTeleportChannelExits(TeleportChannelID channelID) const @@ -498,12 +517,12 @@ std::vector CPathfinderHelper::getTeleportExits(const PathNodeInfo & sourc return teleportationExits; } -bool CPathfinder::isHeroPatrolLocked() const +bool CPathfinderHelper::isHeroPatrolLocked() const { return patrolState == PATROL_LOCKED; } -bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const +bool CPathfinderHelper::isPatrolMovementAllowed(const int3 & dst) const { if(patrolState == PATROL_RADIUS) { @@ -527,7 +546,7 @@ bool CPathfinder::isLayerTransitionPossible() const case ELayer::LAND: if(destLayer == ELayer::AIR) { - if(!config->options.lightweightFlyingMode || isSourceInitialPosition()) + if(!config->options.lightweightFlyingMode || source.isInitialPosition) return true; } else if(destLayer == ELayer::SAIL) @@ -667,7 +686,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea if(!destination.isNodeObjectVisitable()) return BlockingReason::DESTINATION_BLOCKED; - if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO) + if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero) return BlockingReason::DESTINATION_BLOCKED; } else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT) @@ -811,9 +830,9 @@ void DestinationActionRule::process( if(destination.nodeObject->ID == Obj::BOAT) action = CGPathNode::EMBARK; - else if(destination.nodeObject->ID == Obj::HERO) + else if(destination.nodeHero) { - if(objRel == PlayerRelations::ENEMIES) + if(destination.heroRelations == PlayerRelations::ENEMIES) action = CGPathNode::BATTLE; else action = CGPathNode::BLOCKING_VISIT; @@ -870,10 +889,10 @@ void DestinationActionRule::process( CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const { CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL; - if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::HERO) + + if(destination.isNodeObjectVisitable() && destination.nodeHero) { - auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner); - if(objRel == PlayerRelations::ENEMIES) + if(destination.heroRelations == PlayerRelations::ENEMIES) action = CGPathNode::TELEPORT_BATTLE; else action = CGPathNode::TELEPORT_BLOCKING_VISIT; @@ -882,41 +901,15 @@ CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const return action; } -bool CPathfinder::isSourceInitialPosition() const -{ - return source.node->coord == config->nodeStorage->getInitialNode()->coord; -} - -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(source.node->coord).valid() && !isSourceInitialPosition()) - { - return true; - } - - return false; -} - -bool CPathfinder::isDestinationGuarded() const -{ - /// isDestinationGuarded is exception needed for garrisons. - /// When monster standing behind garrison it's visitable and guarded at the same time. - return gs->guardingCreaturePosition(destination.node->coord).valid(); -} - bool CPathfinder::isDestinationGuardian() const { return gs->guardingCreaturePosition(source.node->coord) == destination.node->coord; } -void CPathfinder::initializePatrol() +void CPathfinderHelper::initializePatrol() { auto state = PATROL_NONE; + if(hero->patrol.patrolling && !getPlayerState(hero->tempOwner)->human) { if(hero->patrol.patrolRadius) @@ -934,7 +927,7 @@ void CPathfinder::initializePatrol() void CPathfinder::initializeGraph() { INodeStorage * nodeStorage = config->nodeStorage.get(); - nodeStorage->initialize(config->options, gs, hero); + nodeStorage->initialize(config->options, gs); } bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const @@ -1101,10 +1094,11 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const } CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options) - : CGameInfoCallback(gs, boost::optional()), turn(-1), hero(Hero), options(Options) + : CGameInfoCallback(gs, boost::optional()), turn(-1), hero(Hero), options(Options), owner(Hero->tempOwner) { turnsInfo.reserve(16); updateTurnInfo(); + initializePatrol(); } CPathfinderHelper::~CPathfinderHelper() @@ -1349,11 +1343,11 @@ const CGPathNode * CPathsInfo::getNode(const int3 & coord) const } PathNodeInfo::PathNodeInfo() - : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false) + : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false), isInitialPosition(false) { } -void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject) +void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n) { node = n; @@ -1363,12 +1357,43 @@ void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObjec coord = node->coord; tile = gs->getTile(coord); - nodeObject = tile->topVisitableObj(excludeTopObject); + nodeObject = tile->topVisitableObj(); + + if(nodeObject && nodeObject->ID == Obj::HERO) + { + nodeHero = dynamic_cast(nodeObject); + nodeObject = tile->topVisitableObj(true); + + if(!nodeObject) + nodeObject = nodeHero; + } + else + { + nodeHero = nullptr; + } } guarded = false; } +void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs) +{ + if(gs->guardingCreaturePosition(node->coord).valid() && !isInitialPosition) + { + guarded = true; + } + + if(nodeObject) + { + objectRelations = gs->getPlayerRelations(hlp->owner, nodeObject->tempOwner); + } + + if(nodeHero) + { + heroRelations = gs->getPlayerRelations(hlp->owner, nodeHero->tempOwner); + } +} + CDestinationNodeInfo::CDestinationNodeInfo() : PathNodeInfo(), blocked(false), @@ -1376,9 +1401,9 @@ CDestinationNodeInfo::CDestinationNodeInfo() { } -void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject) +void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n) { - PathNodeInfo::setNode(gs, n, excludeTopObject); + PathNodeInfo::setNode(gs, n); blocked = false; action = CGPathNode::ENodeAction::UNKNOWN; @@ -1395,5 +1420,6 @@ bool CDestinationNodeInfo::isBetterWay() const bool PathNodeInfo::isNodeObjectVisitable() const { /// Hero can't visit objects while walking on water or flying - return canSeeObj(nodeObject) && (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL); + return (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) + && (canSeeObj(nodeObject) || canSeeObj(nodeHero)); } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index f2206a217..5876d6a6b 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -196,14 +196,19 @@ struct DLL_LINKAGE PathNodeInfo { CGPathNode * node; const CGObjectInstance * nodeObject; + const CGHeroInstance * nodeHero; const TerrainTile * tile; int3 coord; bool guarded; PlayerRelations::PlayerRelations objectRelations; + PlayerRelations::PlayerRelations heroRelations; + bool isInitialPosition; PathNodeInfo(); - virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false); + virtual void setNode(CGameState * gs, CGPathNode * n); + + void updateInfo(CPathfinderHelper * hlp, CGameState * gs); bool isNodeObjectVisitable() const; }; @@ -219,7 +224,7 @@ struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo CDestinationNodeInfo(); - virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override; + virtual void setNode(CGameState * gs, CGPathNode * n) override; virtual bool isBetterWay() const; }; @@ -379,7 +384,7 @@ class DLL_LINKAGE INodeStorage { public: using ELayer = EPathfindingLayer; - virtual CGPathNode * getInitialNode() = 0; + virtual std::vector getInitialNodes() = 0; virtual std::vector calculateNeighbours( const PathNodeInfo & source, @@ -393,7 +398,7 @@ public: virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0; - virtual void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) = 0; + virtual void initialize(const PathfinderOptions & options, const CGameState * gs) = 0; }; class DLL_LINKAGE NodeStorage : public INodeStorage @@ -413,9 +418,9 @@ public: return out.getNode(coord, layer); } - void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override; + void initialize(const PathfinderOptions & options, const CGameState * gs) override; - virtual CGPathNode * getInitialNode() override; + virtual std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, @@ -440,6 +445,21 @@ public: PathfinderConfig( std::shared_ptr nodeStorage, std::vector> rules); + + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0; +}; + +class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig +{ +private: + std::unique_ptr pathfinderHelper; + +public: + SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero); + + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; + + static std::vector> buildRuleSet(); }; class CPathfinder : private CGameInfoCallback @@ -450,7 +470,6 @@ public: CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero); CPathfinder( CGameState * _gs, - const CGHeroInstance * _hero, std::shared_ptr config); 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 @@ -458,34 +477,18 @@ public: private: typedef EPathfindingLayer ELayer; - const CGHeroInstance * hero; - std::unique_ptr hlp; std::shared_ptr config; - enum EPatrolState { - PATROL_NONE = 0, - PATROL_LOCKED = 1, - PATROL_RADIUS - } patrolState; - std::unordered_set patrolTiles; - boost::heap::fibonacci_heap> > pq; PathNodeInfo source; //current (source) path node -> we took it from the queue CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider - bool isHeroPatrolLocked() const; - bool isPatrolMovementAllowed(const int3 & dst) const; - bool isLayerTransitionPossible() const; CGPathNode::ENodeAction getTeleportDestAction() const; - bool isSourceInitialPosition() const; - bool isSourceGuarded() const; - bool isDestinationGuarded() const; bool isDestinationGuardian() const; - void initializePatrol(); void initializeGraph(); STRONG_INLINE @@ -527,13 +530,25 @@ struct DLL_LINKAGE TurnInfo class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback { public: + enum EPatrolState + { + PATROL_NONE = 0, + PATROL_LOCKED = 1, + PATROL_RADIUS + } patrolState; + std::unordered_set patrolTiles; + int turn; + PlayerColor owner; const CGHeroInstance * hero; std::vector turnsInfo; const PathfinderOptions & options; CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); ~CPathfinderHelper(); + void initializePatrol(); + bool isHeroPatrolLocked() const; + bool isPatrolMovementAllowed(const int3 & dst) const; void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer layer) const; const TurnInfo * getTurnInfo() const; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 24d3eaab0..c51c00801 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -901,8 +901,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co if (CBonusSystemNode::cachingEnabled && limitOnUs) { // Exclusive access for one thread - static boost::mutex m; - boost::mutex::scoped_lock lock(m); + boost::lock_guard lock(sync); // If the bonus system tree changes(state of a single node or the relations to each other) then // cache all bonus objects. Selector objects doesn't matter. @@ -993,7 +992,8 @@ CBonusSystemNode::CBonusSystemNode() : bonuses(true), exportedBonuses(true), nodeType(UNKNOWN), - cachedLast(0) + cachedLast(0), + sync() { } @@ -1001,7 +1001,8 @@ CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType) : bonuses(true), exportedBonuses(true), nodeType(NodeType), - cachedLast(0) + cachedLast(0), + sync() { } @@ -1010,7 +1011,8 @@ CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other): exportedBonuses(std::move(other.exportedBonuses)), nodeType(other.nodeType), description(other.description), - cachedLast(0) + cachedLast(0), + sync() { std::swap(parents, other.parents); std::swap(children, other.children); @@ -1189,7 +1191,6 @@ void CBonusSystemNode::childDetached(CBonusSystemNode *child) logBonus->error("Error! %s #cannot be detached from# %s", child->nodeName(), nodeName()); throw std::runtime_error("internal error"); } - } void CBonusSystemNode::detachFromAll() diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index d47971302..d43021bca 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -761,6 +761,7 @@ private: // This string needs to be unique, that's why it has to be setted in the following manner: // [property key]_[value] => only for selector mutable std::map cachedRequests; + mutable boost::mutex sync; void getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const; void getAllBonusesRec(BonusList &out) const; diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index e5185c226..c35dd5c5d 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -89,19 +89,24 @@ void CArmedInstance::updateMoraleBonusFromArmy() factionsInArmy -= mixableFactions - 1; } + std::string description; + if(factionsInArmy == 1) { b->val = +1; - b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 - b->description = b->description.substr(0, b->description.size()-3);//trim "+1" + description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 + description = description.substr(0, description.size()-3);//trim "+1" } else if (!factions.empty()) // no bonus from empty garrison { b->val = 2 - (si32)factionsInArmy; - b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d - b->description = b->description.substr(0, b->description.size()-2);//trim value + description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d + description = b->description.substr(0, description.size()-2);//trim value } - boost::algorithm::trim(b->description); + + boost::algorithm::trim(description); + b->description = description; + CBonusSystemNode::treeHasChanged(); //-1 modifier for any Undead unit in army diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index c8d8cb719..6809175e5 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -1,12 +1,12 @@ /* - * CommonConstructors.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ +* CommonConstructors.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ #include "StdInc.h" #include "CommonConstructors.h" @@ -29,7 +29,7 @@ bool CObstacleConstructor::isStaticObject() return true; } -CTownInstanceConstructor::CTownInstanceConstructor(): +CTownInstanceConstructor::CTownInstanceConstructor() : faction(nullptr) { } @@ -47,7 +47,7 @@ void CTownInstanceConstructor::initTypeData(const JsonNode & input) void CTownInstanceConstructor::afterLoadFinalization() { assert(faction); - for (auto entry : filtersJson.Struct()) + for(auto entry : filtersJson.Struct()) { filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) { @@ -79,7 +79,7 @@ CGObjectInstance * CTownInstanceConstructor::create(const ObjectTemplate & tmpl) void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const { auto templ = getOverride(object->cb->getTile(object->pos)->terType, object); - if (templ) + if(templ) object->appearance = templ.get(); } @@ -91,15 +91,17 @@ CHeroInstanceConstructor::CHeroInstanceConstructor() void CHeroInstanceConstructor::initTypeData(const JsonNode & input) { - VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"], - [&](si32 index) { heroClass = VLC->heroh->classes[index]; }); + VLC->modh->identifiers.requestIdentifier( + "heroClass", + input["heroClass"], + [&](si32 index) { heroClass = VLC->heroh->classes[index]; }); filtersJson = input["filters"]; } void CHeroInstanceConstructor::afterLoadFinalization() { - for (auto entry : filtersJson.Struct()) + for(auto entry : filtersJson.Struct()) { filters[entry.first] = LogicalExpression(entry.second, [](const JsonNode & node) { @@ -117,7 +119,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, con return hero->type->ID == id; }; - if (filters.count(templ.stringID)) + if(filters.count(templ.stringID)) { return filters.at(templ.stringID).test(heroTest); } @@ -175,9 +177,9 @@ CGObjectInstance * CDwellingInstanceConstructor::create(const ObjectTemplate & t CGDwelling * obj = createTyped(tmpl); obj->creatures.resize(availableCreatures.size()); - for (auto & entry : availableCreatures) + for(auto & entry : availableCreatures) { - for (const CCreature * cre : entry) + for(const CCreature * cre : entry) obj->creatures.back().second.push_back(cre->idNumber); } return obj; @@ -190,34 +192,34 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR dwelling->creatures.clear(); dwelling->creatures.reserve(availableCreatures.size()); - for (auto & entry : availableCreatures) + for(auto & entry : availableCreatures) { dwelling->creatures.resize(dwelling->creatures.size() + 1); - for (const CCreature * cre : entry) + for(const CCreature * cre : entry) dwelling->creatures.back().second.push_back(cre->idNumber); } bool guarded = false; //TODO: serialize for sanity - if (guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch + if(guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch { - if (guards.Bool()) + if(guards.Bool()) { guarded = true; } } - else if (guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux) + else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux) { - for (auto & stack : JsonRandom::loadCreatures(guards, rng)) + for(auto & stack : JsonRandom::loadCreatures(guards, rng)) { dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count)); } } else //default condition - creatures are of level 5 or higher { - for (auto creatureEntry : availableCreatures) + for(auto creatureEntry : availableCreatures) { - if (creatureEntry.at(0)->level >= 5) + if(creatureEntry.at(0)->level >= 5) { guarded = true; break; @@ -225,22 +227,22 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR } } - if (guarded) + if(guarded) { - for (auto creatureEntry : availableCreatures) + for(auto creatureEntry : availableCreatures) { const CCreature * crea = creatureEntry.at(0); - dwelling->putStack (SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3)); + dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3)); } } } bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) const { - for (auto & entry : availableCreatures) + for(auto & entry : availableCreatures) { - for (const CCreature * cre : entry) - if (crea == cre) + for(const CCreature * cre : entry) + if(crea == cre) return true; } return false; @@ -249,9 +251,9 @@ bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) cons std::vector CDwellingInstanceConstructor::getProducedCreatures() const { std::vector creatures; //no idea why it's 2D, to be honest - for (auto & entry : availableCreatures) + for(auto & entry : availableCreatures) { - for (const CCreature * cre : entry) + for(const CCreature * cre : entry) creatures.push_back(cre); } return creatures; @@ -292,7 +294,7 @@ BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRan bc.resources = Res::ResourceSet(level["reward"]["resources"]); bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng); bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng); - bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells); + bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells); bc.value = static_cast(level["value"].Float()); @@ -314,18 +316,18 @@ void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRando si32 selectedChance = rng.nextInt(totalChance - 1); int cumulativeChance = 0; - for (auto & node : levels) + for(auto & node : levels) { cumulativeChance += static_cast(node["chance"].Float()); - if (selectedChance < cumulativeChance) + if(selectedChance < cumulativeChance) { - bank->setConfig(generateConfig(node, rng)); - break; + bank->setConfig(generateConfig(node, rng)); + break; } } } -CBankInfo::CBankInfo(const JsonVector & Config): +CBankInfo::CBankInfo(const JsonVector & Config) : config(Config) { assert(!Config.empty()); @@ -336,28 +338,28 @@ static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature * army.totalStrength += crea->fightValue * amount; bool walker = true; - if (crea->hasBonusOfType(Bonus::SHOOTER)) + if(crea->hasBonusOfType(Bonus::SHOOTER)) { army.shootersStrength += crea->fightValue * amount; walker = false; } - if (crea->hasBonusOfType(Bonus::FLYING)) + if(crea->hasBonusOfType(Bonus::FLYING)) { army.flyersStrength += crea->fightValue * amount; walker = false; } - if (walker) + if(walker) army.walkersStrength += crea->fightValue * amount; } IObjectInfo::CArmyStructure CBankInfo::minGuards() const { std::vector armies; - for (auto configEntry : config) + for(auto configEntry : config) { auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); IObjectInfo::CArmyStructure army; - for (auto & stack : stacks) + for(auto & stack : stacks) { assert(!stack.allowedCreatures.empty()); auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) @@ -374,11 +376,11 @@ IObjectInfo::CArmyStructure CBankInfo::minGuards() const IObjectInfo::CArmyStructure CBankInfo::maxGuards() const { std::vector armies; - for (auto configEntry : config) + for(auto configEntry : config) { auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); IObjectInfo::CArmyStructure army; - for (auto & stack : stacks) + for(auto & stack : stacks) { assert(!stack.allowedCreatures.empty()); auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) @@ -396,14 +398,14 @@ TPossibleGuards CBankInfo::getPossibleGuards() const { TPossibleGuards out; - for (const JsonNode & configEntry : config) + for(const JsonNode & configEntry : config) { const JsonNode & guardsInfo = configEntry["guards"]; auto stacks = JsonRandom::evaluateCreatures(guardsInfo); IObjectInfo::CArmyStructure army; - for (auto stack : stacks) + for(auto stack : stacks) { army.totalStrength += stack.allowedCreatures.front()->AIValue * (stack.minAmount + stack.maxAmount) / 2; //TODO: add fields for flyers, walkers etc... @@ -415,34 +417,78 @@ TPossibleGuards CBankInfo::getPossibleGuards() const return out; } +std::vector> CBankInfo::getPossibleResourcesReward() const +{ + std::vector> result; + + for(const JsonNode & configEntry : config) + { + const JsonNode & resourcesInfo = configEntry["reward"]["resources"]; + + if(!resourcesInfo.isNull()) + { + result.push_back( + PossibleReward( + configEntry["chance"].Integer(), + TResources(resourcesInfo) + )); + } + } + + return result; +} + +std::vector> CBankInfo::getPossibleCreaturesReward() const +{ + std::vector> aproximateReward; + + for(const JsonNode & configEntry : config) + { + const JsonNode & guardsInfo = configEntry["reward"]["creatures"]; + auto stacks = JsonRandom::evaluateCreatures(guardsInfo); + + for(auto stack : stacks) + { + auto creature = stack.allowedCreatures.front(); + + aproximateReward.push_back( + PossibleReward( + configEntry["chance"].Integer(), + CStackBasicDescriptor(creature, (stack.minAmount + stack.maxAmount) / 2))); + } + } + + return aproximateReward; +} + bool CBankInfo::givesResources() const { - for (const JsonNode & node : config) - if (!node["reward"]["resources"].isNull()) + for(const JsonNode & node : config) + if(!node["reward"]["resources"].isNull()) return true; return false; } bool CBankInfo::givesArtifacts() const { - for (const JsonNode & node : config) - if (!node["reward"]["artifacts"].isNull()) + for(const JsonNode & node : config) + if(!node["reward"]["artifacts"].isNull()) return true; return false; } bool CBankInfo::givesCreatures() const { - for (const JsonNode & node : config) - if (!node["reward"]["creatures"].isNull()) + for(const JsonNode & node : config) + if(!node["reward"]["creatures"].isNull()) return true; return false; } bool CBankInfo::givesSpells() const { - for (const JsonNode & node : config) - if (!node["reward"]["spells"].isNull()) + for(const JsonNode & node : config) + if(!node["reward"]["spells"].isNull()) return true; return false; } diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index b14a2fb12..1dc6ec5d1 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -1,12 +1,12 @@ /* - * CommonConstructors.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 - * - */ +* CommonConstructors.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ #pragma once #include "CObjectClassesHandler.h" @@ -34,7 +34,7 @@ protected: return obj; } public: - CDefaultObjectTypeHandler(){} + CDefaultObjectTypeHandler() {} CGObjectInstance * create(const ObjectTemplate & tmpl) const override { @@ -164,6 +164,15 @@ struct BankConfig typedef std::vector> TPossibleGuards; +template +struct DLL_LINKAGE PossibleReward +{ + int chance; + T data; + + PossibleReward(int chance, const T & data) : chance(chance), data(data) {} +}; + class DLL_LINKAGE CBankInfo : public IObjectInfo { const JsonVector & config; @@ -171,6 +180,8 @@ public: CBankInfo(const JsonVector & Config); TPossibleGuards getPossibleGuards() const; + std::vector> getPossibleResourcesReward() const; + std::vector> getPossibleCreaturesReward() const; // These functions should try to evaluate minimal possible/max possible guards to give provide information on possible thread to AI CArmyStructure minGuards() const override;