diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 2c3114e25..2220e982e 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -1,17 +1,68 @@ name: VCMI on: - push: - branches: - - features/* - - develop - pull_request: + push: + branches: + - features/* + pull_request: + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: + check_last_build: + if: github.event.schedule != '' + runs-on: ubuntu-latest + outputs: + skip_build: ${{ steps.check_if_built.outputs.skip_build }} + defaults: + run: + shell: bash + steps: + - name: Get repo name + id: get_repo_name + run: echo "::set-output name=value::${GITHUB_REPOSITORY#*/}" + - name: Get last successful build for ${{ github.sha }} + uses: octokit/request-action@v2.1.0 + id: get_last_scheduled_run + with: + route: GET /repos/{owner}/{repo}/actions/runs + owner: ${{ github.repository_owner }} + repo: ${{ steps.get_repo_name.outputs.value }} + status: success + per_page: 1 + head_sha: ${{ github.sha }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check if successful build of the current commit exists + id: check_if_built + run: | + if [ ${{ fromJson(steps.get_last_scheduled_run.outputs.data).total_count }} -gt 0 ]; then + echo '::set-output name=skip_build::1' + else + echo '::set-output name=skip_build::0' + fi + - name: Cancel current run + if: steps.check_if_built.outputs.skip_build == 1 + uses: octokit/request-action@v2.1.0 + with: + route: POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel + owner: ${{ github.repository_owner }} + repo: ${{ steps.get_repo_name.outputs.value }} + run_id: ${{ github.run_id }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Wait for the run to be cancelled + if: steps.check_if_built.outputs.skip_build == 1 + run: sleep 60 + build: + needs: check_last_build + if: always() && needs.check_last_build.skip_build != 1 strategy: matrix: include: diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index 2b3679569..fc93ffc03 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -16,7 +16,9 @@ #include "../../lib/CStack.h" #include "../../lib/ScriptHandler.h" +#if SCRIPTING_ENABLED using scripting::Pool; +#endif void actualizeEffect(TBonusListPtr target, const Bonus & ef) { @@ -217,7 +219,9 @@ HypotheticBattle::HypotheticBattle(const Environment * ENV, Subject realBattle) localEnvironment.reset(new HypotheticEnvironment(this, env)); serverCallback.reset(new HypotheticServerCallback(this)); +#if SCRIPTING_ENABLED pool.reset(new scripting::PoolImpl(localEnvironment.get(), serverCallback.get())); +#endif } bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const @@ -420,10 +424,12 @@ int64_t HypotheticBattle::getTreeVersion() const return getBattleNode()->getTreeVersion() + bonusTreeVersion; } +#if SCRIPTING_ENABLED Pool * HypotheticBattle::getContextPool() const { return pool.get(); } +#endif ServerCallback * HypotheticBattle::getServerCallback() { diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index dc2ac3ce7..eb8d57db5 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -136,7 +136,9 @@ public: int64_t getTreeVersion() const; +#if SCRIPTING_ENABLED scripting::Pool * getContextPool() const override; +#endif ServerCallback * getServerCallback(); @@ -189,6 +191,8 @@ private: std::unique_ptr serverCallback; std::unique_ptr localEnvironment; +#if SCRIPTING_ENABLED mutable std::shared_ptr pool; +#endif mutable std::shared_ptr eventBus; }; diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index fef7fcce6..48e2f3169 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -204,7 +204,6 @@ std::shared_ptr ArmyManager::getArmyAvailableToBuyAsCCreatureSet( TResources availableRes) const { std::vector creaturesInDwellings; - int freeHeroSlots = GameConstants::ARMY_SIZE; auto army = std::make_shared(); for(int i = dwelling->creatures.size() - 1; i >= 0; i--) @@ -497,4 +496,4 @@ ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade( } return result; -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Analyzers/ArmyManager.h b/AI/Nullkiller/Analyzers/ArmyManager.h index 5d422aa9e..fddad9fcb 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.h +++ b/AI/Nullkiller/Analyzers/ArmyManager.h @@ -41,6 +41,7 @@ struct ArmyUpgradeInfo class DLL_EXPORT IArmyManager //: public: IAbstractManager { public: + virtual ~IArmyManager() = default; virtual void update() = 0; virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0; virtual ui64 howManyReinforcementsCanBuy( diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 0b168227c..72314e61d 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -129,8 +129,6 @@ void BuildAnalyzer::update() { logAi->trace("Checking town %s", town->name); - auto townInfo = town->town; - developmentInfos.push_back(TownDevelopmentInfo(town)); TownDevelopmentInfo & developmentInfo = developmentInfos.back(); @@ -399,4 +397,4 @@ std::string BuildingInfo::toString() const + ", creature: " + std::to_string(creatureGrows) + " x " + std::to_string(creatureLevel) + " x " + creatureCost.toString() + ", daily: " + dailyIncome.toString(); -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index ded809623..8192fd1a4 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -63,7 +63,7 @@ void DangerHitMapAnalyzer::updateHitMap() auto & node = hitMap[pos.x][pos.y][pos.z]; if(tileDanger > node.maximumDanger.danger - || tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn) + || (tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn)) { node.maximumDanger.danger = tileDanger; node.maximumDanger.turn = turn; @@ -71,7 +71,7 @@ void DangerHitMapAnalyzer::updateHitMap() } if(turn < node.fastestDanger.turn - || turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger) + || (turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger)) { node.fastestDanger.danger = tileDanger; node.fastestDanger.turn = turn; @@ -101,8 +101,8 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & int turn = path.turn(); const HitMapNode & info = hitMap[tile.x][tile.y][tile.z]; - return info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger) - || info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger); + return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger)) + || (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger)); } const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 5ac745c02..8248a2b0b 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -20,6 +20,7 @@ class DLL_EXPORT IHeroManager //: public: IAbstractManager { public: + virtual ~IHeroManager() = default; virtual const std::map & getHeroRoles() const = 0; virtual int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const = 0; virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0; @@ -31,6 +32,7 @@ public: class DLL_EXPORT ISecondarySkillRule { public: + virtual ~ISecondarySkillRule() = default; virtual void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const = 0; }; @@ -52,11 +54,10 @@ private: static SecondarySkillEvaluator scountSkillsScores; CCallback * cb; //this is enough, but we downcast from CCallback - const Nullkiller * ai; std::map heroRoles; public: - HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} + HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; @@ -102,4 +103,4 @@ private: public: void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; -}; \ No newline at end of file +}; diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index bc60f43ba..dacacf393 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -149,7 +149,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const const int3 pos = obj->visitablePos(); - if(obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->memory->alreadyVisited, obj) + if((obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->memory->alreadyVisited, obj)) || obj->wasVisited(ai->playerID)) { return false; diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index 22386683e..5fc50c213 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -53,8 +53,6 @@ Goals::TGoalVec BuildingBehavior::decompose() const for(auto & developmentInfo : developmentInfos) { - auto town = developmentInfo.town; - for(auto & buildingInfo : developmentInfo.toBuild) { if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[Res::GOLD] > 0) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 88263ecb1..da108da88 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -106,10 +106,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { if(path.getHeroStrength() > treat.danger) { - if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger) - || path.exchangeCount == 1 && path.turn() < treat.turn + if((path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)) + || (path.exchangeCount == 1 && path.turn() < treat.turn) || path.turn() < treat.turn - 1 - || path.turn() < treat.turn && treat.turn >= 2) + || (path.turn() < treat.turn && treat.turn >= 2)) { logAi->debug( "Hero %s can eliminate danger for town %s using path %s.", @@ -217,7 +217,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta // dismiss creatures we are not able to pick to be able to hide in garrison if(town->garrisonHero || town->getUpperArmy()->stacksCount() == 0 - || town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL) + || (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL)) { tasks.push_back( Goals::sptr(Composition() @@ -228,7 +228,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta continue; } - if(treat.turn == 0 || path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger) + if(treat.turn == 0 || (path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger)) { if(ai->nullkiller->arePathHeroesLocked(path)) { @@ -294,4 +294,4 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta } logAi->debug("Found %d tasks", tasks.size()); -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index 4a1788c67..f5f84187f 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -55,7 +55,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town) if(shortestPath.nodes.size() > 1 || shortestPath.turn() != 0 || shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4 - || town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get()) + || (town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get())) return nullptr; return shortestPath.targetHero; @@ -76,13 +76,13 @@ bool needToRecruitHero(const CGTownInstance * startupTown) for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects()) { - if(obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD + if((obj->ID == Obj::RESOURCE && obj->subID == Res::GOLD) || obj->ID == Obj::TREASURE_CHEST || obj->ID == Obj::CAMPFIRE || obj->ID == Obj::WATER_WHEEL) { auto path = paths->getPathInfo(obj->visitablePos()); - if((path->accessible == CGPathNode::BLOCKVIS || path->accessible == CGPathNode::VISIT) + if((path->accessible == CGPathNode::BLOCKVIS || path->accessible == CGPathNode::VISITABLE) && path->reachable()) { treasureSourcesCount++; @@ -162,7 +162,7 @@ Goals::TGoalVec StartupBehavior::decompose() const auto garrisonHeroScore = ai->nullkiller->heroManager->evaluateHero(garrisonHero); if(visitingHeroScore > garrisonHeroScore - || ai->nullkiller->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->nullkiller->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN) + || (ai->nullkiller->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->nullkiller->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN)) { if(canRecruitHero || ai->nullkiller->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200) { diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 4997fde20..58f97b837 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -122,7 +122,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero { //No free slot, we might discard our weakest stack weakestStackPower = std::numeric_limits().max(); - for (const auto stack : slots) + for (const auto & stack : slots) { vstd::amin(weakestStackPower, stack.second->getPower()); } @@ -645,7 +645,6 @@ public: } auto heroPtr = task->hero; - auto day = ai->cb->getDate(Date::DAY); auto hero = heroPtr.get(ai->cb.get()); bool checkGold = evaluationContext.danger == 0; auto army = path.heroArmy; @@ -670,11 +669,8 @@ public: class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder { -private: - const Nullkiller * ai; - public: - ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} + ClusterEvaluationContextBuilder(const Nullkiller * ai) {} virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { @@ -699,7 +695,6 @@ public: for(auto objInfo : objects) { auto target = objInfo.first; - auto day = ai->cb->getDate(Date::DAY); bool checkGold = objInfo.second.danger == 0; auto army = hero; @@ -718,9 +713,6 @@ public: if(boost > 8) break; } - - const AIPath & pathToCenter = clusterGoal.getPathToCenter(); - } }; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 5ce11de5b..6bcd09c32 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -61,6 +61,7 @@ struct DLL_EXPORT EvaluationContext class IEvaluationContextBuilder { public: + virtual ~IEvaluationContextBuilder() = default; virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal goal) const = 0; }; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 05865e0b4..608e94d4f 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -558,7 +558,7 @@ bool AINodeStorage::selectNextActor() for(auto actor = actors.begin(); actor != actors.end(); actor++) { if(actor->get()->armyValue > currentActor->get()->armyValue - || actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor) + || (actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor)) { continue; } diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 0db5ff781..4b2812b21 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -24,9 +24,6 @@ namespace AIPathfinding class SummonBoatAction : public VirtualBoatAction { - private: - const CGHeroInstance * hero; - public: virtual void execute(const CGHeroInstance * hero) const override; @@ -71,4 +68,4 @@ namespace AIPathfinding virtual const CGObjectInstance * targetObject() const override; }; -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h index 6d7cbbbac..51977963b 100644 --- a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h @@ -18,6 +18,8 @@ struct AIPathNode; class SpecialAction { public: + virtual ~SpecialAction() = default; + virtual bool canAct(const AIPathNode * source) const { return true; @@ -39,4 +41,4 @@ public: virtual std::string toString() const = 0; virtual const CGObjectInstance * targetObject() const { return nullptr; } -}; \ No newline at end of file +}; diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 68d10fecc..e6806a0b2 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -269,8 +269,6 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other) return result; // already inserted } - auto position = inserted.first; - auto differentMasks = (actor->chainMask & other->chainMask) == 0; if(!differentMasks) return result; @@ -461,15 +459,6 @@ CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, continue; auto creature = creatureInfo.second.back().toCreature(); - auto count = creatureInfo.first; - - if(waitForGrowth) - { - const CGTownInstance * town = dynamic_cast(dwelling); - - count += town ? town->creatureGrowth(creature->level) : creature->growth; - } - dwellingCreatures->addToSlot( dwellingCreatures->getSlotFor(creature), creature->idNumber, @@ -487,4 +476,4 @@ TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chain std::string TownGarrisonActor::toString() const { return town->name; -} \ No newline at end of file +} diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index ff2618df2..2f18df369 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -75,7 +75,8 @@ public: TResources armyCost; std::shared_ptr tiCache; - ChainActor(){} + ChainActor() = default; + virtual ~ChainActor() = default; virtual std::string toString() const; ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); } @@ -168,4 +169,4 @@ private: public: TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask); virtual std::string toString() const override; -}; \ No newline at end of file +}; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 8ffcf1be7..68aab0c35 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -126,7 +126,6 @@ namespace AIPathfinding const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node); auto questObj = dynamic_cast(destination.nodeObject); auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); - auto nodeHero = pathfinderHelper->hero; QuestAction questAction(questInfo); if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE) @@ -157,8 +156,6 @@ namespace AIPathfinding nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) { - auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); - node->specialAction.reset(new QuestAction(questAction)); }); } diff --git a/AI/VCAI/AIhelper.cpp b/AI/VCAI/AIhelper.cpp index 8e46d50f5..75fca29b2 100644 --- a/AI/VCAI/AIhelper.cpp +++ b/AI/VCAI/AIhelper.cpp @@ -19,10 +19,6 @@ AIhelper::AIhelper() armyManager.reset(new ArmyManager()); } -AIhelper::~AIhelper() -{ -} - bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal) { return resourceManager->notifyGoalCompleted(goal); @@ -182,4 +178,4 @@ std::vector::iterator AIhelper::getWeakestCreature(std::vector AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const { return armyManager->getSortedSlots(target, source); -} \ No newline at end of file +} diff --git a/AI/VCAI/AIhelper.h b/AI/VCAI/AIhelper.h index 29d2bc94d..1a33025f0 100644 --- a/AI/VCAI/AIhelper.h +++ b/AI/VCAI/AIhelper.h @@ -36,7 +36,6 @@ class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, pu //TODO: vector public: AIhelper(); - ~AIhelper(); bool canAfford(const TResources & cost) const; TResources reservedResources() const override; diff --git a/AI/VCAI/ArmyManager.h b/AI/VCAI/ArmyManager.h index 7579207da..5e29b41b1 100644 --- a/AI/VCAI/ArmyManager.h +++ b/AI/VCAI/ArmyManager.h @@ -28,6 +28,7 @@ struct SlotInfo class DLL_EXPORT IArmyManager //: public: IAbstractManager { public: + virtual ~IArmyManager() = default; virtual void init(CPlayerSpecificInfoCallback * CB) = 0; virtual void setAI(VCAI * AI) = 0; virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0; diff --git a/AI/VCAI/ResourceManager.cpp b/AI/VCAI/ResourceManager.cpp index 027b9e9c2..d676c4488 100644 --- a/AI/VCAI/ResourceManager.cpp +++ b/AI/VCAI/ResourceManager.cpp @@ -120,14 +120,12 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o return o.goal; } - float goalPriority = 10; //arbitrary, will be divided - for (const resPair & p : missingResources) + for (const resPair p : missingResources) { if (!income[p.first]) //prioritize resources with 0 income { resourceType = p.first; amountToCollect = p.second; - goalPriority /= amountToCollect; //need more resources -> lower priority break; } } @@ -138,7 +136,7 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o std::map daysToEarn; for (auto it : missingResources) daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first]; - auto incomeComparer = [&income](const timePair & lhs, const timePair & rhs) -> bool + auto incomeComparer = [](const timePair & lhs, const timePair & rhs) -> bool { //theoretically income can be negative, but that falls into this comparison return lhs.second < rhs.second; @@ -146,12 +144,9 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o resourceType = boost::max_element(daysToEarn, incomeComparer)->first; amountToCollect = missingResources[resourceType]; - goalPriority /= daysToEarn[resourceType]; //more days - lower priority } - if (resourceType == Res::GOLD) - goalPriority *= 1000; - //this is abstract goal and might take soem time to complete + //this is abstract goal and might take some time to complete return Goals::sptr(Goals::CollectRes(resourceType, amountToCollect).setisAbstract(true)); } diff --git a/CCallback.cpp b/CCallback.cpp index cc1714849..a4c2de397 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -366,10 +366,12 @@ void CCallback::unregisterBattleInterface(std::shared_ptr cl->additionalBattleInts[*player] -= battleEvents; } +#if SCRIPTING_ENABLED scripting::Pool * CBattleCallback::getContextPool() const { return cl->getGlobalContextPool(); } +#endif CBattleCallback::CBattleCallback(boost::optional Player, CClient *C ) { diff --git a/CCallback.h b/CCallback.h index 76f4413d0..36d93898a 100644 --- a/CCallback.h +++ b/CCallback.h @@ -35,6 +35,8 @@ struct ArtifactLocation; class IBattleCallback { public: + virtual ~IBattleCallback() = default; + bool waitTillRealize; //if true, request functions will return after they are realized by server bool unlockGsWhenWaiting;//if true after sending each request, gs mutex will be unlocked so the changes can be applied; NOTICE caller must have gs mx locked prior to any call to actiob callback! //battle @@ -99,7 +101,9 @@ public: int battleMakeAction(const BattleAction * action) override;//for casting spells by hero - DO NOT use it for moving active stack bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions +#if SCRIPTING_ENABLED scripting::Pool * getContextPool() const override; +#endif friend class CCallback; friend class CClient; diff --git a/CMakeLists.txt b/CMakeLists.txt index bb6cf8e6a..9d7e99df3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ set(VCMI_VERSION_MAJOR 1) set(VCMI_VERSION_MINOR 0) set(VCMI_VERSION_PATCH 0) -option(ENABLE_ERM "Enable compilation of ERM scripting module" ON) -option(ENABLE_LUA "Enable compilation of LUA scripting module" ON) +option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) +option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) option(ENABLE_TEST "Enable compilation of unit tests" ON) if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0") @@ -59,6 +59,11 @@ option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linu set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name") set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename") +# ERM depends on LUA implicitly +if(ENABLE_ERM AND NOT ENABLE_LUA) + set(ENABLE_LUA ON) +endif() + ############################################ # Miscellaneous options # ############################################ @@ -192,6 +197,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32) #so far all *nix compilers support suc set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing -Wno-switch -Wno-sign-compare -Wno-unused-local-typedefs") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-overloaded-virtual -Wno-type-limits -Wno-unknown-pragmas") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs") # fuzzylite - Operation.h if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-tags -Wno-unknown-warning-option -Wno-missing-braces") @@ -214,6 +220,10 @@ if(NOT WIN32) endif() endif() +if(ENABLE_LUA) + add_compile_definitions(SCRIPTING_ENABLED=1) +endif() + ############################################ # Finding packages # ############################################ diff --git a/CMakePresets.json b/CMakePresets.json index e1735cbfd..20a5a23ed 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -79,11 +79,7 @@ "name": "macos-arm-conan-ninja-release", "displayName": "Ninja+Conan arm64 release", "description": "VCMI MacOS-arm64 Ninja using Conan", - "inherits": "macos-conan-ninja-release", - "cacheVariables": { - "ENABLE_ERM": "OFF", - "ENABLE_LUA": "OFF" - } + "inherits": "macos-conan-ninja-release" }, { "name": "macos-xcode-release", diff --git a/ChangeLog b/ChangeLog index 57c7f4e37..d33b1499d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,22 @@ -0.99 -> 1.0 +1.0.0 -> 1.1.0 + +GENERAL: +* Mods and their versions and serialized into save files. Game checks mod compatibility before loading +* Logs are stored in system default logs directory +* LUA/ERM libs are not compiled by default +* FFMpeg dependency is optional now + +MODS: +* Supported rewardable objects customization +* Battleground obstacles are extendable now with VLC mechanism +* Introduced "compatibility" section into mods settings + +LAUNCHER: +* Fixed problem with duplicated mods in the list +* Launcher shows compatible mods only +* Uninstall button was moved to the left of layout + +0.99 -> 1.0.0 GENERAL: * Spectator mode was implemented through command-line options diff --git a/client/CGameInfo.cpp b/client/CGameInfo.cpp index 95687c828..0c56d91a7 100644 --- a/client/CGameInfo.cpp +++ b/client/CGameInfo.cpp @@ -72,10 +72,12 @@ const HeroTypeService * CGameInfo::heroTypes() const return globalServices->heroTypes(); } +#if SCRIPTING_ENABLED const scripting::Service * CGameInfo::scripts() const { return globalServices->scripts(); } +#endif const spells::Service * CGameInfo::spells() const { diff --git a/client/CGameInfo.h b/client/CGameInfo.h index 6e2570f2d..809a5c456 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -60,7 +60,9 @@ public: const FactionService * factions() const override; const HeroClassService * heroClasses() const override; const HeroTypeService * heroTypes() const override; +#if SCRIPTING_ENABLED const scripting::Service * scripts() const override; +#endif const spells::Service * spells() const override; const SkillService * skills() const override; const BattleFieldService * battlefields() const override; diff --git a/client/CMT.cpp b/client/CMT.cpp index ebc5274f4..5754814b8 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -686,6 +686,7 @@ void processCommand(const std::string &message) std::cout << "\rExtracting done :)\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n"; } +#if SCRIPTING_ENABLED else if(message=="get scripts") { std::cout << "Command accepted.\t"; @@ -708,6 +709,7 @@ void processCommand(const std::string &message) std::cout << "\rExtracting done :)\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n"; } +#endif else if(message=="get txt") { std::cout << "Command accepted.\t"; diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 40bb64ce1..46495145c 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -17,6 +17,7 @@ #include "lobby/CSelectionBase.h" #include "lobby/CLobbyScreen.h" +#include "windows/InfoWindows.h" #include "mainmenu/CMainMenu.h" @@ -161,6 +162,20 @@ void CServerHandler::startLocalServerAndConnect() threadRunLocalServer->join(); th->update(); + + auto errorMsg = CGI->generaltexth->localizedTexts["server"]["errors"]["existingProcess"].String(); + try + { + CConnection testConnection(settings["server"]["server"].String(), getDefaultPort(), NAME, uuid); + logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); + CInfoWindow::showInfoDialog(errorMsg, {}); + return; + } + catch(...) + { + //no connection means that port is not busy and we can start local server + } + #ifdef VCMI_ANDROID { CAndroidVMHelper envHelper; diff --git a/client/Client.cpp b/client/Client.cpp index a200c2044..e145c1b12 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -263,12 +263,14 @@ void CClient::serialize(BinarySerializer & h, const int version) i->second->saveGame(h, version); } +#if SCRIPTING_ENABLED if(version >= 800) { JsonNode scriptsState; clientScripts->serializeState(h.saving, scriptsState); h & scriptsState; } +#endif } void CClient::serialize(BinaryDeserializer & h, const int version) @@ -329,11 +331,13 @@ void CClient::serialize(BinaryDeserializer & h, const int version) LOCPLINT = prevInt; } +#if SCRIPTING_ENABLED { JsonNode scriptsState; h & scriptsState; clientScripts->serializeState(h.saving, scriptsState); } +#endif logNetwork->trace("Loaded client part of save %d ms", CSH->th->getDiff()); } @@ -352,7 +356,9 @@ void CClient::save(const std::string & fname) void CClient::endGame() { +#if SCRIPTING_ENABLED clientScripts.reset(); +#endif //suggest interfaces to finish their stuff (AI should interrupt any bg working threads) for(auto & i : playerint) @@ -732,6 +738,7 @@ PlayerColor CClient::getLocalPlayer() const return getCurrentPlayer(); } +#if SCRIPTING_ENABLED scripting::Pool * CClient::getGlobalContextPool() const { return clientScripts.get(); @@ -741,11 +748,14 @@ scripting::Pool * CClient::getContextPool() const { return clientScripts.get(); } +#endif void CClient::reinitScripting() { clientEventBus = make_unique(); +#if SCRIPTING_ENABLED clientScripts.reset(new scripting::PoolImpl(this)); +#endif } diff --git a/client/Client.h b/client/Client.h index 7f93aa3fe..38a2b4551 100644 --- a/client/Client.h +++ b/client/Client.h @@ -39,10 +39,12 @@ namespace boost { class thread; } template class CApplier; class CBaseForCLApply; +#if SCRIPTING_ENABLED namespace scripting { class PoolImpl; } +#endif namespace events { @@ -233,13 +235,18 @@ public: void showInfoDialog(InfoWindow * iw) override {}; void showInfoDialog(const std::string & msg, PlayerColor player) override {}; +#if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; scripting::Pool * getContextPool() const override; +#endif + private: std::map> battleCallbacks; //callbacks given to player interfaces std::map> playerEnvironments; +#if SCRIPTING_ENABLED std::shared_ptr clientScripts; +#endif std::unique_ptr clientEventBus; std::shared_ptr> applier; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 7cdf0cc0a..04e42914f 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1083,11 +1083,10 @@ void CBattleInterface::stacksAreAttacked(std::vector attacked std::array killedBySide = {0, 0}; - int targets = 0, damage = 0; + int targets = 0; for(const StackAttackedInfo & attackedInfo : attackedInfos) { ++targets; - damage += (int)attackedInfo.dmg; ui8 side = attackedInfo.defender->side; killedBySide.at(side) += attackedInfo.amountKilled; diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 3828056fe..be4c357a2 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -10,10 +10,11 @@ #include "StdInc.h" #include "CAnimation.h" +#include "SDL_Extensions.h" +#include "SDL_Pixels.h" + #include "../CBitmapHandler.h" #include "../Graphics.h" -#include "../gui/SDL_Extensions.h" -#include "../gui/SDL_Pixels.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/ISimpleResourceLoader.h" diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 67517c192..6eac35e19 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -140,6 +140,7 @@ public: //double click virtual void onDoubleClick(){} + // These are the arguments that can be used to determine what kind of input the CIntObject will receive enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff}; const ui16 & active; void addUsedEvents(ui16 newActions); diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index b88054bfa..bc5401a37 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -155,6 +155,7 @@ typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Su class ColorShifter { public: + virtual ~ColorShifter() = default; virtual SDL_Color shiftColor(SDL_Color clr) const = 0; }; diff --git a/client/mainmenu/CCampaignScreen.cpp b/client/mainmenu/CCampaignScreen.cpp index ea79cd775..2fb189ced 100644 --- a/client/mainmenu/CCampaignScreen.cpp +++ b/client/mainmenu/CCampaignScreen.cpp @@ -9,9 +9,10 @@ */ #include "StdInc.h" -#include "../mainmenu/CMainMenu.h" #include "CCampaignScreen.h" +#include "CMainMenu.h" + #include "../CGameInfo.h" #include "../CMessage.h" #include "../CBitmapHandler.h" diff --git a/client/mainmenu/CCampaignScreen.h b/client/mainmenu/CCampaignScreen.h index d990f51bd..b0a1acb85 100644 --- a/client/mainmenu/CCampaignScreen.h +++ b/client/mainmenu/CCampaignScreen.h @@ -9,6 +9,8 @@ */ #pragma once +#include "../windows/CWindowObject.h" + class CLabel; class CPicture; class CButton; diff --git a/client/mainmenu/CreditsScreen.cpp b/client/mainmenu/CreditsScreen.cpp index beda44054..9e754668b 100644 --- a/client/mainmenu/CreditsScreen.cpp +++ b/client/mainmenu/CreditsScreen.cpp @@ -9,9 +9,10 @@ */ #include "StdInc.h" - #include "CreditsScreen.h" -#include "../mainmenu/CMainMenu.h" + +#include "CMainMenu.h" + #include "../gui/CGuiHandler.h" #include "../widgets/TextControls.h" #include "../widgets/ObjectLists.h" diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 92fa64b8c..6553bc1d4 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -14,6 +14,7 @@ #include "MiscWidgets.h" #include "CComponent.h" +#include "Images.h" #include "../CGameInfo.h" #include "../CMusicHandler.h" @@ -26,8 +27,6 @@ #include "../gui/SDL_Pixels.h" #include "../gui/SDL_Compat.h" -#include "../widgets/Images.h" - #include "../windows/InfoWindows.h" #include "../windows/CAdvmapInterface.h" #include "../windows/GUIClasses.h" diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index e5850d6ca..48b7af3b4 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -10,6 +10,9 @@ #include "StdInc.h" #include "CComponent.h" +#include "CArtifactHolder.h" +#include "Images.h" + #include #include @@ -18,8 +21,6 @@ #include "../CMessage.h" #include "../CGameInfo.h" -#include "../widgets/Images.h" -#include "../widgets/CArtifactHolder.h" #include "../windows/CAdvmapInterface.h" #include "../../lib/CArtHandler.h" diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index 2078429b8..0012f7b17 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -10,12 +10,13 @@ #include "StdInc.h" #include "CGarrisonInt.h" +#include "Buttons.h" +#include "TextControls.h" + #include "../gui/CGuiHandler.h" #include "../CGameInfo.h" #include "../CPlayerInterface.h" -#include "../widgets/Buttons.h" -#include "../widgets/TextControls.h" #include "../windows/CCreatureWindow.h" #include "../windows/GUIClasses.h" diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index bbeba9cb2..dccb49412 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -14,8 +14,9 @@ #include "CHeroWindow.h" #include "CKingdomInterface.h" #include "CSpellWindow.h" -#include "GUIClasses.h" #include "CTradeWindow.h" +#include "GUIClasses.h" +#include "InfoWindows.h" #include "../CBitmapHandler.h" #include "../CGameInfo.h" @@ -35,7 +36,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/SDL_Extensions.h" #include "../widgets/MiscWidgets.h" -#include "../windows/InfoWindows.h" #include "../../CCallback.h" @@ -1217,7 +1217,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) if(itr != LOCPLINT->towns.end()) LOCPLINT->showThievesGuildWindow(*itr); else - LOCPLINT->showInfoDialog("No available town with tavern!"); + LOCPLINT->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["noTownWithTavern"].String()); } return; case SDLK_i: @@ -1249,7 +1249,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) case SDLK_r: if(isActive() && LOCPLINT->ctrlPressed()) { - LOCPLINT->showYesNoDialog("Are you sure you want to restart game?", + LOCPLINT->showYesNoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["confirmRestartGame"].String(), [](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); }, nullptr); } return; @@ -1308,7 +1308,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) if(townWithMarket) //if any town has marketplace, open window GH.pushIntT(townWithMarket); else //if not - complain - LOCPLINT->showInfoDialog("No available marketplace!"); + LOCPLINT->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["noTownWithMarket"].String()); } else if(isActive()) //no ctrl, advmapint is on the top => switch to town { diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index c37c8e2af..9cfd8f991 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -13,6 +13,7 @@ #include "CAdvmapInterface.h" #include "CHeroWindow.h" #include "CTradeWindow.h" +#include "InfoWindows.h" #include "GUIClasses.h" #include "QuickRecruitmentWindow.h" @@ -24,7 +25,6 @@ #include "../Graphics.h" #include "../gui/CGuiHandler.h" #include "../gui/SDL_Extensions.h" -#include "../windows/InfoWindows.h" #include "../widgets/MiscWidgets.h" #include "../widgets/CComponent.h" @@ -842,7 +842,16 @@ void CCastleBuildings::enterDwelling(int level) void CCastleBuildings::enterToTheQuickRecruitmentWindow() { - GH.pushIntT(town, pos); + const auto beginIt = town->creatures.cbegin(); + const auto afterLastIt = town->creatures.size() > GameConstants::CREATURES_PER_TOWN + ? std::next(beginIt, GameConstants::CREATURES_PER_TOWN) + : town->creatures.cend(); + const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt, + [](const auto & creatureInfo) { return creatureInfo.first > 0; }); + if(hasSomeoneToRecruit) + GH.pushIntT(town, pos); + else + CInfoWindow::showInfoDialog(CGI->generaltexth->localizedTexts["townHall"]["noCreaturesToRecruit"].String(), {}); } void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades) @@ -1235,9 +1244,9 @@ void CCastleInterface::recreateIcons() hall = std::make_shared(80, 413, town, true); fort = std::make_shared(122, 413, town, false); - fastArmyPurhase = std::make_shared(Point(122, 413), "itmcl.def", CButton::tooltip(), [&](){builds->enterToTheQuickRecruitmentWindow();}); - fastArmyPurhase->setImageOrder(town->fortLevel()-1, town->fortLevel()-1, town->fortLevel()-1, town->fortLevel()-1); - fastArmyPurhase->setAnimateLonelyFrame(true); + fastArmyPurchase = std::make_shared(Point(122, 413), "itmcl.def", CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); }); + fastArmyPurchase->setImageOrder(town->fortLevel() - 1, town->fortLevel() - 1, town->fortLevel() - 1, town->fortLevel() - 1); + fastArmyPurchase->setAnimateLonelyFrame(true); creainfo.clear(); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 07dfe2d86..088bb63bf 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -209,7 +209,7 @@ class CCastleInterface : public CStatusbarWindow, public CGarrisonHolder std::shared_ptr exit; std::shared_ptr split; - std::shared_ptr fastArmyPurhase; + std::shared_ptr fastArmyPurchase; std::vector> creainfo;//small icons of creatures (bottom-left corner); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 0957aab22..163cb3966 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -12,6 +12,7 @@ #include "CAdvmapInterface.h" #include "CCastleInterface.h" +#include "InfoWindows.h" #include "../CGameInfo.h" #include "../CMT.h" @@ -19,7 +20,6 @@ #include "../gui/CGuiHandler.h" #include "../widgets/CComponent.h" #include "../widgets/MiscWidgets.h" -#include "../windows/InfoWindows.h" #include "../../CCallback.h" diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 32a68dd07..ad48699e2 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -562,7 +562,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) if(!texts.empty()) owner->myInt->showInfoDialog(texts.front()); else - owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available."); + owner->myInt->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["spellUnknownProblem"].String()); } } else //adventure spell diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index d3e78564b..ee6f0f0b4 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "CWindowObject.h" +#include "CAdvmapInterface.h" + #include "../widgets/MiscWidgets.h" #include "../gui/SDL_Pixels.h" @@ -26,7 +28,6 @@ #include "../CPlayerInterface.h" #include "../CMessage.h" #include "../CMusicHandler.h" -#include "../windows/CAdvmapInterface.h" #include "../../CCallback.h" @@ -251,4 +252,4 @@ void CStatusbarWindow::activate() { CIntObject::activate(); GH.statusbar = statusbar; -} \ No newline at end of file +} diff --git a/client/windows/CreaturePurchaseCard.cpp b/client/windows/CreaturePurchaseCard.cpp index 98083799a..7944709fe 100644 --- a/client/windows/CreaturePurchaseCard.cpp +++ b/client/windows/CreaturePurchaseCard.cpp @@ -17,6 +17,7 @@ #include "QuickRecruitmentWindow.h" #include "../gui/CGuiHandler.h" #include "../../lib/CCreatureHandler.h" +#include "CCreatureWindow.h" void CreaturePurchaseCard::initButtons() { @@ -46,6 +47,7 @@ void CreaturePurchaseCard::switchCreatureLevel() auto index = vstd::find_pos(upgradesID, creatureOnTheCard->idNumber); auto nextCreatureId = vstd::circularAt(upgradesID, ++index); creatureOnTheCard = nextCreatureId.toCreature(); + creatureClickArea = std::make_shared(Point(pos.x + CCreatureClickArea::CREATURE_X_POS, pos.y + CCreatureClickArea::CREATURE_Y_POS), picture, creatureOnTheCard); picture = std::make_shared(parent->pos.x, parent->pos.y, creatureOnTheCard); parent->updateAllSliders(); cost->set(creatureOnTheCard->cost * slider->getValue()); @@ -54,14 +56,14 @@ void CreaturePurchaseCard::switchCreatureLevel() void CreaturePurchaseCard::initAmountInfo() { availableAmount = std::make_shared(pos.x + 25, pos.y + 146, FONT_SMALL, CENTER, Colors::YELLOW); - purhaseAmount = std::make_shared(pos.x + 76, pos.y + 146, FONT_SMALL, CENTER, Colors::WHITE); + purchaseAmount = std::make_shared(pos.x + 76, pos.y + 146, FONT_SMALL, CENTER, Colors::WHITE); updateAmountInfo(0); } void CreaturePurchaseCard::updateAmountInfo(int value) { availableAmount->setText(boost::lexical_cast(maxAmount-value)); - purhaseAmount->setText(boost::lexical_cast(value)); + purchaseAmount->setText(boost::lexical_cast(value)); } void CreaturePurchaseCard::initSlider() @@ -96,8 +98,27 @@ void CreaturePurchaseCard::initView() { picture = std::make_shared(pos.x, pos.y, creatureOnTheCard); background = std::make_shared("QuickRecruitmentWindow/CreaturePurchaseCard.png", pos.x-4, pos.y-50); + initButtons(); + + creatureClickArea = std::make_shared(Point(pos.x + CCreatureClickArea::CREATURE_X_POS, pos.y + CCreatureClickArea::CREATURE_Y_POS), picture, creatureOnTheCard); + initAmountInfo(); initSlider(); - initButtons(); initCostBox(); } + +CreaturePurchaseCard::CCreatureClickArea::CCreatureClickArea(const Point & position, const std::shared_ptr creaturePic, const CCreature * creatureOnTheCard) + : CIntObject(RCLICK), + creatureOnTheCard(creatureOnTheCard) +{ + pos.x = position.x; + pos.y = position.y; + pos.w = CREATURE_WIDTH; + pos.h = CREATURE_HEIGHT; +} + +void CreaturePurchaseCard::CCreatureClickArea::clickRight(tribool down, bool previousState) +{ + if (down) + GH.pushIntT(creatureOnTheCard, true); +} diff --git a/client/windows/CreaturePurchaseCard.h b/client/windows/CreaturePurchaseCard.h index 91ce2b4bb..00e08a663 100644 --- a/client/windows/CreaturePurchaseCard.h +++ b/client/windows/CreaturePurchaseCard.h @@ -25,6 +25,7 @@ public: QuickRecruitmentWindow * parent; int maxAmount; void sliderMoved(int to); + CreaturePurchaseCard(const std::vector & creaturesID, Point position, int creaturesMaxAmount, QuickRecruitmentWindow * parents); private: void initView(); @@ -42,10 +43,28 @@ private: void initCostBox(); + // This just wraps a clickeable area. There's a weird layout scheme in the file and + // it's easier to just add a separate invisble box on top + class CCreatureClickArea : public CIntObject + { + public: + CCreatureClickArea(const Point & pos, const std::shared_ptr creaturePic, const CCreature * creatureOnTheCard); + void clickRight(tribool down, bool previousState) override; + const CCreature * creatureOnTheCard; + + // These are obtained by guessing and checking. I'm not sure how the other numbers + // used to set positions were obtained; commit messages don't document it + static constexpr int CREATURE_WIDTH = 110; + static constexpr int CREATURE_HEIGHT = 132; + static constexpr int CREATURE_X_POS = 15; + static constexpr int CREATURE_Y_POS = 44; + }; + std::shared_ptr maxButton, minButton, creatureSwitcher; - std::shared_ptr availableAmount, purhaseAmount; + std::shared_ptr availableAmount, purchaseAmount; std::shared_ptr picture; std::shared_ptr cost; std::vector upgradesID; std::shared_ptr background; + std::shared_ptr creatureClickArea; }; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 867a7abf0..2afd8c033 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -15,6 +15,7 @@ #include "CCreatureWindow.h" #include "CHeroWindow.h" #include "CreatureCostBox.h" +#include "InfoWindows.h" #include "../CBitmapHandler.h" #include "../CGameInfo.h" @@ -36,7 +37,6 @@ #include "../widgets/CComponent.h" #include "../widgets/MiscWidgets.h" -#include "../windows/InfoWindows.h" #include "../lobby/CSavingScreen.h" @@ -1286,7 +1286,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, int skill = hero->secSkills[g].first, level = hero->secSkills[g].second; // <1, 3> secSkillAreas[b].push_back(std::make_shared()); - secSkillAreas[b][g]->pos = genRect(32, 32, pos.x + 32 + g*36 + b*454 , pos.y + qeLayout ? 83 : 88); + secSkillAreas[b][g]->pos = genRect(32, 32, pos.x + 32 + g*36 + b*454 , pos.y + (qeLayout ? 83 : 88)); secSkillAreas[b][g]->baseType = 1; secSkillAreas[b][g]->type = skill; @@ -1301,12 +1301,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, heroAreas[b] = std::make_shared(257 + 228*b, 13, hero); specialtyAreas[b] = std::make_shared(); - specialtyAreas[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + qeLayout ? 41 : 45); + specialtyAreas[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + (qeLayout ? 41 : 45)); specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27]; specialtyAreas[b]->text = hero->type->specDescr; experienceAreas[b] = std::make_shared(); - experienceAreas[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + qeLayout ? 41 : 45); + experienceAreas[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + (qeLayout ? 41 : 45)); experienceAreas[b]->hoverText = CGI->generaltexth->heroscrn[9]; experienceAreas[b]->text = CGI->generaltexth->allTexts[2]; boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast(hero->level)); @@ -1314,7 +1314,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast(hero->exp)); spellPointsAreas[b] = std::make_shared(); - spellPointsAreas[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + qeLayout ? 41 : 45); + spellPointsAreas[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + (qeLayout ? 41 : 45)); spellPointsAreas[b]->hoverText = CGI->generaltexth->heroscrn[22]; spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205]; boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->name); diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index a87ff932f..dc6bc9980 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -9,13 +9,13 @@ */ #pragma once +#include "CWindowObject.h" #include "../lib/GameConstants.h" #include "../lib/ResourceSet.h" #include "../lib/CConfigHandler.h" #include "../widgets/CArtifactHolder.h" #include "../widgets/CGarrisonInt.h" #include "../widgets/Images.h" -#include "../windows/CWindowObject.h" class CGDwelling; class CreatureCostBox; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 3a68d72bf..7f3ee26d3 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "InfoWindows.h" +#include "CAdvmapInterface.h" + #include "../CBitmapHandler.h" #include "../Graphics.h" #include "../CGameInfo.h" @@ -17,7 +19,6 @@ #include "../CMessage.h" #include "../CMusicHandler.h" -#include "../windows/CAdvmapInterface.h" #include "../widgets/CComponent.h" #include "../widgets/MiscWidgets.h" diff --git a/client/windows/QuickRecruitmentWindow.cpp b/client/windows/QuickRecruitmentWindow.cpp index a93e63a6d..e6ffb7492 100644 --- a/client/windows/QuickRecruitmentWindow.cpp +++ b/client/windows/QuickRecruitmentWindow.cpp @@ -35,7 +35,7 @@ void QuickRecruitmentWindow::setCancelButton() void QuickRecruitmentWindow::setBuyButton() { - buyButton = std::make_shared(Point((pos.w/2)-32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purhaseUnits(); }, SDLK_RETURN); + buyButton = std::make_shared(Point((pos.w / 2) - 32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purchaseUnits(); }, SDLK_RETURN); cancelButton->assignedKeys.insert(SDLK_ESCAPE); buyButton->setImageOrder(0, 1, 2, 3); } @@ -46,7 +46,7 @@ void QuickRecruitmentWindow::setMaxButton() maxButton->setImageOrder(0, 1, 2, 3); } -void QuickRecruitmentWindow::setCreaturePurhaseCards() +void QuickRecruitmentWindow::setCreaturePurchaseCards() { int availableAmount = getAvailableCreatures(); Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64); @@ -99,7 +99,7 @@ void QuickRecruitmentWindow::maxAllCards(std::vector> cards); void maxAllSlidersAmount(std::vector> cards); - void purhaseUnits(); + void purchaseUnits(); const CGTownInstance * town; std::shared_ptr maxButton, buyButton, cancelButton; diff --git a/config/translate.json b/config/translate.json index 0071d4971..ba797d4d9 100644 --- a/config/translate.json +++ b/config/translate.json @@ -21,8 +21,19 @@ "Impossible" ] }, + "confirmRestartGame" : "Are you sure you want to restart game?", + "noTownWithMarket": "No available marketplace!", + "noTownWithTavern": "No available town with tavern!", + "spellUnknownProblem": "Unknown problem with this spell, no more information available.", "playerAttacked" : "Player has been attacked: %s" }, + "server" : + { + "errors" : + { + "existingProcess" : "Another vcmiserver process is running, please terminate it first" + } + }, "systemOptions" : { "fullscreenButton" : @@ -44,6 +55,7 @@ "townHall" : { "missingBase" : "Base building %s must be built first", + "noCreaturesToRecruit" : "There are no creatures to recruit!", "greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.", "greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).", "greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).", @@ -62,18 +74,18 @@ "allOf" : "All of the following:", "noneOf" : "None of the following:" }, - "heroWindow": - { - "openCommander": - { - "label": "Open commander window", - "help": "Displays information about commander of this hero" - } - }, - "commanderWindow": - { - "artifactMessage": "Do you want to give this artifact back to hero?" - }, + "heroWindow" : + { + "openCommander" : + { + "label" : "Open commander window", + "help" : "Displays information about commander of this hero" + } + }, + "commanderWindow": + { + "artifactMessage": "Do you want to give this artifact back to hero?" + }, "creatureWindow" : { "showBonuses" : diff --git a/include/vcmi/ServerCallback.h b/include/vcmi/ServerCallback.h index a9a722d4d..af35b4ea5 100644 --- a/include/vcmi/ServerCallback.h +++ b/include/vcmi/ServerCallback.h @@ -27,6 +27,8 @@ struct CatapultAttack; class DLL_LINKAGE ServerCallback { public: + virtual ~ServerCallback() = default; + virtual void complain(const std::string & problem) = 0; virtual bool describeChanges() const = 0; diff --git a/include/vcmi/Services.h b/include/vcmi/Services.h index 7bd2a7bb0..1bc33a1dd 100644 --- a/include/vcmi/Services.h +++ b/include/vcmi/Services.h @@ -32,10 +32,12 @@ namespace spells } } +#if SCRIPTING_ENABLED namespace scripting { class Service; } +#endif class DLL_LINKAGE Services { @@ -47,7 +49,9 @@ public: virtual const FactionService * factions() const = 0; virtual const HeroClassService * heroClasses() const = 0; virtual const HeroTypeService * heroTypes() const = 0; +#if SCRIPTING_ENABLED virtual const scripting::Service * scripts() const = 0; +#endif virtual const spells::Service * spells() const = 0; virtual const SkillService * skills() const = 0; virtual const BattleFieldService * battlefields() const = 0; diff --git a/include/vcmi/scripting/Service.h b/include/vcmi/scripting/Service.h index d09882dfa..e94300598 100644 --- a/include/vcmi/scripting/Service.h +++ b/include/vcmi/scripting/Service.h @@ -10,6 +10,7 @@ #pragma once +#if SCRIPTING_ENABLED #include class Services; @@ -78,3 +79,4 @@ public: } +#endif diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index c0d617442..5bc73743a 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -12,29 +12,53 @@ #include "../../lib/JsonNode.h" #include "../../lib/filesystem/CFileInputStream.h" +#include "../../lib/GameConstants.h" + +namespace +{ +bool isCompatible(const QString & verMin, const QString & verMax) +{ + const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch + QVersionNumber vcmiVersion(GameConstants::VCMI_VERSION_MAJOR, + GameConstants::VCMI_VERSION_MINOR, + GameConstants::VCMI_VERSION_PATCH); + + auto versionMin = QVersionNumber::fromString(verMin); + auto versionMax = QVersionNumber::fromString(verMax); + + auto buildVersion = [maxSections](QVersionNumber & ver) + { + if(ver.segmentCount() < maxSections) + { + auto segments = ver.segments(); + for(int i = segments.size() - 1; i < maxSections; ++i) + segments.append(0); + ver = QVersionNumber(segments); + } + }; + + if(!versionMin.isNull()) + { + buildVersion(versionMin); + if(vcmiVersion < versionMin) + return false; + } + + if(!versionMax.isNull()) + { + buildVersion(versionMax); + if(vcmiVersion > versionMax) + return false; + } + return true; +} +} bool CModEntry::compareVersions(QString lesser, QString greater) { - static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch - - QStringList lesserList = lesser.split("."); - QStringList greaterList = greater.split("."); - - assert(lesserList.size() <= maxSections); - assert(greaterList.size() <= maxSections); - - for(int i = 0; i < maxSections; i++) - { - if(greaterList.size() <= i) // 1.1.1 > 1.1 - return false; - - if(lesserList.size() <= i) // 1.1 < 1.1.1 - return true; - - if(lesserList[i].toInt() != greaterList[i].toInt()) - return lesserList[i].toInt() < greaterList[i].toInt(); // 1.1 < 1.2 - } - return false; + auto versionLesser = QVersionNumber::fromString(lesser); + auto versionGreater = QVersionNumber::fromString(greater); + return versionLesser < versionGreater; } QString CModEntry::sizeToString(double size) @@ -92,6 +116,15 @@ bool CModEntry::isUpdateable() const return false; } +bool CModEntry::isCompatible() const +{ + if(!isInstalled()) + return false; + + auto compatibility = localData["compatibility"].toMap(); + return ::isCompatible(compatibility["min"].toString(), compatibility["max"].toString()); +} + bool CModEntry::isEssential() const { return getValue("storedLocaly").toBool(); @@ -102,6 +135,11 @@ bool CModEntry::isInstalled() const return !localData.isEmpty(); } +bool CModEntry::isValid() const +{ + return !localData.isEmpty() || !repository.isEmpty(); +} + int CModEntry::getModStatus() const { int status = 0; @@ -193,7 +231,11 @@ static QVariant getValue(QVariant input, QString path) QString remainder = "/" + path.section('/', 2, -1); entryName.remove(0, 1); - return getValue(input.toMap().value(entryName), remainder); + QMap keyNormalize; + for(auto & key : input.toMap().keys()) + keyNormalize[key.toLower()] = key; + + return getValue(input.toMap().value(keyNormalize[entryName]), remainder); } else { @@ -203,6 +245,7 @@ static QVariant getValue(QVariant input, QString path) CModEntry CModList::getMod(QString modname) const { + modname = modname.toLower(); QVariantMap repo; QVariantMap local = localModList[modname].toMap(); QVariantMap settings; @@ -241,19 +284,28 @@ CModEntry CModList::getMod(QString modname) const } } + if(settings.value("active").toBool()) + { + auto compatibility = local.value("compatibility").toMap(); + if(compatibility["min"].isValid() || compatibility["max"].isValid()) + if(!isCompatible(compatibility["min"].toString(), compatibility["max"].toString())) + settings["active"] = false; + } + + for(auto entry : repositories) { QVariant repoVal = getValue(entry, path); if(repoVal.isValid()) { - if(repo.empty()) + auto repoValMap = repoVal.toMap(); + auto compatibility = repoValMap["compatibility"].toMap(); + if(isCompatible(compatibility["min"].toString(), compatibility["max"].toString())) { - repo = repoVal.toMap(); - } - else - { - if(CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString())) - repo = repoVal.toMap(); + if(repo.empty() || CModEntry::compareVersions(repo["version"].toString(), repoValMap["version"].toString())) + { + repo = repoValMap; + } } } } @@ -297,12 +349,12 @@ QVector CModList::getModList() const { for(auto it = repo.begin(); it != repo.end(); it++) { - knownMods.insert(it.key()); + knownMods.insert(it.key().toLower()); } } for(auto it = localModList.begin(); it != localModList.end(); it++) { - knownMods.insert(it.key()); + knownMods.insert(it.key().toLower()); } for(auto entry : knownMods) diff --git a/launcher/modManager/cmodlist.h b/launcher/modManager/cmodlist.h index a5de09622..2bce79c6d 100644 --- a/launcher/modManager/cmodlist.h +++ b/launcher/modManager/cmodlist.h @@ -51,6 +51,10 @@ public: bool isInstalled() const; // vcmi essential files bool isEssential() const; + // checks if verison is compatible with vcmi + bool isCompatible() const; + // returns if has any data + bool isValid() const; // see ModStatus enum int getModStatus() const; diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index 2e94b8f81..fb2d3f1e0 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -245,6 +245,7 @@ bool CModFilterModel::filterMatchesThis(const QModelIndex & source) const { CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString()); return (mod.getModStatus() & filterMask) == filteredType && + mod.isValid() && QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent()); } diff --git a/launcher/modManager/cmodlistview_moc.ui b/launcher/modManager/cmodlistview_moc.ui index c936008cf..50542e75d 100644 --- a/launcher/modManager/cmodlistview_moc.ui +++ b/launcher/modManager/cmodlistview_moc.ui @@ -253,7 +253,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><br /></p></body></html> @@ -385,6 +385,31 @@ p, li { white-space: pre-wrap; } + + + + + 0 + 0 + + + + + 51 + 0 + + + + + 140 + 16777215 + + + + Uninstall + + + @@ -460,31 +485,6 @@ p, li { white-space: pre-wrap; } - - - - - 0 - 0 - - - - - 51 - 0 - - - - - 140 - 16777215 - - - - Uninstall - - - diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 3bc854bb3..eea55af17 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -169,6 +169,10 @@ bool CModManager::canEnableMod(QString modname) if(!mod.isInstalled()) return addError(modname, "Mod must be installed first"); + //check for compatibility + if(!mod.isCompatible()) + return addError(modname, "Mod is not compatible, please update VCMI and checkout latest mod revisions"); + for(auto modEntry : mod.getValue("depends").toStringList()) { if(!modList->hasMod(modEntry)) // required mod is not available diff --git a/lib/CGameInterface.cpp b/lib/CGameInterface.cpp index fc4731688..84b9e9745 100644 --- a/lib/CGameInterface.cpp +++ b/lib/CGameInterface.cpp @@ -126,10 +126,12 @@ std::shared_ptr CDynLibHandler::getNewBattleAI(std::string return createAnyAI(dllname, "GetNewBattleAI"); } +#if SCRIPTING_ENABLED std::shared_ptr CDynLibHandler::getNewScriptingModule(const boost::filesystem::path & dllname) { return createAny(dllname, "GetNewModule"); } +#endif BattleAction CGlobalAI::activeStack(const CStack * stack) { diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 35b8671fe..c4ab18378 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -56,10 +56,14 @@ class CSaveFile; class BinaryDeserializer; class BinarySerializer; struct ArtifactLocation; + +#if SCRIPTING_ENABLED namespace scripting { class Module; } +#endif + class DLL_LINKAGE CBattleGameInterface : public IBattleEventsReceiver { @@ -110,7 +114,9 @@ class DLL_LINKAGE CDynLibHandler public: static std::shared_ptr getNewAI(std::string dllname); static std::shared_ptr getNewBattleAI(std::string dllname); +#if SCRIPTING_ENABLED static std::shared_ptr getNewScriptingModule(const boost::filesystem::path & dllname); +#endif }; class DLL_LINKAGE CGlobalAI : public CGameInterface // AI class (to derivate) diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 1c30b1208..e8a72cb5b 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -434,7 +434,9 @@ void CContentHandler::init() handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill"))); handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template"))); +#if SCRIPTING_ENABLED handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script"))); +#endif handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield"))); handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle"))); //TODO: any other types of moddables? @@ -532,6 +534,51 @@ JsonNode addMeta(JsonNode config, std::string meta) return config; } +CModInfo::Version CModInfo::Version::GameVersion() +{ + return Version(GameConstants::VCMI_VERSION_MAJOR, GameConstants::VCMI_VERSION_MINOR, GameConstants::VCMI_VERSION_PATCH); +} + +CModInfo::Version CModInfo::Version::fromString(std::string from) +{ + int major = 0, minor = 0, patch = 0; + try + { + auto pointPos = from.find('.'); + major = std::stoi(from.substr(0, pointPos)); + if(pointPos != std::string::npos) + { + from = from.substr(pointPos + 1); + pointPos = from.find('.'); + minor = std::stoi(from.substr(0, pointPos)); + if(pointPos != std::string::npos) + patch = std::stoi(from.substr(pointPos + 1)); + } + } + catch(const std::invalid_argument & e) + { + return Version(); + } + return Version(major, minor, patch); +} + +std::string CModInfo::Version::toString() const +{ + return std::to_string(major) + '.' + std::to_string(minor) + '.' + std::to_string(patch); +} + +bool CModInfo::Version::compatible(const Version & other, bool checkMinor, bool checkPatch) const +{ + return (major == other.major && + (!checkMinor || minor >= other.minor) && + (!checkPatch || minor > other.minor || (minor == other.minor && patch >= other.patch))); +} + +bool CModInfo::Version::isNull() const +{ + return major == 0 && minor == 0 && patch == 0; +} + CModInfo::CModInfo(): checksum(0), enabled(false), @@ -551,6 +598,12 @@ CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode validation(PENDING), config(addMeta(config, identifier)) { + version = Version::fromString(config["version"].String()); + if(!config["compatibility"].isNull()) + { + vcmiCompatibleMin = Version::fromString(config["compatibility"]["min"].String()); + vcmiCompatibleMax = Version::fromString(config["compatibility"]["max"].String()); + } loadLocalData(local); } @@ -601,6 +654,14 @@ void CModInfo::loadLocalData(const JsonNode & data) validated = data["validated"].Bool(); checksum = strtol(data["checksum"].String().c_str(), nullptr, 16); } + + //check compatibility + bool wasEnabled = enabled; + enabled = enabled && (vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin)); + enabled = enabled && (vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion())); + + if(wasEnabled && !enabled) + logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", name); if (enabled) validation = validated ? PASSED : PENDING; @@ -986,7 +1047,9 @@ void CModHandler::load() for(const TModID & modName : activeMods) content->load(allMods[modName]); +#if SCRIPTING_ENABLED VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load +#endif content->loadCustom(); diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 5df3dfc38..1605f59fd 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -177,6 +177,30 @@ public: FAILED, PASSED }; + + struct Version + { + int major = 0; + int minor = 0; + int patch = 0; + + Version() = default; + Version(int mj, int mi, int p): major(mj), minor(mi), patch(p) {} + + static Version GameVersion(); + static Version fromString(std::string from); + std::string toString() const; + + bool compatible(const Version & other, bool checkMinor = false, bool checkPatch = false) const; + bool isNull() const; + + template void serialize(Handler &h, const int version) + { + h & major; + h & minor; + h & patch; + } + }; /// identifier, identical to name of folder with mod std::string identifier; @@ -184,6 +208,13 @@ public: /// human-readable strings std::string name; std::string description; + + /// version of the mod + Version version; + + /// vcmi versions compatible with the mod + + Version vcmiCompatibleMin, vcmiCompatibleMax; /// list of mods that should be loaded before this one std::set dependencies; @@ -210,18 +241,6 @@ public: static std::string getModDir(std::string name); static std::string getModFile(std::string name); - template void serialize(Handler &h, const int version) - { - h & identifier; - h & description; - h & name; - h & dependencies; - h & conflicts; - h & config; - h & checksum; - h & validation; - h & enabled; - } private: void loadLocalData(const JsonNode & data); }; @@ -256,6 +275,13 @@ class DLL_LINKAGE CModHandler void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); public: + + class Incompatibility: public std::logic_error + { + public: + Incompatibility(const std::string & w): std::logic_error(w) + {} + }; CIdentifierStorage identifiers; @@ -336,8 +362,37 @@ public: template void serialize(Handler &h, const int version) { - h & allMods; - h & activeMods; + if(h.saving) + { + h & activeMods; + for(const auto & m : activeMods) + + h & allMods[m].version; + } + else + { + loadMods(); + std::vector newActiveMods; + h & newActiveMods; + for(auto & m : newActiveMods) + { + if(!allMods.count(m)) + throw Incompatibility(m + " unkown mod"); + + CModInfo::Version mver; + h & mver; + if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver)) + { + std::string err = allMods[m].name + + ": version needed " + mver.toString() + + "but you have installed " + allMods[m].version.toString(); + throw Incompatibility(err); + } + allMods[m].enabled = true; + } + std::swap(activeMods, newActiveMods); + } + h & settings; h & modules; h & identifiers; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 9bbd164a3..3e02f1457 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -386,6 +386,9 @@ class DLL_LINKAGE INodeStorage { public: using ELayer = EPathfindingLayer; + + virtual ~INodeStorage() = default; + virtual std::vector getInitialNodes() = 0; virtual std::vector calculateNeighbours( @@ -448,6 +451,7 @@ public: PathfinderConfig( std::shared_ptr nodeStorage, std::vector> rules); + virtual ~PathfinderConfig() = default; virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0; }; diff --git a/lib/CScriptingModule.cpp b/lib/CScriptingModule.cpp index e85e3c72e..c6da26f01 100644 --- a/lib/CScriptingModule.cpp +++ b/lib/CScriptingModule.cpp @@ -11,6 +11,7 @@ #include "CScriptingModule.h" +#if SCRIPTING_ENABLED namespace scripting { @@ -30,3 +31,4 @@ Module::Module() Module::~Module() = default; } +#endif diff --git a/lib/CScriptingModule.h b/lib/CScriptingModule.h index eacf0c599..77b59249f 100644 --- a/lib/CScriptingModule.h +++ b/lib/CScriptingModule.h @@ -9,6 +9,7 @@ */ #pragma once +#if SCRIPTING_ENABLED #include namespace spells @@ -45,3 +46,4 @@ public: }; } +#endif diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index ae543bcf5..7f6155316 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -51,10 +51,18 @@ const TeamID TeamID::NO_TEAM = TeamID(255); namespace GameConstants { + const int VCMI_VERSION_MAJOR = 1; + const int VCMI_VERSION_MINOR = 1; + const int VCMI_VERSION_PATCH = 0; + + const std::string VCMI_VERSION_STRING = std::to_string(VCMI_VERSION_MAJOR) + "." + + std::to_string(VCMI_VERSION_MINOR) + "." + + std::to_string(VCMI_VERSION_PATCH); + #ifdef VCMI_NO_EXTRA_VERSION - const std::string VCMI_VERSION = std::string("VCMI 1.0.0"); + const std::string VCMI_VERSION = std::string("VCMI ") + VCMI_VERSION_STRING; #else - const std::string VCMI_VERSION = std::string("VCMI 1.0.0.") + GIT_SHA1; + const std::string VCMI_VERSION = std::string("VCMI ") + VCMI_VERSION_STRING + "." + GIT_SHA1; #endif } diff --git a/lib/GameConstants.h b/lib/GameConstants.h index f864cdd53..94f049e69 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -36,6 +36,9 @@ struct IdTag namespace GameConstants { + DLL_LINKAGE extern const int VCMI_VERSION_MAJOR; + DLL_LINKAGE extern const int VCMI_VERSION_MINOR; + DLL_LINKAGE extern const int VCMI_VERSION_PATCH; DLL_LINKAGE extern const std::string VCMI_VERSION; const int PUZZLE_MAP_PIECES = 48; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 532532dac..0f4ad0940 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -27,12 +27,13 @@ class CStackBasicDescriptor; class CGCreature; struct ShashInt3; +#if SCRIPTING_ENABLED namespace scripting { - class Context; class Pool; - class Script; } +#endif + class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback { @@ -132,7 +133,9 @@ class DLL_LINKAGE IGameCallback : public CPrivilegedInfoCallback, public IGameEv public: virtual ~IGameCallback(){}; +#if SCRIPTING_ENABLED virtual scripting::Pool * getGlobalContextPool() const = 0; +#endif //get info virtual bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero); diff --git a/lib/ScriptHandler.cpp b/lib/ScriptHandler.cpp index ebca5ce26..955ca2852 100644 --- a/lib/ScriptHandler.cpp +++ b/lib/ScriptHandler.cpp @@ -11,6 +11,7 @@ #include "ScriptHandler.h" +#if SCRIPTING_ENABLED #include #include @@ -311,3 +312,4 @@ void ScriptHandler::saveState(JsonNode & state) } +#endif diff --git a/lib/ScriptHandler.h b/lib/ScriptHandler.h index 37da9e975..53d91110c 100644 --- a/lib/ScriptHandler.h +++ b/lib/ScriptHandler.h @@ -10,6 +10,7 @@ #pragma once +#if SCRIPTING_ENABLED #include #include "IHandlerBase.h" #include "JsonNode.h" @@ -131,3 +132,4 @@ private: }; } +#endif diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 3d1b98a60..c106bac4e 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -82,10 +82,12 @@ const HeroTypeService * LibClasses::heroTypes() const return heroh; } +#if SCRIPTING_ENABLED const scripting::Service * LibClasses::scripts() const { return scriptHandler; } +#endif const spells::Service * LibClasses::spells() const { @@ -217,7 +219,9 @@ void LibClasses::init(bool onlyEssential) createHandler(tplh, "Template", pomtime); //templates need already resolved identifiers (refactor?) +#if SCRIPTING_ENABLED createHandler(scriptHandler, "Script", pomtime); +#endif createHandler(battlefieldsHandler, "Battlefields", pomtime); @@ -248,7 +252,9 @@ void LibClasses::clear() delete bth; delete tplh; delete terviewh; +#if SCRIPTING_ENABLED delete scriptHandler; +#endif delete battlefieldsHandler; makeNull(); } @@ -268,7 +274,9 @@ void LibClasses::makeNull() bth = nullptr; tplh = nullptr; terviewh = nullptr; +#if SCRIPTING_ENABLED scriptHandler = nullptr; +#endif battlefieldsHandler = nullptr; } @@ -289,10 +297,12 @@ void LibClasses::callWhenDeserializing() //modh->loadConfigFromFile ("defaultMods"); //TODO: remember last saved config } +#if SCRIPTING_ENABLED void LibClasses::scriptsLoaded() { scriptHandler->performRegistration(this); } +#endif LibClasses::~LibClasses() { diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index bff62add2..52e5be6f4 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -33,10 +33,13 @@ class CTerrainViewPatternConfig; class CRmgTemplateStorage; class IHandlerBase; +#if SCRIPTING_ENABLED namespace scripting { class ScriptHandler; } +#endif + /// Loads and constructs several handlers class DLL_LINKAGE LibClasses : public Services @@ -56,7 +59,9 @@ public: const FactionService * factions() const override; const HeroClassService * heroClasses() const override; const HeroTypeService * heroTypes() const override; +#if SCRIPTING_ENABLED const scripting::Service * scripts() const override; +#endif const spells::Service * spells() const override; const SkillService * skills() const override; const BattleFieldService * battlefields() const override; @@ -84,7 +89,9 @@ public: CRmgTemplateStorage * tplh; BattleFieldHandler * battlefieldsHandler; ObstacleHandler * obstacleHandler; +#if SCRIPTING_ENABLED scripting::ScriptHandler * scriptHandler; +#endif LibClasses(); //c-tor, loads .lods and NULLs handlers ~LibClasses(); @@ -94,15 +101,19 @@ public: void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init() +#if SCRIPTING_ENABLED void scriptsLoaded(); +#endif template void serialize(Handler &h, const int version) { +#if SCRIPTING_ENABLED h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on if(!h.saving) { scriptsLoaded(); } +#endif h & heroh; h & arth; @@ -134,9 +145,6 @@ public: callWhenDeserializing(); } } - -private: - void update800(); }; extern DLL_LINKAGE LibClasses * VLC; diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 7bf2100d5..47c797f24 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -965,12 +965,14 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const return const_cast(CBattleInfoEssentials::battleGetFightingHero(side)); } +#if SCRIPTING_ENABLED scripting::Pool * BattleInfo::getContextPool() const { //this is real battle, use global scripting context pool //TODO: make this line not ugly return IObjectInterface::cb->getGlobalContextPool(); } +#endif bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b) { diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index b72fa24c0..84748bc46 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -143,7 +143,9 @@ public: ui8 whatSide(PlayerColor player) const; protected: +#if SCRIPTING_ENABLED scripting::Pool * getContextPool() const override; +#endif }; diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 146cf038d..b5cd714c3 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -23,13 +23,6 @@ struct CObstacleInstance; class IBonusBearer; class CRandomGenerator; -namespace scripting -{ - class Context; - class Pool; - class Script; -} - namespace spells { class Caster; diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index 08d2cf3c4..5a8c1c06c 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -24,15 +24,19 @@ namespace battle using UnitFilter = std::function; } +#if SCRIPTING_ENABLED namespace scripting { class Pool; } +#endif class DLL_LINKAGE IBattleInfoCallback { public: +#if SCRIPTING_ENABLED virtual scripting::Pool * getContextPool() const = 0; +#endif virtual TTerrain battleTerrainType() const = 0; virtual BattleField battleGetBattlefieldType() const = 0; diff --git a/lib/events/ApplyDamage.cpp b/lib/events/ApplyDamage.cpp index 8ba3de747..93b387899 100644 --- a/lib/events/ApplyDamage.cpp +++ b/lib/events/ApplyDamage.cpp @@ -25,10 +25,8 @@ SubscriptionRegistry * ApplyDamage::getRegistry() } CApplyDamage::CApplyDamage(const Environment * env_, BattleStackAttacked * pack_, std::shared_ptr target_) - : env(env_), - pack(pack_), + : pack(pack_), target(target_) - { initalDamage = pack->damageAmount; } diff --git a/lib/events/ApplyDamage.h b/lib/events/ApplyDamage.h index c56d730fe..178d12224 100644 --- a/lib/events/ApplyDamage.h +++ b/lib/events/ApplyDamage.h @@ -28,12 +28,8 @@ public: private: int64_t initalDamage; - const Environment * env; BattleStackAttacked * pack; std::shared_ptr target; }; } - - - diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index a96f3ec87..d478d136e 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -156,7 +156,7 @@ ISimpleResourceLoader * CResourceHandler::createInitial() void CResourceHandler::initialize() { - // Create tree-loke structure that looks like this: + // Create tree-like structure that looks like this: // root // | // |- initial diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 134f54b0c..34b57072d 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -850,17 +850,16 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand) case Obj::WARRIORS_TOMB: { onSelect.addTxt(MetaString::ADVOB_TXT, 161); + onVisited.addTxt(MetaString::ADVOB_TXT, 163); - info.resize(2); + info.resize(1); loadRandomArtifact(rand, info[0], 30, 50, 25, 5); Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID); info[0].reward.bonuses.push_back(bonus); - info[1].reward.bonuses.push_back(bonus); info[0].limiter.numOfGrants = 1; info[0].message.addTxt(MetaString::ADVOB_TXT, 162); info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getName()); - info[1].message.addTxt(MetaString::ADVOB_TXT, 163); } break; case Obj::WAGON: diff --git a/lib/rmg/ObstaclePlacer.cpp b/lib/rmg/ObstaclePlacer.cpp index a0c051fdb..29fba34ab 100644 --- a/lib/rmg/ObstaclePlacer.cpp +++ b/lib/rmg/ObstaclePlacer.cpp @@ -22,22 +22,10 @@ #include "CMapGenerator.h" #include "../CRandomGenerator.h" #include "Functions.h" +#include "../mapping/CMapEditManager.h" -void ObstaclePlacer::process() +void ObstacleProxy::collectPossibleObstacles(const Terrain & terrain) { - auto * manager = zone.getModificator(); - if(!manager) - return; - - auto * riverManager = zone.getModificator(); - - typedef std::vector> ObstacleVector; - //obstacleVector possibleObstacles; - - std::map obstaclesBySize; - typedef std::pair ObstaclePair; - std::vector possibleObstacles; - //get all possible obstacles for this terrain for(auto primaryID : VLC->objtypeh->knownObjects()) { @@ -48,7 +36,7 @@ void ObstaclePlacer::process() { for(auto temp : handler->getTemplates()) { - if(temp->canBePlacedAt(zone.getTerrainType()) && temp->getBlockMapOffset().valid()) + if(temp->canBePlacedAt(terrain) && temp->getBlockMapOffset().valid()) obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp); } } @@ -62,122 +50,169 @@ void ObstaclePlacer::process() { return p1.first > p2.first; //bigger obstacles first }); - - auto blockedArea = zone.area().getSubarea([this](const int3 & t) +} + +int ObstacleProxy::getWeightedObjects(const int3 & tile, const CMap * map, CRandomGenerator & rand, std::list & allObjects, std::vector> & weightedObjects) +{ + int maxWeight = std::numeric_limits::min(); + for(int i = 0; i < possibleObstacles.size(); ++i) { - return map.shouldBeBlocked(t); - }); - blockedArea.subtract(zone.areaUsed()); - zone.areaPossible().subtract(blockedArea); - - - auto prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); - + if(!possibleObstacles[i].first) + continue; + + auto shuffledObstacles = possibleObstacles[i].second; + RandomGeneratorUtil::randomShuffle(shuffledObstacles, rand); + + for(auto temp : shuffledObstacles) + { + auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid); + auto obj = handler->create(temp); + allObjects.emplace_back(*obj); + rmg::Object * rmgObject = &allObjects.back(); + for(auto & offset : obj->getBlockedOffsets()) + { + rmgObject->setPosition(tile - offset); + if(!map->isInTheMap(rmgObject->getPosition())) + continue; + + if(!rmgObject->getArea().getSubarea([map](const int3 & t) + { + return !map->isInTheMap(t); + }).empty()) + continue; + + if(isProhibited(rmgObject->getArea())) + continue; + + int coverageBlocked = 0; + int coveragePossible = 0; + //do not use area intersection in optimization purposes + for(auto & t : rmgObject->getArea().getTilesVector()) + { + auto coverage = verifyCoverage(t); + if(coverage.first) + ++coverageBlocked; + if(coverage.second) + ++coveragePossible; + } + + int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible; + int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first; + assert(coverageOverlap >= 0); + + if(weight > maxWeight) + { + weightedObjects.clear(); + maxWeight = weight; + weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); + if(weight > 0) + break; + } + else if(weight == maxWeight) + weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); + + } + } + + if(maxWeight > 0) + break; + } + + return maxWeight; +} + +void ObstacleProxy::placeObstacles(CMap * map, CRandomGenerator & rand) +{ //reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left auto blockedTiles = blockedArea.getTilesVector(); int tilePos = 0; + std::set objs; + while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size()) { auto tile = blockedArea.getTilesVector()[tilePos]; - + std::list allObjects; - std::vector> weightedObjects; //obj + position - int maxWeight = std::numeric_limits::min(); - for(int i = 0; i < possibleObstacles.size(); ++i) - { - if(!possibleObstacles[i].first) - continue; - - auto shuffledObstacles = possibleObstacles[i].second; - RandomGeneratorUtil::randomShuffle(shuffledObstacles, generator.rand); - - for(auto & temp : shuffledObstacles) - { - auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid); - auto obj = handler->create(temp); - allObjects.emplace_back(*obj); - rmg::Object * rmgObject = &allObjects.back(); - for(auto & offset : obj->getBlockedOffsets()) - { - rmgObject->setPosition(tile - offset); - if(!map.isOnMap(rmgObject->getPosition())) - continue; - - if(!rmgObject->getArea().getSubarea([this](const int3 & t) - { - return !map.isOnMap(t); - }).empty()) - continue; - - if(prohibitedArea.overlap(rmgObject->getArea())) - continue; - - if(!zone.area().contains(rmgObject->getArea())) - continue; - - int coverageBlocked = 0; - int coveragePossible = 0; - //do not use area intersection in optimization purposes - for(auto & t : rmgObject->getArea().getTilesVector()) - { - if(map.shouldBeBlocked(t)) - ++coverageBlocked; - if(zone.areaPossible().contains(t)) - ++coveragePossible; - } - - int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible; - int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first; - assert(coverageOverlap >= 0); - - if(weight > maxWeight) - { - weightedObjects.clear(); - maxWeight = weight; - weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); - if(weight > 0) - break; - } - else if(weight == maxWeight) - weightedObjects.emplace_back(rmgObject, rmgObject->getPosition()); - - } - } - - if(maxWeight > 0) - break; - } - + std::vector> weightedObjects; + int maxWeight = getWeightedObjects(tile, map, rand, allObjects, weightedObjects); + if(weightedObjects.empty()) { tilePos += 1; continue; } - - auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, generator.rand); + + auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, rand); objIter->first->setPosition(objIter->second); - manager->placeObject(*objIter->first, false, false); + placeObject(*objIter->first, objs); + blockedArea.subtract(objIter->first->getArea()); tilePos = 0; - - //river processing - if(riverManager) - { - if(objIter->first->instances().front()->object().typeName == "mountain") - riverManager->riverSource().unite(objIter->first->getArea()); - if(objIter->first->instances().front()->object().typeName == "lake") - riverManager->riverSink().unite(objIter->first->getArea()); - } - + + postProcess(*objIter->first); + if(maxWeight < 0) logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString()); - + for(auto & o : allObjects) { if(&o != objIter->first) o.clear(); } } + + finalInsertion(map->getEditManager(), objs); +} + +void ObstacleProxy::finalInsertion(CMapEditManager * manager, std::set & instances) +{ + manager->insertObjects(instances); //insert as one operation - for undo purposes +} + +std::pair ObstacleProxy::verifyCoverage(const int3 & t) const +{ + return {blockedArea.contains(t), false}; +} + +void ObstacleProxy::placeObject(rmg::Object & object, std::set & instances) +{ + for (auto * instance : object.instances()) + { + instances.insert(&instance->object()); + } +} + +void ObstacleProxy::postProcess(const rmg::Object & object) +{ +} + +bool ObstacleProxy::isProhibited(const rmg::Area & objArea) const +{ + return false; +} + + + +void ObstaclePlacer::process() +{ + manager = zone.getModificator(); + if(!manager) + return; + + riverManager = zone.getModificator(); + + collectPossibleObstacles(zone.getTerrainType()); + + blockedArea = zone.area().getSubarea([this](const int3 & t) + { + return map.shouldBeBlocked(t); + }); + blockedArea.subtract(zone.areaUsed()); + zone.areaPossible().subtract(blockedArea); + + prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); + + placeObstacles(&map.map(), generator.rand); } void ObstaclePlacer::init() @@ -189,3 +224,41 @@ void ObstaclePlacer::init() DEPENDENCY(RoadPlacer); DEPENDENCY_ALL(RockPlacer); } + +std::pair ObstaclePlacer::verifyCoverage(const int3 & t) const +{ + return {map.shouldBeBlocked(t), zone.areaPossible().contains(t)}; +} + +void ObstaclePlacer::placeObject(rmg::Object & object, std::set &) +{ + manager->placeObject(object, false, false); +} + +void ObstaclePlacer::postProcess(const rmg::Object & object) +{ + //river processing + if(riverManager) + { + const auto objTypeName = object.instances().front()->object().typeName; + if(objTypeName == "mountain") + riverManager->riverSource().unite(object.getArea()); + else if(objTypeName == "lake") + riverManager->riverSink().unite(object.getArea()); + } +} + +bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const +{ + if(prohibitedArea.overlap(objArea)) + return true; + + if(!zone.area().contains(objArea)) + return true; + + return false; +} + +void ObstaclePlacer::finalInsertion(CMapEditManager *, std::set &) +{ +} diff --git a/lib/rmg/ObstaclePlacer.h b/lib/rmg/ObstaclePlacer.h index cb1242048..b98d042aa 100644 --- a/lib/rmg/ObstaclePlacer.h +++ b/lib/rmg/ObstaclePlacer.h @@ -11,11 +11,61 @@ #pragma once #include "Zone.h" -class ObstaclePlacer: public Modificator +class CMap; +class CMapEditManager; +class RiverPlacer; +class ObjectManager; +class DLL_LINKAGE ObstacleProxy +{ +public: + ObstacleProxy() = default; + virtual ~ObstacleProxy() = default; + + rmg::Area blockedArea; + + void collectPossibleObstacles(const Terrain & terrain); + + void placeObstacles(CMap * map, CRandomGenerator & rand); + + virtual std::pair verifyCoverage(const int3 & t) const; + + virtual void placeObject(rmg::Object & object, std::set & instances); + + virtual void postProcess(const rmg::Object & object); + + virtual bool isProhibited(const rmg::Area & objArea) const; + + virtual void finalInsertion(CMapEditManager * manager, std::set & instances); + +protected: + int getWeightedObjects(const int3 & tile, const CMap * map, CRandomGenerator & rand, std::list & allObjects, std::vector> & weightedObjects); + + typedef std::vector> ObstacleVector; + std::map obstaclesBySize; + typedef std::pair ObstaclePair; + std::vector possibleObstacles; +}; + +class ObstaclePlacer: public Modificator, public ObstacleProxy { public: MODIFICATOR(ObstaclePlacer); void process() override; void init() override; + + std::pair verifyCoverage(const int3 & t) const override; + + void placeObject(rmg::Object & object, std::set & instances) override; + + void postProcess(const rmg::Object & object) override; + + bool isProhibited(const rmg::Area & objArea) const override; + + void finalInsertion(CMapEditManager * manager, std::set & instances) override; + +private: + rmg::Area prohibitedArea; + RiverPlacer * riverManager; + ObjectManager * manager; }; diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 6d0e42b1d..83fbd5638 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -193,11 +193,7 @@ void Zone::fractalize() rmg::Area clearedTiles(dAreaFree); rmg::Area possibleTiles(dAreaPossible); rmg::Area tilesToIgnore; //will be erased in this iteration - - //the more treasure density, the greater distance between paths. Scaling is experimental. - int totalDensity = 0; - for(auto ti : treasureInfo) - totalDensity += ti.density; + const float minDistance = 10 * 10; //squared if(type != ETemplateZoneType::JUNCTION) diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index a80475041..29578ff56 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -720,10 +720,12 @@ const CreatureService * BaseMechanics::creatures() const return VLC->creatures(); //todo: redirect } +#if SCRIPTING_ENABLED const scripting::Service * BaseMechanics::scripts() const { return VLC->scripts(); //todo: redirect } +#endif const Service * BaseMechanics::spells() const { diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index de9546757..927a5a308 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -35,10 +35,12 @@ namespace vstd class RNG; } +#if SCRIPTING_ENABLED namespace scripting { class Service; } +#endif ///callback to be provided by server @@ -238,7 +240,9 @@ public: //Global environment facade virtual const CreatureService * creatures() const = 0; +#if SCRIPTING_ENABLED virtual const scripting::Service * scripts() const = 0; +#endif virtual const Service * spells() const = 0; virtual const IGameInfoCallback * game() const = 0; @@ -296,7 +300,9 @@ public: std::vector getTargetTypes() const override; const CreatureService * creatures() const override; +#if SCRIPTING_ENABLED const scripting::Service * scripts() const override; +#endif const Service * spells() const override; const IGameInfoCallback * game() const override; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index a84540bbf..799c65369 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1048,17 +1048,19 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG; } } + + int64_t drainedLife = 0; + // only primary target if(defender->alive()) - applyBattleEffects(bat, blm, attackerState, fireShield, defender, distance, false); + drainedLife += applyBattleEffects(bat, attackerState, fireShield, defender, distance, false); //multiple-hex normal attack std::set attackedCreatures = gs->curB->getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target - for(const CStack * stack : attackedCreatures) { if(stack != defender && stack->alive()) //do not hit same stack twice - applyBattleEffects(bat, blm, attackerState, fireShield, stack, distance, true); + drainedLife += applyBattleEffects(bat, attackerState, fireShield, stack, distance, true); } std::shared_ptr bonus = attacker->getBonusLocalFirst(Selector::type()(Bonus::SPELL_LIKE_ATTACK)); @@ -1086,7 +1088,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, { if(stack != defender && stack->alive()) //do not hit same stack twice { - applyBattleEffects(bat, blm, attackerState, fireShield, stack, distance, true); + drainedLife += applyBattleEffects(bat, attackerState, fireShield, stack, distance, true); } } @@ -1134,7 +1136,28 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, addGenericKilledLog(blm, defender, totalKills, multipleTargets); } - sendAndApply(&blm); + + // drain life effect (as well as log entry) must be applied after the attack + if(drainedLife > 0) + { + BattleAttack bat; + bat.stackAttacking = attacker->unitId(); + { + CustomEffectInfo customEffect; + customEffect.sound = soundBase::DRAINLIF; + customEffect.effect = 52; + customEffect.stack = attackerState->unitId(); + bat.customEffects.push_back(std::move(customEffect)); + } + sendAndApply(&bat); + + MetaString text; + attackerState->addText(text, MetaString::GENERAL_TXT, 361); + attackerState->addNameReplacement(text, false); + text.addReplacement(drainedLife); + defender->addNameReplacement(text, true); + blm.lines.push_back(std::move(text)); + } if(!fireShield.empty()) { @@ -1174,12 +1197,24 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender, StacksInjured pack; pack.stacks.push_back(bsa); sendAndApply(&pack); - sendGenericKilledLog(attacker, bsa.killedAmount, false); + + // TODO: this is already implemented in Damage::describeEffect() + { + MetaString text; + text.addTxt(MetaString::GENERAL_TXT, 376); + text.addReplacement(MetaString::SPELL_NAME, SpellID::FIRE_SHIELD); + text.addReplacement(totalDamage); + blm.lines.push_back(std::move(text)); + } + addGenericKilledLog(blm, attacker, bsa.killedAmount, false); } + sendAndApply(&blm); + handleAfterAttackCasting(ranged, attacker, defender); } -void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm, std::shared_ptr attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary) + +int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary) { BattleStackAttacked bsa; if(secondary) @@ -1208,34 +1243,14 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm CStack::prepareAttacked(bsa, getRandomGenerator(), bai.defender->acquireState()); //calculate casualties } - auto addLifeDrain = [&](int64_t & toHeal, EHealLevel level, EHealPower power) - { - attackerState->heal(toHeal, level, power); - - if(toHeal > 0) - { - CustomEffectInfo customEffect; - customEffect.sound = soundBase::DRAINLIF; - customEffect.effect = 52; - customEffect.stack = attackerState->unitId(); - bat.customEffects.push_back(customEffect); - - MetaString text; - attackerState->addText(text, MetaString::GENERAL_TXT, 361); - attackerState->addNameReplacement(text, false); - text.addReplacement((int)toHeal); - def->addNameReplacement(text, true); - blm.lines.push_back(text); - } - }; + int64_t drainedLife = 0; //life drain handling if(attackerState->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving()) { int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(Bonus::LIFE_DRAIN) / 100; - - if(toHeal > 0) - addLifeDrain(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT); + attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT); + drainedLife += toHeal; } //soul steal handling @@ -1248,7 +1263,8 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm if(attackerState->hasBonusOfType(Bonus::SOUL_STEAL, subtype)) { int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(Bonus::SOUL_STEAL, subtype) * attackerState->MaxHealth(); - addLifeDrain(toHeal, EHealLevel::OVERHEAL, ((subtype == 0) ? EHealPower::ONE_BATTLE : EHealPower::PERMANENT)); + attackerState->heal(toHeal, EHealLevel::OVERHEAL, ((subtype == 0) ? EHealPower::ONE_BATTLE : EHealPower::PERMANENT)); + drainedLife += toHeal; break; } } @@ -1263,6 +1279,8 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm auto fireShieldDamage = (std::min(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; fireShield.push_back(std::make_pair(def, fireShieldDamage)); } + + return drainedLife; } void CGameHandler::sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple) @@ -1297,8 +1315,8 @@ void CGameHandler::addGenericKilledLog(BattleLogMessage & blm, const CStack * de txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes } MetaString line; - line.addReplacement(txt.str()); - blm.lines.push_back(line); + line << txt.str(); + blm.lines.push_back(std::move(line)); } } @@ -1652,7 +1670,9 @@ CGameHandler::~CGameHandler() void CGameHandler::reinitScripting() { serverEventBus = make_unique(); +#if SCRIPTING_ENABLED serverScripts.reset(new scripting::PoolImpl(this, spellEnv)); +#endif } void CGameHandler::init(StartInfo *si) @@ -2112,7 +2132,9 @@ void CGameHandler::run(bool resume) logGlobal->info(sbuffer.str()); } +#if SCRIPTING_ENABLED services()->scripts()->run(serverScripts); +#endif if(resume) events::GameResumed::defaultExecute(serverEventBus.get()); @@ -5585,7 +5607,7 @@ bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2) auto topArmy = dialog->exchangingArmies.at(0); auto bottomArmy = dialog->exchangingArmies.at(1); - if (topArmy == o1 && bottomArmy == o2 || bottomArmy == o1 && topArmy == o2) + if ((topArmy == o1 && bottomArmy == o2) || (bottomArmy == o1 && topArmy == o2)) return true; } } @@ -7319,15 +7341,17 @@ CRandomGenerator & CGameHandler::getRandomGenerator() return CRandomGenerator::getDefault(); } +#if SCRIPTING_ENABLED scripting::Pool * CGameHandler::getGlobalContextPool() const { return serverScripts.get(); } -scripting::Pool * CGameHandler::getContextPool() const +scripting::Pool * CGameHandler::getContextPool() const { return serverScripts.get(); } +#endif const ObjectInstanceID CGameHandler::putNewObject(Obj ID, int subID, int3 pos) { diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 2c90edf6e..279fca93e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -34,10 +34,12 @@ class IMarket; class SpellCastEnvironment; +#if SCRIPTING_ENABLED namespace scripting { class PoolImpl; } +#endif template class CApplier; @@ -126,7 +128,8 @@ public: void makeAttack(const CStack * attacker, const CStack * defender, int distance, BattleHex targetHex, bool first, bool ranged, bool counter); - void applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm, std::shared_ptr attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary); //damage, drain life & fire shield + // damage, drain life & fire shield; returns amount of drained life + int64_t applyBattleEffects(BattleAttack & bat, std::shared_ptr attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary); void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple); void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple); @@ -274,12 +277,14 @@ public: h & finishingBattle; h & getRandomGenerator(); +#if SCRIPTING_ENABLED JsonNode scriptsState; if(h.saving) serverScripts->serializeState(h.saving, scriptsState); h & scriptsState; if(!h.saving) serverScripts->serializeState(h.saving, scriptsState); +#endif } void sendMessageToAll(const std::string &message); @@ -326,13 +331,17 @@ public: CRandomGenerator & getRandomGenerator(); +#if SCRIPTING_ENABLED scripting::Pool * getGlobalContextPool() const override; scripting::Pool * getContextPool() const override; +#endif friend class CVCMIServer; private: std::unique_ptr serverEventBus; +#if SCRIPTING_ENABLED std::shared_ptr serverScripts; +#endif void reinitScripting();