diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 95308ae61..57428999f 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -97,6 +97,8 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose) if(!hero) validateObject(details.id); //enemy hero may have left visible area + nullkiller->invalidatePathfinderData(); + const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0)); const int3 to = hero ? hero->convertToVisitablePos(details.end) : (details.end - int3(0,1,0)); @@ -358,6 +360,7 @@ void AIGateway::newObject(const CGObjectInstance * obj) { LOG_TRACE(logAi); NET_EVENT_HANDLER; + nullkiller->invalidatePathfinderData(); if(obj->isVisitable()) addVisitableObj(obj); } @@ -582,6 +585,7 @@ void AIGateway::yourTurn(QueryID queryID) { LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); NET_EVENT_HANDLER; + nullkiller->invalidatePathfinderData(); status.addQuery(queryID, "YourTurn"); requestActionASAP([=](){ answerQuery(queryID, 0); }); status.startedTurn(); diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 062744bd5..89b87324e 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -239,8 +239,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( auto info = BuildingInfo(buildPtr, creature, baseCreatureID, town, ai); - logAi->trace("checking %s", info.name); - logAi->trace("buildInfo %s", info.toString()); + //logAi->trace("checking %s buildInfo %s", info.name, info.toString()); int highestFort = 0; for (auto twn : ai->cb->getTownsInfo()) @@ -258,7 +257,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( } else if(canBuild == EBuildingState::NO_RESOURCES) { - logAi->trace("cant build. Not enough resources. Need %s", info.buildCost.toString()); + //logAi->trace("cant build. Not enough resources. Need %s", info.buildCost.toString()); info.notEnoughRes = true; } else if(canBuild == EBuildingState::PREREQUIRES) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index be3526fda..4386144fe 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -72,8 +72,8 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const { auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID())); auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL); - auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); - auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL)); + auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus), "HeroManager::evaluateSpeciality"); + auto secondarySkillBonuses = hero->getBonusesFrom(BonusSource::SECONDARY_SKILL); float specialityScore = 0.0f; for(auto bonus : *secondarySkillBonuses) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b2c3d0eb1..88ee4f1dc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -37,6 +37,7 @@ Nullkiller::Nullkiller() : activeHero(nullptr) , scanDepth(ScanDepth::MAIN_FULL) , useHeroChain(true) + , pathfinderInvalidated(false) , memory(std::make_unique()) { @@ -239,6 +240,11 @@ void Nullkiller::resetAiState() } } +void Nullkiller::invalidatePathfinderData() +{ + pathfinderInvalidated = true; +} + void Nullkiller::updateAiState(int pass, bool fast) { boost::this_thread::interruption_point(); @@ -253,7 +259,10 @@ void Nullkiller::updateAiState(int pass, bool fast) decomposer->reset(); buildAnalyzer->update(); - if(!fast) + if (!pathfinderInvalidated) + logAi->trace("Skipping paths regeneration - up to date"); + + if(!fast && pathfinderInvalidated) { memory->removeInvisibleObjects(cb.get()); @@ -304,11 +313,13 @@ void Nullkiller::updateAiState(int pass, bool fast) boost::this_thread::interruption_point(); objectClusterizer->clusterize(); + + pathfinderInvalidated = false; } armyManager->update(); - logAi->debug("AI state updated in %ld", timeElapsed(start)); + logAi->debug("AI state updated in %ld ms", timeElapsed(start)); } bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const @@ -379,7 +390,7 @@ void Nullkiller::makeTurn() Goals::TTask bestTask = taskptr(Goals::Invalid()); - while(true) + for(int j = 1; j <= settings->getMaxPriorityPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; j++) { bestTasks.clear(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 941e71f16..369fe7116 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -78,6 +78,7 @@ private: AIGateway * gateway; bool openMap; bool useObjectGraph; + bool pathfinderInvalidated; public: static std::unique_ptr baseGraph; @@ -121,6 +122,7 @@ public: bool isOpenMap() const { return openMap; } bool isObjectGraphAllowed() const { return useObjectGraph; } bool handleTrading(); + void invalidatePathfinderData(); private: void resetAiState(); diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index 6cc7a5266..44ef27176 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -32,7 +32,8 @@ namespace NKAI retreatThresholdRelative(0.3), retreatThresholdAbsolute(10000), safeAttackRatio(1.1), - maxpass(10), + maxPass(10), + maxPriorityPass(10), pathfinderBucketsCount(1), pathfinderBucketSize(32), allowObjectGraph(true), @@ -48,7 +49,8 @@ namespace NKAI maxRoamingHeroes = node["maxRoamingHeroes"].Integer(); mainHeroTurnDistanceLimit = node["mainHeroTurnDistanceLimit"].Integer(); scoutHeroTurnDistanceLimit = node["scoutHeroTurnDistanceLimit"].Integer(); - maxpass = node["maxpass"].Integer(); + maxPass = node["maxPass"].Integer(); + maxPriorityPass = node["maxPriorityPass"].Integer(); pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer(); pathfinderBucketSize = node["pathfinderBucketSize"].Integer(); maxGoldPressure = node["maxGoldPressure"].Float(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index ac8b718ec..01f4f2a9c 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -24,7 +24,8 @@ namespace NKAI int maxRoamingHeroes; int mainHeroTurnDistanceLimit; int scoutHeroTurnDistanceLimit; - int maxpass; + int maxPass; + int maxPriorityPass; int pathfinderBucketsCount; int pathfinderBucketSize; float maxGoldPressure; @@ -41,7 +42,8 @@ namespace NKAI public: explicit Settings(int difficultyLevel); - int getMaxPass() const { return maxpass; } + int getMaxPass() const { return maxPass; } + int getMaxPriorityPass() const { return maxPriorityPass; } float getMaxGoldPressure() const { return maxGoldPressure; } float getRetreatThresholdRelative() const { return retreatThresholdRelative; } float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 2761cc692..eb2a53827 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -23,8 +23,6 @@ constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; #include "Actions/SpecialAction.h" #include "Actors.h" -#include - namespace NKAI { namespace AIPathfinding diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index a46584649..b504f9e7d 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -106,7 +106,7 @@ void AIPathfinder::updatePaths(const std::map if(!pathfinderSettings.useHeroChain) { - logAi->trace("Recalculated paths in %ld", timeElapsed(start)); + logAi->trace("Recalculated paths in %ld ms", timeElapsed(start)); return; } @@ -141,7 +141,7 @@ void AIPathfinder::updatePaths(const std::map } } while(storage->increaseHeroChainTurnLimit()); - logAi->trace("Recalculated paths in %ld", timeElapsed(start)); + logAi->trace("Recalculated paths in %ld ms", timeElapsed(start)); } void AIPathfinder::updateGraphs( diff --git a/Global.h b/Global.h index 5e9b2c681..f43dfa851 100644 --- a/Global.h +++ b/Global.h @@ -134,6 +134,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include #include #include #include @@ -168,6 +169,8 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include +#include #include #include #include diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index e5920ade9..6c9784b80 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -957,7 +957,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS creatureSpells.push_back(spellToCast.toSpell()); } - TConstBonusListPtr bl = casterStack->getBonuses(Selector::type()(BonusType::SPELLCASTER)); + TConstBonusListPtr bl = casterStack->getBonusesOfType(BonusType::SPELLCASTER); for(const auto & bonus : *bl) { diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 1a2ae9089..d3c0a88c7 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -135,7 +135,7 @@ int MapTileStorage::groupCount(size_t fileIndex, size_t rotationIndex, size_t im const auto & animation = animations[fileIndex][rotationIndex]; if (animation) for(int i = 0;; i++) - if(!animation->getImage(imageIndex, i, false)) + if(animation->size(i) <= imageIndex) return i; return 1; } diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index 63a9089de..14671c2db 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -671,10 +671,6 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor const uint32_t * srcPixels = static_cast(intermediate->pixels); uint32_t * dstPixels = static_cast(ret->pixels); - // avoid excessive granulation - xBRZ prefers at least 8-16 lines per task - // TODO: compare performance and size of images, recheck values for potentially better parameters - const int granulation = std::clamp(surf->h / 64 * 8, 8, 64); - switch (algorithm) { case EScalingAlgorithm::NEAREST: @@ -687,11 +683,22 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor case EScalingAlgorithm::XBRZ_OPAQUE: { auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; - tbb::parallel_for(tbb::blocked_range(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range & r) - { - xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); - }); + if(intermediate->h < 32) + { + // for tiny images tbb incurs too high overhead + xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}); + } + else + { + // xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option + const int granulation = intermediate->h > 400 ? 16 : 4; + tbb::parallel_for(tbb::blocked_range(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range & r) + { + xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); + }); + } + break; } default: diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index ad73c3960..22062c85a 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -284,7 +284,7 @@ void CHeroWindow::update() dismissButton->block(noDismiss); - if(curHero->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION)) == 0) + if(curHero->valOfBonuses(BonusType::BEFORE_BATTLE_REPOSITION) == 0) { tacticsButton->block(true); } diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index a4356c34a..55cfe9cb5 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -33,17 +33,18 @@ "pawn" : { - "maxRoamingHeroes" : 4, //H3 value: 3, - "maxpass" : 30, + "maxRoamingHeroes" : 3, //H3 value: 3, + "maxPass" : 30, + "maxPriorityPass" : 10, "mainHeroTurnDistanceLimit" : 10, "scoutHeroTurnDistanceLimit" : 5, "maxGoldPressure" : 0.3, "updateHitmapOnTileReveal" : false, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, + "allowObjectGraph": true, + "pathfinderBucketsCount" : 4, // old value: 3, + "pathfinderBucketSize" : 8, // old value: 7, "retreatThresholdRelative" : 0, "retreatThresholdAbsolute" : 0, "safeAttackRatio" : 1.1, @@ -52,17 +53,18 @@ }, "knight" : { - "maxRoamingHeroes" : 6, //H3 value: 3, - "maxpass" : 30, + "maxRoamingHeroes" : 3, //H3 value: 3, + "maxPass" : 30, + "maxPriorityPass" : 10, "mainHeroTurnDistanceLimit" : 10, "scoutHeroTurnDistanceLimit" : 5, "maxGoldPressure" : 0.3, "updateHitmapOnTileReveal" : false, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, + "allowObjectGraph": true, + "pathfinderBucketsCount" : 4, // old value: 3, + "pathfinderBucketSize" : 8, // old value: 7, "retreatThresholdRelative" : 0.1, "retreatThresholdAbsolute" : 5000, "safeAttackRatio" : 1.1, @@ -71,17 +73,18 @@ }, "rook" : { - "maxRoamingHeroes" : 8, //H3 value: 4 - "maxpass" : 30, + "maxRoamingHeroes" : 4, //H3 value: 4 + "maxPass" : 30, + "maxPriorityPass" : 10, "mainHeroTurnDistanceLimit" : 10, "scoutHeroTurnDistanceLimit" : 5, "maxGoldPressure" : 0.3, "updateHitmapOnTileReveal" : false, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, + "allowObjectGraph": true, + "pathfinderBucketsCount" : 4, // old value: 3, + "pathfinderBucketSize" : 8, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, @@ -90,17 +93,18 @@ }, "queen" : { - "maxRoamingHeroes" : 8, //H3 value: 5 - "maxpass" : 30, + "maxRoamingHeroes" : 6, //H3 value: 5 + "maxPass" : 30, + "maxPriorityPass" : 10, "mainHeroTurnDistanceLimit" : 10, "scoutHeroTurnDistanceLimit" : 5, "maxGoldPressure" : 0.3, "updateHitmapOnTileReveal" : false, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, + "allowObjectGraph": true, + "pathfinderBucketsCount" : 4, // old value: 3, + "pathfinderBucketSize" : 8, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, @@ -110,16 +114,17 @@ "king" : { "maxRoamingHeroes" : 8, //H3 value: 6 - "maxpass" : 30, + "maxPass" : 30, + "maxPriorityPass" : 10, "mainHeroTurnDistanceLimit" : 10, "scoutHeroTurnDistanceLimit" : 5, "maxGoldPressure" : 0.3, "updateHitmapOnTileReveal" : false, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, + "allowObjectGraph": true, + "pathfinderBucketsCount" : 4, // old value: 3, + "pathfinderBucketSize" : 8, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, diff --git a/include/vcmi/Creature.h b/include/vcmi/Creature.h index 8bff40f86..a1334652d 100644 --- a/include/vcmi/Creature.h +++ b/include/vcmi/Creature.h @@ -23,7 +23,8 @@ class DLL_LINKAGE ACreature: public AFactionMember { public: bool isLiving() const; //non-undead, non-non living or alive - ui32 getMovementRange(int turn = 0) const; //get speed (in moving tiles) of creature with all modificators + ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators + ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers }; diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp index c08e6cb4c..521ac7c37 100644 --- a/launcher/modManager/cdownloadmanager_moc.cpp +++ b/launcher/modManager/cdownloadmanager_moc.cpp @@ -122,7 +122,7 @@ void CDownloadManager::downloadFinished(QNetworkReply * reply) } if(downloadComplete) - emit finished(successful, failed, encounteredErrors); + finished(successful, failed, encounteredErrors); file.reply->deleteLater(); file.reply = nullptr; @@ -149,7 +149,7 @@ void CDownloadManager::downloadProgressChanged(qint64 bytesReceived, qint64 byte if(received > total) total = received; - emit downloadProgress(received, total); + downloadProgress(received, total); } bool CDownloadManager::downloadInProgress(const QUrl & url) const diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index fe8b1eaef..a2fd9f711 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -825,7 +825,7 @@ void CModListView::installFiles(QStringList files) while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { - emit extractionProgress(static_cast(prog * 1000.f), 1000); + extractionProgress(static_cast(prog * 1000.f), 1000); qApp->processEvents(); } diff --git a/launcher/modManager/modstatecontroller.cpp b/launcher/modManager/modstatecontroller.cpp index df799a8e1..31132706d 100644 --- a/launcher/modManager/modstatecontroller.cpp +++ b/launcher/modManager/modstatecontroller.cpp @@ -214,7 +214,7 @@ bool ModStateController::doInstallMod(QString modname, QString archivePath) while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { - emit extractionProgress(filesCounter, filesToExtract.size()); + extractionProgress(filesCounter, filesToExtract.size()); qApp->processEvents(); } diff --git a/launcher/modManager/modstateitemmodel_moc.cpp b/launcher/modManager/modstateitemmodel_moc.cpp index 9d9bfe8c1..807f2306f 100644 --- a/launcher/modManager/modstateitemmodel_moc.cpp +++ b/launcher/modManager/modstateitemmodel_moc.cpp @@ -206,7 +206,7 @@ void ModStateItemModel::modChanged(QString modID) int index = modNameToID.indexOf(modID); QModelIndex parent = this->parent(createIndex(0, 0, index)); int row = modIndex[modIndexToName(parent)].indexOf(modID); - emit dataChanged(createIndex(row, 0, index), createIndex(row, 4, index)); + dataChanged(createIndex(row, 0, index), createIndex(row, 4, index)); } void ModStateItemModel::endResetModel() diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index dec7cbaba..03bbe2a48 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -32,38 +32,27 @@ bool INativeTerrainProvider::isNativeTerrain(TerrainId terrain) const TerrainId AFactionMember::getNativeTerrain() const { - const std::string cachingStringNoTerrainPenalty = "type_TERRAIN_NATIVE_NONE"; - static const auto selectorNoTerrainPenalty = Selector::typeSubtype(BonusType::TERRAIN_NATIVE, BonusSubtypeID()); - //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties. - return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty) + return getBonusBearer()->hasBonusOfType(BonusType::TERRAIN_NATIVE) ? TerrainId::ANY_TERRAIN : getFactionID().toEntity(VLC)->getNativeTerrain(); } int32_t AFactionMember::magicResistance() const { - si32 val = getBonusBearer()->valOfBonuses(Selector::type()(BonusType::MAGIC_RESISTANCE)); + si32 val = getBonusBearer()->valOfBonuses(BonusType::MAGIC_RESISTANCE); vstd::amin (val, 100); return val; } int AFactionMember::getAttack(bool ranged) const { - const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK"; - - static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); - - return getBonusBearer()->valOfBonuses(selector, cachingStr); + return getBonusBearer()->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)); } int AFactionMember::getDefense(bool ranged) const { - const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE"; - - static const auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)); - - return getBonusBearer()->valOfBonuses(selector, cachingStr); + return getBonusBearer()->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)); } int AFactionMember::getMinDamage(bool ranged) const @@ -82,11 +71,9 @@ int AFactionMember::getMaxDamage(bool ranged) const int AFactionMember::getPrimSkillLevel(PrimarySkill id) const { - static const CSelector selectorAllSkills = Selector::type()(BonusType::PRIMARY_SKILL); - static const std::string keyAllSkills = "type_PRIMARY_SKILL"; - auto allSkills = getBonusBearer()->getBonuses(selectorAllSkills, keyAllSkills); - auto ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id))); - auto minSkillValue = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[id.getNum()]; + auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL); + int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id))); + int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, id.getNum()); return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves } @@ -114,9 +101,7 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const return 0; } - static const auto moraleSelector = Selector::type()(BonusType::MORALE); - static const std::string cachingStrMor = "type_MORALE"; - bonusList = getBonusBearer()->getBonuses(moraleSelector, cachingStrMor); + bonusList = getBonusBearer()->getBonusesOfType(BonusType::MORALE); return std::clamp(bonusList->totalValue(), maxBadMorale, maxGoodMorale); } @@ -140,9 +125,7 @@ int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const return 0; } - static const auto luckSelector = Selector::type()(BonusType::LUCK); - static const std::string cachingStrLuck = "type_LUCK"; - bonusList = getBonusBearer()->getBonuses(luckSelector, cachingStrLuck); + bonusList = getBonusBearer()->getBonusesOfType(BonusType::LUCK); return std::clamp(bonusList->totalValue(), maxBadLuck, maxGoodLuck); } @@ -161,25 +144,39 @@ int AFactionMember::luckVal() const ui32 ACreature::getMaxHealth() const { - const std::string cachingStr = "type_STACK_HEALTH"; - static const auto selector = Selector::type()(BonusType::STACK_HEALTH); - auto value = getBonusBearer()->valOfBonuses(selector, cachingStr); + auto value = getBonusBearer()->valOfBonuses(BonusType::STACK_HEALTH); return std::max(1, value); //never 0 } +ui32 ACreature::getMovementRange() const +{ + //war machines cannot move + if (getBonusBearer()->hasBonusOfType(BonusType::SIEGE_WEAPON)) + return 0; + + if (getBonusBearer()->hasBonusOfType(BonusType::BIND_EFFECT)) + return 0; + + return getBonusBearer()->valOfBonuses(BonusType::STACKS_SPEED); +} + ui32 ACreature::getMovementRange(int turn) const { - //war machines cannot move - if(getBonusBearer()->hasBonus(Selector::type()(BonusType::SIEGE_WEAPON).And(Selector::turns(turn)))) - { - return 0; - } - if(getBonusBearer()->hasBonus(Selector::type()(BonusType::BIND_EFFECT).And(Selector::turns(turn)))) - { - return 0; - } + if (turn == 0) + return getMovementRange(); - return getBonusBearer()->valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn))); + const std::string cachingStrSW = "type_SIEGE_WEAPON_turns_" + std::to_string(turn); + const std::string cachingStrBE = "type_BIND_EFFECT_turns_" + std::to_string(turn); + const std::string cachingStrSS = "type_STACKS_SPEED_turns_" + std::to_string(turn); + + //war machines cannot move + if(getBonusBearer()->hasBonus(Selector::type()(BonusType::SIEGE_WEAPON).And(Selector::turns(turn)), cachingStrSW)) + return 0; + + if(getBonusBearer()->hasBonus(Selector::type()(BonusType::BIND_EFFECT).And(Selector::turns(turn)), cachingStrBE)) + return 0; + + return getBonusBearer()->valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStrSS); } bool ACreature::isLiving() const //TODO: theoreticaly there exists "LIVING" bonus in stack experience documentation diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 1b4e0afdf..6a809bba7 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -93,7 +93,7 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr & bonu } if (text.find("${val}") != std::string::npos) - boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)))); + boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(bonus->type, bonus->subtype))); if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as().hasValue()) boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as().toCreature()->getNamePluralTranslated()); diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index 069be91c1..bb1b8363e 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -112,24 +112,24 @@ std::vector PlayerState::getObjectsOfType() const return result; } -std::vector PlayerState::getHeroes() const +const std::vector & PlayerState::getHeroes() const { - return getObjectsOfType(); + return constOwnedHeroes; } -std::vector PlayerState::getTowns() const +const std::vector & PlayerState::getTowns() const { - return getObjectsOfType(); + return constOwnedTowns; } -std::vector PlayerState::getHeroes() +const std::vector & PlayerState::getHeroes() { - return getObjectsOfType(); + return ownedHeroes; } -std::vector PlayerState::getTowns() +const std::vector & PlayerState::getTowns() { - return getObjectsOfType(); + return ownedTowns; } std::vector PlayerState::getOwnedObjects() const @@ -141,11 +141,51 @@ void PlayerState::addOwnedObject(CGObjectInstance * object) { assert(object->asOwnable() != nullptr); ownedObjects.push_back(object); + + auto * town = dynamic_cast(object); + auto * hero = dynamic_cast(object); + + if (town) + { + ownedTowns.push_back(town); + constOwnedTowns.push_back(town); + } + + if (hero) + { + ownedHeroes.push_back(hero); + constOwnedHeroes.push_back(hero); + } +} + +void PlayerState::postDeserialize() +{ + for (const auto& object : ownedObjects) + { + auto* town = dynamic_cast(object); + auto* hero = dynamic_cast(object); + + if (town) + { + ownedTowns.push_back(town); + constOwnedTowns.push_back(town); + } + + if (hero) + { + ownedHeroes.push_back(hero); + constOwnedHeroes.push_back(hero); + } + } } void PlayerState::removeOwnedObject(CGObjectInstance * object) { vstd::erase(ownedObjects, object); + vstd::erase(ownedTowns, object); + vstd::erase(constOwnedTowns, object); + vstd::erase(ownedHeroes, object); + vstd::erase(constOwnedHeroes, object); } diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 411b0945f..6456681ed 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -49,6 +49,11 @@ class DLL_LINKAGE PlayerState : public CBonusSystemNode, public Player std::vector ownedObjects; + std::vector constOwnedTowns; //not serialized + std::vector constOwnedHeroes; //not serialized + std::vector ownedTowns; //not serialized + std::vector ownedHeroes; //not serialized + template std::vector getObjectsOfType() const; @@ -92,15 +97,16 @@ public: std::string getNameTextID() const override; void registerIcons(const IconRegistar & cb) const override; - std::vector getHeroes() const; - std::vector getTowns() const; - std::vector getHeroes(); - std::vector getTowns(); + const std::vector & getHeroes() const; + const std::vector & getTowns() const; + const std::vector & getHeroes(); + const std::vector & getTowns(); std::vector getOwnedObjects() const; void addOwnedObject(CGObjectInstance * object); void removeOwnedObject(CGObjectInstance * object); + void postDeserialize(); bool checkVanquished() const { @@ -145,6 +151,9 @@ public: h & enteredWinningCheatCode; h & static_cast(*this); h & destroyedObjects; + + if (!h.saving) + postDeserialize(); } }; diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 2e177d8ef..a8df9add3 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -33,6 +33,11 @@ std::vector IGameSettings::getVector(EGameSettings option) const return getValue(option).convertTo>(); } +int IGameSettings::getVectorValue(EGameSettings option, size_t index) const +{ + return getValue(option)[index].Integer(); +} + GameSettings::GameSettings() = default; GameSettings::~GameSettings() = default; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 0fcae51f5..3cb92e127 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -101,6 +101,7 @@ public: int64_t getInteger(EGameSettings option) const; double getDouble(EGameSettings option) const; std::vector getVector(EGameSettings option) const; + int getVectorValue(EGameSettings option, size_t index) const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/IHandlerBase.h b/lib/IHandlerBase.h index 022617de9..ce6800b3e 100644 --- a/lib/IHandlerBase.h +++ b/lib/IHandlerBase.h @@ -48,12 +48,15 @@ template { const _Object * getObjectImpl(const int32_t index) const { - if(index < 0 || index >= objects.size()) + try + { + return objects.at(index).get(); + } + catch (const std::out_of_range&) { logMod->error("%s id %d is invalid", getTypeNames()[0], index); throw std::runtime_error("Attempt to access invalid index " + std::to_string(index) + " of type " + getTypeNames().front()); } - return objects[index].get(); } public: diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 309f253ff..905d8a3a0 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -399,8 +399,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const { if(heroes[i]) { - battleRepositionHex[i] += heroes[i]->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION)); - battleRepositionHexBlock[i] += heroes[i]->valOfBonuses(Selector::type()(BonusType::BEFORE_BATTLE_REPOSITION_BLOCK)); + battleRepositionHex[i] += heroes[i]->valOfBonuses(BonusType::BEFORE_BATTLE_REPOSITION); + battleRepositionHexBlock[i] += heroes[i]->valOfBonuses(BonusType::BEFORE_BATTLE_REPOSITION_BLOCK); } } int tacticsSkillDiffAttacker = battleRepositionHex[BattleSide::ATTACKER] - battleRepositionHexBlock[BattleSide::DEFENDER]; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 251c1f9b7..b14389fdc 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -711,19 +711,21 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const if (attacker->creatureIndex() == CreatureID::CATAPULT) //catapult cannot attack creatures return false; + if (!attacker->canShoot()) + return false; + //forgetfulness - TConstBonusListPtr forgetfulList = attacker->getBonuses(Selector::type()(BonusType::FORGETFULL)); + TConstBonusListPtr forgetfulList = attacker->getBonusesOfType(BonusType::FORGETFULL); if(!forgetfulList->empty()) { - int forgetful = forgetfulList->valOfBonuses(Selector::type()(BonusType::FORGETFULL)); + int forgetful = forgetfulList->totalValue(); //advanced+ level if(forgetful > 1) return false; } - return attacker->canShoot() && (!battleIsUnitBlocked(attacker) - || attacker->hasBonusOfType(BonusType::FREE_SHOOTING)); + return !battleIsUnitBlocked(attacker) || attacker->hasBonusOfType(BonusType::FREE_SHOOTING); } bool CBattleInfoCallback::battleCanTargetEmptyHex(const battle::Unit * attacker) const @@ -1878,9 +1880,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(vstd::RNG & rand, const ba { const auto * kingMonster = getAliveEnemy([&](const CStack * stack) -> bool //look for enemy, non-shooting stack { - const auto isKing = Selector::type()(BonusType::KING); - - return stack->hasBonus(isKing); + return stack->hasBonusOfType(BonusType::KING); }); if (!kingMonster) @@ -1905,7 +1905,7 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(vstd::RNG & rand,const CStack { RETURN_IF_NOT_BATTLE(SpellID::NONE); - TConstBonusListPtr bl = caster->getBonuses(Selector::type()(BonusType::SPELLCASTER)); + TConstBonusListPtr bl = caster->getBonusesOfType(BonusType::SPELLCASTER); if (!bl->size()) return SpellID::NONE; @@ -1969,7 +1969,7 @@ si8 CBattleInfoCallback::battleMinSpellLevel(BattleSide side) const if(!node) return 0; - auto b = node->getBonuses(Selector::type()(BonusType::BLOCK_MAGIC_BELOW)); + auto b = node->getBonusesOfType(BonusType::BLOCK_MAGIC_BELOW); if(b->size()) return b->totalValue(); @@ -1988,7 +1988,7 @@ si8 CBattleInfoCallback::battleMaxSpellLevel(BattleSide side) const return GameConstants::SPELL_LEVELS; //We can't "just get value" - it'd be 0 if there are bonuses (and all would be blocked) - auto b = node->getBonuses(Selector::type()(BonusType::BLOCK_MAGIC_ABOVE)); + auto b = node->getBonusesOfType(BonusType::BLOCK_MAGIC_ABOVE); if(b->size()) return b->totalValue(); diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 638d97b5e..b9f23468b 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -404,9 +404,7 @@ PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) con PlayerColor initialOwner = getBattle()->getSidePlayer(unit->unitSide()); - static const CSelector selector = Selector::type()(BonusType::HYPNOTIZED); - - if(unit->hasBonus(selector)) + if(unit->hasBonusOfType(BonusType::HYPNOTIZED)) return otherPlayer(initialOwner); else return initialOwner; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 737a9813f..994705b25 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -85,7 +85,7 @@ void CAmmo::serializeJson(JsonSerializeFormat & handler) ///CShots CShots::CShots(const battle::Unit * Owner) : CAmmo(Owner, Selector::type()(BonusType::SHOTS)), - shooter(Owner, Selector::type()(BonusType::SHOOTER)) + shooter(Owner, BonusType::SHOOTER) { } @@ -124,8 +124,8 @@ CCasts::CCasts(const battle::Unit * Owner): CRetaliations::CRetaliations(const battle::Unit * Owner) : CAmmo(Owner, Selector::type()(BonusType::ADDITIONAL_RETALIATION)), totalCache(0), - noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION))), - unlimited(Owner, Selector::type()(BonusType::UNLIMITED_RETALIATIONS)) + noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION)), "CRetaliations::noRetaliation"), + unlimited(Owner, BonusType::UNLIMITED_RETALIATIONS) { } @@ -347,7 +347,7 @@ CUnitState::CUnitState(): attack(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)), 0), defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)), 0), inFrenzy(this, Selector::type()(BonusType::IN_FRENZY)), - cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE))))), + cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"), cloneID(-1) { @@ -591,7 +591,11 @@ void CUnitState::setPosition(BattleHex hex) int32_t CUnitState::getInitiative(int turn) const { - return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn))); + if (turn == 0) + return valOfBonuses(BonusType::STACKS_SPEED); + + std::string cachingStr = "type_STACKS_SPEED_turns_" + std::to_string(turn); + return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStr); } uint8_t CUnitState::getRangedFullDamageDistance() const @@ -602,7 +606,7 @@ uint8_t CUnitState::getRangedFullDamageDistance() const uint8_t rangedFullDamageDistance = GameConstants::BATTLE_SHOOTING_PENALTY_DISTANCE; // overwrite full ranged damage distance with the value set in Additional info field of LIMITED_SHOOTING_RANGE bonus - if(this->hasBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE))) + if(hasBonusOfType(BonusType::LIMITED_SHOOTING_RANGE)) { auto bonus = this->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE)); if(bonus != nullptr && bonus->additionalInfo != CAddInfo::NONE) @@ -620,7 +624,7 @@ uint8_t CUnitState::getShootingRangeDistance() const uint8_t shootingRangeDistance = GameConstants::BATTLE_SHOOTING_RANGE_DISTANCE; // overwrite full ranged damage distance with the value set in Additional info field of LIMITED_SHOOTING_RANGE bonus - if(this->hasBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE))) + if(hasBonusOfType(BonusType::LIMITED_SHOOTING_RANGE)) { auto bonus = this->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE)); if(bonus != nullptr) @@ -632,7 +636,14 @@ uint8_t CUnitState::getShootingRangeDistance() const bool CUnitState::canMove(int turn) const { - return alive() && !hasBonus(Selector::type()(BonusType::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature + if (!alive()) + return false; + + if (turn == 0) + return !hasBonusOfType(BonusType::NOT_ACTIVE); + + std::string cachingStr = "type_NOT_ACTIVE_turns_" + std::to_string(turn); + return !hasBonus(Selector::type()(BonusType::NOT_ACTIVE).And(Selector::turns(turn)), cachingStr); //eg. Ammo Cart or blinded creature } bool CUnitState::defended(int turn) const diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index 6aa4e5483..b6030e87e 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -161,7 +161,7 @@ int DamageCalculator::getActorAttackSlayer() const return 0; auto slayerEffects = info.attacker->getBonuses(selectorSlayer, cachingStrSlayer); - auto slayerAffected = info.defender->unitType()->valOfBonuses(Selector::type()(BonusType::KING)); + auto slayerAffected = info.defender->unitType()->valOfBonuses(BonusType::KING); if(std::shared_ptr slayerEffect = slayerEffects->getFirst(Selector::all)) { @@ -269,26 +269,16 @@ double DamageCalculator::getAttackDoubleDamageFactor() const double DamageCalculator::getAttackJoustingFactor() const { - const std::string cachingStrJousting = "type_JOUSTING"; - static const auto selectorJousting = Selector::type()(BonusType::JOUSTING); - - const std::string cachingStrChargeImmunity = "type_CHARGE_IMMUNITY"; - static const auto selectorChargeImmunity = Selector::type()(BonusType::CHARGE_IMMUNITY); - //applying jousting bonus - if(info.chargeDistance > 0 && info.attacker->hasBonus(selectorJousting, cachingStrJousting) && !info.defender->hasBonus(selectorChargeImmunity, cachingStrChargeImmunity)) - return info.chargeDistance * (info.attacker->valOfBonuses(selectorJousting))/100.0; + if(info.chargeDistance > 0 && info.attacker->hasBonusOfType(BonusType::JOUSTING) && !info.defender->hasBonusOfType(BonusType::CHARGE_IMMUNITY)) + return info.chargeDistance * (info.attacker->valOfBonuses(BonusType::JOUSTING))/100.0; return 0.0; } double DamageCalculator::getAttackHateFactor() const { //assume that unit have only few HATE features and cache them all - const std::string cachingStrHate = "type_HATE"; - static const auto selectorHate = Selector::type()(BonusType::HATE); - - auto allHateEffects = info.attacker->getBonuses(selectorHate, cachingStrHate); - + auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATE); return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0; } @@ -411,7 +401,7 @@ double DamageCalculator::getDefenseForgetfulnessFactor() const { //todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling //get list first, total value of 0 also counts - TConstBonusListPtr forgetfulList = info.attacker->getBonuses(Selector::type()(BonusType::FORGETFULL),"type_FORGETFULL"); + TConstBonusListPtr forgetfulList = info.attacker->getBonusesOfType(BonusType::FORGETFULL); if(!forgetfulList->empty()) { diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index c2d389413..1739f560a 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -195,7 +195,6 @@ std::shared_ptr BonusList::getFirst(const CSelector &selector) cons void BonusList::getBonuses(BonusList & out, const CSelector &selector, const CSelector &limit) const { - out.reserve(bonuses.size()); for(const auto & b : bonuses) { if(selector(b.get()) && (!limit || ((bool)limit && limit(b.get())))) @@ -259,11 +258,6 @@ void BonusList::resize(BonusList::TInternalContainer::size_type sz, const std::s changed(); } -void BonusList::reserve(TInternalContainer::size_type sz) -{ - bonuses.reserve(sz); -} - void BonusList::insert(BonusList::TInternalContainer::iterator position, BonusList::TInternalContainer::size_type n, const std::shared_ptr & x) { bonuses.insert(position, n, x); diff --git a/lib/bonuses/BonusList.h b/lib/bonuses/BonusList.h index 3629cdf9c..66b4f2b42 100644 --- a/lib/bonuses/BonusList.h +++ b/lib/bonuses/BonusList.h @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE BonusList { public: - using TInternalContainer = std::vector>; + using TInternalContainer = boost::container::small_vector, 16>; private: TInternalContainer bonuses; @@ -43,7 +43,6 @@ public: void clear(); bool empty() const { return bonuses.empty(); } void resize(TInternalContainer::size_type sz, const std::shared_ptr & c = nullptr); - void reserve(TInternalContainer::size_type sz); TInternalContainer::size_type capacity() const { return bonuses.capacity(); } STRONG_INLINE std::shared_ptr &operator[] (TInternalContainer::size_type n) { return bonuses[n]; } STRONG_INLINE const std::shared_ptr &operator[] (TInternalContainer::size_type n) const { return bonuses[n]; } diff --git a/lib/bonuses/CBonusProxy.cpp b/lib/bonuses/CBonusProxy.cpp index 607bc89cd..aa99e9a7d 100644 --- a/lib/bonuses/CBonusProxy.cpp +++ b/lib/bonuses/CBonusProxy.cpp @@ -183,10 +183,21 @@ int CTotalsProxy::getRangedValue() const } ///CCheckProxy -CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector): +CCheckProxy::CCheckProxy(const IBonusBearer * Target, BonusType bonusType): + target(Target), + selector(Selector::type()(bonusType)), + cachingStr("type_" + std::to_string(static_cast(bonusType))), + cachedLast(0), + hasBonus(false) +{ + +} + +CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr): target(Target), selector(std::move(Selector)), cachedLast(0), + cachingStr(cachingStr), hasBonus(false) { } @@ -200,11 +211,11 @@ bool CCheckProxy::getHasBonus() const if(treeVersion != cachedLast) { - hasBonus = target->hasBonus(selector); + hasBonus = target->hasBonus(selector, cachingStr); cachedLast = treeVersion; } return hasBonus; } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/CBonusProxy.h b/lib/bonuses/CBonusProxy.h index bff9ace49..f7e3d6cbf 100644 --- a/lib/bonuses/CBonusProxy.h +++ b/lib/bonuses/CBonusProxy.h @@ -73,7 +73,8 @@ private: class DLL_LINKAGE CCheckProxy { public: - CCheckProxy(const IBonusBearer * Target, CSelector Selector); + CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr); + CCheckProxy(const IBonusBearer * Target, BonusType bonusType); CCheckProxy(const CCheckProxy & other); CCheckProxy& operator= (const CCheckProxy & other) = default; @@ -81,10 +82,11 @@ public: private: const IBonusBearer * target; + std::string cachingStr; CSelector selector; mutable int64_t cachedLast; mutable bool hasBonus; }; -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index b5a2d582d..d155edcb8 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -63,22 +63,10 @@ void CBonusSystemNode::getAllParents(TCNodes & out) const //retrieves list of pa void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & selector) const { - //out has been reserved sufficient capacity at getAllBonuses() call - BonusList beforeUpdate; TCNodes lparents; getAllParents(lparents); - if(!lparents.empty()) - { - //estimate on how many bonuses are missing yet - must be positive - beforeUpdate.reserve(std::max(out.capacity() - out.size(), bonuses.size())); - } - else - { - beforeUpdate.reserve(bonuses.size()); //at most all local bonuses - } - for(const auto * parent : lparents) { parent->getAllBonusesRec(beforeUpdate, selector); @@ -111,46 +99,64 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co { if (CBonusSystemNode::cachingEnabled) { - // Exclusive access for one thread - 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. - if (cachedLast != treeChanged) - { - BonusList allBonuses; - allBonuses.reserve(cachedBonuses.capacity()); //we assume we'll get about the same number of bonuses - - cachedBonuses.clear(); - cachedRequests.clear(); - - getAllBonusesRec(allBonuses, Selector::all); - limitBonuses(allBonuses, cachedBonuses); - cachedBonuses.stackBonuses(); - - cachedLast = treeChanged; - } - // If a bonus system request comes with a caching string then look up in the map if there are any // pre-calculated bonus results. Limiters can't be cached so they have to be calculated. - if(!cachingStr.empty()) + if (cachedLast == treeChanged && !cachingStr.empty()) { - auto it = cachedRequests.find(cachingStr); - if(it != cachedRequests.end()) - { - //Cached list contains bonuses for our query with applied limiters - return it->second; - } + RequestsMap::const_accessor accessor; + + //Cached list contains bonuses for our query with applied limiters + if (cachedRequests.find(accessor, cachingStr) && accessor->second.first == cachedLast) + return accessor->second.second; } //We still don't have the bonuses (didn't returned them from cache) //Perform bonus selection auto ret = std::make_shared(); - cachedBonuses.getBonuses(*ret, selector, limit); + + if (cachedLast == treeChanged) + { + // Cached bonuses are up-to-date - use shared/read access and compute results + std::shared_lock lock(sync); + cachedBonuses.getBonuses(*ret, selector, limit); + } + else + { + // 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. + std::lock_guard lock(sync); + if (cachedLast == treeChanged) + { + // While our thread was waiting, another one have updated bonus tree. Use cached bonuses. + cachedBonuses.getBonuses(*ret, selector, limit); + } + else + { + // Cached bonuses may be outdated - regenerate them + BonusList allBonuses; + + cachedBonuses.clear(); + + getAllBonusesRec(allBonuses, Selector::all); + limitBonuses(allBonuses, cachedBonuses); + cachedBonuses.stackBonuses(); + cachedLast = treeChanged; + cachedBonuses.getBonuses(*ret, selector, limit); + } + } // Save the results in the cache - if(!cachingStr.empty()) - cachedRequests[cachingStr] = ret; + if (!cachingStr.empty()) + { + RequestsMap::accessor accessor; + if (cachedRequests.find(accessor, cachingStr)) + { + accessor->second.second = ret; + accessor->second.first = cachedLast; + } + else + cachedRequests.emplace(cachingStr, std::pair{ cachedLast, ret }); + } return ret; } diff --git a/lib/bonuses/CBonusSystemNode.h b/lib/bonuses/CBonusSystemNode.h index 460957f8b..5baad0e96 100644 --- a/lib/bonuses/CBonusSystemNode.h +++ b/lib/bonuses/CBonusSystemNode.h @@ -14,6 +14,8 @@ #include "../serializer/Serializeable.h" +#include + VCMI_LIB_NAMESPACE_BEGIN using TNodes = std::set; @@ -30,6 +32,19 @@ public: UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM, TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES, TOWN }; + + struct HashStringCompare { + static size_t hash(const std::string& data) + { + std::hash hasher; + return hasher(data); + } + static bool equal(const std::string& x, const std::string& y) + { + return x == y; + } + }; + private: BonusList bonuses; //wielded bonuses (local or up-propagated here) BonusList exportedBonuses; //bonuses coming from this node (wielded or propagated away) @@ -49,8 +64,9 @@ private: // Setting a value to cachingStr before getting any bonuses caches the result for later requests. // This string needs to be unique, that's why it has to be set in the following manner: // [property key]_[value] => only for selector - mutable std::map cachedRequests; - mutable boost::mutex sync; + using RequestsMap = tbb::concurrent_hash_map, HashStringCompare>; + mutable RequestsMap cachedRequests; + mutable std::shared_mutex sync; void getAllBonusesRec(BonusList &out, const CSelector & selector) const; TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const; diff --git a/lib/bonuses/IBonusBearer.cpp b/lib/bonuses/IBonusBearer.cpp index 924c8f110..fa3f8b250 100644 --- a/lib/bonuses/IBonusBearer.cpp +++ b/lib/bonuses/IBonusBearer.cpp @@ -42,6 +42,27 @@ TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSe return getAllBonuses(selector, limit, cachingStr); } +TConstBonusListPtr IBonusBearer::getBonusesFrom(BonusSource source) const +{ + std::string cachingStr = "source_" + std::to_string(static_cast(source)); + CSelector s = Selector::sourceTypeSel(source); + return getBonuses(s, cachingStr); +} + +TConstBonusListPtr IBonusBearer::getBonusesOfType(BonusType type) const +{ + std::string cachingStr = "type_" + std::to_string(static_cast(type)); + CSelector s = Selector::type()(type); + return getBonuses(s, cachingStr); +} + +TConstBonusListPtr IBonusBearer::getBonusesOfType(BonusType type, BonusSubtypeID subtype) const +{ + std::string cachingStr = "type_" + std::to_string(static_cast(type)) + "_" + subtype.toString(); + CSelector s = Selector::type()(type); + return getBonuses(s, cachingStr); +} + int IBonusBearer::valOfBonuses(BonusType type) const { //This part is performance-critical @@ -84,7 +105,14 @@ bool IBonusBearer::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const bool IBonusBearer::hasBonusFrom(BonusSource source, BonusSourceID sourceID) const { - return hasBonus(Selector::source(source,sourceID)); + std::string cachingStr = "source_" + std::to_string(static_cast(source)) + "_" + sourceID.toString(); + return hasBonus(Selector::source(source,sourceID), cachingStr); +} + +bool IBonusBearer::hasBonusFrom(BonusSource source) const +{ + std::string cachingStr = "source_" + std::to_string(static_cast(source)); + return hasBonus((Selector::sourceTypeSel(source)), cachingStr); } std::shared_ptr IBonusBearer::getBonus(const CSelector &selector) const diff --git a/lib/bonuses/IBonusBearer.h b/lib/bonuses/IBonusBearer.h index b272edea4..b27ca7f0d 100644 --- a/lib/bonuses/IBonusBearer.h +++ b/lib/bonuses/IBonusBearer.h @@ -20,12 +20,12 @@ public: // * selector is predicate that tests if Bonus matches our criteria IBonusBearer() = default; virtual ~IBonusBearer() = default; - virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0; - int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const; - bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const; - bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const; - TConstBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const; - TConstBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = "") const; + virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const = 0; + int valOfBonuses(const CSelector &selector, const std::string &cachingStr = {}) const; + bool hasBonus(const CSelector &selector, const std::string &cachingStr = {}) const; + bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const; + TConstBonusListPtr getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = {}) const; + TConstBonusListPtr getBonuses(const CSelector &selector, const std::string &cachingStr = {}) const; std::shared_ptr getBonus(const CSelector &selector) const; //returns any bonus visible on node that matches (or nullptr if none matches) @@ -34,8 +34,13 @@ public: bool hasBonusOfType(BonusType type) const;//determines if hero has a bonus of given type (and optionally subtype) int valOfBonuses(BonusType type, BonusSubtypeID subtype) const; //subtype -> subtype of bonus; bool hasBonusOfType(BonusType type, BonusSubtypeID subtype) const;//determines if hero has a bonus of given type (and optionally subtype) + bool hasBonusFrom(BonusSource source) const; bool hasBonusFrom(BonusSource source, BonusSourceID sourceID) const; + TConstBonusListPtr getBonusesFrom(BonusSource source) const; + TConstBonusListPtr getBonusesOfType(BonusType type) const; + TConstBonusListPtr getBonusesOfType(BonusType type, BonusSubtypeID subtype) const; + virtual int64_t getTreeVersion() const = 0; }; diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index 5dc6a2e10..fee9b1085 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -339,7 +339,7 @@ void CampaignState::setCurrentMapAsConquered(std::vector heroe { range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b) { - return a->getHeroStrengthForCampaign() > b->getHeroStrengthForCampaign(); + return a->getValueForCampaign() > b->getValueForCampaign(); }); logGlobal->info("Scenario %d of campaign %s (%s) has been completed", currentMap->getNum(), getFilename(), getNameTranslated()); diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 2c3f183c5..ac7cdc334 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -97,8 +97,6 @@ const PrimarySkill PrimarySkill::ATTACK(0); const PrimarySkill PrimarySkill::DEFENSE(1); const PrimarySkill PrimarySkill::SPELL_POWER(2); const PrimarySkill PrimarySkill::KNOWLEDGE(3); -const PrimarySkill PrimarySkill::BEGIN(0); -const PrimarySkill PrimarySkill::END(4); const PrimarySkill PrimarySkill::EXPERIENCE(4); const BoatId BoatId::NONE(-1); @@ -325,7 +323,7 @@ const Skill * SecondarySkill::toEntity(const Services * services) const const CCreature * CreatureIDBase::toCreature() const { - return dynamic_cast(toEntity(VLC)); + return (*VLC->creh)[num]; } const Creature * CreatureIDBase::toEntity(const Services * services) const @@ -630,6 +628,18 @@ std::string GameResID::entityType() return "resource"; } +const std::array & PrimarySkill::ALL_SKILLS() +{ + static const std::array allSkills = { + PrimarySkill(ATTACK), + PrimarySkill(DEFENSE), + PrimarySkill(SPELL_POWER), + PrimarySkill(KNOWLEDGE) + }; + + return allSkills; +} + const std::array & GameResID::ALL_RESOURCES() { static const std::array allResources = { diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 171613a19..1aacfe828 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -230,8 +230,7 @@ public: static const PrimarySkill SPELL_POWER; static const PrimarySkill KNOWLEDGE; - static const PrimarySkill BEGIN; - static const PrimarySkill END; + static const std::array & ALL_SKILLS(); static const PrimarySkill EXPERIENCE; diff --git a/lib/entities/hero/CHeroClassHandler.cpp b/lib/entities/hero/CHeroClassHandler.cpp index e9207a229..d393e983d 100644 --- a/lib/entities/hero/CHeroClassHandler.cpp +++ b/lib/entities/hero/CHeroClassHandler.cpp @@ -32,7 +32,7 @@ void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * { const auto & skillName = NPrimarySkill::names[pSkill.getNum()]; auto currentPrimarySkillValue = static_cast(node["primarySkills"][skillName].Integer()); - int primarySkillLegalMinimum = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[pSkill.getNum()]; + int primarySkillLegalMinimum = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, pSkill.getNum()); if(currentPrimarySkillValue < primarySkillLegalMinimum) { diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 892435cc6..6cc058435 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -82,13 +82,13 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr //trimming prim skills for(auto & hero : campaignHeroReplacements) { - for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g) + for(auto skill : PrimarySkill::ALL_SKILLS()) { auto sel = Selector::type()(BonusType::PRIMARY_SKILL) - .And(Selector::subtype()(BonusSubtypeID(g))) + .And(Selector::subtype()(BonusSubtypeID(skill))) .And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); - hero.hero->getLocalBonus(sel)->val = hero.hero->getHeroClass()->primarySkillInitial[g.getNum()]; + hero.hero->getLocalBonus(sel)->val = hero.hero->getHeroClass()->primarySkillInitial[skill.getNum()]; } } } @@ -337,14 +337,14 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero) case CampaignBonusType::PRIMARY_SKILL: { const ui8 * ptr = reinterpret_cast(&curBonus->info2); - for(auto g = PrimarySkill::BEGIN; g < PrimarySkill::END; ++g) + for(auto skill : PrimarySkill::ALL_SKILLS()) { - int val = ptr[g.getNum()]; + int val = ptr[skill.getNum()]; if(val == 0) continue; auto currentScenario = *gameState->scenarioOps->campState->currentScenario(); - auto bb = std::make_shared( BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, BonusSourceID(currentScenario), BonusSubtypeID(g) ); + auto bb = std::make_shared( BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::CAMPAIGN_BONUS, val, BonusSourceID(currentScenario), BonusSubtypeID(skill) ); hero->addNewBonus(bb); } break; @@ -551,7 +551,7 @@ void CGameStateCampaign::initHeroes() int maxB = -1; for (int b=0; bgetTotalStrength() > heroes[maxB]->getTotalStrength()) + if (maxB == -1 || heroes[b]->getValueForCampaign() > heroes[maxB]->getValueForCampaign()) { maxB = b; } diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index 23d026cd9..f2783662d 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -38,9 +38,6 @@ void CArmedInstance::randomizeArmy(FactionID type) } } -// Take Angelic Alliance troop-mixing freedom of non-evil units into account. -CSelector CArmedInstance::nonEvilAlignmentMixSelector = Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX); - CArmedInstance::CArmedInstance(IGameCallback *cb) :CArmedInstance(cb, false) { @@ -49,7 +46,7 @@ CArmedInstance::CArmedInstance(IGameCallback *cb) CArmedInstance::CArmedInstance(IGameCallback *cb, bool isHypothetic): CGObjectInstance(cb), CBonusSystemNode(isHypothetic), - nonEvilAlignmentMix(this, nonEvilAlignmentMixSelector), + nonEvilAlignmentMix(this, BonusType::NONEVIL_ALIGNMENT_MIX), // Take Angelic Alliance troop-mixing freedom of non-evil units into account. battle(nullptr) { } diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index b1589c8e3..f7b9fb7a2 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -336,7 +336,7 @@ void CGCreature::setPropertyDer(ObjProperty what, ObjPropertyID identifier) int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const { //calculate relative strength of hero and creatures armies - double relStrength = static_cast(h->getTotalStrength()) / getArmyStrength(); + double relStrength = static_cast(h->getValueForDiplomacy()) / getArmyStrength(); int powerFactor; if(relStrength >= 7) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d2f61fe03..81ef94c21 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -257,8 +257,7 @@ void CGHeroInstance::setMovementPoints(int points) int CGHeroInstance::movementPointsLimit(bool onLand) const { - TurnInfo ti(this); - return movementPointsLimitCached(onLand, &ti); + return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea); } int CGHeroInstance::getLowestCreatureSpeed() const @@ -274,7 +273,7 @@ void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) c lowestCreatureSpeed = realLowestSpeed; //Let updaters run again treeHasChanged(); - ti->updateHeroBonuses(BonusType::MOVEMENT, Selector::subtype()(onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea)); + ti->updateHeroBonuses(BonusType::MOVEMENT); } } @@ -406,7 +405,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand) putArtifact(ArtifactPosition::MACH4, artifact); //everyone has a catapult } - if(!hasBonus(Selector::sourceType()(BonusSource::HERO_BASE_SKILL))) + if(!hasBonusFrom(BonusSource::HERO_BASE_SKILL)) { for(int g=0; g CGHeroInstance::getPrimarySkills() const +{ + std::array result; + + auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL); + for (auto skill : PrimarySkill::ALL_SKILLS()) + { + int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(skill))); + int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, skill.getNum()); + result[skill] = std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect + } + + return result; +} + double CGHeroInstance::getFightingStrength() const { - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); + const auto & primarySkills = getPrimarySkills(); + return getFightingStrengthImpl(primarySkills); +} + +double CGHeroInstance::getFightingStrengthImpl(const std::array & primarySkills) const +{ + return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::ATTACK]) * (1.0 + 0.05*primarySkills[PrimarySkill::DEFENSE])); } double CGHeroInstance::getMagicStrength() const +{ + const auto & primarySkills = getPrimarySkills(); + return getMagicStrengthImpl(primarySkills); +} + +double CGHeroInstance::getMagicStrengthImpl(const std::array & primarySkills) const { if (!hasSpellbook()) return 1; bool atLeastOneCombatSpell = false; for (auto spell : spells) { - if (spellbookContainsSpell(spell) && spell.toSpell()->isCombat()) + if (spell.toSpell()->isCombat()) { atLeastOneCombatSpell = true; break; @@ -725,22 +751,40 @@ double CGHeroInstance::getMagicStrength() const } if (!atLeastOneCombatSpell) return 1; - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * mana / manaLimit()) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER) * mana / manaLimit())); -} - -double CGHeroInstance::getMagicStrengthForCampaign() const -{ - return sqrt((1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::SPELL_POWER))); + return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*primarySkills[PrimarySkill::SPELL_POWER] * mana / manaLimit())); } double CGHeroInstance::getHeroStrength() const { - return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); + const auto & primarySkills = getPrimarySkills(); + return getFightingStrengthImpl(primarySkills) * getMagicStrengthImpl(primarySkills); } -double CGHeroInstance::getHeroStrengthForCampaign() const +uint64_t CGHeroInstance::getValueForDiplomacy() const { - return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrengthForCampaign(), 2.0)); + // H3 formula for hero strength when considering diplomacy skill + uint64_t armyStrength = getArmyStrength(); + double heroStrength = sqrt( + (1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::ATTACK)) * + (1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::DEFENSE)) + ); + + return heroStrength * armyStrength; +} + +uint32_t CGHeroInstance::getValueForCampaign() const +{ + /// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest + uint32_t score = 0; + score += getPrimSkillLevel(PrimarySkill::ATTACK); + score += getPrimSkillLevel(PrimarySkill::DEFENSE); + score += getPrimSkillLevel(PrimarySkill::SPELL_POWER); + score += getPrimSkillLevel(PrimarySkill::DEFENSE); + + for (const auto& secondary : secSkills) + score += secondary.second; + + return score; } ui64 CGHeroInstance::getTotalStrength() const @@ -973,7 +1017,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b // figure out what to raise - pick strongest creature meeting requirements CreatureID creatureTypeRaised = CreatureID::NONE; //now we always have IMPROVED_NECROMANCY, no need for hardcode int requiredCasualtyLevel = 1; - TConstBonusListPtr improvedNecromancy = getBonuses(Selector::type()(BonusType::IMPROVED_NECROMANCY)); + TConstBonusListPtr improvedNecromancy = getBonusesOfType(BonusType::IMPROVED_NECROMANCY); if(!improvedNecromancy->empty()) { int maxCasualtyLevel = 1; @@ -1141,9 +1185,8 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val ) { auto sel = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)) .And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); - if(hasBonus(sel)) - removeBonuses(sel); - + + removeBonuses(sel); addNewBonus(std::make_shared(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, BonusSourceID(id), BonusSubtypeID(which))); } @@ -1281,7 +1324,7 @@ const std::set & CGHeroInstance::getSpellsInSpellbook() const int CGHeroInstance::maxSpellLevel() const { - return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(Selector::type()(BonusType::MAX_LEARNABLE_SPELL_LEVEL))); + return std::min(GameConstants::SPELL_LEVELS, valOfBonuses(BonusType::MAX_LEARNABLE_SPELL_LEVEL)); } void CGHeroInstance::attachToBoat(CGBoat* newBoat) @@ -1655,11 +1698,11 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) { auto primarySkills = handler.enterStruct("primarySkills"); - for(auto i = PrimarySkill::BEGIN; i < PrimarySkill::END; ++i) + for(auto skill : PrimarySkill::ALL_SKILLS()) { - int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(i)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL))); + int value = valOfBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(skill)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL))); - handler.serializeInt(NPrimarySkill::names[i.getNum()], value, 0); + handler.serializeInt(NPrimarySkill::names[skill.getNum()], value, 0); } } } @@ -1850,7 +1893,7 @@ bool CGHeroInstance::isMissionCritical() const void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance & stack) const { - TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId()))); + TConstBonusListPtr lista = getBonusesOfType(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId())); for(const auto & it : *lista) { auto nid = CreatureID(it->additionalInfo[0]); @@ -1921,9 +1964,9 @@ const IOwnableObject * CGHeroInstance::asOwnable() const int CGHeroInstance::getBasePrimarySkillValue(PrimarySkill which) const { - std::string cachingStr = "type_PRIMARY_SKILL_base_" + std::to_string(static_cast(which)); + std::string cachingStr = "CGHeroInstance::getBasePrimarySkillValue" + std::to_string(static_cast(which)); auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); - auto minSkillValue = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[which.getNum()]; + auto minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, which.getNum()); return std::max(valOfBonuses(selector, cachingStr), minSkillValue); } diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 0a07232c3..2a7494415 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -62,6 +62,9 @@ private: mutable int lowestCreatureSpeed; ui32 movement; //remaining movement points + double getFightingStrengthImpl(const std::array & primarySkills) const; + double getMagicStrengthImpl(const std::array & primarySkills) const; + public: ////////////////////////////////////////////////////////////////////////// @@ -201,6 +204,7 @@ public: std::vector getLevelUpProposedSecondarySkills(vstd::RNG & rand) const; ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill + std::array getPrimarySkills() const; /// Returns true if hero has free secondary skill slot. bool canLearnSkill() const; @@ -225,9 +229,11 @@ public: double getFightingStrength() const; // takes attack / defense skill into account double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account - double getMagicStrengthForCampaign() const; // takes knowledge / spell power skill into account double getHeroStrength() const; // includes fighting and magic strength - double getHeroStrengthForCampaign() const; // includes fighting and the for-campaign-version of magic strength + + uint32_t getValueForCampaign() const; + uint64_t getValueForDiplomacy() const; + ui64 getTotalStrength() const; // includes fighting strength and army strength TExpType calculateXp(TExpType exp) const; //apply learning skill int getBasePrimarySkillValue(PrimarySkill which) const; //the value of a base-skill without items or temporary bonuses diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index bb0ec1570..e9ca3c9cd 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -161,7 +161,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde()); //statue-of-legion-like bonus: % to base+castle - TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(BonusType::CREATURE_GROWTH_PERCENT)); + TConstBonusListPtr bonuses2 = getBonusesOfType(BonusType::CREATURE_GROWTH_PERCENT); for(const auto & b : *bonuses2) { const auto growth = b->val * (base + castleBonus) / 100; @@ -173,7 +173,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const //other *-of-legion-like bonuses (%d to growth cumulative with grail) // Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry) - TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1))); + TConstBonusListPtr bonuses = getBonusesOfType(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1)); for(const auto & b : *bonuses) ret.entries.emplace_back(b->val, b->Description(cb)); diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 7a7fc1af6..35219cac2 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -596,11 +596,12 @@ void CPathfinderHelper::getNeighbours( continue; const TerrainTile & destTile = map->getTile(destCoord); - if(!destTile.getTerrain()->isPassable()) + const TerrainType* terrain = destTile.getTerrain(); + if(!terrain->isPassable()) continue; /// Following condition let us avoid diagonal movement over coast when sailing - if(srcTile.isWater() && limitCoastSailing && destTile.isWater() && dir.x && dir.y) //diagonal move through water + if(srcTile.isWater() && limitCoastSailing && terrain->isWater() && dir.x && dir.y) //diagonal move through water { const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; @@ -608,7 +609,7 @@ void CPathfinderHelper::getNeighbours( continue; } - if(indeterminate(onLand) || onLand == destTile.isLand()) + if(indeterminate(onLand) || onLand == terrain->isLand()) { vec.push_back(destCoord); } diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h index 4791a450f..9a903ced1 100644 --- a/lib/pathfinder/CPathfinder.h +++ b/lib/pathfinder/CPathfinder.h @@ -13,9 +13,6 @@ #include "../IGameCallback.h" #include "../bonuses/BonusEnum.h" -#include -#include - VCMI_LIB_NAMESPACE_BEGIN class CGWhirlpool; diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp index b260efafa..17258de4e 100644 --- a/lib/pathfinder/TurnInfo.cpp +++ b/lib/pathfinder/TurnInfo.cpp @@ -41,7 +41,7 @@ TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): maxMovePointsWater(-1), turn(turn) { - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, ""); + bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); bonusCache = std::make_unique(bonuses); nativeTerrain = hero->getNativeTerrain(); } @@ -125,7 +125,7 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; } -void TurnInfo::updateHeroBonuses(BonusType type, const CSelector& sel) const +void TurnInfo::updateHeroBonuses(BonusType type) const { switch(type) { @@ -144,7 +144,7 @@ void TurnInfo::updateHeroBonuses(BonusType type, const CSelector& sel) const bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); break; default: - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, ""); + bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, "all_days" + std::to_string(turn)); } } diff --git a/lib/pathfinder/TurnInfo.h b/lib/pathfinder/TurnInfo.h index 6fff27e6f..02e4597df 100644 --- a/lib/pathfinder/TurnInfo.h +++ b/lib/pathfinder/TurnInfo.h @@ -46,7 +46,7 @@ struct DLL_LINKAGE TurnInfo bool hasBonusOfType(const BonusType type, const BonusSubtypeID subtype) const; int valOfBonuses(const BonusType type) const; int valOfBonuses(const BonusType type, const BonusSubtypeID subtype) const; - void updateHeroBonuses(BonusType type, const CSelector& sel) const; + void updateHeroBonuses(BonusType type) const; int getMaxMovePoints(const EPathfindingLayer & layer) const; }; diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 5544a0ba2..a4c2d8aa8 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -38,10 +38,7 @@ std::vector Rewardable::Interface::getAvailableRewards(const CGHeroInstanc const Rewardable::VisitInfo & visit = configuration.info[i]; if(event == visit.visitType && (!hero || visit.limiter.heroAllowed(hero))) - { - logGlobal->trace("Reward %d is allowed", i); ret.push_back(static_cast(i)); - } } return ret; } @@ -119,7 +116,8 @@ void Rewardable::Interface::grantRewardBeforeLevelup(const Rewardable::VisitInfo } for(int i=0; i< info.reward.primary.size(); i++) - cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); + if (info.reward.primary[i] != 0) + cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); TExpType expToGive = 0; diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 9db041b2e..25c232ca5 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -215,6 +215,15 @@ public: load( data[i]); } + template + void load(boost::container::small_vector& data) + { + uint32_t length = readAndCheckLength(); + data.resize(length); + for (uint32_t i = 0; i < length; i++) + load(data[i]); + } + template , int > = 0> void load(std::deque & data) { diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 851d53836..738cf17c1 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -275,6 +275,15 @@ public: for(uint32_t i=0;i + void save(const boost::container::small_vector& data) + { + uint32_t length = data.size(); + *this& length; + for (uint32_t i = 0; i < length; i++) + save(data[i]); + } + template , int > = 0> void save(const std::deque & data) { diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index c55e18719..c35f583d0 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -212,7 +212,7 @@ protected: { if(!m->isMagicalEffect()) //Always pass on non-magical return true; - TConstBonusListPtr levelImmunities = target->getBonuses(Selector::type()(BonusType::LEVEL_SPELL_IMMUNITY)); + TConstBonusListPtr levelImmunities = target->getBonusesOfType(BonusType::LEVEL_SPELL_IMMUNITY); return levelImmunities->size() == 0 || levelImmunities->totalValue() < m->getSpellLevel() || m->getSpellLevel() <= 0; diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index dd1f49493..54e395b21 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -223,6 +223,9 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor) endif() +# Qt defines 'emit' as macros, which conflicts with TBB definition of method with same name +target_compile_definitions(vcmieditor PRIVATE QT_NO_EMIT) + if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) target_compile_definitions(vcmieditor PRIVATE VCMIQT_STATIC) endif() diff --git a/mapeditor/inspector/artifactwidget.cpp b/mapeditor/inspector/artifactwidget.cpp index 3723eb428..459ffae8b 100644 --- a/mapeditor/inspector/artifactwidget.cpp +++ b/mapeditor/inspector/artifactwidget.cpp @@ -23,7 +23,7 @@ ArtifactWidget::ArtifactWidget(CArtifactFittingSet & fittingSet, QWidget * paren connect(ui->saveButton, &QPushButton::clicked, this, [this]() { - emit saveArtifact(ui->artifact->currentData().toInt(), ArtifactPosition(ui->possiblePositions->currentData().toInt())); + saveArtifact(ui->artifact->currentData().toInt(), ArtifactPosition(ui->possiblePositions->currentData().toInt())); close(); }); connect(ui->cancelButton, &QPushButton::clicked, this, &ArtifactWidget::close); diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index 395d729e6..1a14d7fa1 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -428,8 +428,8 @@ bool QuestDelegate::eventFilter(QObject * object, QEvent * event) return false; if(event->type() == QEvent::Close) { - emit commitData(ed); - emit closeEditor(ed); + commitData(ed); + closeEditor(ed); return true; } } diff --git a/mapeditor/mapsettings/rumorsettings.cpp b/mapeditor/mapsettings/rumorsettings.cpp index 7d47cc6c9..8b135e8b7 100644 --- a/mapeditor/mapsettings/rumorsettings.cpp +++ b/mapeditor/mapsettings/rumorsettings.cpp @@ -60,7 +60,7 @@ void RumorSettings::on_add_clicked() item->setData(Qt::UserRole, QVariant("")); item->setFlags(item->flags() | Qt::ItemIsEditable); ui->rumors->addItem(item); - emit ui->rumors->itemActivated(item); + ui->rumors->itemActivated(item); } void RumorSettings::on_remove_clicked() diff --git a/mapeditor/mapview.cpp b/mapeditor/mapview.cpp index 29aeb9aa4..175aecd18 100644 --- a/mapeditor/mapview.cpp +++ b/mapeditor/mapview.cpp @@ -47,7 +47,7 @@ void MinimapView::mouseMoveEvent(QMouseEvent *mouseEvent) auto pos = mapToScene(mouseEvent->pos()); pos *= 32; - emit cameraPositionChanged(pos); + cameraPositionChanged(pos); } void MinimapView::mousePressEvent(QMouseEvent* event) @@ -90,7 +90,7 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent) if(tile == tilePrev) //do not redraw return; - emit currentCoordinates(tile.x, tile.y); + currentCoordinates(tile.x, tile.y); switch(selectionTool) { @@ -563,7 +563,7 @@ void MapView::mouseReleaseEvent(QMouseEvent *event) auto selection = sc->selectionObjectsView.getSelection(); if(selection.size() == 1) { - emit openObjectProperties(*selection.begin(), tab); + openObjectProperties(*selection.begin(), tab); } break; } @@ -618,7 +618,7 @@ void MapView::dropEvent(QDropEvent * event) { auto * obj = sc->selectionObjectsView.newObject; controller->commitObjectCreate(sc->level); - emit openObjectProperties(obj, false); + openObjectProperties(obj, false); } else { @@ -736,13 +736,13 @@ void MapScene::updateViews() void MapScene::terrainSelected(bool anythingSelected) { isTerrainSelected = anythingSelected; - emit selected(isTerrainSelected || isObjectSelected); + selected(isTerrainSelected || isObjectSelected); } void MapScene::objectSelected(bool anythingSelected) { isObjectSelected = anythingSelected; - emit selected(isTerrainSelected || isObjectSelected); + selected(isTerrainSelected || isObjectSelected); } MinimapScene::MinimapScene(int lvl): diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index c055a05df..207ca66a7 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -182,14 +182,14 @@ void ObjectPickerLayer::select(const CGObjectInstance * obj) if(obj && possibleObjects.count(obj)) { clear(); - emit selectionMade(obj); + selectionMade(obj); } } void ObjectPickerLayer::discard() { clear(); - emit selectionMade(nullptr); + selectionMade(nullptr); } SelectionTerrainLayer::SelectionTerrainLayer(MapSceneBase * s): AbstractLayer(s) @@ -277,7 +277,7 @@ const std::set & SelectionTerrainLayer::selection() const void SelectionTerrainLayer::onSelection() { - emit selectionMade(!area.empty()); + selectionMade(!area.empty()); } @@ -610,7 +610,7 @@ void SelectionObjectsLayer::clear() void SelectionObjectsLayer::onSelection() { - emit selectionMade(!selectedObjects.empty()); + selectionMade(!selectedObjects.empty()); } void SelectionObjectsLayer::setLockObject(const CGObjectInstance * object, bool lock) diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index e98053e40..058f84f20 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -198,7 +198,7 @@ void BattleFlowProcessor::castOpeningSpells(const CBattleInfoCallback & battle) if (!h) continue; - TConstBonusListPtr bl = h->getBonuses(Selector::type()(BonusType::OPENING_BATTLE_SPELL)); + TConstBonusListPtr bl = h->getBonusesOfType(BonusType::OPENING_BATTLE_SPELL); for (auto b : *bl) { @@ -629,7 +629,7 @@ bool BattleFlowProcessor::makeAutomaticAction(const CBattleInfoCallback & battle void BattleFlowProcessor::stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * st) { - auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED))); + auto bl = *(st->getBonusesOfType(BonusType::ENCHANTED)); for(auto b : bl) { if (!b->subtype.as().hasValue()) @@ -678,10 +678,10 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c if (st->alive()) { //unbind - if (st->hasBonus(Selector::type()(BonusType::BIND_EFFECT))) + if (st->hasBonusOfType(BonusType::BIND_EFFECT)) { bool unbind = true; - BonusList bl = *(st->getBonuses(Selector::type()(BonusType::BIND_EFFECT))); + BonusList bl = *(st->getBonusesOfType(BonusType::BIND_EFFECT)); auto adjacent = battle.battleAdjacentUnits(st); for (auto b : bl)