1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge remote-tracking branch 'origin/develop' into terrain-rewrite

# Conflicts:
#	lib/Terrain.cpp
#	lib/Terrain.h
This commit is contained in:
Tomasz Zieliński 2022-09-23 20:01:13 +02:00
commit a5077245a8
96 changed files with 945 additions and 386 deletions

View File

@ -4,14 +4,65 @@ on:
push: push:
branches: branches:
- features/* - features/*
- develop
pull_request: pull_request:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
env: env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release BUILD_TYPE: Release
jobs: 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: build:
needs: check_last_build
if: always() && needs.check_last_build.skip_build != 1
strategy: strategy:
matrix: matrix:
include: include:

View File

@ -16,7 +16,9 @@
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
#include "../../lib/ScriptHandler.h" #include "../../lib/ScriptHandler.h"
#if SCRIPTING_ENABLED
using scripting::Pool; using scripting::Pool;
#endif
void actualizeEffect(TBonusListPtr target, const Bonus & ef) void actualizeEffect(TBonusListPtr target, const Bonus & ef)
{ {
@ -217,7 +219,9 @@ HypotheticBattle::HypotheticBattle(const Environment * ENV, Subject realBattle)
localEnvironment.reset(new HypotheticEnvironment(this, env)); localEnvironment.reset(new HypotheticEnvironment(this, env));
serverCallback.reset(new HypotheticServerCallback(this)); serverCallback.reset(new HypotheticServerCallback(this));
#if SCRIPTING_ENABLED
pool.reset(new scripting::PoolImpl(localEnvironment.get(), serverCallback.get())); pool.reset(new scripting::PoolImpl(localEnvironment.get(), serverCallback.get()));
#endif
} }
bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const
@ -420,10 +424,12 @@ int64_t HypotheticBattle::getTreeVersion() const
return getBattleNode()->getTreeVersion() + bonusTreeVersion; return getBattleNode()->getTreeVersion() + bonusTreeVersion;
} }
#if SCRIPTING_ENABLED
Pool * HypotheticBattle::getContextPool() const Pool * HypotheticBattle::getContextPool() const
{ {
return pool.get(); return pool.get();
} }
#endif
ServerCallback * HypotheticBattle::getServerCallback() ServerCallback * HypotheticBattle::getServerCallback()
{ {

View File

@ -136,7 +136,9 @@ public:
int64_t getTreeVersion() const; int64_t getTreeVersion() const;
#if SCRIPTING_ENABLED
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif
ServerCallback * getServerCallback(); ServerCallback * getServerCallback();
@ -189,6 +191,8 @@ private:
std::unique_ptr<HypotheticServerCallback> serverCallback; std::unique_ptr<HypotheticServerCallback> serverCallback;
std::unique_ptr<HypotheticEnvironment> localEnvironment; std::unique_ptr<HypotheticEnvironment> localEnvironment;
#if SCRIPTING_ENABLED
mutable std::shared_ptr<scripting::Pool> pool; mutable std::shared_ptr<scripting::Pool> pool;
#endif
mutable std::shared_ptr<events::EventBus> eventBus; mutable std::shared_ptr<events::EventBus> eventBus;
}; };

View File

@ -204,7 +204,6 @@ std::shared_ptr<CCreatureSet> ArmyManager::getArmyAvailableToBuyAsCCreatureSet(
TResources availableRes) const TResources availableRes) const
{ {
std::vector<creInfo> creaturesInDwellings; std::vector<creInfo> creaturesInDwellings;
int freeHeroSlots = GameConstants::ARMY_SIZE;
auto army = std::make_shared<TemporaryArmy>(); auto army = std::make_shared<TemporaryArmy>();
for(int i = dwelling->creatures.size() - 1; i >= 0; i--) for(int i = dwelling->creatures.size() - 1; i >= 0; i--)

View File

@ -41,6 +41,7 @@ struct ArmyUpgradeInfo
class DLL_EXPORT IArmyManager //: public: IAbstractManager class DLL_EXPORT IArmyManager //: public: IAbstractManager
{ {
public: public:
virtual ~IArmyManager() = default;
virtual void update() = 0; virtual void update() = 0;
virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0; virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0;
virtual ui64 howManyReinforcementsCanBuy( virtual ui64 howManyReinforcementsCanBuy(

View File

@ -129,8 +129,6 @@ void BuildAnalyzer::update()
{ {
logAi->trace("Checking town %s", town->name); logAi->trace("Checking town %s", town->name);
auto townInfo = town->town;
developmentInfos.push_back(TownDevelopmentInfo(town)); developmentInfos.push_back(TownDevelopmentInfo(town));
TownDevelopmentInfo & developmentInfo = developmentInfos.back(); TownDevelopmentInfo & developmentInfo = developmentInfos.back();

View File

@ -63,7 +63,7 @@ void DangerHitMapAnalyzer::updateHitMap()
auto & node = hitMap[pos.x][pos.y][pos.z]; auto & node = hitMap[pos.x][pos.y][pos.z];
if(tileDanger > node.maximumDanger.danger 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.danger = tileDanger;
node.maximumDanger.turn = turn; node.maximumDanger.turn = turn;
@ -71,7 +71,7 @@ void DangerHitMapAnalyzer::updateHitMap()
} }
if(turn < node.fastestDanger.turn 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.danger = tileDanger;
node.fastestDanger.turn = turn; node.fastestDanger.turn = turn;
@ -101,8 +101,8 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
int turn = path.turn(); int turn = path.turn();
const HitMapNode & info = hitMap[tile.x][tile.y][tile.z]; const HitMapNode & info = hitMap[tile.x][tile.y][tile.z];
return info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.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); || (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger));
} }
const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const const HitMapNode & DangerHitMapAnalyzer::getObjectTreat(const CGObjectInstance * obj) const

View File

@ -20,6 +20,7 @@
class DLL_EXPORT IHeroManager //: public: IAbstractManager class DLL_EXPORT IHeroManager //: public: IAbstractManager
{ {
public: public:
virtual ~IHeroManager() = default;
virtual const std::map<HeroPtr, HeroRole> & getHeroRoles() const = 0; virtual const std::map<HeroPtr, HeroRole> & getHeroRoles() const = 0;
virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const = 0; virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const = 0;
virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0; virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0;
@ -31,6 +32,7 @@ public:
class DLL_EXPORT ISecondarySkillRule class DLL_EXPORT ISecondarySkillRule
{ {
public: public:
virtual ~ISecondarySkillRule() = default;
virtual void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const = 0; virtual void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const = 0;
}; };
@ -52,11 +54,10 @@ private:
static SecondarySkillEvaluator scountSkillsScores; static SecondarySkillEvaluator scountSkillsScores;
CCallback * cb; //this is enough, but we downcast from CCallback CCallback * cb; //this is enough, but we downcast from CCallback
const Nullkiller * ai;
std::map<HeroPtr, HeroRole> heroRoles; std::map<HeroPtr, HeroRole> heroRoles;
public: public:
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {}
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override; const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
HeroRole getHeroRole(const HeroPtr & hero) const override; HeroRole getHeroRole(const HeroPtr & hero) const override;
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override; int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;

View File

@ -149,7 +149,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
const int3 pos = obj->visitablePos(); 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)) || obj->wasVisited(ai->playerID))
{ {
return false; return false;

View File

@ -53,8 +53,6 @@ Goals::TGoalVec BuildingBehavior::decompose() const
for(auto & developmentInfo : developmentInfos) for(auto & developmentInfo : developmentInfos)
{ {
auto town = developmentInfo.town;
for(auto & buildingInfo : developmentInfo.toBuild) for(auto & buildingInfo : developmentInfo.toBuild)
{ {
if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[Res::GOLD] > 0) if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[Res::GOLD] > 0)

View File

@ -106,10 +106,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
{ {
if(path.getHeroStrength() > treat.danger) if(path.getHeroStrength() > treat.danger)
{ {
if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, 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 || (path.exchangeCount == 1 && path.turn() < treat.turn)
|| path.turn() < treat.turn - 1 || path.turn() < treat.turn - 1
|| path.turn() < treat.turn && treat.turn >= 2) || (path.turn() < treat.turn && treat.turn >= 2))
{ {
logAi->debug( logAi->debug(
"Hero %s can eliminate danger for town %s using path %s.", "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 // dismiss creatures we are not able to pick to be able to hide in garrison
if(town->garrisonHero if(town->garrisonHero
|| town->getUpperArmy()->stacksCount() == 0 || town->getUpperArmy()->stacksCount() == 0
|| town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL) || (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL))
{ {
tasks.push_back( tasks.push_back(
Goals::sptr(Composition() Goals::sptr(Composition()
@ -228,7 +228,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
continue; 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)) if(ai->nullkiller->arePathHeroesLocked(path))
{ {

View File

@ -55,7 +55,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town)
if(shortestPath.nodes.size() > 1 if(shortestPath.nodes.size() > 1
|| shortestPath.turn() != 0 || shortestPath.turn() != 0
|| shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4 || shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4
|| town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get()) || (town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get()))
return nullptr; return nullptr;
return shortestPath.targetHero; return shortestPath.targetHero;
@ -76,13 +76,13 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects()) 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::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE || obj->ID == Obj::CAMPFIRE
|| obj->ID == Obj::WATER_WHEEL) || obj->ID == Obj::WATER_WHEEL)
{ {
auto path = paths->getPathInfo(obj->visitablePos()); 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()) && path->reachable())
{ {
treasureSourcesCount++; treasureSourcesCount++;
@ -162,7 +162,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
auto garrisonHeroScore = ai->nullkiller->heroManager->evaluateHero(garrisonHero); auto garrisonHeroScore = ai->nullkiller->heroManager->evaluateHero(garrisonHero);
if(visitingHeroScore > garrisonHeroScore 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) if(canRecruitHero || ai->nullkiller->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
{ {

View File

@ -122,7 +122,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
{ {
//No free slot, we might discard our weakest stack //No free slot, we might discard our weakest stack
weakestStackPower = std::numeric_limits<ui64>().max(); weakestStackPower = std::numeric_limits<ui64>().max();
for (const auto stack : slots) for (const auto & stack : slots)
{ {
vstd::amin(weakestStackPower, stack.second->getPower()); vstd::amin(weakestStackPower, stack.second->getPower());
} }
@ -645,7 +645,6 @@ public:
} }
auto heroPtr = task->hero; auto heroPtr = task->hero;
auto day = ai->cb->getDate(Date::DAY);
auto hero = heroPtr.get(ai->cb.get()); auto hero = heroPtr.get(ai->cb.get());
bool checkGold = evaluationContext.danger == 0; bool checkGold = evaluationContext.danger == 0;
auto army = path.heroArmy; auto army = path.heroArmy;
@ -670,11 +669,8 @@ public:
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
{ {
private:
const Nullkiller * ai;
public: public:
ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{ {
@ -699,7 +695,6 @@ public:
for(auto objInfo : objects) for(auto objInfo : objects)
{ {
auto target = objInfo.first; auto target = objInfo.first;
auto day = ai->cb->getDate(Date::DAY);
bool checkGold = objInfo.second.danger == 0; bool checkGold = objInfo.second.danger == 0;
auto army = hero; auto army = hero;
@ -718,9 +713,6 @@ public:
if(boost > 8) if(boost > 8)
break; break;
} }
const AIPath & pathToCenter = clusterGoal.getPathToCenter();
} }
}; };

View File

@ -61,6 +61,7 @@ struct DLL_EXPORT EvaluationContext
class IEvaluationContextBuilder class IEvaluationContextBuilder
{ {
public: public:
virtual ~IEvaluationContextBuilder() = default;
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal goal) const = 0; virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal goal) const = 0;
}; };

View File

@ -558,7 +558,7 @@ bool AINodeStorage::selectNextActor()
for(auto actor = actors.begin(); actor != actors.end(); actor++) for(auto actor = actors.begin(); actor != actors.end(); actor++)
{ {
if(actor->get()->armyValue > currentActor->get()->armyValue if(actor->get()->armyValue > currentActor->get()->armyValue
|| actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor) || (actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor))
{ {
continue; continue;
} }

View File

@ -24,9 +24,6 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction class SummonBoatAction : public VirtualBoatAction
{ {
private:
const CGHeroInstance * hero;
public: public:
virtual void execute(const CGHeroInstance * hero) const override; virtual void execute(const CGHeroInstance * hero) const override;

View File

@ -18,6 +18,8 @@ struct AIPathNode;
class SpecialAction class SpecialAction
{ {
public: public:
virtual ~SpecialAction() = default;
virtual bool canAct(const AIPathNode * source) const virtual bool canAct(const AIPathNode * source) const
{ {
return true; return true;

View File

@ -269,8 +269,6 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
return result; // already inserted return result; // already inserted
} }
auto position = inserted.first;
auto differentMasks = (actor->chainMask & other->chainMask) == 0; auto differentMasks = (actor->chainMask & other->chainMask) == 0;
if(!differentMasks) return result; if(!differentMasks) return result;
@ -461,15 +459,6 @@ CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling,
continue; continue;
auto creature = creatureInfo.second.back().toCreature(); auto creature = creatureInfo.second.back().toCreature();
auto count = creatureInfo.first;
if(waitForGrowth)
{
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(dwelling);
count += town ? town->creatureGrowth(creature->level) : creature->growth;
}
dwellingCreatures->addToSlot( dwellingCreatures->addToSlot(
dwellingCreatures->getSlotFor(creature), dwellingCreatures->getSlotFor(creature),
creature->idNumber, creature->idNumber,

View File

@ -75,7 +75,8 @@ public:
TResources armyCost; TResources armyCost;
std::shared_ptr<TurnInfo> tiCache; std::shared_ptr<TurnInfo> tiCache;
ChainActor(){} ChainActor() = default;
virtual ~ChainActor() = default;
virtual std::string toString() const; virtual std::string toString() const;
ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); } ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); }

View File

@ -126,7 +126,6 @@ namespace AIPathfinding
const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node); const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node);
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject); auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
auto nodeHero = pathfinderHelper->hero;
QuestAction questAction(questInfo); QuestAction questAction(questInfo);
if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE) 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) nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{ {
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
node->specialAction.reset(new QuestAction(questAction)); node->specialAction.reset(new QuestAction(questAction));
}); });
} }

View File

@ -19,10 +19,6 @@ AIhelper::AIhelper()
armyManager.reset(new ArmyManager()); armyManager.reset(new ArmyManager());
} }
AIhelper::~AIhelper()
{
}
bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal) bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
{ {
return resourceManager->notifyGoalCompleted(goal); return resourceManager->notifyGoalCompleted(goal);

View File

@ -36,7 +36,6 @@ class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, pu
//TODO: vector<IAbstractManager> //TODO: vector<IAbstractManager>
public: public:
AIhelper(); AIhelper();
~AIhelper();
bool canAfford(const TResources & cost) const; bool canAfford(const TResources & cost) const;
TResources reservedResources() const override; TResources reservedResources() const override;

View File

@ -28,6 +28,7 @@ struct SlotInfo
class DLL_EXPORT IArmyManager //: public: IAbstractManager class DLL_EXPORT IArmyManager //: public: IAbstractManager
{ {
public: public:
virtual ~IArmyManager() = default;
virtual void init(CPlayerSpecificInfoCallback * CB) = 0; virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
virtual void setAI(VCAI * AI) = 0; virtual void setAI(VCAI * AI) = 0;
virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0; virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0;

View File

@ -120,14 +120,12 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
return o.goal; 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 if (!income[p.first]) //prioritize resources with 0 income
{ {
resourceType = p.first; resourceType = p.first;
amountToCollect = p.second; amountToCollect = p.second;
goalPriority /= amountToCollect; //need more resources -> lower priority
break; break;
} }
} }
@ -138,7 +136,7 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
std::map<Res::ERes, float> daysToEarn; std::map<Res::ERes, float> daysToEarn;
for (auto it : missingResources) for (auto it : missingResources)
daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first]; 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 //theoretically income can be negative, but that falls into this comparison
return lhs.second < rhs.second; return lhs.second < rhs.second;
@ -146,12 +144,9 @@ Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o
resourceType = boost::max_element(daysToEarn, incomeComparer)->first; resourceType = boost::max_element(daysToEarn, incomeComparer)->first;
amountToCollect = missingResources[resourceType]; 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)); return Goals::sptr(Goals::CollectRes(resourceType, amountToCollect).setisAbstract(true));
} }

View File

@ -366,10 +366,12 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
cl->additionalBattleInts[*player] -= battleEvents; cl->additionalBattleInts[*player] -= battleEvents;
} }
#if SCRIPTING_ENABLED
scripting::Pool * CBattleCallback::getContextPool() const scripting::Pool * CBattleCallback::getContextPool() const
{ {
return cl->getGlobalContextPool(); return cl->getGlobalContextPool();
} }
#endif
CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C ) CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C )
{ {

View File

@ -35,6 +35,8 @@ struct ArtifactLocation;
class IBattleCallback class IBattleCallback
{ {
public: public:
virtual ~IBattleCallback() = default;
bool waitTillRealize; //if true, request functions will return after they are realized by server 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! 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 //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 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 bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
#if SCRIPTING_ENABLED
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif
friend class CCallback; friend class CCallback;
friend class CClient; friend class CClient;

View File

@ -41,8 +41,8 @@ set(VCMI_VERSION_MAJOR 1)
set(VCMI_VERSION_MINOR 0) set(VCMI_VERSION_MINOR 0)
set(VCMI_VERSION_PATCH 0) set(VCMI_VERSION_PATCH 0)
option(ENABLE_ERM "Enable compilation of ERM scripting module" ON) option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
option(ENABLE_LUA "Enable compilation of LUA scripting module" ON) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
option(ENABLE_TEST "Enable compilation of unit tests" ON) option(ENABLE_TEST "Enable compilation of unit tests" ON)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0") 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_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename") 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 # # 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-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-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-reorder")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs") # fuzzylite - Operation.h
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-tags -Wno-unknown-warning-option -Wno-missing-braces") 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()
endif() endif()
if(ENABLE_LUA)
add_compile_definitions(SCRIPTING_ENABLED=1)
endif()
############################################ ############################################
# Finding packages # # Finding packages #
############################################ ############################################

View File

@ -79,11 +79,7 @@
"name": "macos-arm-conan-ninja-release", "name": "macos-arm-conan-ninja-release",
"displayName": "Ninja+Conan arm64 release", "displayName": "Ninja+Conan arm64 release",
"description": "VCMI MacOS-arm64 Ninja using Conan", "description": "VCMI MacOS-arm64 Ninja using Conan",
"inherits": "macos-conan-ninja-release", "inherits": "macos-conan-ninja-release"
"cacheVariables": {
"ENABLE_ERM": "OFF",
"ENABLE_LUA": "OFF"
}
}, },
{ {
"name": "macos-xcode-release", "name": "macos-xcode-release",

View File

@ -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: GENERAL:
* Spectator mode was implemented through command-line options * Spectator mode was implemented through command-line options

View File

@ -72,10 +72,12 @@ const HeroTypeService * CGameInfo::heroTypes() const
return globalServices->heroTypes(); return globalServices->heroTypes();
} }
#if SCRIPTING_ENABLED
const scripting::Service * CGameInfo::scripts() const const scripting::Service * CGameInfo::scripts() const
{ {
return globalServices->scripts(); return globalServices->scripts();
} }
#endif
const spells::Service * CGameInfo::spells() const const spells::Service * CGameInfo::spells() const
{ {

View File

@ -60,7 +60,9 @@ public:
const FactionService * factions() const override; const FactionService * factions() const override;
const HeroClassService * heroClasses() const override; const HeroClassService * heroClasses() const override;
const HeroTypeService * heroTypes() const override; const HeroTypeService * heroTypes() const override;
#if SCRIPTING_ENABLED
const scripting::Service * scripts() const override; const scripting::Service * scripts() const override;
#endif
const spells::Service * spells() const override; const spells::Service * spells() const override;
const SkillService * skills() const override; const SkillService * skills() const override;
const BattleFieldService * battlefields() const override; const BattleFieldService * battlefields() const override;

View File

@ -686,6 +686,7 @@ void processCommand(const std::string &message)
std::cout << "\rExtracting done :)\n"; std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n";
} }
#if SCRIPTING_ENABLED
else if(message=="get scripts") else if(message=="get scripts")
{ {
std::cout << "Command accepted.\t"; std::cout << "Command accepted.\t";
@ -708,6 +709,7 @@ void processCommand(const std::string &message)
std::cout << "\rExtracting done :)\n"; std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n";
} }
#endif
else if(message=="get txt") else if(message=="get txt")
{ {
std::cout << "Command accepted.\t"; std::cout << "Command accepted.\t";

View File

@ -17,6 +17,7 @@
#include "lobby/CSelectionBase.h" #include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h" #include "lobby/CLobbyScreen.h"
#include "windows/InfoWindows.h"
#include "mainmenu/CMainMenu.h" #include "mainmenu/CMainMenu.h"
@ -161,6 +162,20 @@ void CServerHandler::startLocalServerAndConnect()
threadRunLocalServer->join(); threadRunLocalServer->join();
th->update(); 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 #ifdef VCMI_ANDROID
{ {
CAndroidVMHelper envHelper; CAndroidVMHelper envHelper;

View File

@ -263,12 +263,14 @@ void CClient::serialize(BinarySerializer & h, const int version)
i->second->saveGame(h, version); i->second->saveGame(h, version);
} }
#if SCRIPTING_ENABLED
if(version >= 800) if(version >= 800)
{ {
JsonNode scriptsState; JsonNode scriptsState;
clientScripts->serializeState(h.saving, scriptsState); clientScripts->serializeState(h.saving, scriptsState);
h & scriptsState; h & scriptsState;
} }
#endif
} }
void CClient::serialize(BinaryDeserializer & h, const int version) void CClient::serialize(BinaryDeserializer & h, const int version)
@ -329,11 +331,13 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
LOCPLINT = prevInt; LOCPLINT = prevInt;
} }
#if SCRIPTING_ENABLED
{ {
JsonNode scriptsState; JsonNode scriptsState;
h & scriptsState; h & scriptsState;
clientScripts->serializeState(h.saving, scriptsState); clientScripts->serializeState(h.saving, scriptsState);
} }
#endif
logNetwork->trace("Loaded client part of save %d ms", CSH->th->getDiff()); 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() void CClient::endGame()
{ {
#if SCRIPTING_ENABLED
clientScripts.reset(); clientScripts.reset();
#endif
//suggest interfaces to finish their stuff (AI should interrupt any bg working threads) //suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
for(auto & i : playerint) for(auto & i : playerint)
@ -732,6 +738,7 @@ PlayerColor CClient::getLocalPlayer() const
return getCurrentPlayer(); return getCurrentPlayer();
} }
#if SCRIPTING_ENABLED
scripting::Pool * CClient::getGlobalContextPool() const scripting::Pool * CClient::getGlobalContextPool() const
{ {
return clientScripts.get(); return clientScripts.get();
@ -741,11 +748,14 @@ scripting::Pool * CClient::getContextPool() const
{ {
return clientScripts.get(); return clientScripts.get();
} }
#endif
void CClient::reinitScripting() void CClient::reinitScripting()
{ {
clientEventBus = make_unique<events::EventBus>(); clientEventBus = make_unique<events::EventBus>();
#if SCRIPTING_ENABLED
clientScripts.reset(new scripting::PoolImpl(this)); clientScripts.reset(new scripting::PoolImpl(this));
#endif
} }

View File

@ -39,10 +39,12 @@ namespace boost { class thread; }
template<typename T> class CApplier; template<typename T> class CApplier;
class CBaseForCLApply; class CBaseForCLApply;
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class PoolImpl; class PoolImpl;
} }
#endif
namespace events namespace events
{ {
@ -233,13 +235,18 @@ public:
void showInfoDialog(InfoWindow * iw) override {}; void showInfoDialog(InfoWindow * iw) override {};
void showInfoDialog(const std::string & msg, PlayerColor player) override {}; void showInfoDialog(const std::string & msg, PlayerColor player) override {};
#if SCRIPTING_ENABLED
scripting::Pool * getGlobalContextPool() const override; scripting::Pool * getGlobalContextPool() const override;
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif
private: private:
std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
std::map<PlayerColor, std::shared_ptr<CPlayerEnvironment>> playerEnvironments; std::map<PlayerColor, std::shared_ptr<CPlayerEnvironment>> playerEnvironments;
#if SCRIPTING_ENABLED
std::shared_ptr<scripting::PoolImpl> clientScripts; std::shared_ptr<scripting::PoolImpl> clientScripts;
#endif
std::unique_ptr<events::EventBus> clientEventBus; std::unique_ptr<events::EventBus> clientEventBus;
std::shared_ptr<CApplier<CBaseForCLApply>> applier; std::shared_ptr<CApplier<CBaseForCLApply>> applier;

View File

@ -1083,11 +1083,10 @@ void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attacked
std::array<int, 2> killedBySide = {0, 0}; std::array<int, 2> killedBySide = {0, 0};
int targets = 0, damage = 0; int targets = 0;
for(const StackAttackedInfo & attackedInfo : attackedInfos) for(const StackAttackedInfo & attackedInfo : attackedInfos)
{ {
++targets; ++targets;
damage += (int)attackedInfo.dmg;
ui8 side = attackedInfo.defender->side; ui8 side = attackedInfo.defender->side;
killedBySide.at(side) += attackedInfo.amountKilled; killedBySide.at(side) += attackedInfo.amountKilled;

View File

@ -10,10 +10,11 @@
#include "StdInc.h" #include "StdInc.h"
#include "CAnimation.h" #include "CAnimation.h"
#include "SDL_Extensions.h"
#include "SDL_Pixels.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
#include "../Graphics.h" #include "../Graphics.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/SDL_Pixels.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/ISimpleResourceLoader.h" #include "../lib/filesystem/ISimpleResourceLoader.h"

View File

@ -140,6 +140,7 @@ public:
//double click //double click
virtual void onDoubleClick(){} 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}; 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; const ui16 & active;
void addUsedEvents(ui16 newActions); void addUsedEvents(ui16 newActions);

View File

@ -155,6 +155,7 @@ typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Su
class ColorShifter class ColorShifter
{ {
public: public:
virtual ~ColorShifter() = default;
virtual SDL_Color shiftColor(SDL_Color clr) const = 0; virtual SDL_Color shiftColor(SDL_Color clr) const = 0;
}; };

View File

@ -9,9 +9,10 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "../mainmenu/CMainMenu.h"
#include "CCampaignScreen.h" #include "CCampaignScreen.h"
#include "CMainMenu.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CMessage.h" #include "../CMessage.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"

View File

@ -9,6 +9,8 @@
*/ */
#pragma once #pragma once
#include "../windows/CWindowObject.h"
class CLabel; class CLabel;
class CPicture; class CPicture;
class CButton; class CButton;

View File

@ -9,9 +9,10 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "CreditsScreen.h" #include "CreditsScreen.h"
#include "../mainmenu/CMainMenu.h"
#include "CMainMenu.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../widgets/TextControls.h" #include "../widgets/TextControls.h"
#include "../widgets/ObjectLists.h" #include "../widgets/ObjectLists.h"

View File

@ -14,6 +14,7 @@
#include "MiscWidgets.h" #include "MiscWidgets.h"
#include "CComponent.h" #include "CComponent.h"
#include "Images.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CMusicHandler.h" #include "../CMusicHandler.h"
@ -26,8 +27,6 @@
#include "../gui/SDL_Pixels.h" #include "../gui/SDL_Pixels.h"
#include "../gui/SDL_Compat.h" #include "../gui/SDL_Compat.h"
#include "../widgets/Images.h"
#include "../windows/InfoWindows.h" #include "../windows/InfoWindows.h"
#include "../windows/CAdvmapInterface.h" #include "../windows/CAdvmapInterface.h"
#include "../windows/GUIClasses.h" #include "../windows/GUIClasses.h"

View File

@ -10,6 +10,9 @@
#include "StdInc.h" #include "StdInc.h"
#include "CComponent.h" #include "CComponent.h"
#include "CArtifactHolder.h"
#include "Images.h"
#include <vcmi/spells/Service.h> #include <vcmi/spells/Service.h>
#include <vcmi/spells/Spell.h> #include <vcmi/spells/Spell.h>
@ -18,8 +21,6 @@
#include "../CMessage.h" #include "../CMessage.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../widgets/Images.h"
#include "../widgets/CArtifactHolder.h"
#include "../windows/CAdvmapInterface.h" #include "../windows/CAdvmapInterface.h"
#include "../../lib/CArtHandler.h" #include "../../lib/CArtHandler.h"

View File

@ -10,12 +10,13 @@
#include "StdInc.h" #include "StdInc.h"
#include "CGarrisonInt.h" #include "CGarrisonInt.h"
#include "Buttons.h"
#include "TextControls.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../widgets/Buttons.h"
#include "../widgets/TextControls.h"
#include "../windows/CCreatureWindow.h" #include "../windows/CCreatureWindow.h"
#include "../windows/GUIClasses.h" #include "../windows/GUIClasses.h"

View File

@ -14,8 +14,9 @@
#include "CHeroWindow.h" #include "CHeroWindow.h"
#include "CKingdomInterface.h" #include "CKingdomInterface.h"
#include "CSpellWindow.h" #include "CSpellWindow.h"
#include "GUIClasses.h"
#include "CTradeWindow.h" #include "CTradeWindow.h"
#include "GUIClasses.h"
#include "InfoWindows.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -35,7 +36,6 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h" #include "../gui/SDL_Extensions.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h" #include "../../CCallback.h"
@ -1217,7 +1217,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
if(itr != LOCPLINT->towns.end()) if(itr != LOCPLINT->towns.end())
LOCPLINT->showThievesGuildWindow(*itr); LOCPLINT->showThievesGuildWindow(*itr);
else else
LOCPLINT->showInfoDialog("No available town with tavern!"); LOCPLINT->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["noTownWithTavern"].String());
} }
return; return;
case SDLK_i: case SDLK_i:
@ -1249,7 +1249,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
case SDLK_r: case SDLK_r:
if(isActive() && LOCPLINT->ctrlPressed()) 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); [](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); }, nullptr);
} }
return; return;
@ -1308,7 +1308,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
if(townWithMarket) //if any town has marketplace, open window if(townWithMarket) //if any town has marketplace, open window
GH.pushIntT<CMarketplaceWindow>(townWithMarket); GH.pushIntT<CMarketplaceWindow>(townWithMarket);
else //if not - complain 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 else if(isActive()) //no ctrl, advmapint is on the top => switch to town
{ {

View File

@ -13,6 +13,7 @@
#include "CAdvmapInterface.h" #include "CAdvmapInterface.h"
#include "CHeroWindow.h" #include "CHeroWindow.h"
#include "CTradeWindow.h" #include "CTradeWindow.h"
#include "InfoWindows.h"
#include "GUIClasses.h" #include "GUIClasses.h"
#include "QuickRecruitmentWindow.h" #include "QuickRecruitmentWindow.h"
@ -24,7 +25,6 @@
#include "../Graphics.h" #include "../Graphics.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/SDL_Extensions.h" #include "../gui/SDL_Extensions.h"
#include "../windows/InfoWindows.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../widgets/CComponent.h" #include "../widgets/CComponent.h"
@ -842,7 +842,16 @@ void CCastleBuildings::enterDwelling(int level)
void CCastleBuildings::enterToTheQuickRecruitmentWindow() void CCastleBuildings::enterToTheQuickRecruitmentWindow()
{ {
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<QuickRecruitmentWindow>(town, pos); GH.pushIntT<QuickRecruitmentWindow>(town, pos);
else
CInfoWindow::showInfoDialog(CGI->generaltexth->localizedTexts["townHall"]["noCreaturesToRecruit"].String(), {});
} }
void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades) void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades)
@ -1235,9 +1244,9 @@ void CCastleInterface::recreateIcons()
hall = std::make_shared<CTownInfo>(80, 413, town, true); hall = std::make_shared<CTownInfo>(80, 413, town, true);
fort = std::make_shared<CTownInfo>(122, 413, town, false); fort = std::make_shared<CTownInfo>(122, 413, town, false);
fastArmyPurhase = std::make_shared<CButton>(Point(122, 413), "itmcl.def", CButton::tooltip(), [&](){builds->enterToTheQuickRecruitmentWindow();}); fastArmyPurchase = std::make_shared<CButton>(Point(122, 413), "itmcl.def", CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); });
fastArmyPurhase->setImageOrder(town->fortLevel()-1, town->fortLevel()-1, town->fortLevel()-1, town->fortLevel()-1); fastArmyPurchase->setImageOrder(town->fortLevel() - 1, town->fortLevel() - 1, town->fortLevel() - 1, town->fortLevel() - 1);
fastArmyPurhase->setAnimateLonelyFrame(true); fastArmyPurchase->setAnimateLonelyFrame(true);
creainfo.clear(); creainfo.clear();

View File

@ -209,7 +209,7 @@ class CCastleInterface : public CStatusbarWindow, public CGarrisonHolder
std::shared_ptr<CButton> exit; std::shared_ptr<CButton> exit;
std::shared_ptr<CButton> split; std::shared_ptr<CButton> split;
std::shared_ptr<CButton> fastArmyPurhase; std::shared_ptr<CButton> fastArmyPurchase;
std::vector<std::shared_ptr<CCreaInfo>> creainfo;//small icons of creatures (bottom-left corner); std::vector<std::shared_ptr<CCreaInfo>> creainfo;//small icons of creatures (bottom-left corner);

View File

@ -12,6 +12,7 @@
#include "CAdvmapInterface.h" #include "CAdvmapInterface.h"
#include "CCastleInterface.h" #include "CCastleInterface.h"
#include "InfoWindows.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CMT.h" #include "../CMT.h"
@ -19,7 +20,6 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h" #include "../widgets/CComponent.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h" #include "../../CCallback.h"

View File

@ -562,7 +562,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
if(!texts.empty()) if(!texts.empty())
owner->myInt->showInfoDialog(texts.front()); owner->myInt->showInfoDialog(texts.front());
else 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 else //adventure spell

View File

@ -10,6 +10,8 @@
#include "StdInc.h" #include "StdInc.h"
#include "CWindowObject.h" #include "CWindowObject.h"
#include "CAdvmapInterface.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../gui/SDL_Pixels.h" #include "../gui/SDL_Pixels.h"
@ -26,7 +28,6 @@
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../CMessage.h" #include "../CMessage.h"
#include "../CMusicHandler.h" #include "../CMusicHandler.h"
#include "../windows/CAdvmapInterface.h"
#include "../../CCallback.h" #include "../../CCallback.h"

View File

@ -17,6 +17,7 @@
#include "QuickRecruitmentWindow.h" #include "QuickRecruitmentWindow.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../../lib/CCreatureHandler.h" #include "../../lib/CCreatureHandler.h"
#include "CCreatureWindow.h"
void CreaturePurchaseCard::initButtons() void CreaturePurchaseCard::initButtons()
{ {
@ -46,6 +47,7 @@ void CreaturePurchaseCard::switchCreatureLevel()
auto index = vstd::find_pos(upgradesID, creatureOnTheCard->idNumber); auto index = vstd::find_pos(upgradesID, creatureOnTheCard->idNumber);
auto nextCreatureId = vstd::circularAt(upgradesID, ++index); auto nextCreatureId = vstd::circularAt(upgradesID, ++index);
creatureOnTheCard = nextCreatureId.toCreature(); creatureOnTheCard = nextCreatureId.toCreature();
creatureClickArea = std::make_shared<CCreatureClickArea>(Point(pos.x + CCreatureClickArea::CREATURE_X_POS, pos.y + CCreatureClickArea::CREATURE_Y_POS), picture, creatureOnTheCard);
picture = std::make_shared<CCreaturePic>(parent->pos.x, parent->pos.y, creatureOnTheCard); picture = std::make_shared<CCreaturePic>(parent->pos.x, parent->pos.y, creatureOnTheCard);
parent->updateAllSliders(); parent->updateAllSliders();
cost->set(creatureOnTheCard->cost * slider->getValue()); cost->set(creatureOnTheCard->cost * slider->getValue());
@ -54,14 +56,14 @@ void CreaturePurchaseCard::switchCreatureLevel()
void CreaturePurchaseCard::initAmountInfo() void CreaturePurchaseCard::initAmountInfo()
{ {
availableAmount = std::make_shared<CLabel>(pos.x + 25, pos.y + 146, FONT_SMALL, CENTER, Colors::YELLOW); availableAmount = std::make_shared<CLabel>(pos.x + 25, pos.y + 146, FONT_SMALL, CENTER, Colors::YELLOW);
purhaseAmount = std::make_shared<CLabel>(pos.x + 76, pos.y + 146, FONT_SMALL, CENTER, Colors::WHITE); purchaseAmount = std::make_shared<CLabel>(pos.x + 76, pos.y + 146, FONT_SMALL, CENTER, Colors::WHITE);
updateAmountInfo(0); updateAmountInfo(0);
} }
void CreaturePurchaseCard::updateAmountInfo(int value) void CreaturePurchaseCard::updateAmountInfo(int value)
{ {
availableAmount->setText(boost::lexical_cast<std::string>(maxAmount-value)); availableAmount->setText(boost::lexical_cast<std::string>(maxAmount-value));
purhaseAmount->setText(boost::lexical_cast<std::string>(value)); purchaseAmount->setText(boost::lexical_cast<std::string>(value));
} }
void CreaturePurchaseCard::initSlider() void CreaturePurchaseCard::initSlider()
@ -96,8 +98,27 @@ void CreaturePurchaseCard::initView()
{ {
picture = std::make_shared<CCreaturePic>(pos.x, pos.y, creatureOnTheCard); picture = std::make_shared<CCreaturePic>(pos.x, pos.y, creatureOnTheCard);
background = std::make_shared<CPicture>("QuickRecruitmentWindow/CreaturePurchaseCard.png", pos.x-4, pos.y-50); background = std::make_shared<CPicture>("QuickRecruitmentWindow/CreaturePurchaseCard.png", pos.x-4, pos.y-50);
initButtons();
creatureClickArea = std::make_shared<CCreatureClickArea>(Point(pos.x + CCreatureClickArea::CREATURE_X_POS, pos.y + CCreatureClickArea::CREATURE_Y_POS), picture, creatureOnTheCard);
initAmountInfo(); initAmountInfo();
initSlider(); initSlider();
initButtons();
initCostBox(); initCostBox();
} }
CreaturePurchaseCard::CCreatureClickArea::CCreatureClickArea(const Point & position, const std::shared_ptr<CCreaturePic> 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<CStackWindow>(creatureOnTheCard, true);
}

View File

@ -25,6 +25,7 @@ public:
QuickRecruitmentWindow * parent; QuickRecruitmentWindow * parent;
int maxAmount; int maxAmount;
void sliderMoved(int to); void sliderMoved(int to);
CreaturePurchaseCard(const std::vector<CreatureID> & creaturesID, Point position, int creaturesMaxAmount, QuickRecruitmentWindow * parents); CreaturePurchaseCard(const std::vector<CreatureID> & creaturesID, Point position, int creaturesMaxAmount, QuickRecruitmentWindow * parents);
private: private:
void initView(); void initView();
@ -42,10 +43,28 @@ private:
void initCostBox(); 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<CCreaturePic> 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<CButton> maxButton, minButton, creatureSwitcher; std::shared_ptr<CButton> maxButton, minButton, creatureSwitcher;
std::shared_ptr<CLabel> availableAmount, purhaseAmount; std::shared_ptr<CLabel> availableAmount, purchaseAmount;
std::shared_ptr<CCreaturePic> picture; std::shared_ptr<CCreaturePic> picture;
std::shared_ptr<CreatureCostBox> cost; std::shared_ptr<CreatureCostBox> cost;
std::vector<CreatureID> upgradesID; std::vector<CreatureID> upgradesID;
std::shared_ptr<CPicture> background; std::shared_ptr<CPicture> background;
std::shared_ptr<CCreatureClickArea> creatureClickArea;
}; };

View File

@ -15,6 +15,7 @@
#include "CCreatureWindow.h" #include "CCreatureWindow.h"
#include "CHeroWindow.h" #include "CHeroWindow.h"
#include "CreatureCostBox.h" #include "CreatureCostBox.h"
#include "InfoWindows.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -36,7 +37,6 @@
#include "../widgets/CComponent.h" #include "../widgets/CComponent.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../lobby/CSavingScreen.h" #include "../lobby/CSavingScreen.h"
@ -1286,7 +1286,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
int skill = hero->secSkills[g].first, int skill = hero->secSkills[g].first,
level = hero->secSkills[g].second; // <1, 3> level = hero->secSkills[g].second; // <1, 3>
secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>()); secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
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]->baseType = 1;
secSkillAreas[b][g]->type = skill; secSkillAreas[b][g]->type = skill;
@ -1301,12 +1301,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
heroAreas[b] = std::make_shared<CHeroArea>(257 + 228*b, 13, hero); heroAreas[b] = std::make_shared<CHeroArea>(257 + 228*b, 13, hero);
specialtyAreas[b] = std::make_shared<LRClickableAreaWText>(); specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
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]->hoverText = CGI->generaltexth->heroscrn[27];
specialtyAreas[b]->text = hero->type->specDescr; specialtyAreas[b]->text = hero->type->specDescr;
experienceAreas[b] = std::make_shared<LRClickableAreaWText>(); experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
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]->hoverText = CGI->generaltexth->heroscrn[9];
experienceAreas[b]->text = CGI->generaltexth->allTexts[2]; experienceAreas[b]->text = CGI->generaltexth->allTexts[2];
boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->level)); boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->level));
@ -1314,7 +1314,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->exp)); boost::algorithm::replace_first(experienceAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->exp));
spellPointsAreas[b] = std::make_shared<LRClickableAreaWText>(); spellPointsAreas[b] = std::make_shared<LRClickableAreaWText>();
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]->hoverText = CGI->generaltexth->heroscrn[22];
spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205]; spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205];
boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->name); boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->name);

View File

@ -9,13 +9,13 @@
*/ */
#pragma once #pragma once
#include "CWindowObject.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
#include "../lib/ResourceSet.h" #include "../lib/ResourceSet.h"
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
#include "../widgets/CArtifactHolder.h" #include "../widgets/CArtifactHolder.h"
#include "../widgets/CGarrisonInt.h" #include "../widgets/CGarrisonInt.h"
#include "../widgets/Images.h" #include "../widgets/Images.h"
#include "../windows/CWindowObject.h"
class CGDwelling; class CGDwelling;
class CreatureCostBox; class CreatureCostBox;

View File

@ -10,6 +10,8 @@
#include "StdInc.h" #include "StdInc.h"
#include "InfoWindows.h" #include "InfoWindows.h"
#include "CAdvmapInterface.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
#include "../Graphics.h" #include "../Graphics.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -17,7 +19,6 @@
#include "../CMessage.h" #include "../CMessage.h"
#include "../CMusicHandler.h" #include "../CMusicHandler.h"
#include "../windows/CAdvmapInterface.h"
#include "../widgets/CComponent.h" #include "../widgets/CComponent.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"

View File

@ -35,7 +35,7 @@ void QuickRecruitmentWindow::setCancelButton()
void QuickRecruitmentWindow::setBuyButton() void QuickRecruitmentWindow::setBuyButton()
{ {
buyButton = std::make_shared<CButton>(Point((pos.w/2)-32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purhaseUnits(); }, SDLK_RETURN); buyButton = std::make_shared<CButton>(Point((pos.w / 2) - 32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purchaseUnits(); }, SDLK_RETURN);
cancelButton->assignedKeys.insert(SDLK_ESCAPE); cancelButton->assignedKeys.insert(SDLK_ESCAPE);
buyButton->setImageOrder(0, 1, 2, 3); buyButton->setImageOrder(0, 1, 2, 3);
} }
@ -46,7 +46,7 @@ void QuickRecruitmentWindow::setMaxButton()
maxButton->setImageOrder(0, 1, 2, 3); maxButton->setImageOrder(0, 1, 2, 3);
} }
void QuickRecruitmentWindow::setCreaturePurhaseCards() void QuickRecruitmentWindow::setCreaturePurchaseCards()
{ {
int availableAmount = getAvailableCreatures(); int availableAmount = getAvailableCreatures();
Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64); Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64);
@ -99,7 +99,7 @@ void QuickRecruitmentWindow::maxAllCards(std::vector<std::shared_ptr<CreaturePur
} }
void QuickRecruitmentWindow::purhaseUnits() void QuickRecruitmentWindow::purchaseUnits()
{ {
for(auto selected : cards) for(auto selected : cards)
{ {
@ -154,6 +154,6 @@ QuickRecruitmentWindow::QuickRecruitmentWindow(const CGTownInstance * townd, Rec
initWindow(startupPosition); initWindow(startupPosition);
setButtons(); setButtons();
setCreaturePurhaseCards(); setCreaturePurchaseCards();
maxAllCards(cards); maxAllCards(cards);
} }

View File

@ -31,11 +31,11 @@ private:
void setBuyButton(); void setBuyButton();
void setMaxButton(); void setMaxButton();
void setCreaturePurhaseCards(); void setCreaturePurchaseCards();
void maxAllCards(std::vector<std::shared_ptr<CreaturePurchaseCard>> cards); void maxAllCards(std::vector<std::shared_ptr<CreaturePurchaseCard>> cards);
void maxAllSlidersAmount(std::vector<std::shared_ptr<CreaturePurchaseCard>> cards); void maxAllSlidersAmount(std::vector<std::shared_ptr<CreaturePurchaseCard>> cards);
void purhaseUnits(); void purchaseUnits();
const CGTownInstance * town; const CGTownInstance * town;
std::shared_ptr<CButton> maxButton, buyButton, cancelButton; std::shared_ptr<CButton> maxButton, buyButton, cancelButton;

View File

@ -21,8 +21,19 @@
"Impossible" "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" "playerAttacked" : "Player has been attacked: %s"
}, },
"server" :
{
"errors" :
{
"existingProcess" : "Another vcmiserver process is running, please terminate it first"
}
},
"systemOptions" : "systemOptions" :
{ {
"fullscreenButton" : "fullscreenButton" :
@ -44,6 +55,7 @@
"townHall" : "townHall" :
{ {
"missingBase" : "Base building %s must be built first", "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.", "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).", "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).", "greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).",

View File

@ -27,6 +27,8 @@ struct CatapultAttack;
class DLL_LINKAGE ServerCallback class DLL_LINKAGE ServerCallback
{ {
public: public:
virtual ~ServerCallback() = default;
virtual void complain(const std::string & problem) = 0; virtual void complain(const std::string & problem) = 0;
virtual bool describeChanges() const = 0; virtual bool describeChanges() const = 0;

View File

@ -32,10 +32,12 @@ namespace spells
} }
} }
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class Service; class Service;
} }
#endif
class DLL_LINKAGE Services class DLL_LINKAGE Services
{ {
@ -47,7 +49,9 @@ public:
virtual const FactionService * factions() const = 0; virtual const FactionService * factions() const = 0;
virtual const HeroClassService * heroClasses() const = 0; virtual const HeroClassService * heroClasses() const = 0;
virtual const HeroTypeService * heroTypes() const = 0; virtual const HeroTypeService * heroTypes() const = 0;
#if SCRIPTING_ENABLED
virtual const scripting::Service * scripts() const = 0; virtual const scripting::Service * scripts() const = 0;
#endif
virtual const spells::Service * spells() const = 0; virtual const spells::Service * spells() const = 0;
virtual const SkillService * skills() const = 0; virtual const SkillService * skills() const = 0;
virtual const BattleFieldService * battlefields() const = 0; virtual const BattleFieldService * battlefields() const = 0;

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#if SCRIPTING_ENABLED
#include <vcmi/Environment.h> #include <vcmi/Environment.h>
class Services; class Services;
@ -78,3 +79,4 @@ public:
} }
#endif

View File

@ -12,29 +12,53 @@
#include "../../lib/JsonNode.h" #include "../../lib/JsonNode.h"
#include "../../lib/filesystem/CFileInputStream.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) bool CModEntry::compareVersions(QString lesser, QString greater)
{ {
static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch auto versionLesser = QVersionNumber::fromString(lesser);
auto versionGreater = QVersionNumber::fromString(greater);
QStringList lesserList = lesser.split("."); return versionLesser < versionGreater;
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;
} }
QString CModEntry::sizeToString(double size) QString CModEntry::sizeToString(double size)
@ -92,6 +116,15 @@ bool CModEntry::isUpdateable() const
return false; 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 bool CModEntry::isEssential() const
{ {
return getValue("storedLocaly").toBool(); return getValue("storedLocaly").toBool();
@ -102,6 +135,11 @@ bool CModEntry::isInstalled() const
return !localData.isEmpty(); return !localData.isEmpty();
} }
bool CModEntry::isValid() const
{
return !localData.isEmpty() || !repository.isEmpty();
}
int CModEntry::getModStatus() const int CModEntry::getModStatus() const
{ {
int status = 0; int status = 0;
@ -193,7 +231,11 @@ static QVariant getValue(QVariant input, QString path)
QString remainder = "/" + path.section('/', 2, -1); QString remainder = "/" + path.section('/', 2, -1);
entryName.remove(0, 1); entryName.remove(0, 1);
return getValue(input.toMap().value(entryName), remainder); QMap<QString, QString> keyNormalize;
for(auto & key : input.toMap().keys())
keyNormalize[key.toLower()] = key;
return getValue(input.toMap().value(keyNormalize[entryName]), remainder);
} }
else else
{ {
@ -203,6 +245,7 @@ static QVariant getValue(QVariant input, QString path)
CModEntry CModList::getMod(QString modname) const CModEntry CModList::getMod(QString modname) const
{ {
modname = modname.toLower();
QVariantMap repo; QVariantMap repo;
QVariantMap local = localModList[modname].toMap(); QVariantMap local = localModList[modname].toMap();
QVariantMap settings; 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) for(auto entry : repositories)
{ {
QVariant repoVal = getValue(entry, path); QVariant repoVal = getValue(entry, path);
if(repoVal.isValid()) 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(); if(repo.empty() || CModEntry::compareVersions(repo["version"].toString(), repoValMap["version"].toString()))
{
repo = repoValMap;
} }
else
{
if(CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString()))
repo = repoVal.toMap();
} }
} }
} }
@ -297,12 +349,12 @@ QVector<QString> CModList::getModList() const
{ {
for(auto it = repo.begin(); it != repo.end(); it++) 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++) for(auto it = localModList.begin(); it != localModList.end(); it++)
{ {
knownMods.insert(it.key()); knownMods.insert(it.key().toLower());
} }
for(auto entry : knownMods) for(auto entry : knownMods)

View File

@ -51,6 +51,10 @@ public:
bool isInstalled() const; bool isInstalled() const;
// vcmi essential files // vcmi essential files
bool isEssential() const; bool isEssential() const;
// checks if verison is compatible with vcmi
bool isCompatible() const;
// returns if has any data
bool isValid() const;
// see ModStatus enum // see ModStatus enum
int getModStatus() const; int getModStatus() const;

View File

@ -245,6 +245,7 @@ bool CModFilterModel::filterMatchesThis(const QModelIndex & source) const
{ {
CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString()); CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString());
return (mod.getModStatus() & filterMask) == filteredType && return (mod.getModStatus() & filterMask) == filteredType &&
mod.isValid() &&
QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent()); QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent());
} }

View File

@ -253,7 +253,7 @@
</property> </property>
<property name="html"> <property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -385,6 +385,31 @@ p, li { white-space: pre-wrap; }
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QPushButton" name="uninstallButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>51</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="enableButton"> <widget class="QPushButton" name="enableButton">
<property name="sizePolicy"> <property name="sizePolicy">
@ -460,31 +485,6 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="uninstallButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>51</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="installButton"> <widget class="QPushButton" name="installButton">
<property name="sizePolicy"> <property name="sizePolicy">

View File

@ -169,6 +169,10 @@ bool CModManager::canEnableMod(QString modname)
if(!mod.isInstalled()) if(!mod.isInstalled())
return addError(modname, "Mod must be installed first"); 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()) for(auto modEntry : mod.getValue("depends").toStringList())
{ {
if(!modList->hasMod(modEntry)) // required mod is not available if(!modList->hasMod(modEntry)) // required mod is not available

View File

@ -126,10 +126,12 @@ std::shared_ptr<CBattleGameInterface> CDynLibHandler::getNewBattleAI(std::string
return createAnyAI<CBattleGameInterface>(dllname, "GetNewBattleAI"); return createAnyAI<CBattleGameInterface>(dllname, "GetNewBattleAI");
} }
#if SCRIPTING_ENABLED
std::shared_ptr<scripting::Module> CDynLibHandler::getNewScriptingModule(const boost::filesystem::path & dllname) std::shared_ptr<scripting::Module> CDynLibHandler::getNewScriptingModule(const boost::filesystem::path & dllname)
{ {
return createAny<scripting::Module>(dllname, "GetNewModule"); return createAny<scripting::Module>(dllname, "GetNewModule");
} }
#endif
BattleAction CGlobalAI::activeStack(const CStack * stack) BattleAction CGlobalAI::activeStack(const CStack * stack)
{ {

View File

@ -56,10 +56,14 @@ class CSaveFile;
class BinaryDeserializer; class BinaryDeserializer;
class BinarySerializer; class BinarySerializer;
struct ArtifactLocation; struct ArtifactLocation;
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class Module; class Module;
} }
#endif
class DLL_LINKAGE CBattleGameInterface : public IBattleEventsReceiver class DLL_LINKAGE CBattleGameInterface : public IBattleEventsReceiver
{ {
@ -110,7 +114,9 @@ class DLL_LINKAGE CDynLibHandler
public: public:
static std::shared_ptr<CGlobalAI> getNewAI(std::string dllname); static std::shared_ptr<CGlobalAI> getNewAI(std::string dllname);
static std::shared_ptr<CBattleGameInterface> getNewBattleAI(std::string dllname); static std::shared_ptr<CBattleGameInterface> getNewBattleAI(std::string dllname);
#if SCRIPTING_ENABLED
static std::shared_ptr<scripting::Module> getNewScriptingModule(const boost::filesystem::path & dllname); static std::shared_ptr<scripting::Module> getNewScriptingModule(const boost::filesystem::path & dllname);
#endif
}; };
class DLL_LINKAGE CGlobalAI : public CGameInterface // AI class (to derivate) class DLL_LINKAGE CGlobalAI : public CGameInterface // AI class (to derivate)

View File

@ -434,7 +434,9 @@ void CContentHandler::init()
handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); 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("skills", ContentTypeHandler(VLC->skillh, "skill")));
handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template"))); handlers.insert(std::make_pair("templates", ContentTypeHandler((IHandlerBase *)VLC->tplh, "template")));
#if SCRIPTING_ENABLED
handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script"))); 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("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield")));
handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle"))); handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle")));
//TODO: any other types of moddables? //TODO: any other types of moddables?
@ -532,6 +534,51 @@ JsonNode addMeta(JsonNode config, std::string meta)
return config; 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(): CModInfo::CModInfo():
checksum(0), checksum(0),
enabled(false), enabled(false),
@ -551,6 +598,12 @@ CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode
validation(PENDING), validation(PENDING),
config(addMeta(config, identifier)) 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); loadLocalData(local);
} }
@ -602,6 +655,14 @@ void CModInfo::loadLocalData(const JsonNode & data)
checksum = strtol(data["checksum"].String().c_str(), nullptr, 16); 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) if (enabled)
validation = validated ? PASSED : PENDING; validation = validated ? PASSED : PENDING;
else else
@ -986,7 +1047,9 @@ void CModHandler::load()
for(const TModID & modName : activeMods) for(const TModID & modName : activeMods)
content->load(allMods[modName]); content->load(allMods[modName]);
#if SCRIPTING_ENABLED
VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load
#endif
content->loadCustom(); content->loadCustom();

View File

@ -178,6 +178,30 @@ public:
PASSED 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 <typename Handler> void serialize(Handler &h, const int version)
{
h & major;
h & minor;
h & patch;
}
};
/// identifier, identical to name of folder with mod /// identifier, identical to name of folder with mod
std::string identifier; std::string identifier;
@ -185,6 +209,13 @@ public:
std::string name; std::string name;
std::string description; 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 /// list of mods that should be loaded before this one
std::set <TModID> dependencies; std::set <TModID> dependencies;
@ -210,18 +241,6 @@ public:
static std::string getModDir(std::string name); static std::string getModDir(std::string name);
static std::string getModFile(std::string name); static std::string getModFile(std::string name);
template <typename Handler> 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: private:
void loadLocalData(const JsonNode & data); void loadLocalData(const JsonNode & data);
}; };
@ -257,6 +276,13 @@ class DLL_LINKAGE CModHandler
void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods);
public: public:
class Incompatibility: public std::logic_error
{
public:
Incompatibility(const std::string & w): std::logic_error(w)
{}
};
CIdentifierStorage identifiers; CIdentifierStorage identifiers;
std::shared_ptr<CContentHandler> content; //(!)Do not serialize std::shared_ptr<CContentHandler> content; //(!)Do not serialize
@ -336,8 +362,37 @@ public:
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & allMods; if(h.saving)
{
h & activeMods; h & activeMods;
for(const auto & m : activeMods)
h & allMods[m].version;
}
else
{
loadMods();
std::vector<TModID> 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 & settings;
h & modules; h & modules;
h & identifiers; h & identifiers;

View File

@ -386,6 +386,9 @@ class DLL_LINKAGE INodeStorage
{ {
public: public:
using ELayer = EPathfindingLayer; using ELayer = EPathfindingLayer;
virtual ~INodeStorage() = default;
virtual std::vector<CGPathNode *> getInitialNodes() = 0; virtual std::vector<CGPathNode *> getInitialNodes() = 0;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual std::vector<CGPathNode *> calculateNeighbours(
@ -448,6 +451,7 @@ public:
PathfinderConfig( PathfinderConfig(
std::shared_ptr<INodeStorage> nodeStorage, std::shared_ptr<INodeStorage> nodeStorage,
std::vector<std::shared_ptr<IPathfindingRule>> rules); std::vector<std::shared_ptr<IPathfindingRule>> rules);
virtual ~PathfinderConfig() = default;
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0; virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0;
}; };

View File

@ -11,6 +11,7 @@
#include "CScriptingModule.h" #include "CScriptingModule.h"
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
@ -30,3 +31,4 @@ Module::Module()
Module::~Module() = default; Module::~Module() = default;
} }
#endif

View File

@ -9,6 +9,7 @@
*/ */
#pragma once #pragma once
#if SCRIPTING_ENABLED
#include <vcmi/scripting/Service.h> #include <vcmi/scripting/Service.h>
namespace spells namespace spells
@ -45,3 +46,4 @@ public:
}; };
} }
#endif

View File

@ -51,10 +51,18 @@ const TeamID TeamID::NO_TEAM = TeamID(255);
namespace GameConstants 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 #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 #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 #endif
} }

View File

@ -36,6 +36,9 @@ struct IdTag
namespace GameConstants 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; DLL_LINKAGE extern const std::string VCMI_VERSION;
const int PUZZLE_MAP_PIECES = 48; const int PUZZLE_MAP_PIECES = 48;

View File

@ -27,12 +27,13 @@ class CStackBasicDescriptor;
class CGCreature; class CGCreature;
struct ShashInt3; struct ShashInt3;
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class Context;
class Pool; class Pool;
class Script;
} }
#endif
class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback
{ {
@ -132,7 +133,9 @@ class DLL_LINKAGE IGameCallback : public CPrivilegedInfoCallback, public IGameEv
public: public:
virtual ~IGameCallback(){}; virtual ~IGameCallback(){};
#if SCRIPTING_ENABLED
virtual scripting::Pool * getGlobalContextPool() const = 0; virtual scripting::Pool * getGlobalContextPool() const = 0;
#endif
//get info //get info
virtual bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero); virtual bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero);

View File

@ -11,6 +11,7 @@
#include "ScriptHandler.h" #include "ScriptHandler.h"
#if SCRIPTING_ENABLED
#include <vcmi/Services.h> #include <vcmi/Services.h>
#include <vcmi/Environment.h> #include <vcmi/Environment.h>
@ -311,3 +312,4 @@ void ScriptHandler::saveState(JsonNode & state)
} }
#endif

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#if SCRIPTING_ENABLED
#include <vcmi/scripting/Service.h> #include <vcmi/scripting/Service.h>
#include "IHandlerBase.h" #include "IHandlerBase.h"
#include "JsonNode.h" #include "JsonNode.h"
@ -131,3 +132,4 @@ private:
}; };
} }
#endif

View File

@ -82,10 +82,12 @@ const HeroTypeService * LibClasses::heroTypes() const
return heroh; return heroh;
} }
#if SCRIPTING_ENABLED
const scripting::Service * LibClasses::scripts() const const scripting::Service * LibClasses::scripts() const
{ {
return scriptHandler; return scriptHandler;
} }
#endif
const spells::Service * LibClasses::spells() const 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?) createHandler(tplh, "Template", pomtime); //templates need already resolved identifiers (refactor?)
#if SCRIPTING_ENABLED
createHandler(scriptHandler, "Script", pomtime); createHandler(scriptHandler, "Script", pomtime);
#endif
createHandler(battlefieldsHandler, "Battlefields", pomtime); createHandler(battlefieldsHandler, "Battlefields", pomtime);
@ -248,7 +252,9 @@ void LibClasses::clear()
delete bth; delete bth;
delete tplh; delete tplh;
delete terviewh; delete terviewh;
#if SCRIPTING_ENABLED
delete scriptHandler; delete scriptHandler;
#endif
delete battlefieldsHandler; delete battlefieldsHandler;
makeNull(); makeNull();
} }
@ -268,7 +274,9 @@ void LibClasses::makeNull()
bth = nullptr; bth = nullptr;
tplh = nullptr; tplh = nullptr;
terviewh = nullptr; terviewh = nullptr;
#if SCRIPTING_ENABLED
scriptHandler = nullptr; scriptHandler = nullptr;
#endif
battlefieldsHandler = nullptr; battlefieldsHandler = nullptr;
} }
@ -289,10 +297,12 @@ void LibClasses::callWhenDeserializing()
//modh->loadConfigFromFile ("defaultMods"); //TODO: remember last saved config //modh->loadConfigFromFile ("defaultMods"); //TODO: remember last saved config
} }
#if SCRIPTING_ENABLED
void LibClasses::scriptsLoaded() void LibClasses::scriptsLoaded()
{ {
scriptHandler->performRegistration(this); scriptHandler->performRegistration(this);
} }
#endif
LibClasses::~LibClasses() LibClasses::~LibClasses()
{ {

View File

@ -33,10 +33,13 @@ class CTerrainViewPatternConfig;
class CRmgTemplateStorage; class CRmgTemplateStorage;
class IHandlerBase; class IHandlerBase;
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class ScriptHandler; class ScriptHandler;
} }
#endif
/// Loads and constructs several handlers /// Loads and constructs several handlers
class DLL_LINKAGE LibClasses : public Services class DLL_LINKAGE LibClasses : public Services
@ -56,7 +59,9 @@ public:
const FactionService * factions() const override; const FactionService * factions() const override;
const HeroClassService * heroClasses() const override; const HeroClassService * heroClasses() const override;
const HeroTypeService * heroTypes() const override; const HeroTypeService * heroTypes() const override;
#if SCRIPTING_ENABLED
const scripting::Service * scripts() const override; const scripting::Service * scripts() const override;
#endif
const spells::Service * spells() const override; const spells::Service * spells() const override;
const SkillService * skills() const override; const SkillService * skills() const override;
const BattleFieldService * battlefields() const override; const BattleFieldService * battlefields() const override;
@ -84,7 +89,9 @@ public:
CRmgTemplateStorage * tplh; CRmgTemplateStorage * tplh;
BattleFieldHandler * battlefieldsHandler; BattleFieldHandler * battlefieldsHandler;
ObstacleHandler * obstacleHandler; ObstacleHandler * obstacleHandler;
#if SCRIPTING_ENABLED
scripting::ScriptHandler * scriptHandler; scripting::ScriptHandler * scriptHandler;
#endif
LibClasses(); //c-tor, loads .lods and NULLs handlers LibClasses(); //c-tor, loads .lods and NULLs handlers
~LibClasses(); ~LibClasses();
@ -94,15 +101,19 @@ public:
void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init() void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
#if SCRIPTING_ENABLED
void scriptsLoaded(); void scriptsLoaded();
#endif
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> 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 h & scriptHandler;//must be first (or second after modh), it can modify factories other handlers depends on
if(!h.saving) if(!h.saving)
{ {
scriptsLoaded(); scriptsLoaded();
} }
#endif
h & heroh; h & heroh;
h & arth; h & arth;
@ -134,9 +145,6 @@ public:
callWhenDeserializing(); callWhenDeserializing();
} }
} }
private:
void update800();
}; };
extern DLL_LINKAGE LibClasses * VLC; extern DLL_LINKAGE LibClasses * VLC;

View File

@ -965,12 +965,14 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side)); return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
} }
#if SCRIPTING_ENABLED
scripting::Pool * BattleInfo::getContextPool() const scripting::Pool * BattleInfo::getContextPool() const
{ {
//this is real battle, use global scripting context pool //this is real battle, use global scripting context pool
//TODO: make this line not ugly //TODO: make this line not ugly
return IObjectInterface::cb->getGlobalContextPool(); return IObjectInterface::cb->getGlobalContextPool();
} }
#endif
bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b) bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b)
{ {

View File

@ -143,7 +143,9 @@ public:
ui8 whatSide(PlayerColor player) const; ui8 whatSide(PlayerColor player) const;
protected: protected:
#if SCRIPTING_ENABLED
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif
}; };

View File

@ -23,13 +23,6 @@ struct CObstacleInstance;
class IBonusBearer; class IBonusBearer;
class CRandomGenerator; class CRandomGenerator;
namespace scripting
{
class Context;
class Pool;
class Script;
}
namespace spells namespace spells
{ {
class Caster; class Caster;

View File

@ -24,15 +24,19 @@ namespace battle
using UnitFilter = std::function<bool(const Unit *)>; using UnitFilter = std::function<bool(const Unit *)>;
} }
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class Pool; class Pool;
} }
#endif
class DLL_LINKAGE IBattleInfoCallback class DLL_LINKAGE IBattleInfoCallback
{ {
public: public:
#if SCRIPTING_ENABLED
virtual scripting::Pool * getContextPool() const = 0; virtual scripting::Pool * getContextPool() const = 0;
#endif
virtual TTerrain battleTerrainType() const = 0; virtual TTerrain battleTerrainType() const = 0;
virtual BattleField battleGetBattlefieldType() const = 0; virtual BattleField battleGetBattlefieldType() const = 0;

View File

@ -25,10 +25,8 @@ SubscriptionRegistry<ApplyDamage> * ApplyDamage::getRegistry()
} }
CApplyDamage::CApplyDamage(const Environment * env_, BattleStackAttacked * pack_, std::shared_ptr<battle::Unit> target_) CApplyDamage::CApplyDamage(const Environment * env_, BattleStackAttacked * pack_, std::shared_ptr<battle::Unit> target_)
: env(env_), : pack(pack_),
pack(pack_),
target(target_) target(target_)
{ {
initalDamage = pack->damageAmount; initalDamage = pack->damageAmount;
} }

View File

@ -28,12 +28,8 @@ public:
private: private:
int64_t initalDamage; int64_t initalDamage;
const Environment * env;
BattleStackAttacked * pack; BattleStackAttacked * pack;
std::shared_ptr<battle::Unit> target; std::shared_ptr<battle::Unit> target;
}; };
} }

View File

@ -156,7 +156,7 @@ ISimpleResourceLoader * CResourceHandler::createInitial()
void CResourceHandler::initialize() void CResourceHandler::initialize()
{ {
// Create tree-loke structure that looks like this: // Create tree-like structure that looks like this:
// root // root
// | // |
// |- initial // |- initial

View File

@ -850,17 +850,16 @@ void CGOnceVisitable::initObj(CRandomGenerator & rand)
case Obj::WARRIORS_TOMB: case Obj::WARRIORS_TOMB:
{ {
onSelect.addTxt(MetaString::ADVOB_TXT, 161); 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); loadRandomArtifact(rand, info[0], 30, 50, 25, 5);
Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID); Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID);
info[0].reward.bonuses.push_back(bonus); info[0].reward.bonuses.push_back(bonus);
info[1].reward.bonuses.push_back(bonus);
info[0].limiter.numOfGrants = 1; info[0].limiter.numOfGrants = 1;
info[0].message.addTxt(MetaString::ADVOB_TXT, 162); info[0].message.addTxt(MetaString::ADVOB_TXT, 162);
info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getName()); info[0].message.addReplacement(VLC->arth->objects[info[0].reward.artifacts.back()]->getName());
info[1].message.addTxt(MetaString::ADVOB_TXT, 163);
} }
break; break;
case Obj::WAGON: case Obj::WAGON:

View File

@ -22,22 +22,10 @@
#include "CMapGenerator.h" #include "CMapGenerator.h"
#include "../CRandomGenerator.h" #include "../CRandomGenerator.h"
#include "Functions.h" #include "Functions.h"
#include "../mapping/CMapEditManager.h"
void ObstaclePlacer::process() void ObstacleProxy::collectPossibleObstacles(const Terrain & terrain)
{ {
auto * manager = zone.getModificator<ObjectManager>();
if(!manager)
return;
auto * riverManager = zone.getModificator<RiverPlacer>();
typedef std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleVector;
//obstacleVector possibleObstacles;
std::map<int, ObstacleVector> obstaclesBySize;
typedef std::pair<int, ObstacleVector> ObstaclePair;
std::vector<ObstaclePair> possibleObstacles;
//get all possible obstacles for this terrain //get all possible obstacles for this terrain
for(auto primaryID : VLC->objtypeh->knownObjects()) for(auto primaryID : VLC->objtypeh->knownObjects())
{ {
@ -48,7 +36,7 @@ void ObstaclePlacer::process()
{ {
for(auto temp : handler->getTemplates()) 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); obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);
} }
} }
@ -62,26 +50,10 @@ void ObstaclePlacer::process()
{ {
return p1.first > p2.first; //bigger obstacles first 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<rmg::Object> & allObjects, std::vector<std::pair<rmg::Object*, int3>> & weightedObjects)
{ {
return map.shouldBeBlocked(t);
});
blockedArea.subtract(zone.areaUsed());
zone.areaPossible().subtract(blockedArea);
auto prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left
auto blockedTiles = blockedArea.getTilesVector();
int tilePos = 0;
while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size())
{
auto tile = blockedArea.getTilesVector()[tilePos];
std::list<rmg::Object> allObjects;
std::vector<std::pair<rmg::Object*, int3>> weightedObjects; //obj + position
int maxWeight = std::numeric_limits<int>::min(); int maxWeight = std::numeric_limits<int>::min();
for(int i = 0; i < possibleObstacles.size(); ++i) for(int i = 0; i < possibleObstacles.size(); ++i)
{ {
@ -89,9 +61,9 @@ void ObstaclePlacer::process()
continue; continue;
auto shuffledObstacles = possibleObstacles[i].second; auto shuffledObstacles = possibleObstacles[i].second;
RandomGeneratorUtil::randomShuffle(shuffledObstacles, generator.rand); RandomGeneratorUtil::randomShuffle(shuffledObstacles, rand);
for(auto & temp : shuffledObstacles) for(auto temp : shuffledObstacles)
{ {
auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid); auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid);
auto obj = handler->create(temp); auto obj = handler->create(temp);
@ -100,19 +72,16 @@ void ObstaclePlacer::process()
for(auto & offset : obj->getBlockedOffsets()) for(auto & offset : obj->getBlockedOffsets())
{ {
rmgObject->setPosition(tile - offset); rmgObject->setPosition(tile - offset);
if(!map.isOnMap(rmgObject->getPosition())) if(!map->isInTheMap(rmgObject->getPosition()))
continue; continue;
if(!rmgObject->getArea().getSubarea([this](const int3 & t) if(!rmgObject->getArea().getSubarea([map](const int3 & t)
{ {
return !map.isOnMap(t); return !map->isInTheMap(t);
}).empty()) }).empty())
continue; continue;
if(prohibitedArea.overlap(rmgObject->getArea())) if(isProhibited(rmgObject->getArea()))
continue;
if(!zone.area().contains(rmgObject->getArea()))
continue; continue;
int coverageBlocked = 0; int coverageBlocked = 0;
@ -120,9 +89,10 @@ void ObstaclePlacer::process()
//do not use area intersection in optimization purposes //do not use area intersection in optimization purposes
for(auto & t : rmgObject->getArea().getTilesVector()) for(auto & t : rmgObject->getArea().getTilesVector())
{ {
if(map.shouldBeBlocked(t)) auto coverage = verifyCoverage(t);
if(coverage.first)
++coverageBlocked; ++coverageBlocked;
if(zone.areaPossible().contains(t)) if(coverage.second)
++coveragePossible; ++coveragePossible;
} }
@ -148,26 +118,38 @@ void ObstaclePlacer::process()
break; 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<CGObjectInstance*> objs;
while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size())
{
auto tile = blockedArea.getTilesVector()[tilePos];
std::list<rmg::Object> allObjects;
std::vector<std::pair<rmg::Object*, int3>> weightedObjects;
int maxWeight = getWeightedObjects(tile, map, rand, allObjects, weightedObjects);
if(weightedObjects.empty()) if(weightedObjects.empty())
{ {
tilePos += 1; tilePos += 1;
continue; continue;
} }
auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, generator.rand); auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, rand);
objIter->first->setPosition(objIter->second); objIter->first->setPosition(objIter->second);
manager->placeObject(*objIter->first, false, false); placeObject(*objIter->first, objs);
blockedArea.subtract(objIter->first->getArea()); blockedArea.subtract(objIter->first->getArea());
tilePos = 0; tilePos = 0;
//river processing postProcess(*objIter->first);
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());
}
if(maxWeight < 0) if(maxWeight < 0)
logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString()); logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString());
@ -178,6 +160,59 @@ void ObstaclePlacer::process()
o.clear(); o.clear();
} }
} }
finalInsertion(map->getEditManager(), objs);
}
void ObstacleProxy::finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances)
{
manager->insertObjects(instances); //insert as one operation - for undo purposes
}
std::pair<bool, bool> ObstacleProxy::verifyCoverage(const int3 & t) const
{
return {blockedArea.contains(t), false};
}
void ObstacleProxy::placeObject(rmg::Object & object, std::set<CGObjectInstance*> & 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<ObjectManager>();
if(!manager)
return;
riverManager = zone.getModificator<RiverPlacer>();
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() void ObstaclePlacer::init()
@ -189,3 +224,41 @@ void ObstaclePlacer::init()
DEPENDENCY(RoadPlacer); DEPENDENCY(RoadPlacer);
DEPENDENCY_ALL(RockPlacer); DEPENDENCY_ALL(RockPlacer);
} }
std::pair<bool, bool> ObstaclePlacer::verifyCoverage(const int3 & t) const
{
return {map.shouldBeBlocked(t), zone.areaPossible().contains(t)};
}
void ObstaclePlacer::placeObject(rmg::Object & object, std::set<CGObjectInstance*> &)
{
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<CGObjectInstance*> &)
{
}

View File

@ -11,11 +11,61 @@
#pragma once #pragma once
#include "Zone.h" #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<bool, bool> verifyCoverage(const int3 & t) const;
virtual void placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances);
virtual void postProcess(const rmg::Object & object);
virtual bool isProhibited(const rmg::Area & objArea) const;
virtual void finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances);
protected:
int getWeightedObjects(const int3 & tile, const CMap * map, CRandomGenerator & rand, std::list<rmg::Object> & allObjects, std::vector<std::pair<rmg::Object*, int3>> & weightedObjects);
typedef std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleVector;
std::map<int, ObstacleVector> obstaclesBySize;
typedef std::pair<int, ObstacleVector> ObstaclePair;
std::vector<ObstaclePair> possibleObstacles;
};
class ObstaclePlacer: public Modificator, public ObstacleProxy
{ {
public: public:
MODIFICATOR(ObstaclePlacer); MODIFICATOR(ObstaclePlacer);
void process() override; void process() override;
void init() override; void init() override;
std::pair<bool, bool> verifyCoverage(const int3 & t) const override;
void placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances) override;
void postProcess(const rmg::Object & object) override;
bool isProhibited(const rmg::Area & objArea) const override;
void finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances) override;
private:
rmg::Area prohibitedArea;
RiverPlacer * riverManager;
ObjectManager * manager;
}; };

View File

@ -194,10 +194,6 @@ void Zone::fractalize()
rmg::Area possibleTiles(dAreaPossible); rmg::Area possibleTiles(dAreaPossible);
rmg::Area tilesToIgnore; //will be erased in this iteration 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 const float minDistance = 10 * 10; //squared
if(type != ETemplateZoneType::JUNCTION) if(type != ETemplateZoneType::JUNCTION)

View File

@ -720,10 +720,12 @@ const CreatureService * BaseMechanics::creatures() const
return VLC->creatures(); //todo: redirect return VLC->creatures(); //todo: redirect
} }
#if SCRIPTING_ENABLED
const scripting::Service * BaseMechanics::scripts() const const scripting::Service * BaseMechanics::scripts() const
{ {
return VLC->scripts(); //todo: redirect return VLC->scripts(); //todo: redirect
} }
#endif
const Service * BaseMechanics::spells() const const Service * BaseMechanics::spells() const
{ {

View File

@ -35,10 +35,12 @@ namespace vstd
class RNG; class RNG;
} }
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class Service; class Service;
} }
#endif
///callback to be provided by server ///callback to be provided by server
@ -238,7 +240,9 @@ public:
//Global environment facade //Global environment facade
virtual const CreatureService * creatures() const = 0; virtual const CreatureService * creatures() const = 0;
#if SCRIPTING_ENABLED
virtual const scripting::Service * scripts() const = 0; virtual const scripting::Service * scripts() const = 0;
#endif
virtual const Service * spells() const = 0; virtual const Service * spells() const = 0;
virtual const IGameInfoCallback * game() const = 0; virtual const IGameInfoCallback * game() const = 0;
@ -296,7 +300,9 @@ public:
std::vector<AimType> getTargetTypes() const override; std::vector<AimType> getTargetTypes() const override;
const CreatureService * creatures() const override; const CreatureService * creatures() const override;
#if SCRIPTING_ENABLED
const scripting::Service * scripts() const override; const scripting::Service * scripts() const override;
#endif
const Service * spells() const override; const Service * spells() const override;
const IGameInfoCallback * game() const override; const IGameInfoCallback * game() const override;

View File

@ -1048,17 +1048,19 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG; bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
} }
} }
int64_t drainedLife = 0;
// only primary target // only primary target
if(defender->alive()) if(defender->alive())
applyBattleEffects(bat, blm, attackerState, fireShield, defender, distance, false); drainedLife += applyBattleEffects(bat, attackerState, fireShield, defender, distance, false);
//multiple-hex normal attack //multiple-hex normal attack
std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(attacker, targetHex, bat.shot()); //creatures other than primary target
for(const CStack * stack : attackedCreatures) for(const CStack * stack : attackedCreatures)
{ {
if(stack != defender && stack->alive()) //do not hit same stack twice 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<const Bonus> bonus = attacker->getBonusLocalFirst(Selector::type()(Bonus::SPELL_LIKE_ATTACK)); std::shared_ptr<const Bonus> 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 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); 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()) if(!fireShield.empty())
{ {
@ -1174,12 +1197,24 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
StacksInjured pack; StacksInjured pack;
pack.stacks.push_back(bsa); pack.stacks.push_back(bsa);
sendAndApply(&pack); 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); handleAfterAttackCasting(ranged, attacker, defender);
} }
void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary)
int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary)
{ {
BattleStackAttacked bsa; BattleStackAttacked bsa;
if(secondary) if(secondary)
@ -1208,34 +1243,14 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm
CStack::prepareAttacked(bsa, getRandomGenerator(), bai.defender->acquireState()); //calculate casualties CStack::prepareAttacked(bsa, getRandomGenerator(), bai.defender->acquireState()); //calculate casualties
} }
auto addLifeDrain = [&](int64_t & toHeal, EHealLevel level, EHealPower power) int64_t drainedLife = 0;
{
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);
}
};
//life drain handling //life drain handling
if(attackerState->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving()) if(attackerState->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
{ {
int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(Bonus::LIFE_DRAIN) / 100; int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(Bonus::LIFE_DRAIN) / 100;
attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
if(toHeal > 0) drainedLife += toHeal;
addLifeDrain(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
} }
//soul steal handling //soul steal handling
@ -1248,7 +1263,8 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm
if(attackerState->hasBonusOfType(Bonus::SOUL_STEAL, subtype)) if(attackerState->hasBonusOfType(Bonus::SOUL_STEAL, subtype))
{ {
int64_t toHeal = bsa.killedAmount * attackerState->valOfBonuses(Bonus::SOUL_STEAL, subtype) * attackerState->MaxHealth(); 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; break;
} }
} }
@ -1263,6 +1279,8 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, BattleLogMessage & blm
auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
fireShield.push_back(std::make_pair(def, fireShieldDamage)); fireShield.push_back(std::make_pair(def, fireShieldDamage));
} }
return drainedLife;
} }
void CGameHandler::sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple) 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 txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->nameSing); // creature perishes
} }
MetaString line; MetaString line;
line.addReplacement(txt.str()); line << txt.str();
blm.lines.push_back(line); blm.lines.push_back(std::move(line));
} }
} }
@ -1652,7 +1670,9 @@ CGameHandler::~CGameHandler()
void CGameHandler::reinitScripting() void CGameHandler::reinitScripting()
{ {
serverEventBus = make_unique<events::EventBus>(); serverEventBus = make_unique<events::EventBus>();
#if SCRIPTING_ENABLED
serverScripts.reset(new scripting::PoolImpl(this, spellEnv)); serverScripts.reset(new scripting::PoolImpl(this, spellEnv));
#endif
} }
void CGameHandler::init(StartInfo *si) void CGameHandler::init(StartInfo *si)
@ -2112,7 +2132,9 @@ void CGameHandler::run(bool resume)
logGlobal->info(sbuffer.str()); logGlobal->info(sbuffer.str());
} }
#if SCRIPTING_ENABLED
services()->scripts()->run(serverScripts); services()->scripts()->run(serverScripts);
#endif
if(resume) if(resume)
events::GameResumed::defaultExecute(serverEventBus.get()); events::GameResumed::defaultExecute(serverEventBus.get());
@ -5585,7 +5607,7 @@ bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2)
auto topArmy = dialog->exchangingArmies.at(0); auto topArmy = dialog->exchangingArmies.at(0);
auto bottomArmy = dialog->exchangingArmies.at(1); 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; return true;
} }
} }
@ -7319,6 +7341,7 @@ CRandomGenerator & CGameHandler::getRandomGenerator()
return CRandomGenerator::getDefault(); return CRandomGenerator::getDefault();
} }
#if SCRIPTING_ENABLED
scripting::Pool * CGameHandler::getGlobalContextPool() const scripting::Pool * CGameHandler::getGlobalContextPool() const
{ {
return serverScripts.get(); return serverScripts.get();
@ -7328,6 +7351,7 @@ scripting::Pool * CGameHandler::getContextPool() const
{ {
return serverScripts.get(); return serverScripts.get();
} }
#endif
const ObjectInstanceID CGameHandler::putNewObject(Obj ID, int subID, int3 pos) const ObjectInstanceID CGameHandler::putNewObject(Obj ID, int subID, int3 pos)
{ {

View File

@ -34,10 +34,12 @@ class IMarket;
class SpellCastEnvironment; class SpellCastEnvironment;
#if SCRIPTING_ENABLED
namespace scripting namespace scripting
{ {
class PoolImpl; class PoolImpl;
} }
#endif
template<typename T> class CApplier; template<typename T> 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 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<battle::CUnitState> 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<battle::CUnitState> attackerState, FireShieldInfo & fireShield, const CStack * def, int distance, bool secondary);
void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple); void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
void addGenericKilledLog(BattleLogMessage & blm, 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 & finishingBattle;
h & getRandomGenerator(); h & getRandomGenerator();
#if SCRIPTING_ENABLED
JsonNode scriptsState; JsonNode scriptsState;
if(h.saving) if(h.saving)
serverScripts->serializeState(h.saving, scriptsState); serverScripts->serializeState(h.saving, scriptsState);
h & scriptsState; h & scriptsState;
if(!h.saving) if(!h.saving)
serverScripts->serializeState(h.saving, scriptsState); serverScripts->serializeState(h.saving, scriptsState);
#endif
} }
void sendMessageToAll(const std::string &message); void sendMessageToAll(const std::string &message);
@ -326,13 +331,17 @@ public:
CRandomGenerator & getRandomGenerator(); CRandomGenerator & getRandomGenerator();
#if SCRIPTING_ENABLED
scripting::Pool * getGlobalContextPool() const override; scripting::Pool * getGlobalContextPool() const override;
scripting::Pool * getContextPool() const override; scripting::Pool * getContextPool() const override;
#endif
friend class CVCMIServer; friend class CVCMIServer;
private: private:
std::unique_ptr<events::EventBus> serverEventBus; std::unique_ptr<events::EventBus> serverEventBus;
#if SCRIPTING_ENABLED
std::shared_ptr<scripting::PoolImpl> serverScripts; std::shared_ptr<scripting::PoolImpl> serverScripts;
#endif
void reinitScripting(); void reinitScripting();