mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
commit
9121fcdc97
10
.github/workflows/github.yml
vendored
10
.github/workflows/github.yml
vendored
@ -20,11 +20,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux-qt6
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
preset: linux-clang-test
|
||||
- platform: linux
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-24.04
|
||||
test: 1
|
||||
preset: linux-gcc-test
|
||||
- platform: linux
|
||||
@ -124,7 +124,7 @@ jobs:
|
||||
# also, running it on multiple presets is redundant and slightly increases already long CI built times
|
||||
if: ${{ startsWith(matrix.preset, 'linux-clang-test') }}
|
||||
run: |
|
||||
pip3 install jstyleson
|
||||
sudo apt install python3-jstyleson
|
||||
python3 CI/linux-qt6/validate_json.py
|
||||
|
||||
- name: Dependencies
|
||||
@ -201,8 +201,8 @@ jobs:
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC12=1; fi
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC12:+-DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12}
|
||||
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC14=1; fi
|
||||
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC14:+-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
@ -77,7 +77,6 @@ AIGateway::AIGateway()
|
||||
destinationTeleport = ObjectInstanceID();
|
||||
destinationTeleportPos = int3(-1);
|
||||
nullkiller.reset(new Nullkiller());
|
||||
announcedCheatingProblem = false;
|
||||
}
|
||||
|
||||
AIGateway::~AIGateway()
|
||||
@ -378,7 +377,7 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
|
||||
nullkiller->memory->removeFromMemory(obj);
|
||||
nullkiller->objectClusterizer->onObjectRemoved(obj->id);
|
||||
|
||||
if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed())
|
||||
if(nullkiller->baseGraph && nullkiller->isObjectGraphAllowed())
|
||||
{
|
||||
nullkiller->baseGraph->removeObject(obj);
|
||||
}
|
||||
@ -829,13 +828,9 @@ void AIGateway::makeTurn()
|
||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||
setThreadName("AIGateway::makeTurn");
|
||||
|
||||
if(cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
|
||||
cb->sendMessage("vcmieagles");
|
||||
else
|
||||
if(nullkiller->isOpenMap())
|
||||
{
|
||||
if(!announcedCheatingProblem)
|
||||
cb->sendMessage("Nullkiller AI currently requires the ability to cheat in order to function correctly! Please enable!");
|
||||
announcedCheatingProblem = true;
|
||||
cb->sendMessage("vcmieagles");
|
||||
}
|
||||
|
||||
retrieveVisitableObjs();
|
||||
|
@ -95,7 +95,7 @@ public:
|
||||
std::unique_ptr<boost::thread> makingTurn;
|
||||
private:
|
||||
boost::mutex turnInterruptionMutex;
|
||||
bool announcedCheatingProblem;
|
||||
|
||||
public:
|
||||
ObjectInstanceID selectedObject;
|
||||
|
||||
|
@ -381,7 +381,7 @@ void ObjectClusterizer::clusterizeObject(
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
if(ai->settings->isObjectGraphAllowed())
|
||||
if(ai->isObjectGraphAllowed())
|
||||
{
|
||||
ai->pathfinder->calculateQuickPathsWithBlocker(pathCache, heroes, obj->visitablePos());
|
||||
}
|
||||
|
@ -47,7 +47,11 @@ bool CaptureObjectsBehavior::operator==(const CaptureObjectsBehavior & other) co
|
||||
&& vectorEquals(objectSubTypes, other.objectSubTypes);
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath> & paths, const Nullkiller * nullkiller, const CGObjectInstance * objToVisit)
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
|
||||
const std::vector<AIPath> & paths,
|
||||
const Nullkiller * nullkiller,
|
||||
const CGObjectInstance * objToVisit,
|
||||
bool force)
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
@ -72,7 +76,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !shouldVisit(nullkiller, path.targetHero, objToVisit))
|
||||
if(objToVisit && !force && !shouldVisit(nullkiller, path.targetHero, objToVisit))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Hero %s should not visit obj %s", path.targetHero->getNameTranslated(), objToVisit->getObjectName());
|
||||
@ -200,12 +204,12 @@ void CaptureObjectsBehavior::decomposeObjects(
|
||||
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
nullkiller->pathfinder->calculatePathInfo(paths, objToVisit->visitablePos(), nullkiller->settings->isObjectGraphAllowed());
|
||||
nullkiller->pathfinder->calculatePathInfo(paths, objToVisit->visitablePos(), nullkiller->isObjectGraphAllowed());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found %d paths", paths.size());
|
||||
#endif
|
||||
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit));
|
||||
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(sync);
|
||||
|
@ -68,7 +68,11 @@ namespace Goals
|
||||
|
||||
bool operator==(const CaptureObjectsBehavior & other) const override;
|
||||
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const Nullkiller * nullkiller, const CGObjectInstance * objToVisit = nullptr);
|
||||
static Goals::TGoalVec getVisitGoals(
|
||||
const std::vector<AIPath> & paths,
|
||||
const Nullkiller * nullkiller,
|
||||
const CGObjectInstance * objToVisit = nullptr,
|
||||
bool force = false);
|
||||
|
||||
private:
|
||||
bool objectMatchesFilter(const CGObjectInstance * obj) const;
|
||||
|
@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose(const Nullkiller * ai) const
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(const Nullkiller * ai, std::shared_ptr<ObjectCluster> cluster) const
|
||||
{
|
||||
auto center = cluster->calculateCenter(ai->cb.get());
|
||||
auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->settings->isObjectGraphAllowed());
|
||||
auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->isObjectGraphAllowed());
|
||||
|
||||
auto blockerPos = cluster->blocker->visitablePos();
|
||||
std::vector<AIPath> blockerPaths;
|
||||
|
110
AI/Nullkiller/Behaviors/ExplorationBehavior.cpp
Normal file
110
AI/Nullkiller/Behaviors/ExplorationBehavior.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* ExplorationBehavior.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ExplorationBehavior.h"
|
||||
#include "../AIGateway.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../Goals/Invalid.h"
|
||||
#include "../Goals/Composition.h"
|
||||
#include "../Goals/ExecuteHeroChain.h"
|
||||
#include "../Markers/ExplorationPoint.h"
|
||||
#include "../Goals/CaptureObject.h"
|
||||
#include "../Goals/ExploreNeighbourTile.h"
|
||||
#include "../Helpers/ExplorationHelper.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
std::string ExplorationBehavior::toString() const
|
||||
{
|
||||
return "Explore";
|
||||
}
|
||||
|
||||
Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(!vstd::contains(ai->memory->alreadyVisited, obj))
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
{
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
case Obj::PILLAR_OF_FIRE:
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE != ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
{
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(obj->ID.num)
|
||||
{
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
auto tObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability)
|
||||
break;
|
||||
for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits)
|
||||
{
|
||||
if(!cb->getObj(exit))
|
||||
{
|
||||
// Always attempt to visit two-way teleports if one of channel exits is not visible
|
||||
tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto heroes = ai->cb->getHeroesInfo();
|
||||
|
||||
for(const CGHeroInstance * hero : heroes)
|
||||
{
|
||||
ExplorationHelper scanResult(hero, ai);
|
||||
|
||||
if(scanResult.scanSector(1))
|
||||
{
|
||||
tasks.push_back(scanResult.makeComposition());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(scanResult.scanSector(15))
|
||||
{
|
||||
tasks.push_back(scanResult.makeComposition());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->getScanDepth() == ScanDepth::ALL_FULL)
|
||||
{
|
||||
if(scanResult.scanMap())
|
||||
{
|
||||
tasks.push_back(scanResult.makeComposition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
}
|
38
AI/Nullkiller/Behaviors/ExplorationBehavior.h
Normal file
38
AI/Nullkiller/Behaviors/ExplorationBehavior.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* ExplorationBehavior.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "lib/VCMI_Lib.h"
|
||||
#include "../Goals/CGoal.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
namespace Goals
|
||||
{
|
||||
class ExplorationBehavior : public CGoal<ExplorationBehavior>
|
||||
{
|
||||
public:
|
||||
ExplorationBehavior()
|
||||
:CGoal(EXPLORATION_BEHAVIOR)
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const ExplorationBehavior & other) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -69,7 +69,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con
|
||||
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->settings->isObjectGraphAllowed());
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->isObjectGraphAllowed());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Gather army found %d paths", paths.size());
|
||||
@ -231,7 +231,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->settings->isObjectGraphAllowed());
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->isObjectGraphAllowed());
|
||||
auto goals = CaptureObjectsBehavior::getVisitGoals(paths, ai);
|
||||
|
||||
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
||||
|
@ -15,6 +15,8 @@ set(Nullkiller_SRCS
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.cpp
|
||||
Pathfinding/Rules/AIPreviousNodeRule.cpp
|
||||
Pathfinding/ObjectGraph.cpp
|
||||
Pathfinding/GraphPaths.cpp
|
||||
Pathfinding/ObjectGraphCalculator.cpp
|
||||
AIUtility.cpp
|
||||
Analyzers/ArmyManager.cpp
|
||||
Analyzers/HeroManager.cpp
|
||||
@ -38,10 +40,12 @@ set(Nullkiller_SRCS
|
||||
Goals/ExchangeSwapTownHeroes.cpp
|
||||
Goals/CompleteQuest.cpp
|
||||
Goals/StayAtTown.cpp
|
||||
Goals/ExploreNeighbourTile.cpp
|
||||
Markers/ArmyUpgrade.cpp
|
||||
Markers/HeroExchange.cpp
|
||||
Markers/UnlockCluster.cpp
|
||||
Markers/DefendTown.cpp
|
||||
Markers/ExplorationPoint.cpp
|
||||
Engine/Nullkiller.cpp
|
||||
Engine/DeepDecomposer.cpp
|
||||
Engine/PriorityEvaluator.cpp
|
||||
@ -57,7 +61,9 @@ set(Nullkiller_SRCS
|
||||
Behaviors/GatherArmyBehavior.cpp
|
||||
Behaviors/ClusterBehavior.cpp
|
||||
Behaviors/StayAtTownBehavior.cpp
|
||||
Behaviors/ExplorationBehavior.cpp
|
||||
Helpers/ArmyFormation.cpp
|
||||
Helpers/ExplorationHelper.cpp
|
||||
AIGateway.cpp
|
||||
)
|
||||
|
||||
@ -80,6 +86,8 @@ set(Nullkiller_HEADERS
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.h
|
||||
Pathfinding/Rules/AIPreviousNodeRule.h
|
||||
Pathfinding/ObjectGraph.h
|
||||
Pathfinding/GraphPaths.h
|
||||
Pathfinding/ObjectGraphCalculator.h
|
||||
AIUtility.h
|
||||
pforeach.h
|
||||
Analyzers/ArmyManager.h
|
||||
@ -107,10 +115,12 @@ set(Nullkiller_HEADERS
|
||||
Goals/CompleteQuest.h
|
||||
Goals/Goals.h
|
||||
Goals/StayAtTown.h
|
||||
Goals/ExploreNeighbourTile.h
|
||||
Markers/ArmyUpgrade.h
|
||||
Markers/HeroExchange.h
|
||||
Markers/UnlockCluster.h
|
||||
Markers/DefendTown.h
|
||||
Markers/ExplorationPoint.h
|
||||
Engine/Nullkiller.h
|
||||
Engine/DeepDecomposer.h
|
||||
Engine/PriorityEvaluator.h
|
||||
@ -126,7 +136,9 @@ set(Nullkiller_HEADERS
|
||||
Behaviors/GatherArmyBehavior.h
|
||||
Behaviors/ClusterBehavior.h
|
||||
Behaviors/StayAtTownBehavior.h
|
||||
Behaviors/ExplorationBehavior.h
|
||||
Helpers/ArmyFormation.h
|
||||
Helpers/ExplorationHelper.h
|
||||
AIGateway.h
|
||||
)
|
||||
|
||||
|
@ -19,8 +19,11 @@
|
||||
#include "../Behaviors/GatherArmyBehavior.h"
|
||||
#include "../Behaviors/ClusterBehavior.h"
|
||||
#include "../Behaviors/StayAtTownBehavior.h"
|
||||
#include "../Behaviors/ExplorationBehavior.h"
|
||||
#include "../Goals/Invalid.h"
|
||||
#include "../Goals/Composition.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -35,13 +38,45 @@ Nullkiller::Nullkiller()
|
||||
{
|
||||
memory = std::make_unique<AIMemory>();
|
||||
settings = std::make_unique<Settings>();
|
||||
|
||||
useObjectGraph = settings->isObjectGraphAllowed();
|
||||
openMap = settings->isOpenMap() || useObjectGraph;
|
||||
}
|
||||
|
||||
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
{
|
||||
if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const TeamState * team = cb->getPlayerTeam(playerID);
|
||||
|
||||
auto hasHumanInTeam = vstd::contains_if(team->players, [cb](PlayerColor teamMateID) -> bool
|
||||
{
|
||||
return cb->getPlayerState(teamMateID)->isHuman();
|
||||
});
|
||||
|
||||
if(hasHumanInTeam)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return cb->getStartInfo()->difficulty >= 3;
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
{
|
||||
this->cb = cb;
|
||||
this->gateway = gateway;
|
||||
this->playerID = gateway->playerID;
|
||||
|
||||
playerID = gateway->playerID;
|
||||
|
||||
if(openMap && !canUseOpenMap(cb, playerID))
|
||||
{
|
||||
useObjectGraph = false;
|
||||
openMap = false;
|
||||
}
|
||||
|
||||
baseGraph.reset();
|
||||
|
||||
@ -190,7 +225,7 @@ void Nullkiller::resetAiState()
|
||||
useHeroChain = true;
|
||||
objectClusterizer->reset();
|
||||
|
||||
if(!baseGraph && settings->isObjectGraphAllowed())
|
||||
if(!baseGraph && isObjectGraphAllowed())
|
||||
{
|
||||
baseGraph = std::make_unique<ObjectGraph>();
|
||||
baseGraph->updateGraph(this);
|
||||
@ -237,12 +272,12 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
cfg.useHeroChain = useHeroChain;
|
||||
cfg.allowBypassObjects = true;
|
||||
|
||||
if(scanDepth == ScanDepth::SMALL || settings->isObjectGraphAllowed())
|
||||
if(scanDepth == ScanDepth::SMALL || isObjectGraphAllowed())
|
||||
{
|
||||
cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
|
||||
}
|
||||
|
||||
if(scanDepth != ScanDepth::ALL_FULL || settings->isObjectGraphAllowed())
|
||||
if(scanDepth != ScanDepth::ALL_FULL || isObjectGraphAllowed())
|
||||
{
|
||||
cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
|
||||
}
|
||||
@ -251,7 +286,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, cfg);
|
||||
|
||||
if(settings->isObjectGraphAllowed())
|
||||
if(isObjectGraphAllowed())
|
||||
{
|
||||
pathfinder->updateGraphs(
|
||||
activeHeroes,
|
||||
@ -354,6 +389,9 @@ void Nullkiller::makeTurn()
|
||||
decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
|
||||
|
||||
if(!isOpenMap())
|
||||
decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
|
||||
{
|
||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||
|
@ -76,6 +76,8 @@ private:
|
||||
TResources lockedResources;
|
||||
bool useHeroChain;
|
||||
AIGateway * gateway;
|
||||
bool openMap;
|
||||
bool useObjectGraph;
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ObjectGraph> baseGraph;
|
||||
@ -116,6 +118,8 @@ public:
|
||||
void lockResources(const TResources & res);
|
||||
const TResources & getLockedResources() const { return lockedResources; }
|
||||
ScanDepth getScanDepth() const { return scanDepth; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
bool isObjectGraphAllowed() const { return useObjectGraph; }
|
||||
|
||||
private:
|
||||
void resetAiState();
|
||||
|
@ -735,6 +735,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class ExplorePointEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::EXPLORATION_POINT)
|
||||
return;
|
||||
|
||||
int tilesDiscovered = task->value;
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered);
|
||||
}
|
||||
};
|
||||
|
||||
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
@ -1056,6 +1070,7 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
|
||||
evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
|
||||
evaluationContextBuilders.push_back(std::make_shared<StayAtTownManaRecoveryEvaluator>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ExplorePointEvaluator>());
|
||||
}
|
||||
|
||||
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
||||
|
@ -28,8 +28,9 @@ namespace NKAI
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
maxGoldPressure(0.3f),
|
||||
maxpass(10),
|
||||
allowObjectGraph(false),
|
||||
useTroopsFromGarrisons(false)
|
||||
allowObjectGraph(true),
|
||||
useTroopsFromGarrisons(false),
|
||||
openMap(true)
|
||||
{
|
||||
JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings");
|
||||
|
||||
@ -63,6 +64,11 @@ namespace NKAI
|
||||
allowObjectGraph = node.Struct()["allowObjectGraph"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["openMap"].isNull())
|
||||
{
|
||||
openMap = node.Struct()["openMap"].Bool();
|
||||
}
|
||||
|
||||
if(!node.Struct()["useTroopsFromGarrisons"].isNull())
|
||||
{
|
||||
useTroopsFromGarrisons = node.Struct()["useTroopsFromGarrisons"].Bool();
|
||||
|
@ -28,6 +28,7 @@ namespace NKAI
|
||||
float maxGoldPressure;
|
||||
bool allowObjectGraph;
|
||||
bool useTroopsFromGarrisons;
|
||||
bool openMap;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
@ -39,5 +40,6 @@ namespace NKAI
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
|
||||
bool isOpenMap() const { return openMap; }
|
||||
};
|
||||
}
|
||||
|
@ -73,7 +73,10 @@ namespace Goals
|
||||
CAPTURE_OBJECT,
|
||||
SAVE_RESOURCES,
|
||||
STAY_AT_TOWN_BEHAVIOR,
|
||||
STAY_AT_TOWN
|
||||
STAY_AT_TOWN,
|
||||
EXPLORATION_BEHAVIOR,
|
||||
EXPLORATION_POINT,
|
||||
EXPLORE_NEIGHBOUR_TILE
|
||||
};
|
||||
|
||||
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
||||
|
@ -18,10 +18,11 @@ class AIGateway;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
template<typename T> class DLL_EXPORT CGoal : public AbstractGoal
|
||||
template<typename T>
|
||||
class DLL_EXPORT CGoal : public AbstractGoal
|
||||
{
|
||||
public:
|
||||
CGoal<T>(EGoals goal = INVALID) : AbstractGoal(goal)
|
||||
CGoal(EGoals goal = INVALID) : AbstractGoal(goal)
|
||||
{
|
||||
isAbstract = true;
|
||||
value = 0;
|
||||
@ -32,7 +33,7 @@ namespace Goals
|
||||
town = nullptr;
|
||||
}
|
||||
|
||||
CGoal<T> * clone() const override
|
||||
CGoal * clone() const override
|
||||
{
|
||||
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
|
||||
}
|
||||
@ -70,15 +71,16 @@ namespace Goals
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> class DLL_EXPORT ElementarGoal : public CGoal<T>, public ITask
|
||||
template<typename T>
|
||||
class DLL_EXPORT ElementarGoal : public CGoal<T>, public ITask
|
||||
{
|
||||
public:
|
||||
ElementarGoal<T>(EGoals goal = INVALID) : CGoal<T>(goal), ITask()
|
||||
ElementarGoal(EGoals goal = INVALID) : CGoal<T>(goal), ITask()
|
||||
{
|
||||
AbstractGoal::isAbstract = false;
|
||||
}
|
||||
|
||||
ElementarGoal<T>(const ElementarGoal<T> & other) : CGoal<T>(other), ITask(other)
|
||||
ElementarGoal(const ElementarGoal<T> & other) : CGoal<T>(other), ITask(other)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(i > 0 && ai->nullkiller->settings->isObjectGraphAllowed())
|
||||
else if(i > 0 && ai->nullkiller->isObjectGraphAllowed())
|
||||
{
|
||||
auto chainMask = i < chainPath.nodes.size() - 1 ? chainPath.nodes[i + 1].chainMask : node->chainMask;
|
||||
|
||||
|
69
AI/Nullkiller/Goals/ExploreNeighbourTile.cpp
Normal file
69
AI/Nullkiller/Goals/ExploreNeighbourTile.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* ExploreNeighbourTile.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ExploreNeighbourTile.h"
|
||||
#include "../AIGateway.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../Helpers/ExplorationHelper.h"
|
||||
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
bool ExploreNeighbourTile::operator==(const ExploreNeighbourTile & other) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExploreNeighbourTile::accept(AIGateway * ai)
|
||||
{
|
||||
ExplorationHelper h(hero, ai->nullkiller.get());
|
||||
|
||||
for(int i = 0; i < tilesToExplore && hero->movementPointsRemaining() > 0; i++)
|
||||
{
|
||||
int3 pos = hero->visitablePos();
|
||||
float value = 0;
|
||||
int3 target = int3(-1);
|
||||
foreach_neighbour(pos, [&](int3 tile)
|
||||
{
|
||||
auto pathInfo = ai->myCb->getPathsInfo(hero)->getPathInfo(tile);
|
||||
|
||||
if(pathInfo->turns > 0)
|
||||
return;
|
||||
|
||||
if(pathInfo->accessible == EPathAccessibility::ACCESSIBLE)
|
||||
{
|
||||
float newValue = h.howManyTilesWillBeDiscovered(tile);
|
||||
|
||||
newValue /= std::min(0.1f, pathInfo->getCost());
|
||||
|
||||
if(newValue > value)
|
||||
{
|
||||
value = newValue;
|
||||
target = tile;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(!target.valid() || !ai->moveHeroToTile(target, hero))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ExploreNeighbourTile::toString() const
|
||||
{
|
||||
return "Explore neighbour tiles by " + hero->getNameTranslated();
|
||||
}
|
||||
|
||||
}
|
45
AI/Nullkiller/Goals/ExploreNeighbourTile.h
Normal file
45
AI/Nullkiller/Goals/ExploreNeighbourTile.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* ExploreNeighbourTile.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CGoal.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
class AIGateway;
|
||||
class FuzzyHelper;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
class DLL_EXPORT ExploreNeighbourTile : public ElementarGoal<ExploreNeighbourTile>
|
||||
{
|
||||
private:
|
||||
int tilesToExplore;
|
||||
|
||||
public:
|
||||
ExploreNeighbourTile(const CGHeroInstance * hero, int amount)
|
||||
: ElementarGoal(Goals::EXPLORE_NEIGHBOUR_TILE)
|
||||
{
|
||||
tilesToExplore = amount;
|
||||
sethero(hero);
|
||||
}
|
||||
|
||||
bool operator==(const ExploreNeighbourTile & other) const override;
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
|
||||
private:
|
||||
//TSubgoal decomposeSingle() const override;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
235
AI/Nullkiller/Helpers/ExplorationHelper.cpp
Normal file
235
AI/Nullkiller/Helpers/ExplorationHelper.cpp
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* ExplorationHelper.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ExplorationHelper.h"
|
||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../Goals/Invalid.h"
|
||||
#include "../Goals/Composition.h"
|
||||
#include "../Goals/ExecuteHeroChain.h"
|
||||
#include "../Markers/ExplorationPoint.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||
#include "../Goals/ExploreNeighbourTile.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai)
|
||||
:ai(ai), cbp(ai->cb.get()), hero(hero)
|
||||
{
|
||||
ts = cbp->getPlayerTeam(ai->playerID);
|
||||
sightRadius = hero->getSightRadius();
|
||||
bestGoal = sptr(Goals::Invalid());
|
||||
bestValue = 0;
|
||||
bestTilesDiscovered = 0;
|
||||
ourPos = hero->visitablePos();
|
||||
allowDeadEndCancellation = true;
|
||||
}
|
||||
|
||||
TSubgoal ExplorationHelper::makeComposition() const
|
||||
{
|
||||
Composition c;
|
||||
c.addNext(ExplorationPoint(bestTile, bestTilesDiscovered));
|
||||
c.addNextSequence({bestGoal, sptr(ExploreNeighbourTile(hero, 5))});
|
||||
return sptr(c);
|
||||
}
|
||||
|
||||
|
||||
bool ExplorationHelper::scanSector(int scanRadius)
|
||||
{
|
||||
int3 tile = int3(0, 0, ourPos.z);
|
||||
|
||||
const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
|
||||
|
||||
for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
|
||||
{
|
||||
for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
|
||||
{
|
||||
if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
|
||||
{
|
||||
scanTile(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !bestGoal->invalid();
|
||||
}
|
||||
|
||||
bool ExplorationHelper::scanMap()
|
||||
{
|
||||
int3 mapSize = cbp->getMapSize();
|
||||
int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
|
||||
|
||||
std::vector<int3> from;
|
||||
std::vector<int3> to;
|
||||
|
||||
from.reserve(perimeter);
|
||||
to.reserve(perimeter);
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
|
||||
{
|
||||
bool hasInvisibleNeighbor = false;
|
||||
|
||||
foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
|
||||
{
|
||||
if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
||||
{
|
||||
hasInvisibleNeighbor = true;
|
||||
}
|
||||
});
|
||||
|
||||
if(hasInvisibleNeighbor)
|
||||
from.push_back(pos);
|
||||
}
|
||||
});
|
||||
|
||||
logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
|
||||
|
||||
for(const int3 & tile : from)
|
||||
{
|
||||
scanTile(tile);
|
||||
}
|
||||
|
||||
if(!bestGoal->invalid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
allowDeadEndCancellation = false;
|
||||
|
||||
for(int i = 0; i < sightRadius; i++)
|
||||
{
|
||||
getVisibleNeighbours(from, to);
|
||||
vstd::concatenate(from, to);
|
||||
vstd::removeDuplicates(from);
|
||||
}
|
||||
|
||||
logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
|
||||
|
||||
for(const int3 & tile : from)
|
||||
{
|
||||
scanTile(tile);
|
||||
}
|
||||
|
||||
return !bestGoal->invalid();
|
||||
}
|
||||
|
||||
void ExplorationHelper::scanTile(const int3 & tile)
|
||||
{
|
||||
if(tile == ourPos
|
||||
|| !ai->cb->getTile(tile, false)
|
||||
|| !ai->pathfinder->isTileAccessible(hero, tile)) //shouldn't happen, but it does
|
||||
return;
|
||||
|
||||
int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
|
||||
if(!tilesDiscovered)
|
||||
return;
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(tile);
|
||||
auto waysToVisit = CaptureObjectsBehavior::getVisitGoals(paths, ai, ai->cb->getTopObj(tile));
|
||||
|
||||
for(int i = 0; i != paths.size(); i++)
|
||||
{
|
||||
auto & path = paths[i];
|
||||
auto goal = waysToVisit[i];
|
||||
|
||||
if(path.exchangeCount > 1 || path.targetHero != hero || path.movementCost() <= 0.0 || goal->invalid())
|
||||
continue;
|
||||
|
||||
float ourValue = (float)tilesDiscovered * tilesDiscovered / path.movementCost();
|
||||
|
||||
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
|
||||
{
|
||||
auto obj = cb->getTopObj(tile);
|
||||
|
||||
// picking up resources does not yield any exploration at all.
|
||||
// if it blocks the way to some explorable tile AIPathfinder will take care of it
|
||||
if(obj && obj->isBlockedVisitable())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger()))
|
||||
{
|
||||
bestGoal = goal;
|
||||
bestValue = ourValue;
|
||||
bestTile = tile;
|
||||
bestTilesDiscovered = tilesDiscovered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExplorationHelper::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
|
||||
{
|
||||
for(const int3 & tile : tiles)
|
||||
{
|
||||
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
|
||||
{
|
||||
if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
||||
{
|
||||
out.push_back(neighbour);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
|
||||
{
|
||||
int ret = 0;
|
||||
int3 npos = int3(0, 0, pos.z);
|
||||
|
||||
const auto & slice = (*(ts->fogOfWarMap))[pos.z];
|
||||
|
||||
for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
|
||||
{
|
||||
for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
|
||||
{
|
||||
if(cbp->isInTheMap(npos)
|
||||
&& pos.dist2d(npos) - 0.5 < sightRadius
|
||||
&& !slice[npos.x][npos.y])
|
||||
{
|
||||
if(allowDeadEndCancellation
|
||||
&& !hasReachableNeighbor(npos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
|
||||
{
|
||||
for(const int3 & dir : int3::getDirs())
|
||||
{
|
||||
int3 tile = pos + dir;
|
||||
if(cbp->isInTheMap(tile))
|
||||
{
|
||||
auto isAccessible = ai->pathfinder->isTileAccessible(hero, tile);
|
||||
|
||||
if(isAccessible)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
51
AI/Nullkiller/Helpers/ExplorationHelper.h
Normal file
51
AI/Nullkiller/Helpers/ExplorationHelper.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* ExplorationHelper.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../AIUtility.h"
|
||||
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
#include "../Goals/AbstractGoal.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
class ExplorationHelper
|
||||
{
|
||||
private:
|
||||
const CGHeroInstance * hero;
|
||||
int sightRadius;
|
||||
float bestValue;
|
||||
Goals::TSubgoal bestGoal;
|
||||
int3 bestTile;
|
||||
int bestTilesDiscovered;
|
||||
const Nullkiller * ai;
|
||||
CCallback * cbp;
|
||||
const TeamState * ts;
|
||||
int3 ourPos;
|
||||
bool allowDeadEndCancellation;
|
||||
|
||||
public:
|
||||
ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai);
|
||||
Goals::TSubgoal makeComposition() const;
|
||||
bool scanSector(int scanRadius);
|
||||
bool scanMap();
|
||||
int howManyTilesWillBeDiscovered(const int3 & pos) const;
|
||||
|
||||
private:
|
||||
void scanTile(const int3 & tile);
|
||||
bool hasReachableNeighbor(const int3 & pos) const;
|
||||
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const;
|
||||
};
|
||||
|
||||
}
|
32
AI/Nullkiller/Markers/ExplorationPoint.cpp
Normal file
32
AI/Nullkiller/Markers/ExplorationPoint.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* HeroExchange.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ExplorationPoint.h"
|
||||
#include "../AIGateway.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../Analyzers/ArmyManager.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
bool ExplorationPoint::operator==(const ExplorationPoint & other) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ExplorationPoint::toString() const
|
||||
{
|
||||
return "Explore " +tile.toString() + " for " + std::to_string(value) + " tiles";
|
||||
}
|
||||
|
||||
}
|
35
AI/Nullkiller/Markers/ExplorationPoint.h
Normal file
35
AI/Nullkiller/Markers/ExplorationPoint.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* ExplorationPoint.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../Goals/CGoal.h"
|
||||
#include "../Pathfinding/AINodeStorage.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
namespace Goals
|
||||
{
|
||||
class DLL_EXPORT ExplorationPoint : public CGoal<ExplorationPoint>
|
||||
{
|
||||
public:
|
||||
ExplorationPoint(int3 tile, int tilesToReviel)
|
||||
: CGoal(Goals::EXPLORATION_POINT)
|
||||
{
|
||||
settile(tile);
|
||||
setvalue(tilesToReviel);
|
||||
}
|
||||
|
||||
bool operator==(const ExplorationPoint & other) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "AINodeStorage.h"
|
||||
#include "ObjectGraph.h"
|
||||
#include "GraphPaths.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
|
393
AI/Nullkiller/Pathfinding/GraphPaths.cpp
Normal file
393
AI/Nullkiller/Pathfinding/GraphPaths.cpp
Normal file
@ -0,0 +1,393 @@
|
||||
/*
|
||||
* GraphPaths.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "GraphPaths.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
#include "Actions/QuestAction.h"
|
||||
#include "../pforeach.h"
|
||||
#include "Actions/BoatActions.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const
|
||||
{
|
||||
return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
|
||||
}
|
||||
|
||||
GraphPaths::GraphPaths()
|
||||
: visualKey(""), graph(), pathNodes()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SpecialAction> getCompositeAction(
|
||||
const Nullkiller * ai,
|
||||
std::shared_ptr<ISpecialActionFactory> linkActionFactory,
|
||||
std::shared_ptr<SpecialAction> transitionAction)
|
||||
{
|
||||
if(!linkActionFactory)
|
||||
return transitionAction;
|
||||
|
||||
auto linkAction = linkActionFactory->create(ai);
|
||||
|
||||
if(!transitionAction)
|
||||
return linkAction;
|
||||
|
||||
std::vector<std::shared_ptr<const SpecialAction>> actionsArray = {
|
||||
transitionAction,
|
||||
linkAction
|
||||
};
|
||||
|
||||
return std::make_shared<CompositeAction>(actionsArray);
|
||||
}
|
||||
|
||||
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth)
|
||||
{
|
||||
graph.copyFrom(*ai->baseGraph);
|
||||
graph.connectHeroes(ai);
|
||||
|
||||
visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
|
||||
pathNodes.clear();
|
||||
|
||||
GraphNodeComparer cmp(pathNodes);
|
||||
GraphPathNode::TFibHeap pq(cmp);
|
||||
|
||||
pathNodes[targetHero->visitablePos()][GrapthPathNodeType::NORMAL].cost = 0;
|
||||
pq.emplace(GraphPathNodePointer(targetHero->visitablePos(), GrapthPathNodeType::NORMAL));
|
||||
|
||||
while(!pq.empty())
|
||||
{
|
||||
GraphPathNodePointer pos = pq.top();
|
||||
pq.pop();
|
||||
|
||||
auto & node = getOrCreateNode(pos);
|
||||
std::shared_ptr<SpecialAction> transitionAction;
|
||||
|
||||
if(node.obj)
|
||||
{
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
|| node.obj->ID == Obj::BORDERGUARD
|
||||
|| node.obj->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
|
||||
auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
|
||||
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
&& questObj->quest->mission == Rewardable::Limiter{}
|
||||
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto questAction = std::make_shared<AIPathfinding::QuestAction>(questInfo);
|
||||
|
||||
if(!questAction->canAct(ai, targetHero))
|
||||
{
|
||||
transitionAction = questAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.isInQueue = false;
|
||||
|
||||
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq, scanDepth](int3 target, const ObjectLink & o)
|
||||
{
|
||||
auto compositeAction = getCompositeAction(ai, o.specialAction, transitionAction);
|
||||
auto targetNodeType = o.danger || compositeAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto targetPointer = GraphPathNodePointer(target, targetNodeType);
|
||||
auto & targetNode = getOrCreateNode(targetPointer);
|
||||
|
||||
if(targetNode.tryUpdate(pos, node, o))
|
||||
{
|
||||
if(targetNode.cost > scanDepth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
targetNode.specialAction = compositeAction;
|
||||
|
||||
auto targetGraphNode = graph.getNode(target);
|
||||
|
||||
if(targetGraphNode.objID.hasValue())
|
||||
{
|
||||
targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false);
|
||||
|
||||
if(targetNode.obj && targetNode.obj->ID == Obj::HERO)
|
||||
return;
|
||||
}
|
||||
|
||||
if(targetNode.isInQueue)
|
||||
{
|
||||
pq.increase(targetNode.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetNode.handle = pq.emplace(targetPointer);
|
||||
targetNode.isInQueue = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GraphPaths::dumpToLog() const
|
||||
{
|
||||
logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
|
||||
{
|
||||
for(auto & tile : pathNodes)
|
||||
{
|
||||
for(auto & node : tile.second)
|
||||
{
|
||||
if(!node.previous.valid())
|
||||
continue;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace(
|
||||
"%s -> %s: %f !%d",
|
||||
node.previous.coord.toString(),
|
||||
tile.first.toString(),
|
||||
node.cost,
|
||||
node.danger);
|
||||
}
|
||||
|
||||
logBuilder.addLine(node.previous.coord, tile.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
|
||||
{
|
||||
auto newCost = prev.cost + link.cost;
|
||||
|
||||
if(newCost < cost)
|
||||
{
|
||||
previous = pos;
|
||||
danger = prev.danger + link.danger;
|
||||
cost = newCost;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
|
||||
{
|
||||
auto nodes = pathNodes.find(tile);
|
||||
|
||||
if(nodes == pathNodes.end())
|
||||
return;
|
||||
|
||||
for(auto & node : nodes->second)
|
||||
{
|
||||
if(!node.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = node.danger;
|
||||
float cost = node.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
auto current = GraphPathNodePointer(nodes->first, node.nodeType);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto currentTile = pathNodes.find(current.coord);
|
||||
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
if(!currentNode.previous.valid())
|
||||
break;
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
|
||||
current = currentNode.previous;
|
||||
}
|
||||
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & path : entryPaths)
|
||||
{
|
||||
if(path.targetHero != hero)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = cost;
|
||||
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
||||
n.danger = danger;
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
n.specialAction = getNode(*graphTile).specialAction;
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
node.parentIndex++;
|
||||
}
|
||||
|
||||
path.nodes.insert(path.nodes.begin(), n);
|
||||
}
|
||||
|
||||
path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
|
||||
{
|
||||
auto nodes = pathNodes.find(tile);
|
||||
|
||||
if(nodes == pathNodes.end())
|
||||
return;
|
||||
|
||||
for(auto & targetNode : nodes->second)
|
||||
{
|
||||
if(!targetNode.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = targetNode.danger;
|
||||
float cost = targetNode.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
auto current = GraphPathNodePointer(nodes->first, targetNode.nodeType);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto currentTile = pathNodes.find(current.coord);
|
||||
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
|
||||
current = currentNode.previous;
|
||||
}
|
||||
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & entryPath : entryPaths)
|
||||
{
|
||||
if(entryPath.targetHero != hero)
|
||||
continue;
|
||||
|
||||
auto & path = paths.emplace_back();
|
||||
|
||||
path.targetHero = entryPath.targetHero;
|
||||
path.heroArmy = entryPath.heroArmy;
|
||||
path.exchangeCount = entryPath.exchangeCount;
|
||||
path.armyLoss = entryPath.armyLoss + ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
|
||||
// final node
|
||||
n.coord = tile;
|
||||
n.cost = targetNode.cost;
|
||||
n.danger = targetNode.danger;
|
||||
n.parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(n);
|
||||
|
||||
for(auto entryNode = entryPath.nodes.rbegin(); entryNode != entryPath.nodes.rend(); entryNode++)
|
||||
{
|
||||
auto blocker = ai->objectClusterizer->getBlocker(*entryNode);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
// blocker node
|
||||
path.nodes.push_back(*entryNode);
|
||||
path.nodes.back().parentIndex = path.nodes.size() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
auto & node = getNode(*graphTile);
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = node.cost;
|
||||
n.turns = static_cast<ui8>(node.cost);
|
||||
n.danger = node.danger;
|
||||
n.specialAction = node.specialAction;
|
||||
n.parentIndex = path.nodes.size();
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
auto blocker = ai->objectClusterizer->getBlocker(n);
|
||||
|
||||
if(!blocker)
|
||||
continue;
|
||||
|
||||
// blocker node
|
||||
path.nodes.push_back(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
118
AI/Nullkiller/Pathfinding/GraphPaths.h
Normal file
118
AI/Nullkiller/Pathfinding/GraphPaths.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* GraphPaths.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ObjectGraph.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
class Nullkiller;
|
||||
|
||||
struct GraphPathNode;
|
||||
|
||||
enum GrapthPathNodeType
|
||||
{
|
||||
NORMAL,
|
||||
|
||||
BATTLE,
|
||||
|
||||
LAST
|
||||
};
|
||||
|
||||
struct GraphPathNodePointer
|
||||
{
|
||||
int3 coord = int3(-1);
|
||||
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
|
||||
|
||||
GraphPathNodePointer() = default;
|
||||
|
||||
GraphPathNodePointer(int3 coord, GrapthPathNodeType type)
|
||||
:coord(coord), nodeType(type)
|
||||
{ }
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return coord.valid();
|
||||
}
|
||||
};
|
||||
|
||||
using GraphNodeStorage = std::unordered_map<int3, GraphPathNode[GrapthPathNodeType::LAST]>;
|
||||
|
||||
class GraphNodeComparer
|
||||
{
|
||||
const GraphNodeStorage & pathNodes;
|
||||
|
||||
public:
|
||||
GraphNodeComparer(const GraphNodeStorage & pathNodes)
|
||||
:pathNodes(pathNodes)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const;
|
||||
};
|
||||
|
||||
struct GraphPathNode
|
||||
{
|
||||
const float BAD_COST = 100000;
|
||||
|
||||
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
|
||||
GraphPathNodePointer previous;
|
||||
float cost = BAD_COST;
|
||||
uint64_t danger = 0;
|
||||
const CGObjectInstance * obj = nullptr;
|
||||
std::shared_ptr<SpecialAction> specialAction;
|
||||
|
||||
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
||||
|
||||
TFibHeap::handle_type handle;
|
||||
bool isInQueue = false;
|
||||
|
||||
bool reachable() const
|
||||
{
|
||||
return cost < BAD_COST;
|
||||
}
|
||||
|
||||
bool tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link);
|
||||
};
|
||||
|
||||
class GraphPaths
|
||||
{
|
||||
ObjectGraph graph;
|
||||
GraphNodeStorage pathNodes;
|
||||
std::string visualKey;
|
||||
|
||||
public:
|
||||
GraphPaths();
|
||||
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth);
|
||||
void addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void dumpToLog() const;
|
||||
|
||||
private:
|
||||
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
|
||||
{
|
||||
auto & node = pathNodes[pos.coord][pos.nodeType];
|
||||
|
||||
node.nodeType = pos.nodeType;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const GraphPathNode & getNode(const GraphPathNodePointer & pos) const
|
||||
{
|
||||
auto & node = pathNodes.at(pos.coord)[pos.nodeType];
|
||||
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ObjectGraph.h"
|
||||
#include "ObjectGraphCalculator.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
#include "../../../CCallback.h"
|
||||
@ -22,392 +23,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
struct ConnectionCostInfo
|
||||
{
|
||||
float totalCost = 0;
|
||||
float avg = 0;
|
||||
int connectionsCount = 0;
|
||||
};
|
||||
|
||||
class ObjectGraphCalculator
|
||||
{
|
||||
private:
|
||||
ObjectGraph * target;
|
||||
const Nullkiller * ai;
|
||||
std::mutex syncLock;
|
||||
|
||||
std::map<const CGHeroInstance *, HeroRole> actors;
|
||||
std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
|
||||
|
||||
std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
|
||||
std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
|
||||
|
||||
public:
|
||||
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
||||
:ai(ai), target(target), syncLock()
|
||||
{
|
||||
}
|
||||
|
||||
void setGraphObjects()
|
||||
{
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
|
||||
{
|
||||
addObjectActor(obj);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
addObjectActor(town);
|
||||
}
|
||||
}
|
||||
|
||||
void calculateConnections()
|
||||
{
|
||||
updatePaths();
|
||||
|
||||
std::vector<AIPath> pathCache;
|
||||
|
||||
foreach_tile_pos(ai->cb.get(), [this, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
calculateConnections(pos, pathCache);
|
||||
});
|
||||
|
||||
removeExtraConnections();
|
||||
}
|
||||
|
||||
float getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
float neighborCost = std::numeric_limits<float>::max();
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Checking junction %s", pos.toString());
|
||||
}
|
||||
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
auto costTotal = this->getConnectionsCost(pathCache);
|
||||
|
||||
if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
|
||||
{
|
||||
neighborCost = costTotal.avg;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Better node found at %s", neighbor.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return neighborCost;
|
||||
}
|
||||
|
||||
void addMinimalDistanceJunctions()
|
||||
{
|
||||
tbb::concurrent_unordered_set<int3, std::hash<int3>> junctions;
|
||||
|
||||
pforeachTilePaths(ai->cb->getMapSize(), ai, [this, &junctions](const int3 & pos, std::vector<AIPath> & paths)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
return;
|
||||
|
||||
if(ai->cb->getGuardingCreaturePosition(pos).valid())
|
||||
return;
|
||||
|
||||
ConnectionCostInfo currentCost = getConnectionsCost(paths);
|
||||
|
||||
if(currentCost.connectionsCount <= 2)
|
||||
return;
|
||||
|
||||
float neighborCost = getNeighborConnectionsCost(pos, paths);
|
||||
|
||||
if(currentCost.avg < neighborCost)
|
||||
{
|
||||
junctions.insert(pos);
|
||||
}
|
||||
});
|
||||
|
||||
for(auto pos : junctions)
|
||||
{
|
||||
addJunctionActor(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void updatePaths()
|
||||
{
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 5;
|
||||
ps.scoutTurnDistanceLimit = 1;
|
||||
ps.allowBypassObjects = false;
|
||||
|
||||
ai->pathfinder->updatePaths(actors, ps);
|
||||
}
|
||||
|
||||
void calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
{
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
if(target->hasNodeAt(neighbor))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
for(auto & path : pathCache)
|
||||
{
|
||||
if(pos == path.targetHero->visitablePos())
|
||||
{
|
||||
target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto obj = ai->cb->getTopObj(pos);
|
||||
|
||||
if((obj && obj->ID == Obj::BOAT) || target->isVirtualBoat(pos))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path : pathCache)
|
||||
{
|
||||
auto from = path.targetHero->visitablePos();
|
||||
auto fromObj = actorObjectMap[path.targetHero];
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos, path.targetHero, true);
|
||||
auto updated = target->tryAddConnection(
|
||||
from,
|
||||
pos,
|
||||
path.movementCost(),
|
||||
danger);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
fromObj ? fromObj->getObjectName() : "J", from.toString(),
|
||||
"Boat", pos.toString(),
|
||||
pos.toString(),
|
||||
path.movementCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
|
||||
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path1 : pathCache)
|
||||
{
|
||||
for(AIPath & path2 : pathCache)
|
||||
{
|
||||
if(path1.targetHero == path2.targetHero)
|
||||
continue;
|
||||
|
||||
auto pos1 = path1.targetHero->visitablePos();
|
||||
auto pos2 = path2.targetHero->visitablePos();
|
||||
|
||||
if(guardPos.valid() && guardPos != pos1 && guardPos != pos2)
|
||||
continue;
|
||||
|
||||
auto obj1 = actorObjectMap[path1.targetHero];
|
||||
auto obj2 = actorObjectMap[path2.targetHero];
|
||||
|
||||
auto tile1 = cb->getTile(pos1);
|
||||
auto tile2 = cb->getTile(pos2);
|
||||
|
||||
if(tile2->isWater() && !tile1->isWater())
|
||||
{
|
||||
if(!cb->getTile(pos)->isWater())
|
||||
continue;
|
||||
|
||||
auto startingObjIsBoat = (obj1 && obj1->ID == Obj::BOAT) || target->isVirtualBoat(pos1);
|
||||
|
||||
if(!startingObjIsBoat)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
|
||||
|
||||
auto updated = target->tryAddConnection(
|
||||
pos1,
|
||||
pos2,
|
||||
path1.movementCost() + path2.movementCost(),
|
||||
danger);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
|
||||
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
|
||||
pos.toString(),
|
||||
path1.movementCost() + path2.movementCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isExtraConnection(float direct, float side1, float side2) const
|
||||
{
|
||||
float sideRatio = (side1 + side2) / direct;
|
||||
|
||||
return sideRatio < 1.25f && direct > side1 && direct > side2;
|
||||
}
|
||||
|
||||
void removeExtraConnections()
|
||||
{
|
||||
std::vector<std::pair<int3, int3>> connectionsToRemove;
|
||||
|
||||
for(auto & actor : temporaryActorHeroes)
|
||||
{
|
||||
auto pos = actor->visitablePos();
|
||||
auto & currentNode = target->getNode(pos);
|
||||
|
||||
target->iterateConnections(pos, [this, &pos, &connectionsToRemove, ¤tNode](int3 n1, ObjectLink o1)
|
||||
{
|
||||
target->iterateConnections(n1, [&pos, &o1, ¤tNode, &connectionsToRemove, this](int3 n2, ObjectLink o2)
|
||||
{
|
||||
auto direct = currentNode.connections.find(n2);
|
||||
|
||||
if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost))
|
||||
{
|
||||
connectionsToRemove.push_back({pos, n2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(connectionsToRemove);
|
||||
|
||||
for(auto & c : connectionsToRemove)
|
||||
{
|
||||
target->removeConnection(c.first, c.second);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addObjectActor(const CGObjectInstance * obj)
|
||||
{
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
auto visitablePos = obj->visitablePos();
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = obj;
|
||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::BOAT ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->addObject(obj);
|
||||
|
||||
auto shipyard = dynamic_cast<const IShipyard *>(obj);
|
||||
|
||||
if(shipyard && shipyard->bestLocation().valid())
|
||||
{
|
||||
int3 virtualBoat = shipyard->bestLocation();
|
||||
|
||||
addJunctionActor(virtualBoat, true);
|
||||
target->addVirtualBoat(virtualBoat, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(syncLock);
|
||||
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = nullptr;
|
||||
actors[objectActor] = isVirtualBoat ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->registerJunction(visitablePos);
|
||||
}
|
||||
|
||||
ConnectionCostInfo getConnectionsCost(std::vector<AIPath> & paths) const
|
||||
{
|
||||
std::map<int3, float> costs;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
auto fromPos = path.targetHero->visitablePos();
|
||||
auto cost = costs.find(fromPos);
|
||||
|
||||
if(cost == costs.end())
|
||||
{
|
||||
costs.emplace(fromPos, path.movementCost());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(path.movementCost() < cost->second)
|
||||
{
|
||||
costs[fromPos] = path.movementCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionCostInfo result;
|
||||
|
||||
for(auto & cost : costs)
|
||||
{
|
||||
result.totalCost += cost.second;
|
||||
result.connectionsCount++;
|
||||
}
|
||||
|
||||
if(result.connectionsCount)
|
||||
{
|
||||
result.avg = result.totalCost / result.connectionsCount;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
bool ObjectGraph::tryAddConnection(
|
||||
const int3 & from,
|
||||
const int3 & to,
|
||||
@ -538,372 +153,4 @@ void ObjectGraph::dumpToLog(std::string visualKey) const
|
||||
});
|
||||
}
|
||||
|
||||
bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const
|
||||
{
|
||||
return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
|
||||
}
|
||||
|
||||
GraphPaths::GraphPaths()
|
||||
: visualKey(""), graph(), pathNodes()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SpecialAction> getCompositeAction(
|
||||
const Nullkiller * ai,
|
||||
std::shared_ptr<ISpecialActionFactory> linkActionFactory,
|
||||
std::shared_ptr<SpecialAction> transitionAction)
|
||||
{
|
||||
if(!linkActionFactory)
|
||||
return transitionAction;
|
||||
|
||||
auto linkAction = linkActionFactory->create(ai);
|
||||
|
||||
if(!transitionAction)
|
||||
return linkAction;
|
||||
|
||||
std::vector<std::shared_ptr<const SpecialAction>> actionsArray = {
|
||||
transitionAction,
|
||||
linkAction
|
||||
};
|
||||
|
||||
return std::make_shared<CompositeAction>(actionsArray);
|
||||
}
|
||||
|
||||
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth)
|
||||
{
|
||||
graph.copyFrom(*ai->baseGraph);
|
||||
graph.connectHeroes(ai);
|
||||
|
||||
visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
|
||||
pathNodes.clear();
|
||||
|
||||
GraphNodeComparer cmp(pathNodes);
|
||||
GraphPathNode::TFibHeap pq(cmp);
|
||||
|
||||
pathNodes[targetHero->visitablePos()][GrapthPathNodeType::NORMAL].cost = 0;
|
||||
pq.emplace(GraphPathNodePointer(targetHero->visitablePos(), GrapthPathNodeType::NORMAL));
|
||||
|
||||
while(!pq.empty())
|
||||
{
|
||||
GraphPathNodePointer pos = pq.top();
|
||||
pq.pop();
|
||||
|
||||
auto & node = getOrCreateNode(pos);
|
||||
std::shared_ptr<SpecialAction> transitionAction;
|
||||
|
||||
if(node.obj)
|
||||
{
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
|| node.obj->ID == Obj::BORDERGUARD
|
||||
|| node.obj->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
|
||||
auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
|
||||
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
&& questObj->quest->mission == Rewardable::Limiter{}
|
||||
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto questAction = std::make_shared<AIPathfinding::QuestAction>(questInfo);
|
||||
|
||||
if(!questAction->canAct(ai, targetHero))
|
||||
{
|
||||
transitionAction = questAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.isInQueue = false;
|
||||
|
||||
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq, scanDepth](int3 target, const ObjectLink & o)
|
||||
{
|
||||
auto compositeAction = getCompositeAction(ai, o.specialAction, transitionAction);
|
||||
auto targetNodeType = o.danger || compositeAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto targetPointer = GraphPathNodePointer(target, targetNodeType);
|
||||
auto & targetNode = getOrCreateNode(targetPointer);
|
||||
|
||||
if(targetNode.tryUpdate(pos, node, o))
|
||||
{
|
||||
if(targetNode.cost > scanDepth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
targetNode.specialAction = compositeAction;
|
||||
|
||||
auto targetGraphNode = graph.getNode(target);
|
||||
|
||||
if(targetGraphNode.objID.hasValue())
|
||||
{
|
||||
targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false);
|
||||
|
||||
if(targetNode.obj && targetNode.obj->ID == Obj::HERO)
|
||||
return;
|
||||
}
|
||||
|
||||
if(targetNode.isInQueue)
|
||||
{
|
||||
pq.increase(targetNode.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetNode.handle = pq.emplace(targetPointer);
|
||||
targetNode.isInQueue = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GraphPaths::dumpToLog() const
|
||||
{
|
||||
logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
|
||||
{
|
||||
for(auto & tile : pathNodes)
|
||||
{
|
||||
for(auto & node : tile.second)
|
||||
{
|
||||
if(!node.previous.valid())
|
||||
continue;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace(
|
||||
"%s -> %s: %f !%d",
|
||||
node.previous.coord.toString(),
|
||||
tile.first.toString(),
|
||||
node.cost,
|
||||
node.danger);
|
||||
}
|
||||
|
||||
logBuilder.addLine(node.previous.coord, tile.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
|
||||
{
|
||||
auto newCost = prev.cost + link.cost;
|
||||
|
||||
if(newCost < cost)
|
||||
{
|
||||
previous = pos;
|
||||
danger = prev.danger + link.danger;
|
||||
cost = newCost;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
|
||||
{
|
||||
auto nodes = pathNodes.find(tile);
|
||||
|
||||
if(nodes == pathNodes.end())
|
||||
return;
|
||||
|
||||
for(auto & node : nodes->second)
|
||||
{
|
||||
if(!node.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = node.danger;
|
||||
float cost = node.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
auto current = GraphPathNodePointer(nodes->first, node.nodeType);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto currentTile = pathNodes.find(current.coord);
|
||||
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
if(!currentNode.previous.valid())
|
||||
break;
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
|
||||
current = currentNode.previous;
|
||||
}
|
||||
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & path : entryPaths)
|
||||
{
|
||||
if(path.targetHero != hero)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = cost;
|
||||
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
||||
n.danger = danger;
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
n.specialAction = getNode(*graphTile).specialAction;
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
node.parentIndex++;
|
||||
}
|
||||
|
||||
path.nodes.insert(path.nodes.begin(), n);
|
||||
}
|
||||
|
||||
path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
|
||||
{
|
||||
auto nodes = pathNodes.find(tile);
|
||||
|
||||
if(nodes == pathNodes.end())
|
||||
return;
|
||||
|
||||
for(auto & targetNode : nodes->second)
|
||||
{
|
||||
if(!targetNode.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = targetNode.danger;
|
||||
float cost = targetNode.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
auto current = GraphPathNodePointer(nodes->first, targetNode.nodeType);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto currentTile = pathNodes.find(current.coord);
|
||||
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
|
||||
current = currentNode.previous;
|
||||
}
|
||||
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & entryPath : entryPaths)
|
||||
{
|
||||
if(entryPath.targetHero != hero)
|
||||
continue;
|
||||
|
||||
auto & path = paths.emplace_back();
|
||||
|
||||
path.targetHero = entryPath.targetHero;
|
||||
path.heroArmy = entryPath.heroArmy;
|
||||
path.exchangeCount = entryPath.exchangeCount;
|
||||
path.armyLoss = entryPath.armyLoss + ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
|
||||
// final node
|
||||
n.coord = tile;
|
||||
n.cost = targetNode.cost;
|
||||
n.danger = targetNode.danger;
|
||||
n.parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(n);
|
||||
|
||||
for(auto entryNode = entryPath.nodes.rbegin(); entryNode != entryPath.nodes.rend(); entryNode++)
|
||||
{
|
||||
auto blocker = ai->objectClusterizer->getBlocker(*entryNode);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
// blocker node
|
||||
path.nodes.push_back(*entryNode);
|
||||
path.nodes.back().parentIndex = path.nodes.size() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
auto & node = getNode(*graphTile);
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = node.cost;
|
||||
n.turns = static_cast<ui8>(node.cost);
|
||||
n.danger = node.danger;
|
||||
n.specialAction = node.specialAction;
|
||||
n.parentIndex = path.nodes.size();
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
auto blocker = ai->objectClusterizer->getBlocker(n);
|
||||
|
||||
if(!blocker)
|
||||
continue;
|
||||
|
||||
// blocker node
|
||||
path.nodes.push_back(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -112,102 +112,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphPathNode;
|
||||
|
||||
enum GrapthPathNodeType
|
||||
{
|
||||
NORMAL,
|
||||
|
||||
BATTLE,
|
||||
|
||||
LAST
|
||||
};
|
||||
|
||||
struct GraphPathNodePointer
|
||||
{
|
||||
int3 coord = int3(-1);
|
||||
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
|
||||
|
||||
GraphPathNodePointer() = default;
|
||||
|
||||
GraphPathNodePointer(int3 coord, GrapthPathNodeType type)
|
||||
:coord(coord), nodeType(type)
|
||||
{ }
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return coord.valid();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::unordered_map<int3, GraphPathNode[GrapthPathNodeType::LAST]> GraphNodeStorage;
|
||||
|
||||
class GraphNodeComparer
|
||||
{
|
||||
const GraphNodeStorage & pathNodes;
|
||||
|
||||
public:
|
||||
GraphNodeComparer(const GraphNodeStorage & pathNodes)
|
||||
:pathNodes(pathNodes)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const;
|
||||
};
|
||||
|
||||
struct GraphPathNode
|
||||
{
|
||||
const float BAD_COST = 100000;
|
||||
|
||||
GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
|
||||
GraphPathNodePointer previous;
|
||||
float cost = BAD_COST;
|
||||
uint64_t danger = 0;
|
||||
const CGObjectInstance * obj = nullptr;
|
||||
std::shared_ptr<SpecialAction> specialAction;
|
||||
|
||||
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
||||
|
||||
TFibHeap::handle_type handle;
|
||||
bool isInQueue = false;
|
||||
|
||||
bool reachable() const
|
||||
{
|
||||
return cost < BAD_COST;
|
||||
}
|
||||
|
||||
bool tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link);
|
||||
};
|
||||
|
||||
class GraphPaths
|
||||
{
|
||||
ObjectGraph graph;
|
||||
GraphNodeStorage pathNodes;
|
||||
std::string visualKey;
|
||||
|
||||
public:
|
||||
GraphPaths();
|
||||
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth);
|
||||
void addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void dumpToLog() const;
|
||||
|
||||
private:
|
||||
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
|
||||
{
|
||||
auto & node = pathNodes[pos.coord][pos.nodeType];
|
||||
|
||||
node.nodeType = pos.nodeType;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const GraphPathNode & getNode(const GraphPathNodePointer & pos) const
|
||||
{
|
||||
auto & node = pathNodes.at(pos.coord)[pos.nodeType];
|
||||
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
387
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp
Normal file
387
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp
Normal file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
* ObjectGraphCalculator.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "ObjectGraphCalculator.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
#include "Actions/QuestAction.h"
|
||||
#include "../pforeach.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
ObjectGraphCalculator::ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
||||
:ai(ai), target(target), syncLock()
|
||||
{
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::setGraphObjects()
|
||||
{
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
|
||||
{
|
||||
addObjectActor(obj);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
addObjectActor(town);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::calculateConnections()
|
||||
{
|
||||
updatePaths();
|
||||
|
||||
std::vector<AIPath> pathCache;
|
||||
|
||||
foreach_tile_pos(ai->cb.get(), [this, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
calculateConnections(pos, pathCache);
|
||||
});
|
||||
|
||||
removeExtraConnections();
|
||||
}
|
||||
|
||||
float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
float neighborCost = std::numeric_limits<float>::max();
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Checking junction %s", pos.toString());
|
||||
}
|
||||
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
auto costTotal = this->getConnectionsCost(pathCache);
|
||||
|
||||
if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
|
||||
{
|
||||
neighborCost = costTotal.avg;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Better node found at %s", neighbor.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return neighborCost;
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::addMinimalDistanceJunctions()
|
||||
{
|
||||
tbb::concurrent_unordered_set<int3, std::hash<int3>> junctions;
|
||||
|
||||
pforeachTilePaths(ai->cb->getMapSize(), ai, [this, &junctions](const int3 & pos, std::vector<AIPath> & paths)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
return;
|
||||
|
||||
if(ai->cb->getGuardingCreaturePosition(pos).valid())
|
||||
return;
|
||||
|
||||
ConnectionCostInfo currentCost = getConnectionsCost(paths);
|
||||
|
||||
if(currentCost.connectionsCount <= 2)
|
||||
return;
|
||||
|
||||
float neighborCost = getNeighborConnectionsCost(pos, paths);
|
||||
|
||||
if(currentCost.avg < neighborCost)
|
||||
{
|
||||
junctions.insert(pos);
|
||||
}
|
||||
});
|
||||
|
||||
for(auto pos : junctions)
|
||||
{
|
||||
addJunctionActor(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::updatePaths()
|
||||
{
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 5;
|
||||
ps.scoutTurnDistanceLimit = 1;
|
||||
ps.allowBypassObjects = false;
|
||||
|
||||
ai->pathfinder->updatePaths(actors, ps);
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
{
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
if(target->hasNodeAt(neighbor))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
for(auto & path : pathCache)
|
||||
{
|
||||
if(pos == path.targetHero->visitablePos())
|
||||
{
|
||||
target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto obj = ai->cb->getTopObj(pos);
|
||||
|
||||
if((obj && obj->ID == Obj::BOAT) || target->isVirtualBoat(pos))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path : pathCache)
|
||||
{
|
||||
auto from = path.targetHero->visitablePos();
|
||||
auto fromObj = actorObjectMap[path.targetHero];
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos, path.targetHero, true);
|
||||
auto updated = target->tryAddConnection(
|
||||
from,
|
||||
pos,
|
||||
path.movementCost(),
|
||||
danger);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
fromObj ? fromObj->getObjectName() : "J", from.toString(),
|
||||
"Boat", pos.toString(),
|
||||
pos.toString(),
|
||||
path.movementCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
|
||||
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path1 : pathCache)
|
||||
{
|
||||
for(AIPath & path2 : pathCache)
|
||||
{
|
||||
if(path1.targetHero == path2.targetHero)
|
||||
continue;
|
||||
|
||||
auto pos1 = path1.targetHero->visitablePos();
|
||||
auto pos2 = path2.targetHero->visitablePos();
|
||||
|
||||
if(guardPos.valid() && guardPos != pos1 && guardPos != pos2)
|
||||
continue;
|
||||
|
||||
auto obj1 = actorObjectMap[path1.targetHero];
|
||||
auto obj2 = actorObjectMap[path2.targetHero];
|
||||
|
||||
auto tile1 = cb->getTile(pos1);
|
||||
auto tile2 = cb->getTile(pos2);
|
||||
|
||||
if(tile2->isWater() && !tile1->isWater())
|
||||
{
|
||||
if(!cb->getTile(pos)->isWater())
|
||||
continue;
|
||||
|
||||
auto startingObjIsBoat = (obj1 && obj1->ID == Obj::BOAT) || target->isVirtualBoat(pos1);
|
||||
|
||||
if(!startingObjIsBoat)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
|
||||
|
||||
auto updated = target->tryAddConnection(
|
||||
pos1,
|
||||
pos2,
|
||||
path1.movementCost() + path2.movementCost(),
|
||||
danger);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
|
||||
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
|
||||
pos.toString(),
|
||||
path1.movementCost() + path2.movementCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectGraphCalculator::isExtraConnection(float direct, float side1, float side2) const
|
||||
{
|
||||
float sideRatio = (side1 + side2) / direct;
|
||||
|
||||
return sideRatio < 1.25f && direct > side1 && direct > side2;
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::removeExtraConnections()
|
||||
{
|
||||
std::vector<std::pair<int3, int3>> connectionsToRemove;
|
||||
|
||||
for(auto & actor : temporaryActorHeroes)
|
||||
{
|
||||
auto pos = actor->visitablePos();
|
||||
auto & currentNode = target->getNode(pos);
|
||||
|
||||
target->iterateConnections(pos, [this, &pos, &connectionsToRemove, ¤tNode](int3 n1, ObjectLink o1)
|
||||
{
|
||||
target->iterateConnections(n1, [&pos, &o1, ¤tNode, &connectionsToRemove, this](int3 n2, ObjectLink o2)
|
||||
{
|
||||
auto direct = currentNode.connections.find(n2);
|
||||
|
||||
if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost))
|
||||
{
|
||||
connectionsToRemove.push_back({pos, n2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(connectionsToRemove);
|
||||
|
||||
for(auto & c : connectionsToRemove)
|
||||
{
|
||||
target->removeConnection(c.first, c.second);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::addObjectActor(const CGObjectInstance * obj)
|
||||
{
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
auto visitablePos = obj->visitablePos();
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = obj;
|
||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::BOAT ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->addObject(obj);
|
||||
|
||||
auto shipyard = dynamic_cast<const IShipyard *>(obj);
|
||||
|
||||
if(shipyard && shipyard->bestLocation().valid())
|
||||
{
|
||||
int3 virtualBoat = shipyard->bestLocation();
|
||||
|
||||
addJunctionActor(virtualBoat, true);
|
||||
target->addVirtualBoat(virtualBoat, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGraphCalculator::addJunctionActor(const int3 & visitablePos, bool isVirtualBoat)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(syncLock);
|
||||
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = nullptr;
|
||||
actors[objectActor] = isVirtualBoat ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->registerJunction(visitablePos);
|
||||
}
|
||||
|
||||
ConnectionCostInfo ObjectGraphCalculator::getConnectionsCost(std::vector<AIPath> & paths) const
|
||||
{
|
||||
std::map<int3, float> costs;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
auto fromPos = path.targetHero->visitablePos();
|
||||
auto cost = costs.find(fromPos);
|
||||
|
||||
if(cost == costs.end())
|
||||
{
|
||||
costs.emplace(fromPos, path.movementCost());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(path.movementCost() < cost->second)
|
||||
{
|
||||
costs[fromPos] = path.movementCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionCostInfo result;
|
||||
|
||||
for(auto & cost : costs)
|
||||
{
|
||||
result.totalCost += cost.second;
|
||||
result.connectionsCount++;
|
||||
}
|
||||
|
||||
if(result.connectionsCount)
|
||||
{
|
||||
result.avg = result.totalCost / result.connectionsCount;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
56
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h
Normal file
56
AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* ObjectGraphCalculator.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ObjectGraph.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
struct ConnectionCostInfo
|
||||
{
|
||||
float totalCost = 0;
|
||||
float avg = 0;
|
||||
int connectionsCount = 0;
|
||||
};
|
||||
|
||||
class ObjectGraphCalculator
|
||||
{
|
||||
private:
|
||||
ObjectGraph * target;
|
||||
const Nullkiller * ai;
|
||||
std::mutex syncLock;
|
||||
|
||||
std::map<const CGHeroInstance *, HeroRole> actors;
|
||||
std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
|
||||
|
||||
std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
|
||||
std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
|
||||
|
||||
public:
|
||||
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai);
|
||||
void setGraphObjects();
|
||||
void calculateConnections();
|
||||
float getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache);
|
||||
void addMinimalDistanceJunctions();
|
||||
|
||||
private:
|
||||
void updatePaths();
|
||||
void calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache);
|
||||
bool isExtraConnection(float direct, float side1, float side2) const;
|
||||
void removeExtraConnections();
|
||||
void addObjectActor(const CGObjectInstance * obj);
|
||||
void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false);
|
||||
ConnectionCostInfo getConnectionsCost(std::vector<AIPath> & paths) const;
|
||||
};
|
||||
|
||||
}
|
@ -18,10 +18,11 @@ class VCAI;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
template<typename T> class DLL_EXPORT CGoal : public AbstractGoal
|
||||
template<typename T>
|
||||
class DLL_EXPORT CGoal : public AbstractGoal
|
||||
{
|
||||
public:
|
||||
CGoal<T>(EGoals goal = INVALID) : AbstractGoal(goal)
|
||||
CGoal(EGoals goal = INVALID) : AbstractGoal(goal)
|
||||
{
|
||||
priority = 0;
|
||||
isElementar = false;
|
||||
@ -56,7 +57,7 @@ namespace Goals
|
||||
return f->evaluate(static_cast<T &>(*this)); //casting enforces template instantiation
|
||||
}
|
||||
|
||||
CGoal<T> * clone() const override
|
||||
CGoal * clone() const override
|
||||
{
|
||||
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo apt remove needrestart
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo apt remove needrestart
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
|
93
ChangeLog.md
93
ChangeLog.md
@ -1,3 +1,96 @@
|
||||
# 1.5.1 -> 1.5.2
|
||||
|
||||
### Stability
|
||||
* Fixed crash on closing game while combat or map animations are playing
|
||||
* Fixed crash on closing game while network thread is waiting for dialog to be closed
|
||||
* Fixed random crash on starting random map with 'random' number of players
|
||||
* Fixed crash caused by thread races on loading map list
|
||||
* Failure to read data from network connection will show up as 'disconnection' and not as a crash
|
||||
* Fixed a possible crash when replaying a manually played battle with the 'unlimited battle replay' option set
|
||||
* Fixed crash when loading save made on a 64-bit system or connecting to multiplayer game with a 64-bit host on a 32-bit system (and vice versa)
|
||||
* Fixed crash when ending a battle in a draw when a hero has the Necromancy skill
|
||||
* Fixed crash when having SPELL_LIKE_ATTACK bonus with invalid spell ID
|
||||
* Fixed transfer of non-first artefacts in backpack if hero does not transfer as well
|
||||
* Game will now abort loading if a corrupt mod is detected instead of crashing without explanation later
|
||||
|
||||
### Multiplayer
|
||||
* Contact between allied players will no longer break simturns
|
||||
* Having hero in range of object owned by another player will now be registered as contact
|
||||
* Multiplayer saves are now visible when starting a single player game
|
||||
* Added chat command '!vote' to initiate a vote to change the duration of simultaneous turns or to change turn timers
|
||||
* Added chat command '!help' to list all available chat commands
|
||||
* All multiplayer chat commands now use a leading exclamation mark
|
||||
|
||||
### Campaigns
|
||||
* If the hero attacks an enemy player and is defeated, he will be correctly registered as defeated by the defending player.
|
||||
* Allow standard victory condition on 'To kill a hero' campaign mission in line with H3
|
||||
* Fixes Adrienne starting without Inferno spell in campaign
|
||||
|
||||
### Interface
|
||||
* For artefacts that are part of a combined artefact, the game will now show which component of that artefact your hero has.
|
||||
* Fixed broken in 1.5.1 shortcut for artifact sets saving
|
||||
* Fixed full screen toggle (F4) not applying changes immediately
|
||||
* Retaliation preview now accounts for creatures that don't receive retaliations (Sprites, Archdevils, etc)
|
||||
* Fixed not visible retaliation preview if damage estimation string is longer than battle log line due to long creature name
|
||||
* Game will now select last save on loading screen
|
||||
* High Scores screen and Campaign Epilogue screen are now displayed with background on resolutions higher than 800x600
|
||||
* Fixed non-functioning shortcut 'P' to access Puzzle Map from adventure map
|
||||
* Added keyboard shortcuts to markets and altars. 'Space' to confirm deal and 'M' to trade maximum possible amount
|
||||
* Pressing 'Escape' in main menu will now trigger 'Back' and 'Quit' buttons
|
||||
* Added keyboard shortcuts to hero exchange window:
|
||||
* * 'F10' will now swap armies
|
||||
* * 'F11' will now swap artifacts. Additionally, 'Ctrl+F11' will swap equipped artifacts, and 'Shift+F11' will swap backpacks
|
||||
* * Added unassigned shortcuts to move armies or artifacts to left or right side
|
||||
* Added keyboard shortcuts to access buildings from town interface:
|
||||
* * 'F' will now open Fort window
|
||||
* * 'B' will now open Town Hall window
|
||||
* * 'G' will now open Mage Guild window
|
||||
* * 'M' will now open Marketplace
|
||||
* * 'R' will now open recruitment interface
|
||||
* * 'T' will now open Tavern window
|
||||
* * 'G' will now open Thieves Guild
|
||||
* * 'E' will now open hero exchange screen, if both heroes are present in town
|
||||
* * 'H' will now open hero screen. Additionally, 'Shift+H' will open garrisoned hero screen, and 'Ctrl+H' will open visiting hero screen
|
||||
* * 'Space' will now swap visiting and garrisoned heroes
|
||||
* Added keyboard shortcuts to switch between tabs in Scenario Selection window:
|
||||
* * 'E' will open Extra Options tab
|
||||
* * 'T' will open Turn Options tab
|
||||
* * 'I' will open Invite Players window (only for lobby games)
|
||||
* * 'R' will now replay video in campaigns
|
||||
* Added keyboard shortcuts to Adventure map:
|
||||
* * 'Ctrl+L' will now prompt to open Load Game screen
|
||||
* * 'Ctrl+M' will now prompt to go to main menu
|
||||
* * 'Ctrl+N' will now prompt to go to New Game screen
|
||||
* * 'Ctrl+Q' will now prompt to quit game
|
||||
* * Page Up, Page Down, Home and End keys will now move hero on adventure map similar to numpad equivalents
|
||||
* * Fixed non-functioning shortcuts '+' and '-' on numpad to zoom adventure map
|
||||
* Added keyboard shortcuts to Battle interface:
|
||||
* * 'V' now allows to view information of hovered unit
|
||||
* * 'I' now allows to view information of active unit
|
||||
|
||||
### Mechanics
|
||||
* Game will no longer pick creatures exclusive to AB campaigns for random creatures or for Refugee Camp, in line with H3
|
||||
* If original movement rules are on, it is not possible to attack guards from visitable object directly, only from free tile
|
||||
* Fixed bug leading that allowed picking up objects while flying on top of water
|
||||
* Hero can now land when flying from guarded tile to accessible guarded tile irregardless of original movement rules switch
|
||||
* Interface will now use same arrow for U-turns in path as H3
|
||||
|
||||
### AI
|
||||
* Nullkiller AI can now explore the map
|
||||
* Nullkiller AI will no longer use the map reveal cheat when allied with a human or when playing on low difficulty
|
||||
* Nullkiller AI is now used by default for allied players
|
||||
|
||||
### Launcher
|
||||
* When extracting data from gog.com offline installer game will extract files directly into used data directory instead of temporary directory
|
||||
|
||||
### Map Editor
|
||||
* Fixed victory / loss conditions widget initialization
|
||||
|
||||
### Modding
|
||||
* Hero specialties with multiple bonuses that have TIMES_HERO_LEVEL updater now work as expected
|
||||
* Spells that apply multiple bonuses with same type and subtype but different value type now work as expected
|
||||
* Added option to toggle layout of guards in creature banks
|
||||
|
||||
# 1.5.0 -> 1.5.1
|
||||
|
||||
### Stability
|
||||
|
@ -242,16 +242,23 @@
|
||||
"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
|
||||
|
||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "按下任意键立即开始战斗",
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "近战攻击 %CREATURE (%DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "近战攻击 %CREATURE (%DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "射击 %CREATURE (%SHOTS, %DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "射击 %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "近战攻击 %CREATURE (%DAMAGE)。",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "近战攻击 %CREATURE (%DAMAGE, %KILLS)。",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "射击 %CREATURE (%SHOTS, %DAMAGE)。",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "射击 %CREATURE (%SHOTS, %DAMAGE, %KILLS)。",
|
||||
"vcmi.battleWindow.damageEstimation.shots" : "%d 弹药剩余",
|
||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d 弹药剩余",
|
||||
"vcmi.battleWindow.damageEstimation.damage" : "%d 伤害",
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭",
|
||||
|
||||
"vcmi.battleWindow.damageRetaliation.will" : "将会反击",
|
||||
"vcmi.battleWindow.damageRetaliation.may" : "可能反击",
|
||||
"vcmi.battleWindow.damageRetaliation.never" : "不会反击。",
|
||||
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE)。",
|
||||
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS)。",
|
||||
|
||||
"vcmi.battleWindow.killed" : "已消灭",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s 死于精准射击",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s 死于精准射击",
|
||||
|
@ -252,6 +252,13 @@
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "obrażenia: %d",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "zginie: %d",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "zginie: %d",
|
||||
|
||||
"vcmi.battleWindow.damageRetaliation.will" : "Nastąpi odwet ",
|
||||
"vcmi.battleWindow.damageRetaliation.may" : "Możliwy odwet ",
|
||||
"vcmi.battleWindow.damageRetaliation.never" : "Nie będzie odwetu.",
|
||||
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
|
||||
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
|
||||
|
||||
"vcmi.battleWindow.killed" : "Zabici",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s zostało zabitych poprzez celne strzały!",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s został zabity poprzez celny strzał!",
|
||||
|
@ -10,8 +10,8 @@ android {
|
||||
applicationId "is.xyz.vcmi"
|
||||
minSdk 19
|
||||
targetSdk 33
|
||||
versionCode 1515
|
||||
versionName "1.5.1"
|
||||
versionCode 1520
|
||||
versionName "1.5.2"
|
||||
setProperty("archivesBaseName", "vcmi")
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ set(client_SRCS
|
||||
|
||||
windows/CCastleInterface.cpp
|
||||
windows/CCreatureWindow.cpp
|
||||
windows/CExchangeWindow.cpp
|
||||
windows/CHeroOverview.cpp
|
||||
windows/CHeroWindow.cpp
|
||||
windows/CKingdomInterface.cpp
|
||||
@ -332,6 +333,7 @@ set(client_HEADERS
|
||||
|
||||
windows/CCastleInterface.h
|
||||
windows/CCreatureWindow.h
|
||||
windows/CExchangeWindow.h
|
||||
windows/CHeroOverview.h
|
||||
windows/CHeroWindow.h
|
||||
windows/CKingdomInterface.h
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
#include "windows/CCastleInterface.h"
|
||||
#include "windows/CCreatureWindow.h"
|
||||
#include "windows/CExchangeWindow.h"
|
||||
#include "windows/CHeroWindow.h"
|
||||
#include "windows/CKingdomInterface.h"
|
||||
#include "windows/CMarketWindow.h"
|
||||
@ -136,7 +137,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
|
||||
LOCPLINT = this;
|
||||
playerID=Player;
|
||||
human=true;
|
||||
battleInt = nullptr;
|
||||
battleInt.reset();
|
||||
castleInt = nullptr;
|
||||
makingTurn = false;
|
||||
showingDialog = new ConditionalWait();
|
||||
|
@ -43,6 +43,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
|
||||
// Check if it's LobbyClientConnected for our client
|
||||
if(pack.uuid == handler.logicConnection->uuid)
|
||||
{
|
||||
handler.logicConnection->setSerializationVersion(pack.version);
|
||||
handler.logicConnection->connectionID = pack.clientId;
|
||||
if(handler.mapToStart)
|
||||
{
|
||||
|
@ -886,9 +886,9 @@ void AdventureMapInterface::hotkeySwitchMapLevel()
|
||||
widget->getMapView()->onMapLevelSwitched();
|
||||
}
|
||||
|
||||
void AdventureMapInterface::hotkeyZoom(int delta)
|
||||
void AdventureMapInterface::hotkeyZoom(int delta, bool useDeadZone)
|
||||
{
|
||||
widget->getMapView()->onMapZoomLevelChanged(delta);
|
||||
widget->getMapView()->onMapZoomLevelChanged(delta, useDeadZone);
|
||||
}
|
||||
|
||||
void AdventureMapInterface::onScreenResize()
|
||||
|
@ -120,7 +120,7 @@ public:
|
||||
void hotkeyEndingTurn();
|
||||
void hotkeyNextTown();
|
||||
void hotkeySwitchMapLevel();
|
||||
void hotkeyZoom(int delta);
|
||||
void hotkeyZoom(int delta, bool useDeadZone);
|
||||
|
||||
/// Called by PlayerInterface when specified player is ready to start his turn
|
||||
void onHotseatWaitStarted(PlayerColor playerID);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "AdventureMapShortcuts.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMT.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
@ -77,21 +78,26 @@ std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts()
|
||||
{ EShortcut::ADVENTURE_CAST_SPELL, optionHeroSelected(), [this]() { this->showSpellbook(); } },
|
||||
{ EShortcut::ADVENTURE_GAME_OPTIONS, optionInMapView(), [this]() { this->adventureOptions(); } },
|
||||
{ EShortcut::GLOBAL_OPTIONS, optionInMapView(), [this]() { this->systemOptions(); } },
|
||||
{ EShortcut::ADVENTURE_FIRST_HERO, optionInMapView(), [this]() { this->firstHero(); } },
|
||||
{ EShortcut::ADVENTURE_NEXT_HERO, optionHasNextHero(), [this]() { this->nextHero(); } },
|
||||
{ EShortcut::GAME_END_TURN, optionCanEndTurn(), [this]() { this->endTurn(); } },
|
||||
{ EShortcut::ADVENTURE_END_TURN, optionCanEndTurn(), [this]() { this->endTurn(); } },
|
||||
{ EShortcut::ADVENTURE_THIEVES_GUILD, optionInMapView(), [this]() { this->showThievesGuild(); } },
|
||||
{ EShortcut::ADVENTURE_VIEW_SCENARIO, optionInMapView(), [this]() { this->showScenarioInfo(); } },
|
||||
{ EShortcut::GAME_SAVE_GAME, optionInMapView(), [this]() { this->saveGame(); } },
|
||||
{ EShortcut::GAME_LOAD_GAME, optionInMapView(), [this]() { this->loadGame(); } },
|
||||
{ EShortcut::ADVENTURE_QUIT_GAME, optionInMapView(), [this]() { this->quitGame(); } },
|
||||
{ EShortcut::ADVENTURE_TO_MAIN_MENU, optionInMapView(), [this]() { this->toMainMenu(); } },
|
||||
{ EShortcut::ADVENTURE_SAVE_GAME, optionInMapView(), [this]() { this->saveGame(); } },
|
||||
{ EShortcut::ADVENTURE_NEW_GAME, optionInMapView(), [this]() { this->newGame(); } },
|
||||
{ EShortcut::ADVENTURE_LOAD_GAME, optionInMapView(), [this]() { this->loadGame(); } },
|
||||
{ EShortcut::ADVENTURE_RESTART_GAME, optionInMapView(), [this]() { this->restartGame(); } },
|
||||
{ EShortcut::ADVENTURE_DIG_GRAIL, optionHeroSelected(), [this]() { this->digGrail(); } },
|
||||
{ EShortcut::ADVENTURE_VIEW_PUZZLE, optionSidePanelActive(),[this]() { this->viewPuzzleMap(); } },
|
||||
{ EShortcut::GAME_RESTART_GAME, optionInMapView(), [this]() { this->restartGame(); } },
|
||||
{ EShortcut::ADVENTURE_VISIT_OBJECT, optionCanVisitObject(), [this]() { this->visitObject(); } },
|
||||
{ EShortcut::ADVENTURE_VIEW_SELECTED, optionInMapView(), [this]() { this->openObject(); } },
|
||||
{ EShortcut::GAME_OPEN_MARKETPLACE, optionInMapView(), [this]() { this->showMarketplace(); } },
|
||||
{ EShortcut::ADVENTURE_ZOOM_IN, optionSidePanelActive(),[this]() { this->zoom(+1); } },
|
||||
{ EShortcut::ADVENTURE_ZOOM_OUT, optionSidePanelActive(),[this]() { this->zoom(-1); } },
|
||||
{ EShortcut::ADVENTURE_MARKETPLACE, optionInMapView(), [this]() { this->showMarketplace(); } },
|
||||
{ EShortcut::ADVENTURE_ZOOM_IN, optionSidePanelActive(),[this]() { this->zoom(+10); } },
|
||||
{ EShortcut::ADVENTURE_ZOOM_OUT, optionSidePanelActive(),[this]() { this->zoom(-10); } },
|
||||
{ EShortcut::ADVENTURE_ZOOM_RESET, optionSidePanelActive(),[this]() { this->zoom( 0); } },
|
||||
{ EShortcut::ADVENTURE_FIRST_TOWN, optionInMapView(), [this]() { this->firstTown(); } },
|
||||
{ EShortcut::ADVENTURE_NEXT_TOWN, optionInMapView(), [this]() { this->nextTown(); } },
|
||||
{ EShortcut::ADVENTURE_NEXT_OBJECT, optionInMapView(), [this]() { this->nextObject(); } },
|
||||
{ EShortcut::ADVENTURE_MOVE_HERO_SW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, +1}); } },
|
||||
@ -223,6 +229,16 @@ void AdventureMapShortcuts::systemOptions()
|
||||
GH.windows().createAndPushWindow<SettingsMainWindow>();
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::firstHero()
|
||||
{
|
||||
if (!LOCPLINT->localState->getWanderingHeroes().empty())
|
||||
{
|
||||
const auto * hero = LOCPLINT->localState->getWanderingHero(0);
|
||||
LOCPLINT->localState->setSelection(hero);
|
||||
owner.centerOnObject(hero);
|
||||
}
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::nextHero()
|
||||
{
|
||||
const auto * currHero = LOCPLINT->localState->getCurrentHero();
|
||||
@ -288,6 +304,49 @@ void AdventureMapShortcuts::showScenarioInfo()
|
||||
AdventureOptions::showScenarioInfo();
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::toMainMenu()
|
||||
{
|
||||
LOCPLINT->showYesNoDialog(
|
||||
CGI->generaltexth->allTexts[578],
|
||||
[]()
|
||||
{
|
||||
CSH->endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("main");
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::newGame()
|
||||
{
|
||||
LOCPLINT->showYesNoDialog(
|
||||
CGI->generaltexth->allTexts[578],
|
||||
[]()
|
||||
{
|
||||
CSH->endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("new");
|
||||
},
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::quitGame()
|
||||
{
|
||||
LOCPLINT->showYesNoDialog(
|
||||
CGI->generaltexth->allTexts[578],
|
||||
[]()
|
||||
{
|
||||
GH.dispatchMainThread( []()
|
||||
{
|
||||
handleQuit(false);
|
||||
});
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::saveGame()
|
||||
{
|
||||
GH.windows().createAndPushWindow<CSavingScreen>();
|
||||
@ -366,6 +425,16 @@ void AdventureMapShortcuts::showMarketplace()
|
||||
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::firstTown()
|
||||
{
|
||||
if (!LOCPLINT->localState->getOwnedTowns().empty())
|
||||
{
|
||||
const auto * town = LOCPLINT->localState->getOwnedTown(0);
|
||||
LOCPLINT->localState->setSelection(town);
|
||||
owner.centerOnObject(town);
|
||||
}
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::nextTown()
|
||||
{
|
||||
owner.hotkeyNextTown();
|
||||
@ -373,7 +442,7 @@ void AdventureMapShortcuts::nextTown()
|
||||
|
||||
void AdventureMapShortcuts::zoom( int distance)
|
||||
{
|
||||
owner.hotkeyZoom(distance);
|
||||
owner.hotkeyZoom(distance, false);
|
||||
}
|
||||
|
||||
void AdventureMapShortcuts::nextObject()
|
||||
@ -494,7 +563,7 @@ bool AdventureMapShortcuts::optionInWorldView()
|
||||
|
||||
bool AdventureMapShortcuts::optionSidePanelActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionMapScrollingActive()
|
||||
|
@ -49,10 +49,14 @@ class AdventureMapShortcuts
|
||||
void showSpellbook();
|
||||
void adventureOptions();
|
||||
void systemOptions();
|
||||
void firstHero();
|
||||
void nextHero();
|
||||
void endTurn();
|
||||
void showThievesGuild();
|
||||
void showScenarioInfo();
|
||||
void toMainMenu();
|
||||
void newGame();
|
||||
void quitGame();
|
||||
void saveGame();
|
||||
void loadGame();
|
||||
void digGrail();
|
||||
@ -61,6 +65,7 @@ class AdventureMapShortcuts
|
||||
void visitObject();
|
||||
void openObject();
|
||||
void showMarketplace();
|
||||
void firstTown();
|
||||
void nextTown();
|
||||
void nextObject();
|
||||
void zoom( int distance);
|
||||
|
@ -45,7 +45,7 @@ AdventureOptions::AdventureOptions()
|
||||
scenInfo = std::make_shared<CButton>(Point(24, 198), AnimationPath::builtin("ADVINFO.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
|
||||
scenInfo->addCallback(AdventureOptions::showScenarioInfo);
|
||||
|
||||
replay = std::make_shared<CButton>(Point(24, 257), AnimationPath::builtin("ADVTURN.DEF"), CButton::tooltip(), [&](){ close(); });
|
||||
replay = std::make_shared<CButton>(Point(24, 257), AnimationPath::builtin("ADVTURN.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_REPLAY_TURN);
|
||||
replay->addCallback([]{ LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.replayOpponentTurnNotImplemented")); });
|
||||
|
||||
exit = std::make_shared<CButton>(Point(203, 313), AnimationPath::builtin("IOK6432.DEF"), CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
|
||||
|
@ -158,9 +158,6 @@ void CInGameConsole::keyPressed (EShortcut key)
|
||||
break;
|
||||
|
||||
case EShortcut::GAME_ACTIVATE_CONSOLE:
|
||||
if(GH.isKeyboardAltDown())
|
||||
return; //QoL for alt-tab operating system shortcut
|
||||
|
||||
if(!enteredText.empty())
|
||||
endEnteringText(false);
|
||||
else
|
||||
|
@ -332,7 +332,7 @@ void BattleInterface::battleFinished(const BattleResult& br, QueryID queryID)
|
||||
GH.windows().pushWindow(wnd);
|
||||
|
||||
curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
CPlayerInterface::battleInt.reset();
|
||||
}
|
||||
|
||||
void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
|
@ -82,6 +82,13 @@ std::vector<std::string> BattleConsole::getVisibleText()
|
||||
|
||||
auto result = CMessage::breakText(text, pos.w, FONT_SMALL);
|
||||
|
||||
if(result.size() > 2 && text.find('\n') != std::string::npos)
|
||||
{
|
||||
// Text has too many lines to fit into console, but has line breaks. Try ignore them and fit text that way
|
||||
std::string cleanText = boost::algorithm::replace_all_copy(text, "\n", " ");
|
||||
result = CMessage::breakText(cleanText, pos.w, FONT_SMALL);
|
||||
}
|
||||
|
||||
if(result.size() > 2)
|
||||
result.resize(2);
|
||||
return result;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../windows/CMessage.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
@ -77,6 +78,8 @@ BattleWindow::BattleWindow(BattleInterface & owner):
|
||||
addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this));
|
||||
addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this));
|
||||
addShortcut(EShortcut::BATTLE_SELECT_ACTION, std::bind(&BattleWindow::bSwitchActionf, this));
|
||||
addShortcut(EShortcut::BATTLE_OPEN_ACTIVE_UNIT, std::bind(&BattleWindow::bOpenActiveUnit, this));
|
||||
addShortcut(EShortcut::BATTLE_OPEN_HOVERED_UNIT, std::bind(&BattleWindow::bOpenHoveredUnit, this));
|
||||
|
||||
addShortcut(EShortcut::BATTLE_TOGGLE_QUEUE, [this](){ this->toggleQueueVisibility();});
|
||||
addShortcut(EShortcut::BATTLE_TOGGLE_HEROES_STATS, [this](){ this->toggleStickyHeroWindowsVisibility();});
|
||||
@ -189,11 +192,6 @@ void BattleWindow::createTimerInfoWindows()
|
||||
}
|
||||
}
|
||||
|
||||
BattleWindow::~BattleWindow()
|
||||
{
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
|
||||
{
|
||||
auto rect = readRect(config["rect"]);
|
||||
@ -755,6 +753,8 @@ void BattleWindow::blockUI(bool on)
|
||||
bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
|
||||
|
||||
setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on);
|
||||
setShortcutBlocked(EShortcut::BATTLE_OPEN_ACTIVE_UNIT, on);
|
||||
setShortcutBlocked(EShortcut::BATTLE_OPEN_HOVERED_UNIT, on);
|
||||
setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.getBattle()->battleCanFlee());
|
||||
setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.getBattle()->battleGetSurrenderCost() < 0);
|
||||
setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
|
||||
@ -769,6 +769,26 @@ void BattleWindow::blockUI(bool on)
|
||||
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
|
||||
}
|
||||
|
||||
void BattleWindow::bOpenActiveUnit()
|
||||
{
|
||||
const auto * unit = owner.stacksController->getActiveStack();
|
||||
|
||||
if (unit)
|
||||
GH.windows().createAndPushWindow<CStackWindow>(unit, false);;
|
||||
}
|
||||
|
||||
void BattleWindow::bOpenHoveredUnit()
|
||||
{
|
||||
const auto units = owner.stacksController->getHoveredStacksUnitIds();
|
||||
|
||||
if (!units.empty())
|
||||
{
|
||||
const auto * unit = owner.getBattle()->battleGetStackByID(units[0]);
|
||||
if (unit)
|
||||
GH.windows().createAndPushWindow<CStackWindow>(unit, false);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()
|
||||
{
|
||||
return queue->getHoveredUnitIdIfAny();
|
||||
|
@ -56,6 +56,8 @@ class BattleWindow : public InterfaceObjectConfigurable
|
||||
void bConsoleDownf();
|
||||
void bTacticNextStack();
|
||||
void bTacticPhaseEnd();
|
||||
void bOpenActiveUnit();
|
||||
void bOpenHoveredUnit();
|
||||
|
||||
/// functions for handling actions after they were confirmed by popup window
|
||||
void reallyFlee();
|
||||
@ -80,7 +82,6 @@ class BattleWindow : public InterfaceObjectConfigurable
|
||||
|
||||
public:
|
||||
BattleWindow(BattleInterface & owner );
|
||||
~BattleWindow();
|
||||
|
||||
/// Closes window once battle finished
|
||||
void close();
|
||||
|
@ -164,6 +164,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
|
||||
{
|
||||
if(ev.key.keysym.sym == SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT))
|
||||
{
|
||||
// FIXME: dead code? Looks like intercepted by OS/SDL and delivered as SDL_Quit instead?
|
||||
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
||||
handleQuit(true);
|
||||
return;
|
||||
@ -175,16 +176,6 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
|
||||
handleQuit(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F4)
|
||||
{
|
||||
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
||||
Settings full = settings.write["video"]["fullscreen"];
|
||||
full->Bool() = !full->Bool();
|
||||
|
||||
GH.onScreenResize(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(ev.type == SDL_USEREVENT)
|
||||
{
|
||||
|
@ -75,9 +75,16 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
|
||||
|
||||
auto shortcutsVector = GH.shortcuts().translateKeycode(keyName);
|
||||
|
||||
if (vstd::contains(shortcutsVector, EShortcut::LOBBY_ACTIVATE_INTERFACE))
|
||||
if (vstd::contains(shortcutsVector, EShortcut::MAIN_MENU_LOBBY))
|
||||
CSH->getGlobalLobby().activateInterface();
|
||||
|
||||
if (vstd::contains(shortcutsVector, EShortcut::GLOBAL_FULLSCREEN))
|
||||
{
|
||||
Settings full = settings.write["video"]["fullscreen"];
|
||||
full->Bool() = !full->Bool();
|
||||
GH.onScreenResize(true);
|
||||
}
|
||||
|
||||
if (vstd::contains(shortcutsVector, EShortcut::SPECTATE_TRACK_HERO))
|
||||
{
|
||||
Settings s = settings.write["session"];
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/Images.h"
|
||||
@ -95,7 +96,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
|
||||
listBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 48, 220, 324), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1);
|
||||
accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 50), Point(0, 40), 8, 0, 0, 1 | 4, Rect(200, 0, 320, 320));
|
||||
|
||||
buttonClose = std::make_shared<CButton>(Point(86, 384), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); } );
|
||||
buttonClose = std::make_shared<CButton>(Point(86, 384), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this]() { close(); }, EShortcut::GLOBAL_RETURN );
|
||||
|
||||
center();
|
||||
}
|
||||
|
@ -12,12 +12,11 @@
|
||||
#include "GlobalLobbyLoginWindow.h"
|
||||
|
||||
#include "GlobalLobbyClient.h"
|
||||
#include "GlobalLobbyWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/Images.h"
|
||||
@ -47,8 +46,8 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
||||
labelUsername = std::make_shared<CLabel>( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, loginAs.toString());
|
||||
backgroundUsername = std::make_shared<TransparentFilledRectangle>(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
|
||||
inputUsername = std::make_shared<CTextInput>(Rect(15, 93, 260, 16), FONT_SMALL, ETextAlignment::CENTERLEFT, true);
|
||||
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); });
|
||||
buttonClose = std::make_shared<CButton>(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
|
||||
buttonLogin = std::make_shared<CButton>(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
buttonClose = std::make_shared<CButton>(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }, EShortcut::GLOBAL_CANCEL);
|
||||
labelStatus = std::make_shared<CTextBox>( "", Rect(15, 115, 255, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||
|
||||
auto buttonRegister = std::make_shared<CToggleButton>(Point(10, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
@ -25,10 +26,8 @@
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/ObjectLists.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/MetaString.h"
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/modding/CModHandler.h"
|
||||
#include "../../lib/modding/CModInfo.h"
|
||||
|
||||
@ -148,8 +147,8 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
|
||||
labelVersionTitle = std::make_shared<CLabel>( 10, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.version").toString());
|
||||
labelVersionValue = std::make_shared<CLabel>( 10, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.gameVersion);
|
||||
|
||||
buttonJoin = std::make_shared<CButton>(Point(10, 360), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onJoin(); });
|
||||
buttonClose = std::make_shared<CButton>(Point(100, 360), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
|
||||
buttonJoin = std::make_shared<CButton>(Point(10, 360), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onJoin(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
buttonClose = std::make_shared<CButton>(Point(100, 360), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }, EShortcut::GLOBAL_CANCEL);
|
||||
|
||||
MetaString joinStatusText;
|
||||
std::string errorMessage = getJoinRoomErrorMessage(roomDescription, modVerificationList);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
@ -74,8 +75,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
|
||||
|
||||
labelDescription = std::make_shared<CTextBox>("", Rect(10, 195, pos.w - 20, 80), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||
|
||||
buttonCreate = std::make_shared<CButton>(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); });
|
||||
buttonClose = std::make_shared<CButton>(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); });
|
||||
buttonCreate = std::make_shared<CButton>(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
buttonClose = std::make_shared<CButton>(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }, EShortcut::GLOBAL_CANCEL);
|
||||
|
||||
filledBackground->playerColored(PlayerColor(1));
|
||||
|
||||
|
@ -492,7 +492,7 @@ void InterfaceObjectConfigurable::loadButtonHotkey(std::shared_ptr<CButton> butt
|
||||
return;
|
||||
|
||||
button->addCallback(target->second.callback);
|
||||
target->second.assignedToButton = true;
|
||||
target->second.assignedButtons.push_back(button);
|
||||
}
|
||||
|
||||
std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
|
||||
@ -821,8 +821,9 @@ void InterfaceObjectConfigurable::keyPressed(EShortcut key)
|
||||
if (target == shortcuts.end())
|
||||
return;
|
||||
|
||||
if (target->second.assignedToButton)
|
||||
return; // will be handled by button instance
|
||||
for (auto const & button :target->second.assignedButtons)
|
||||
if (button->isActive())
|
||||
return; // will be handled by button instance
|
||||
|
||||
if (target->second.blocked)
|
||||
return;
|
||||
|
@ -118,7 +118,7 @@ private:
|
||||
struct ShortcutState
|
||||
{
|
||||
std::function<void()> callback;
|
||||
mutable bool assignedToButton = false;
|
||||
mutable std::vector<std::shared_ptr<CButton>> assignedButtons;
|
||||
bool blocked = false;
|
||||
};
|
||||
|
||||
|
@ -66,25 +66,55 @@ enum class EShortcut
|
||||
MAIN_MENU_CAMPAIGN_AB,
|
||||
MAIN_MENU_CAMPAIGN_CUSTOM,
|
||||
|
||||
MAIN_MENU_HOTSEAT,
|
||||
MAIN_MENU_LOBBY,
|
||||
MAIN_MENU_HOST_GAME,
|
||||
MAIN_MENU_JOIN_GAME,
|
||||
|
||||
HIGH_SCORES_CAMPAIGNS,
|
||||
HIGH_SCORES_SCENARIOS,
|
||||
HIGH_SCORES_RESET,
|
||||
|
||||
// Game lobby / scenario selection
|
||||
LOBBY_BEGIN_STANDARD_GAME, // b
|
||||
LOBBY_BEGIN_CAMPAIGN, // Return
|
||||
LOBBY_LOAD_GAME, // l, Return
|
||||
LOBBY_SAVE_GAME, // s, Return
|
||||
LOBBY_RANDOM_MAP, // Open random map tab
|
||||
LOBBY_HIDE_CHAT,
|
||||
LOBBY_TOGGLE_CHAT,
|
||||
LOBBY_ADDITIONAL_OPTIONS, // Open additional options tab
|
||||
LOBBY_SELECT_SCENARIO, // Open map list tab
|
||||
LOBBY_REPLAY_VIDEO,
|
||||
LOBBY_EXTRA_OPTIONS,
|
||||
LOBBY_TURN_OPTIONS,
|
||||
LOBBY_INVITE_PLAYERS,
|
||||
LOBBY_FLIP_COIN,
|
||||
LOBBY_RANDOM_TOWN,
|
||||
LOBBY_RANDOM_TOWN_VS,
|
||||
|
||||
MAPS_SIZE_S,
|
||||
MAPS_SIZE_M,
|
||||
MAPS_SIZE_L,
|
||||
MAPS_SIZE_XL,
|
||||
MAPS_SIZE_ALL,
|
||||
|
||||
MAPS_SORT_PLAYERS,
|
||||
MAPS_SORT_SIZE,
|
||||
MAPS_SORT_FORMAT,
|
||||
MAPS_SORT_NAME,
|
||||
MAPS_SORT_VICTORY,
|
||||
MAPS_SORT_DEFEAT,
|
||||
MAPS_SORT_MAPS,
|
||||
MAPS_SORT_CHANGEDATE,
|
||||
|
||||
SETTINGS_LOAD_GAME,
|
||||
SETTINGS_SAVE_GAME,
|
||||
SETTINGS_NEW_GAME,
|
||||
SETTINGS_RESTART_GAME,
|
||||
SETTINGS_TO_MAIN_MENU,
|
||||
SETTINGS_QUIT_GAME,
|
||||
|
||||
// In-game hotkeys, require game state but may be available in windows other than adventure map
|
||||
GAME_END_TURN,
|
||||
GAME_LOAD_GAME,
|
||||
GAME_SAVE_GAME,
|
||||
GAME_RESTART_GAME,
|
||||
GAME_TO_MAIN_MENU,
|
||||
GAME_QUIT_GAME,
|
||||
GAME_OPEN_MARKETPLACE,
|
||||
GAME_OPEN_THIEVES_GUILD,
|
||||
GAME_ACTIVATE_CONSOLE, // Tab, activates in-game console
|
||||
|
||||
// Adventure map screen
|
||||
@ -98,10 +128,11 @@ enum class EShortcut
|
||||
ADVENTURE_VIEW_SELECTED,// Open window with currently selected hero/town
|
||||
ADVENTURE_NEXT_TOWN,
|
||||
ADVENTURE_NEXT_HERO,
|
||||
ADVENTURE_NEXT_OBJECT, // TODO: context-sensitive next object - select next hero/town, depending on current selection
|
||||
ADVENTURE_FIRST_TOWN, // TODO: select first available town in the list
|
||||
ADVENTURE_FIRST_HERO, // TODO: select first available hero in the list
|
||||
ADVENTURE_NEXT_OBJECT, // context-sensitive next object - select next hero/town, depending on current selection
|
||||
ADVENTURE_FIRST_TOWN, // select first available town in the list
|
||||
ADVENTURE_FIRST_HERO, // select first available hero in the list
|
||||
ADVENTURE_VIEW_SCENARIO,// View Scenario Information window
|
||||
ADVENTURE_REPLAY_TURN,
|
||||
ADVENTURE_DIG_GRAIL,
|
||||
ADVENTURE_VIEW_PUZZLE,
|
||||
ADVENTURE_VIEW_WORLD,
|
||||
@ -113,11 +144,19 @@ enum class EShortcut
|
||||
ADVENTURE_KINGDOM_OVERVIEW,
|
||||
ADVENTURE_QUEST_LOG,
|
||||
ADVENTURE_CAST_SPELL,
|
||||
ADVENTURE_MARKETPLACE,
|
||||
ADVENTURE_THIEVES_GUILD,
|
||||
ADVENTURE_EXIT_WORLD_VIEW,
|
||||
ADVENTURE_ZOOM_IN,
|
||||
ADVENTURE_ZOOM_OUT,
|
||||
ADVENTURE_ZOOM_RESET,
|
||||
ADVENTURE_END_TURN,
|
||||
ADVENTURE_LOAD_GAME,
|
||||
ADVENTURE_SAVE_GAME,
|
||||
ADVENTURE_NEW_GAME,
|
||||
ADVENTURE_RESTART_GAME,
|
||||
ADVENTURE_TO_MAIN_MENU,
|
||||
ADVENTURE_QUIT_GAME,
|
||||
|
||||
// Move hero one tile in specified direction. Bound to cursors & numpad buttons
|
||||
ADVENTURE_MOVE_HERO_SW,
|
||||
@ -145,8 +184,20 @@ enum class EShortcut
|
||||
BATTLE_TACTICS_END,
|
||||
BATTLE_SELECT_ACTION, // Alternative actions toggle
|
||||
BATTLE_TOGGLE_HEROES_STATS,
|
||||
BATTLE_OPEN_ACTIVE_UNIT,
|
||||
BATTLE_OPEN_HOVERED_UNIT,
|
||||
|
||||
LOBBY_ACTIVATE_INTERFACE,
|
||||
MARKET_DEAL,
|
||||
MARKET_MAX_AMOUNT,
|
||||
MARKET_SACRIFICE_ALL,
|
||||
MARKET_SACRIFICE_BACKPACK,
|
||||
MARKET_RESOURCE_PLAYER,
|
||||
MARKET_ARTIFACT_RESOURCE,
|
||||
MARKET_RESOURCE_ARTIFACT,
|
||||
MARKET_CREATURE_RESOURCE,
|
||||
MARKET_RESOURCE_RESOURCE,
|
||||
MARKET_CREATURE_EXPERIENCE,
|
||||
MARKET_ARTIFACT_EXPERIENCE,
|
||||
|
||||
SPECTATE_TRACK_HERO,
|
||||
SPECTATE_SKIP_BATTLE,
|
||||
@ -154,11 +205,22 @@ enum class EShortcut
|
||||
|
||||
// Town screen
|
||||
TOWN_OPEN_TAVERN,
|
||||
TOWN_OPEN_HALL,
|
||||
TOWN_OPEN_FORT,
|
||||
TOWN_OPEN_MARKET,
|
||||
TOWN_OPEN_MAGE_GUILD,
|
||||
TOWN_OPEN_THIEVES_GUILD,
|
||||
TOWN_OPEN_RECRUITMENT,
|
||||
TOWN_OPEN_HERO_EXCHANGE,
|
||||
TOWN_OPEN_HERO,
|
||||
TOWN_OPEN_VISITING_HERO,
|
||||
TOWN_OPEN_GARRISONED_HERO,
|
||||
TOWN_SWAP_ARMIES, // Swap garrisoned and visiting armies
|
||||
|
||||
// Creature & creature recruitment screen
|
||||
RECRUITMENT_MAX, // Set number of creatures to recruit to max
|
||||
RECRUITMENT_MIN, // Set number of creatures to recruit to min (1)
|
||||
RECRUITMENT_SWITCH_LEVEL,
|
||||
RECRUITMENT_UPGRADE, // Upgrade current creature
|
||||
RECRUITMENT_UPGRADE_ALL, // Upgrade all creatures (Hill Fort / Skeleton Transformer)
|
||||
|
||||
@ -172,17 +234,44 @@ enum class EShortcut
|
||||
HERO_LOOSE_FORMATION,
|
||||
HERO_TIGHT_FORMATION,
|
||||
HERO_TOGGLE_TACTICS, // b
|
||||
HERO_ARMY_SPLIT,
|
||||
HERO_BACKPACK,
|
||||
HERO_COSTUME_0,
|
||||
HERO_COSTUME_1,
|
||||
HERO_COSTUME_2,
|
||||
HERO_COSTUME_3,
|
||||
HERO_COSTUME_4,
|
||||
HERO_COSTUME_5,
|
||||
HERO_COSTUME_6,
|
||||
HERO_COSTUME_7,
|
||||
HERO_COSTUME_8,
|
||||
HERO_COSTUME_9,
|
||||
HERO_COSTUME_SAVE_0,
|
||||
HERO_COSTUME_SAVE_1,
|
||||
HERO_COSTUME_SAVE_2,
|
||||
HERO_COSTUME_SAVE_3,
|
||||
HERO_COSTUME_SAVE_4,
|
||||
HERO_COSTUME_SAVE_5,
|
||||
HERO_COSTUME_SAVE_6,
|
||||
HERO_COSTUME_SAVE_7,
|
||||
HERO_COSTUME_SAVE_8,
|
||||
HERO_COSTUME_SAVE_9,
|
||||
|
||||
HERO_COSTUME_LOAD_0,
|
||||
HERO_COSTUME_LOAD_1,
|
||||
HERO_COSTUME_LOAD_2,
|
||||
HERO_COSTUME_LOAD_3,
|
||||
HERO_COSTUME_LOAD_4,
|
||||
HERO_COSTUME_LOAD_5,
|
||||
HERO_COSTUME_LOAD_6,
|
||||
HERO_COSTUME_LOAD_7,
|
||||
HERO_COSTUME_LOAD_8,
|
||||
HERO_COSTUME_LOAD_9,
|
||||
|
||||
EXCHANGE_ARMY_TO_LEFT,
|
||||
EXCHANGE_ARMY_TO_RIGHT,
|
||||
EXCHANGE_ARMY_SWAP,
|
||||
EXCHANGE_ARTIFACTS_TO_LEFT,
|
||||
EXCHANGE_ARTIFACTS_TO_RIGHT,
|
||||
EXCHANGE_ARTIFACTS_SWAP,
|
||||
EXCHANGE_EQUIPPED_TO_LEFT,
|
||||
EXCHANGE_EQUIPPED_TO_RIGHT,
|
||||
EXCHANGE_EQUIPPED_SWAP,
|
||||
EXCHANGE_BACKPACK_TO_LEFT,
|
||||
EXCHANGE_BACKPACK_TO_RIGHT,
|
||||
EXCHANGE_BACKPACK_SWAP,
|
||||
EXCHANGE_BACKPACK_LEFT,
|
||||
EXCHANGE_BACKPACK_RIGHT,
|
||||
|
||||
// Spellbook screen
|
||||
SPELLBOOK_TAB_ADVENTURE,
|
||||
|
@ -22,6 +22,25 @@ ShortcutHandler::ShortcutHandler()
|
||||
mappedKeyboardShortcuts = loadShortcuts(config["keyboard"]);
|
||||
mappedJoystickShortcuts = loadShortcuts(config["joystickButtons"]);
|
||||
mappedJoystickAxes = loadShortcuts(config["joystickAxes"]);
|
||||
|
||||
#ifndef ENABLE_GOLDMASTER
|
||||
std::vector<EShortcut> assignedShortcuts;
|
||||
std::vector<EShortcut> missingShortcuts;
|
||||
|
||||
for (auto const & entry : config["keyboard"].Struct())
|
||||
{
|
||||
EShortcut shortcutID = findShortcut(entry.first);
|
||||
assert(!vstd::contains(assignedShortcuts, shortcutID));
|
||||
assignedShortcuts.push_back(shortcutID);
|
||||
}
|
||||
|
||||
for (EShortcut id = vstd::next(EShortcut::NONE, 1); id < EShortcut::AFTER_LAST; id = vstd::next(id, 1))
|
||||
if (!vstd::contains(assignedShortcuts, id))
|
||||
missingShortcuts.push_back(id);
|
||||
|
||||
if (!missingShortcuts.empty())
|
||||
logGlobal->error("Found %d shortcuts without config entry!", missingShortcuts.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::multimap<std::string, EShortcut> ShortcutHandler::loadShortcuts(const JsonNode & data) const
|
||||
@ -131,22 +150,24 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"mainMenuCampaignRoe", EShortcut::MAIN_MENU_CAMPAIGN_ROE },
|
||||
{"mainMenuCampaignAb", EShortcut::MAIN_MENU_CAMPAIGN_AB },
|
||||
{"mainMenuCampaignCustom", EShortcut::MAIN_MENU_CAMPAIGN_CUSTOM },
|
||||
{"mainMenuLobby", EShortcut::MAIN_MENU_LOBBY },
|
||||
{"lobbyBeginStandardGame", EShortcut::LOBBY_BEGIN_STANDARD_GAME },
|
||||
{"lobbyBeginCampaign", EShortcut::LOBBY_BEGIN_CAMPAIGN },
|
||||
{"lobbyLoadGame", EShortcut::LOBBY_LOAD_GAME },
|
||||
{"lobbySaveGame", EShortcut::LOBBY_SAVE_GAME },
|
||||
{"lobbyRandomMap", EShortcut::LOBBY_RANDOM_MAP },
|
||||
{"lobbyHideChat", EShortcut::LOBBY_HIDE_CHAT },
|
||||
{"lobbyToggleChat", EShortcut::LOBBY_TOGGLE_CHAT },
|
||||
{"lobbyAdditionalOptions", EShortcut::LOBBY_ADDITIONAL_OPTIONS },
|
||||
{"lobbySelectScenario", EShortcut::LOBBY_SELECT_SCENARIO },
|
||||
{"gameEndTurn", EShortcut::GAME_END_TURN },
|
||||
{"gameLoadGame", EShortcut::GAME_LOAD_GAME },
|
||||
{"gameSaveGame", EShortcut::GAME_SAVE_GAME },
|
||||
{"gameRestartGame", EShortcut::GAME_RESTART_GAME },
|
||||
{"gameMainMenu", EShortcut::GAME_TO_MAIN_MENU },
|
||||
{"gameQuitGame", EShortcut::GAME_QUIT_GAME },
|
||||
{"gameOpenMarketplace", EShortcut::GAME_OPEN_MARKETPLACE },
|
||||
{"gameOpenThievesGuild", EShortcut::GAME_OPEN_THIEVES_GUILD },
|
||||
{"gameEndTurn", EShortcut::ADVENTURE_END_TURN }, // compatibility ID - extra's use this string
|
||||
{"adventureEndTurn", EShortcut::ADVENTURE_END_TURN },
|
||||
{"adventureLoadGame", EShortcut::ADVENTURE_LOAD_GAME },
|
||||
{"adventureSaveGame", EShortcut::ADVENTURE_SAVE_GAME },
|
||||
{"adventureRestartGame", EShortcut::ADVENTURE_RESTART_GAME },
|
||||
{"adventureMainMenu", EShortcut::ADVENTURE_TO_MAIN_MENU },
|
||||
{"adventureQuitGame", EShortcut::ADVENTURE_QUIT_GAME },
|
||||
{"adventureMarketplace", EShortcut::ADVENTURE_MARKETPLACE },
|
||||
{"adventureThievesGuild", EShortcut::ADVENTURE_THIEVES_GUILD },
|
||||
{"gameActivateConsole", EShortcut::GAME_ACTIVATE_CONSOLE },
|
||||
{"adventureGameOptions", EShortcut::ADVENTURE_GAME_OPTIONS },
|
||||
{"adventureToggleGrid", EShortcut::ADVENTURE_TOGGLE_GRID },
|
||||
@ -201,7 +222,6 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT },
|
||||
{"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END },
|
||||
{"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION },
|
||||
{"lobbyActivateInterface", EShortcut::LOBBY_ACTIVATE_INTERFACE },
|
||||
{"spectateTrackHero", EShortcut::SPECTATE_TRACK_HERO },
|
||||
{"spectateSkipBattle", EShortcut::SPECTATE_SKIP_BATTLE },
|
||||
{"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT },
|
||||
@ -218,20 +238,118 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"heroLooseFormation", EShortcut::HERO_LOOSE_FORMATION },
|
||||
{"heroTightFormation", EShortcut::HERO_TIGHT_FORMATION },
|
||||
{"heroToggleTactics", EShortcut::HERO_TOGGLE_TACTICS },
|
||||
{"heroCostume0", EShortcut::HERO_COSTUME_0 },
|
||||
{"heroCostume1", EShortcut::HERO_COSTUME_1 },
|
||||
{"heroCostume2", EShortcut::HERO_COSTUME_2 },
|
||||
{"heroCostume3", EShortcut::HERO_COSTUME_3 },
|
||||
{"heroCostume4", EShortcut::HERO_COSTUME_4 },
|
||||
{"heroCostume5", EShortcut::HERO_COSTUME_5 },
|
||||
{"heroCostume6", EShortcut::HERO_COSTUME_6 },
|
||||
{"heroCostume7", EShortcut::HERO_COSTUME_7 },
|
||||
{"heroCostume8", EShortcut::HERO_COSTUME_8 },
|
||||
{"heroCostume9", EShortcut::HERO_COSTUME_9 },
|
||||
{"heroCostumeSave0", EShortcut::HERO_COSTUME_SAVE_0 },
|
||||
{"heroCostumeSave1", EShortcut::HERO_COSTUME_SAVE_1 },
|
||||
{"heroCostumeSave2", EShortcut::HERO_COSTUME_SAVE_2 },
|
||||
{"heroCostumeSave3", EShortcut::HERO_COSTUME_SAVE_3 },
|
||||
{"heroCostumeSave4", EShortcut::HERO_COSTUME_SAVE_4 },
|
||||
{"heroCostumeSave5", EShortcut::HERO_COSTUME_SAVE_5 },
|
||||
{"heroCostumeSave6", EShortcut::HERO_COSTUME_SAVE_6 },
|
||||
{"heroCostumeSave7", EShortcut::HERO_COSTUME_SAVE_7 },
|
||||
{"heroCostumeSave8", EShortcut::HERO_COSTUME_SAVE_8 },
|
||||
{"heroCostumeSave9", EShortcut::HERO_COSTUME_SAVE_9 },
|
||||
{"heroCostumeLoad0", EShortcut::HERO_COSTUME_LOAD_0 },
|
||||
{"heroCostumeLoad1", EShortcut::HERO_COSTUME_LOAD_1 },
|
||||
{"heroCostumeLoad2", EShortcut::HERO_COSTUME_LOAD_2 },
|
||||
{"heroCostumeLoad3", EShortcut::HERO_COSTUME_LOAD_3 },
|
||||
{"heroCostumeLoad4", EShortcut::HERO_COSTUME_LOAD_4 },
|
||||
{"heroCostumeLoad5", EShortcut::HERO_COSTUME_LOAD_5 },
|
||||
{"heroCostumeLoad6", EShortcut::HERO_COSTUME_LOAD_6 },
|
||||
{"heroCostumeLoad7", EShortcut::HERO_COSTUME_LOAD_7 },
|
||||
{"heroCostumeLoad8", EShortcut::HERO_COSTUME_LOAD_8 },
|
||||
{"heroCostumeLoad9", EShortcut::HERO_COSTUME_LOAD_9 },
|
||||
{"spellbookTabAdventure", EShortcut::SPELLBOOK_TAB_ADVENTURE },
|
||||
{"spellbookTabCombat", EShortcut::SPELLBOOK_TAB_COMBAT }
|
||||
{"spellbookTabCombat", EShortcut::SPELLBOOK_TAB_COMBAT },
|
||||
{"mainMenuHotseat", EShortcut::MAIN_MENU_HOTSEAT },
|
||||
{"mainMenuHostGame", EShortcut::MAIN_MENU_HOST_GAME },
|
||||
{"mainMenuJoinGame", EShortcut::MAIN_MENU_JOIN_GAME },
|
||||
{"highScoresCampaigns", EShortcut::HIGH_SCORES_CAMPAIGNS },
|
||||
{"highScoresScenarios", EShortcut::HIGH_SCORES_SCENARIOS },
|
||||
{"highScoresReset", EShortcut::HIGH_SCORES_RESET },
|
||||
{"lobbyReplayVideo", EShortcut::LOBBY_REPLAY_VIDEO },
|
||||
{"lobbyExtraOptions", EShortcut::LOBBY_EXTRA_OPTIONS },
|
||||
{"lobbyTurnOptions", EShortcut::LOBBY_TURN_OPTIONS },
|
||||
{"lobbyInvitePlayers", EShortcut::LOBBY_INVITE_PLAYERS },
|
||||
{"lobbyFlipCoin", EShortcut::LOBBY_FLIP_COIN },
|
||||
{"lobbyRandomTown", EShortcut::LOBBY_RANDOM_TOWN },
|
||||
{"lobbyRandomTownVs", EShortcut::LOBBY_RANDOM_TOWN_VS },
|
||||
{"mapsSizeS", EShortcut::MAPS_SIZE_S },
|
||||
{"mapsSizeM", EShortcut::MAPS_SIZE_M },
|
||||
{"mapsSizeL", EShortcut::MAPS_SIZE_L },
|
||||
{"mapsSizeXl", EShortcut::MAPS_SIZE_XL },
|
||||
{"mapsSizeAll", EShortcut::MAPS_SIZE_ALL },
|
||||
{"mapsSortPlayers", EShortcut::MAPS_SORT_PLAYERS },
|
||||
{"mapsSortSize", EShortcut::MAPS_SORT_SIZE },
|
||||
{"mapsSortFormat", EShortcut::MAPS_SORT_FORMAT },
|
||||
{"mapsSortName", EShortcut::MAPS_SORT_NAME },
|
||||
{"mapsSortVictory", EShortcut::MAPS_SORT_VICTORY },
|
||||
{"mapsSortDefeat", EShortcut::MAPS_SORT_DEFEAT },
|
||||
{"mapsSortMaps", EShortcut::MAPS_SORT_MAPS },
|
||||
{"mapsSortChangedate", EShortcut::MAPS_SORT_CHANGEDATE },
|
||||
{"settingsLoadGame", EShortcut::SETTINGS_LOAD_GAME },
|
||||
{"settingsSaveGame", EShortcut::SETTINGS_SAVE_GAME },
|
||||
{"settingsNewGame", EShortcut::SETTINGS_NEW_GAME },
|
||||
{"settingsRestartGame", EShortcut::SETTINGS_RESTART_GAME },
|
||||
{"settingsToMainMenu", EShortcut::SETTINGS_TO_MAIN_MENU },
|
||||
{"settingsQuitGame", EShortcut::SETTINGS_QUIT_GAME },
|
||||
{"adventureReplayTurn", EShortcut::ADVENTURE_REPLAY_TURN },
|
||||
{"adventureNewGame", EShortcut::ADVENTURE_NEW_GAME },
|
||||
{"battleOpenActiveUnit", EShortcut::BATTLE_OPEN_ACTIVE_UNIT },
|
||||
{"battleOpenHoveredUnit", EShortcut::BATTLE_OPEN_HOVERED_UNIT },
|
||||
{"marketDeal", EShortcut::MARKET_DEAL },
|
||||
{"marketMaxAmount", EShortcut::MARKET_MAX_AMOUNT },
|
||||
{"marketSacrificeAll", EShortcut::MARKET_SACRIFICE_ALL },
|
||||
{"marketSacrificeBackpack", EShortcut::MARKET_SACRIFICE_BACKPACK },
|
||||
{"marketResourcePlayer", EShortcut::MARKET_RESOURCE_PLAYER },
|
||||
{"marketArtifactResource", EShortcut::MARKET_ARTIFACT_RESOURCE },
|
||||
{"marketResourceArtifact", EShortcut::MARKET_RESOURCE_ARTIFACT },
|
||||
{"marketCreatureResource", EShortcut::MARKET_CREATURE_RESOURCE },
|
||||
{"marketResourceResource", EShortcut::MARKET_RESOURCE_RESOURCE },
|
||||
{"marketCreatureExperience", EShortcut::MARKET_CREATURE_EXPERIENCE },
|
||||
{"marketArtifactExperience", EShortcut::MARKET_ARTIFACT_EXPERIENCE },
|
||||
{"townOpenHall", EShortcut::TOWN_OPEN_HALL },
|
||||
{"townOpenFort", EShortcut::TOWN_OPEN_FORT },
|
||||
{"townOpenMarket", EShortcut::TOWN_OPEN_MARKET },
|
||||
{"townOpenMageGuild", EShortcut::TOWN_OPEN_MAGE_GUILD },
|
||||
{"townOpenThievesGuild", EShortcut::TOWN_OPEN_THIEVES_GUILD },
|
||||
{"townOpenRecruitment", EShortcut::TOWN_OPEN_RECRUITMENT },
|
||||
{"townOpenHeroExchange", EShortcut::TOWN_OPEN_HERO_EXCHANGE },
|
||||
{"townOpenHero", EShortcut::TOWN_OPEN_HERO },
|
||||
{"townOpenVisitingHero", EShortcut::TOWN_OPEN_VISITING_HERO },
|
||||
{"townOpenGarrisonedHero", EShortcut::TOWN_OPEN_GARRISONED_HERO },
|
||||
{"recruitmentSwitchLevel", EShortcut::RECRUITMENT_SWITCH_LEVEL },
|
||||
{"heroArmySplit", EShortcut::HERO_ARMY_SPLIT },
|
||||
{"heroBackpack", EShortcut::HERO_BACKPACK },
|
||||
{"exchangeArmyToLeft", EShortcut::EXCHANGE_ARMY_TO_LEFT },
|
||||
{"exchangeArmyToRight", EShortcut::EXCHANGE_ARMY_TO_RIGHT },
|
||||
{"exchangeArmySwap", EShortcut::EXCHANGE_ARMY_SWAP },
|
||||
{"exchangeArtifactsToLeft", EShortcut::EXCHANGE_ARTIFACTS_TO_LEFT },
|
||||
{"exchangeArtifactsToRight", EShortcut::EXCHANGE_ARTIFACTS_TO_RIGHT },
|
||||
{"exchangeArtifactsSwap", EShortcut::EXCHANGE_ARTIFACTS_SWAP },
|
||||
{"exchangeBackpackLeft", EShortcut::EXCHANGE_BACKPACK_LEFT },
|
||||
{"exchangeBackpackRight", EShortcut::EXCHANGE_BACKPACK_RIGHT },
|
||||
{"exchangeEquippedToLeft", EShortcut::EXCHANGE_EQUIPPED_TO_LEFT },
|
||||
{"exchangeEquippedToRight", EShortcut::EXCHANGE_EQUIPPED_TO_RIGHT},
|
||||
{"exchangeEquippedSwap", EShortcut::EXCHANGE_EQUIPPED_SWAP },
|
||||
{"exchangeBackpackToLeft", EShortcut::EXCHANGE_BACKPACK_TO_LEFT },
|
||||
{"exchangeBackpackToRight", EShortcut::EXCHANGE_BACKPACK_TO_RIGHT},
|
||||
{"exchangeBackpackSwap", EShortcut::EXCHANGE_BACKPACK_SWAP },
|
||||
};
|
||||
|
||||
#ifndef ENABLE_GOLDMASTER
|
||||
std::vector<EShortcut> assignedShortcuts;
|
||||
std::vector<EShortcut> missingShortcuts;
|
||||
for (auto const & entry : shortcutNames)
|
||||
assignedShortcuts.push_back(entry.second);
|
||||
|
||||
for (EShortcut id = vstd::next(EShortcut::NONE, 1); id < EShortcut::AFTER_LAST; id = vstd::next(id, 1))
|
||||
if (!vstd::contains(assignedShortcuts, id))
|
||||
missingShortcuts.push_back(id);
|
||||
|
||||
if (!missingShortcuts.empty())
|
||||
logGlobal->error("Found %d shortcuts without assigned string name!", missingShortcuts.size());
|
||||
#endif
|
||||
|
||||
if (shortcutNames.count(identifier))
|
||||
return shortcutNames.at(identifier);
|
||||
return EShortcut::NONE;
|
||||
|
@ -71,9 +71,18 @@ CBonusSelection::CBonusSelection()
|
||||
|
||||
panelBackground = std::make_shared<CPicture>(ImagePath::builtin("CAMPBRF.BMP"), 456, 6);
|
||||
|
||||
buttonStart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
|
||||
const auto & playVideo = [this]()
|
||||
{
|
||||
GH.windows().createAndPushWindow<CPrologEpilogVideo>(
|
||||
getCampaign()->scenario(CSH->campaignMap).prolog,
|
||||
[this]() { redraw(); } );
|
||||
};
|
||||
|
||||
buttonStart = std::make_shared<CButton>(
|
||||
Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT
|
||||
);
|
||||
buttonRestart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBRESTB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
|
||||
buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), [this](){ GH.windows().createAndPushWindow<CPrologEpilogVideo>(getCampaign()->scenario(CSH->campaignMap).prolog, [this](){ redraw(); }); });
|
||||
buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), playVideo, EShortcut::LOBBY_REPLAY_VIDEO);
|
||||
buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
|
||||
|
||||
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
|
||||
@ -104,8 +113,11 @@ CBonusSelection::CBonusSelection()
|
||||
|
||||
if(getCampaign()->playerSelectedDifficulty())
|
||||
{
|
||||
buttonDifficultyLeft = std::make_shared<CButton>(settings["general"]["enableUiEnhancements"].Bool() ? Point(693, 495) : Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
|
||||
buttonDifficultyRight = std::make_shared<CButton>(settings["general"]["enableUiEnhancements"].Bool() ? Point(739, 495) : Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
|
||||
Point posLeft = settings["general"]["enableUiEnhancements"].Bool() ? Point(693, 495) : Point(694, 508);
|
||||
Point posRight = settings["general"]["enableUiEnhancements"].Bool() ? Point(739, 495) : Point(738, 508);
|
||||
|
||||
buttonDifficultyLeft = std::make_shared<CButton>(posLeft, AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this), EShortcut::MOVE_LEFT);
|
||||
buttonDifficultyRight = std::make_shared<CButton>(posRight, AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this), EShortcut::MOVE_RIGHT);
|
||||
}
|
||||
|
||||
for(auto scenarioID : getCampaign()->allScenarios())
|
||||
@ -125,7 +137,7 @@ CBonusSelection::CBonusSelection()
|
||||
tabExtraOptions->recActions = UPDATE | SHOWALL | LCLICK | RCLICK_POPUP;
|
||||
tabExtraOptions->recreate(true);
|
||||
tabExtraOptions->setEnabled(false);
|
||||
buttonExtraOptions = std::make_shared<CButton>(Point(643, 431), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::NONE);
|
||||
buttonExtraOptions = std::make_shared<CButton>(Point(643, 431), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::LOBBY_EXTRA_OPTIONS);
|
||||
buttonExtraOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE);
|
||||
}
|
||||
}
|
||||
|
@ -57,12 +57,12 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
|
||||
buttonOptions = std::make_shared<CButton>(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
|
||||
if(settings["general"]["enableUiEnhancements"].Bool())
|
||||
{
|
||||
buttonTurnOptions = std::make_shared<CButton>(Point(619, 105), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE);
|
||||
buttonExtraOptions = std::make_shared<CButton>(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabExtraOptions), EShortcut::NONE);
|
||||
buttonTurnOptions = std::make_shared<CButton>(Point(619, 105), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::LOBBY_TURN_OPTIONS);
|
||||
buttonExtraOptions = std::make_shared<CButton>(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabExtraOptions), EShortcut::LOBBY_EXTRA_OPTIONS);
|
||||
}
|
||||
};
|
||||
|
||||
buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
|
||||
buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_TOGGLE_CHAT);
|
||||
buttonChat->setTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
|
||||
|
||||
switch(screenType)
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/Slider.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
@ -143,8 +144,8 @@ InfoCard::InfoCard()
|
||||
chat = std::make_shared<CChatBox>(Rect(18, 126, 335, 143));
|
||||
pvpBox = std::make_shared<PvPBox>(Rect(17, 396, 338, 105));
|
||||
|
||||
buttonInvitePlayers = std::make_shared<CButton>(Point(20, 365), AnimationPath::builtin("pregameInvitePlayers"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateRoomInviteInterface(); } );
|
||||
buttonOpenGlobalLobby = std::make_shared<CButton>(Point(188, 365), AnimationPath::builtin("pregameReturnToLobby"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateInterface(); });
|
||||
buttonInvitePlayers = std::make_shared<CButton>(Point(20, 365), AnimationPath::builtin("pregameInvitePlayers"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateRoomInviteInterface(); }, EShortcut::LOBBY_INVITE_PLAYERS );
|
||||
buttonOpenGlobalLobby = std::make_shared<CButton>(Point(188, 365), AnimationPath::builtin("pregameReturnToLobby"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateInterface(); }, EShortcut::MAIN_MENU_LOBBY );
|
||||
|
||||
buttonInvitePlayers->setTextOverlay (MetaString::createFromTextID("vcmi.lobby.invite.header").toString(), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
buttonOpenGlobalLobby->setTextOverlay(MetaString::createFromTextID("vcmi.lobby.backToLobby").toString(), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
@ -418,7 +419,7 @@ PvPBox::PvPBox(const Rect & rect)
|
||||
LobbyPvPAction lpa;
|
||||
lpa.action = LobbyPvPAction::COIN;
|
||||
CSH->sendLobbyPack(lpa);
|
||||
}, EShortcut::NONE);
|
||||
}, EShortcut::LOBBY_FLIP_COIN);
|
||||
buttonFlipCoin->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.coin.hover"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
|
||||
buttonRandomTown = std::make_shared<CButton>(Point(190, 31), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.help")), [getBannedTowns](){
|
||||
@ -426,7 +427,7 @@ PvPBox::PvPBox(const Rect & rect)
|
||||
lpa.action = LobbyPvPAction::RANDOM_TOWN;
|
||||
lpa.bannedTowns = getBannedTowns();
|
||||
CSH->sendLobbyPack(lpa);
|
||||
}, EShortcut::NONE);
|
||||
}, EShortcut::LOBBY_RANDOM_TOWN);
|
||||
buttonRandomTown->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.hover"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
|
||||
buttonRandomTownVs = std::make_shared<CButton>(Point(190, 56), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.help")), [getBannedTowns](){
|
||||
@ -434,7 +435,7 @@ PvPBox::PvPBox(const Rect & rect)
|
||||
lpa.action = LobbyPvPAction::RANDOM_TOWN_VS;
|
||||
lpa.bannedTowns = getBannedTowns();
|
||||
CSH->sendLobbyPack(lpa);
|
||||
}, EShortcut::NONE);
|
||||
}, EShortcut::LOBBY_RANDOM_TOWN_VS);
|
||||
buttonRandomTownVs->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.hover"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
}
|
||||
|
||||
|
@ -169,24 +169,23 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
|
||||
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
|
||||
|
||||
// TODO: Global constants?
|
||||
int sizes[] = {CMapHeader::MAP_SIZE_SMALL,
|
||||
CMapHeader::MAP_SIZE_MIDDLE,
|
||||
CMapHeader::MAP_SIZE_LARGE,
|
||||
CMapHeader::MAP_SIZE_XLARGE,
|
||||
0};
|
||||
const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
|
||||
for(int i = 0; i < 5; i++)
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), AnimationPath::builtin(filterIconNmes[i]), CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
|
||||
constexpr std::array sizes = {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, 0};
|
||||
constexpr std::array filterIconNmes = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
|
||||
constexpr std::array filterShortcuts = { EShortcut::MAPS_SIZE_S, EShortcut::MAPS_SIZE_M, EShortcut::MAPS_SIZE_L, EShortcut::MAPS_SIZE_XL, EShortcut::MAPS_SIZE_ALL };
|
||||
|
||||
int xpos[] = {23, 55, 88, 121, 306, 339};
|
||||
const char * sortIconNames[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
|
||||
for(int i = 0; i < 5; i++)
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), AnimationPath::builtin(filterIconNmes[i]), CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true), filterShortcuts[i]));
|
||||
|
||||
constexpr std::array xpos = {23, 55, 88, 121, 306, 339};
|
||||
constexpr std::array sortIconNames = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
|
||||
constexpr std::array sortShortcuts = { EShortcut::MAPS_SORT_PLAYERS, EShortcut::MAPS_SORT_SIZE, EShortcut::MAPS_SORT_FORMAT, EShortcut::MAPS_SORT_NAME, EShortcut::MAPS_SORT_VICTORY, EShortcut::MAPS_SORT_DEFEAT };
|
||||
for(int i = 0; i < 6; i++)
|
||||
{
|
||||
ESortBy criteria = (ESortBy)i;
|
||||
if(criteria == _name)
|
||||
criteria = generalSortingBy;
|
||||
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), AnimationPath::builtin(sortIconNames[i]), CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), AnimationPath::builtin(sortIconNames[i]), CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria), sortShortcuts[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,8 +211,8 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
|
||||
pos.x += 3;
|
||||
pos.y += 6;
|
||||
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), AnimationPath::builtin("CamCusM.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), AnimationPath::builtin("CamCusL.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), AnimationPath::builtin("CamCusM.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps), EShortcut::MAPS_SORT_MAPS));
|
||||
buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), AnimationPath::builtin("CamCusL.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name), EShortcut::MAPS_SORT_NAME));
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
@ -222,7 +221,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
|
||||
|
||||
if(enableUiEnhancements)
|
||||
{
|
||||
auto sortByDate = std::make_shared<CButton>(Point(371, 85), AnimationPath::builtin("selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate));
|
||||
auto sortByDate = std::make_shared<CButton>(Point(371, 85), AnimationPath::builtin("selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate), EShortcut::MAPS_SORT_CHANGEDATE);
|
||||
sortByDate->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/selectionTabSortDate")));
|
||||
buttonsSortBy.push_back(sortByDate);
|
||||
}
|
||||
@ -677,6 +676,8 @@ void SelectionTab::selectFileName(std::string fname)
|
||||
}
|
||||
}
|
||||
|
||||
filter(-1);
|
||||
|
||||
for(int i = (int)curItems.size() - 1; i >= 0; i--)
|
||||
{
|
||||
if(curItems[i]->fileURI == fname)
|
||||
@ -687,13 +688,25 @@ void SelectionTab::selectFileName(std::string fname)
|
||||
}
|
||||
}
|
||||
|
||||
filter(-1);
|
||||
selectAbs(-1);
|
||||
|
||||
if(tabType == ESelectionScreen::saveGame && inputName->getText().empty())
|
||||
inputName->setText("NEWGAME");
|
||||
}
|
||||
|
||||
void SelectionTab::selectNewestFile()
|
||||
{
|
||||
time_t newestTime = 0;
|
||||
std::string newestFile = "";
|
||||
for(int i = (int)allItems.size() - 1; i >= 0; i--)
|
||||
if(allItems[i]->lastWrite > newestTime)
|
||||
{
|
||||
newestTime = allItems[i]->lastWrite;
|
||||
newestFile = allItems[i]->fileURI;
|
||||
}
|
||||
selectFileName(newestFile);
|
||||
}
|
||||
|
||||
std::shared_ptr<ElementInfo> SelectionTab::getSelectedMapInfo() const
|
||||
{
|
||||
return curItems.empty() || curItems[selectionPos]->isFolder ? nullptr : curItems[selectionPos];
|
||||
@ -733,6 +746,8 @@ void SelectionTab::restoreLastSelection()
|
||||
selectFileName(settings["general"]["lastCampaign"].String());
|
||||
break;
|
||||
case ESelectionScreen::loadGame:
|
||||
selectNewestFile();
|
||||
break;
|
||||
case ESelectionScreen::saveGame:
|
||||
selectFileName(settings["general"]["lastSave"].String());
|
||||
}
|
||||
@ -795,7 +810,7 @@ void SelectionTab::parseSaves(const std::unordered_set<ResourcePath> & files)
|
||||
switch(CSH->getLoadMode())
|
||||
{
|
||||
case ELoadMode::SINGLE:
|
||||
if(isMultiplayer || isCampaign || isTutorial)
|
||||
if(isCampaign || isTutorial)
|
||||
mapInfo->mapHeader.reset();
|
||||
break;
|
||||
case ELoadMode::CAMPAIGN:
|
||||
@ -806,10 +821,14 @@ void SelectionTab::parseSaves(const std::unordered_set<ResourcePath> & files)
|
||||
if(!isTutorial)
|
||||
mapInfo->mapHeader.reset();
|
||||
break;
|
||||
default:
|
||||
case ELoadMode::MULTI:
|
||||
if(!isMultiplayer)
|
||||
mapInfo->mapHeader.reset();
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
mapInfo->mapHeader.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
allItems.push_back(mapInfo);
|
||||
|
@ -101,6 +101,7 @@ public:
|
||||
int getLine() const;
|
||||
int getLine(const Point & position) const;
|
||||
void selectFileName(std::string fname);
|
||||
void selectNewestFile();
|
||||
std::shared_ptr<ElementInfo> getSelectedMapInfo() const;
|
||||
void rememberCurrentSelection();
|
||||
void restoreLastSelection();
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CVideoHandler.h"
|
||||
@ -81,7 +82,8 @@ CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
|
||||
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
updateShadow();
|
||||
|
||||
backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
|
||||
|
||||
addHighScores();
|
||||
addButtons();
|
||||
@ -109,10 +111,10 @@ void CHighScoreScreen::addButtons()
|
||||
|
||||
buttons.clear();
|
||||
|
||||
buttons.push_back(std::make_shared<CButton>(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaignClick(); }));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonScenarioClick(); }));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); }));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); }));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaignClick(); }, EShortcut::HIGH_SCORES_CAMPAIGNS));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonScenarioClick(); }, EShortcut::HIGH_SCORES_SCENARIOS));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); }, EShortcut::HIGH_SCORES_RESET));
|
||||
buttons.push_back(std::make_shared<CButton>(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); }, EShortcut::GLOBAL_RETURN));
|
||||
}
|
||||
|
||||
void CHighScoreScreen::addHighScores()
|
||||
@ -222,8 +224,8 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
|
||||
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
updateShadow();
|
||||
|
||||
backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
|
||||
background = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), Colors::BLACK);
|
||||
|
||||
if(won)
|
||||
@ -289,6 +291,9 @@ int CHighScoreInputScreen::addEntry(std::string text) {
|
||||
|
||||
void CHighScoreInputScreen::show(Canvas & to)
|
||||
{
|
||||
if(background)
|
||||
background->show(to);
|
||||
|
||||
CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false,
|
||||
[&]()
|
||||
{
|
||||
@ -303,7 +308,11 @@ void CHighScoreInputScreen::show(Canvas & to)
|
||||
else
|
||||
close();
|
||||
});
|
||||
redraw();
|
||||
|
||||
if(input)
|
||||
input->showAll(to);
|
||||
for(auto & text : texts)
|
||||
text->showAll(to);
|
||||
|
||||
CIntObject::show(to);
|
||||
}
|
||||
@ -326,6 +335,7 @@ void CHighScoreInputScreen::deactivate()
|
||||
{
|
||||
CCS->videoh->close();
|
||||
CCS->soundh->stopSound(videoSoundHandle);
|
||||
CIntObject::deactivate();
|
||||
}
|
||||
|
||||
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
|
||||
@ -372,7 +382,8 @@ CHighScoreInput::CHighScoreInput(std::string playerName, std::function<void(std:
|
||||
|
||||
buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT);
|
||||
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL);
|
||||
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
||||
// FIXME: broken. Never activates?
|
||||
// statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
|
||||
textInput = std::make_shared<CTextInput>(Rect(18, 104, 200, 25), FONT_SMALL, ETextAlignment::CENTER, true);
|
||||
textInput->setText(playerName);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class CLabel;
|
||||
class CMultiLineLabel;
|
||||
class CAnimImage;
|
||||
class CTextInput;
|
||||
class CFilledTexture;
|
||||
|
||||
class TransparentFilledRectangle;
|
||||
|
||||
@ -61,6 +62,7 @@ private:
|
||||
HighScorePage highscorepage;
|
||||
|
||||
std::shared_ptr<CPicture> background;
|
||||
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
|
||||
std::vector<std::shared_ptr<CButton>> buttons;
|
||||
std::vector<std::shared_ptr<CLabel>> texts;
|
||||
std::vector<std::shared_ptr<CAnimImage>> images;
|
||||
@ -93,6 +95,7 @@ class CHighScoreInputScreen : public CWindowObject
|
||||
std::vector<std::shared_ptr<CMultiLineLabel>> texts;
|
||||
std::shared_ptr<CHighScoreInput> input;
|
||||
std::shared_ptr<TransparentFilledRectangle> background;
|
||||
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
|
||||
|
||||
std::string video;
|
||||
int videoSoundHandle;
|
||||
|
@ -108,9 +108,9 @@ void CMenuScreen::show(Canvas & to)
|
||||
if(!config["video"].isNull())
|
||||
{
|
||||
// redraw order: background -> video -> buttons and pictures
|
||||
background->redraw();
|
||||
background->showAll(to);
|
||||
CCS->videoh->update((int)config["video"]["x"].Float() + pos.x, (int)config["video"]["y"].Float() + pos.y, to.getInternalSurface(), true, false);
|
||||
tabs->redraw();
|
||||
tabs->showAll(to);
|
||||
}
|
||||
CIntObject::show(to);
|
||||
}
|
||||
@ -457,11 +457,11 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
|
||||
playerName->setText(getPlayerName());
|
||||
playerName->setCallback(std::bind(&CMultiMode::onNameChange, this, _1));
|
||||
|
||||
buttonHotseat = std::make_shared<CButton>(Point(373, 78 + 57 * 0), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
|
||||
buttonLobby = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this));
|
||||
buttonHotseat = std::make_shared<CButton>(Point(373, 78 + 57 * 0), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this), EShortcut::MAIN_MENU_HOTSEAT);
|
||||
buttonLobby = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this), EShortcut::MAIN_MENU_LOBBY);
|
||||
|
||||
buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 3), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this));
|
||||
buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this));
|
||||
buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 3), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this), EShortcut::MAIN_MENU_HOST_GAME);
|
||||
buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this), EShortcut::MAIN_MENU_JOIN_GAME);
|
||||
|
||||
buttonCancel = std::make_shared<CButton>(Point(373, 424), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/FramerateManager.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../render/Canvas.h"
|
||||
|
||||
|
||||
@ -27,7 +28,8 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
addUsedEvents(LCLICK | TIME);
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
updateShadow();
|
||||
|
||||
backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
|
||||
|
||||
auto audioData = CCS->videoh->getAudio(spe.prologVideo);
|
||||
videoSoundHandle = CCS->soundh->playSound(audioData, -1);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../../lib/campaign/CampaignScenarioPrologEpilog.h"
|
||||
|
||||
class CMultiLineLabel;
|
||||
class CFilledTexture;
|
||||
|
||||
class CPrologEpilogVideo : public CWindowObject
|
||||
{
|
||||
@ -25,6 +26,7 @@ class CPrologEpilogVideo : public CWindowObject
|
||||
std::function<void()> exitCb;
|
||||
|
||||
std::shared_ptr<CMultiLineLabel> text;
|
||||
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
|
||||
|
||||
bool voiceStopped = false;
|
||||
|
||||
|
@ -664,15 +664,15 @@ size_t MapRendererPath::selectImageArrow(bool reachableToday, const int3 & curr,
|
||||
// is (directionToArrowIndex[7][5])
|
||||
//
|
||||
const static size_t directionToArrowIndex[9][9] = {
|
||||
{16, 17, 18, 7, 0, 19, 6, 5, 0 },
|
||||
{8, 9, 18, 7, 0, 19, 6, 0, 20},
|
||||
{8, 1, 10, 7, 0, 19, 0, 21, 20},
|
||||
{24, 17, 18, 15, 0, 0, 6, 5, 4 },
|
||||
{16, 17, 18, 7, 0, 19, 6, 5, 12},
|
||||
{8, 9, 18, 7, 0, 19, 6, 13, 20},
|
||||
{8, 1, 10, 7, 0, 19, 14, 21, 20},
|
||||
{24, 17, 18, 15, 0, 11, 6, 5, 4 },
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{8, 1, 2, 0, 0, 11, 22, 21, 20},
|
||||
{24, 17, 0, 23, 0, 3, 14, 5, 4 },
|
||||
{24, 0, 2, 23, 0, 3, 22, 13, 4 },
|
||||
{0, 1, 2, 23, 0, 3, 22, 21, 12}
|
||||
{8, 1, 2, 15, 0, 11, 22, 21, 20},
|
||||
{24, 17, 10, 23, 0, 3, 14, 5, 4 },
|
||||
{24, 9, 2, 23, 0, 3, 22, 13, 4 },
|
||||
{16, 1, 2, 23, 0, 3, 22, 21, 12}
|
||||
};
|
||||
|
||||
size_t enterDirection = (curr.x - next.x + 1) + 3 * (curr.y - next.y + 1);
|
||||
|
@ -239,9 +239,9 @@ void MapView::onViewWorldActivated(uint32_t tileSize)
|
||||
controller->setTileSize(Point(tileSize, tileSize));
|
||||
}
|
||||
|
||||
void MapView::onMapZoomLevelChanged(int stepsChange)
|
||||
void MapView::onMapZoomLevelChanged(int stepsChange, bool useDeadZone)
|
||||
{
|
||||
controller->modifyTileSize(stepsChange);
|
||||
controller->modifyTileSize(stepsChange, useDeadZone);
|
||||
}
|
||||
|
||||
void MapView::onViewMapActivated()
|
||||
|
@ -87,7 +87,7 @@ public:
|
||||
void onViewWorldActivated(uint32_t tileSize);
|
||||
|
||||
/// Changes zoom level / tile size of current view by specified factor
|
||||
void onMapZoomLevelChanged(int stepsChange);
|
||||
void onMapZoomLevelChanged(int stepsChange, bool useDeadZone);
|
||||
|
||||
/// Switches view from View World mode back to standard view
|
||||
void onViewMapActivated();
|
||||
|
@ -87,7 +87,7 @@ void MapViewActions::mouseMoved(const Point & cursorPosition, const Point & last
|
||||
|
||||
void MapViewActions::wheelScrolled(int distance)
|
||||
{
|
||||
adventureInt->hotkeyZoom(distance * 4);
|
||||
adventureInt->hotkeyZoom(distance * 4, true);
|
||||
}
|
||||
|
||||
void MapViewActions::mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance)
|
||||
@ -114,7 +114,7 @@ void MapViewActions::gesturePinch(const Point & centerPosition, double lastUpdat
|
||||
int oldZoomSteps = std::round(std::log(pinchZoomFactor) / std::log(1.01));
|
||||
|
||||
if (newZoomSteps != oldZoomSteps)
|
||||
adventureInt->hotkeyZoom(newZoomSteps - oldZoomSteps);
|
||||
adventureInt->hotkeyZoom(newZoomSteps - oldZoomSteps, true);
|
||||
|
||||
pinchZoomFactor = newZoom;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ void MapViewController::setTileSize(const Point & tileSize)
|
||||
setViewCenter(newViewCenter, model->getLevel());
|
||||
}
|
||||
|
||||
void MapViewController::modifyTileSize(int stepsChange)
|
||||
void MapViewController::modifyTileSize(int stepsChange, bool useDeadZone)
|
||||
{
|
||||
// we want to zoom in/out in fixed 10% steps, to allow player to return back to exactly 100% zoom just by scrolling
|
||||
// so, zooming in for 5 steps will put game at 1.1^5 = 1.61 scale
|
||||
@ -118,10 +118,13 @@ void MapViewController::modifyTileSize(int stepsChange)
|
||||
if (actualZoom != currentZoom)
|
||||
{
|
||||
targetTileSize = actualZoom;
|
||||
if(actualZoom.x >= defaultTileSize - zoomTileDeadArea && actualZoom.x <= defaultTileSize + zoomTileDeadArea)
|
||||
actualZoom.x = defaultTileSize;
|
||||
if(actualZoom.y >= defaultTileSize - zoomTileDeadArea && actualZoom.y <= defaultTileSize + zoomTileDeadArea)
|
||||
actualZoom.y = defaultTileSize;
|
||||
if (useDeadZone)
|
||||
{
|
||||
if(actualZoom.x >= defaultTileSize - zoomTileDeadArea && actualZoom.x <= defaultTileSize + zoomTileDeadArea)
|
||||
actualZoom.x = defaultTileSize;
|
||||
if(actualZoom.y >= defaultTileSize - zoomTileDeadArea && actualZoom.y <= defaultTileSize + zoomTileDeadArea)
|
||||
actualZoom.y = defaultTileSize;
|
||||
}
|
||||
|
||||
bool isInDeadZone = targetTileSize != actualZoom || actualZoom == Point(defaultTileSize, defaultTileSize);
|
||||
|
||||
|
@ -55,7 +55,7 @@ class MapViewController : public IMapObjectObserver
|
||||
|
||||
private:
|
||||
const int defaultTileSize = 32;
|
||||
const int zoomTileDeadArea = 5;
|
||||
const int zoomTileDeadArea = 4;
|
||||
Point targetTileSize = Point(32, 32);
|
||||
bool wasInDeadZone = true;
|
||||
|
||||
@ -97,7 +97,7 @@ public:
|
||||
void setViewCenter(const int3 & position);
|
||||
void setViewCenter(const Point & position, int level);
|
||||
void setTileSize(const Point & tileSize);
|
||||
void modifyTileSize(int stepsChange);
|
||||
void modifyTileSize(int stepsChange, bool useDeadZone);
|
||||
void tick(uint32_t timePassed);
|
||||
void afterRender();
|
||||
|
||||
|
@ -140,11 +140,6 @@ void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
|
||||
CArtPlace::showPopupWindow(cursorPosition);
|
||||
}
|
||||
|
||||
CHeroArtPlace::CHeroArtPlace(Point position, const CArtifactInstance * art)
|
||||
: CArtPlace(position, art)
|
||||
{
|
||||
}
|
||||
|
||||
void CArtPlace::lockSlot(bool on)
|
||||
{
|
||||
if(locked == on)
|
||||
@ -219,20 +214,35 @@ void CArtPlace::setGestureCallback(const ClickFunctor & callback)
|
||||
gestureCallback = callback;
|
||||
}
|
||||
|
||||
void CHeroArtPlace::addCombinedArtInfo(const std::map<const CArtifact*, int> & arts)
|
||||
void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts)
|
||||
{
|
||||
for(const auto & combinedArt : arts)
|
||||
for(const auto & availableArts : arts)
|
||||
{
|
||||
std::string artList;
|
||||
text += "\n\n";
|
||||
text += "{" + combinedArt.first->getNameTranslated() + "}";
|
||||
if(arts.size() == 1)
|
||||
const auto combinedArt = availableArts.first.toArtifact();
|
||||
MetaString info;
|
||||
info.appendEOL();
|
||||
info.appendEOL();
|
||||
info.appendRawString("{");
|
||||
info.appendName(combinedArt->getId());
|
||||
info.appendRawString("}");
|
||||
info.appendRawString(" (%d/%d)");
|
||||
info.replaceNumber(availableArts.second.size());
|
||||
info.replaceNumber(combinedArt->getConstituents().size());
|
||||
for(const auto part : combinedArt->getConstituents())
|
||||
{
|
||||
for(const auto part : combinedArt.first->getConstituents())
|
||||
artList += "\n" + part->getNameTranslated();
|
||||
info.appendEOL();
|
||||
if(vstd::contains(availableArts.second, part->getId()))
|
||||
{
|
||||
info.appendName(part->getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
info.appendRawString("{#A9A9A9|");
|
||||
info.appendName(part->getId());
|
||||
info.appendRawString("}");
|
||||
}
|
||||
}
|
||||
text += " (" + boost::str(boost::format("%d") % combinedArt.second) + " / " +
|
||||
boost::str(boost::format("%d") % combinedArt.first->getConstituents().size()) + ")" + artList;
|
||||
text += info.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ public:
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
void showPopupWindow(const Point & cursorPosition) override;
|
||||
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
|
||||
void addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts);
|
||||
|
||||
private:
|
||||
const CArtifactInstance * ourArt;
|
||||
@ -59,13 +60,6 @@ public:
|
||||
void showPopupWindow(const Point & cursorPosition) override;
|
||||
};
|
||||
|
||||
class CHeroArtPlace: public CArtPlace
|
||||
{
|
||||
public:
|
||||
CHeroArtPlace(Point position, const CArtifactInstance * art = nullptr);
|
||||
void addCombinedArtInfo(const std::map<const CArtifact*, int> & arts);
|
||||
};
|
||||
|
||||
namespace ArtifactUtilsClient
|
||||
{
|
||||
bool askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot);
|
||||
|
@ -82,7 +82,7 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
|
||||
const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % slotsColumnsMax),
|
||||
slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
|
||||
backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
|
||||
artPlace = std::make_shared<CHeroArtPlace>(pos);
|
||||
artPlace = std::make_shared<CArtPlace>(pos);
|
||||
artPlace->setArtifact(nullptr);
|
||||
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
|
||||
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
|
||||
|
@ -57,12 +57,12 @@ void CArtifactsOfHeroBase::init(
|
||||
pos += position;
|
||||
for(int g = 0; g < ArtifactPosition::BACKPACK_START; g++)
|
||||
{
|
||||
artWorn[ArtifactPosition(g)] = std::make_shared<CHeroArtPlace>(slotPos[g]);
|
||||
artWorn[ArtifactPosition(g)] = std::make_shared<CArtPlace>(slotPos[g]);
|
||||
}
|
||||
backpack.clear();
|
||||
for(int s = 0; s < 5; s++)
|
||||
{
|
||||
auto artPlace = std::make_shared<CHeroArtPlace>(Point(403 + 46 * s, 365));
|
||||
auto artPlace = std::make_shared<CArtPlace>(Point(403 + 46 * s, 365));
|
||||
backpack.push_back(artPlace);
|
||||
}
|
||||
for(auto artPlace : artWorn)
|
||||
@ -224,21 +224,21 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
|
||||
{
|
||||
artPlace->lockSlot(slotInfo->locked);
|
||||
artPlace->setArtifact(slotInfo->artifact);
|
||||
if(!slotInfo->artifact->isCombined())
|
||||
if(slotInfo->locked || slotInfo->artifact->isCombined())
|
||||
return;
|
||||
|
||||
// If the artifact is part of at least one combined artifact, add additional information
|
||||
std::map<const ArtifactID, std::vector<ArtifactID>> arts;
|
||||
for(const auto combinedArt : slotInfo->artifact->artType->getPartOf())
|
||||
{
|
||||
// If the artifact is part of at least one combined artifact, add additional information
|
||||
std::map<const CArtifact*, int> arts;
|
||||
for(const auto combinedArt : slotInfo->artifact->artType->getPartOf())
|
||||
arts.try_emplace(combinedArt->getId(), std::vector<ArtifactID>{});
|
||||
for(const auto part : combinedArt->getConstituents())
|
||||
{
|
||||
arts.insert(std::pair(combinedArt, 0));
|
||||
for(const auto part : combinedArt->getConstituents())
|
||||
{
|
||||
if(curHero->hasArt(part->getId(), false))
|
||||
arts.at(combinedArt)++;
|
||||
}
|
||||
if(curHero->hasArt(part->getId(), false, false, false))
|
||||
arts.at(combinedArt->getId()).emplace_back(part->getId());
|
||||
}
|
||||
artPlace->addCombinedArtInfo(arts);
|
||||
}
|
||||
artPlace->addCombinedArtInfo(arts);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ class CButton;
|
||||
class CArtifactsOfHeroBase : virtual public CIntObject
|
||||
{
|
||||
protected:
|
||||
using ArtPlacePtr = std::shared_ptr<CHeroArtPlace>;
|
||||
using ArtPlacePtr = std::shared_ptr<CArtPlace>;
|
||||
using BpackScrollFunctor = std::function<void(int)>;
|
||||
|
||||
public:
|
||||
@ -62,7 +62,7 @@ protected:
|
||||
Point(381,295) //18
|
||||
};
|
||||
|
||||
virtual void init(const CHeroArtPlace::ClickFunctor & lClickCallback, const CHeroArtPlace::ClickFunctor & showPopupCallback,
|
||||
virtual void init(const CArtPlace::ClickFunctor & lClickCallback, const CArtPlace::ClickFunctor & showPopupCallback,
|
||||
const Point & position, const BpackScrollFunctor & scrollCallback);
|
||||
// Assigns an artifacts to an artifact place depending on it's new slot ID
|
||||
virtual void setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot);
|
||||
|
@ -42,49 +42,27 @@ void CArtifactsOfHeroMain::keyPressed(EShortcut key)
|
||||
{
|
||||
if(!shortcutPressed)
|
||||
{
|
||||
uint32_t costumeIdx;
|
||||
switch(key)
|
||||
int saveIdx = vstd::find_pos(costumeSaveShortcuts, key);
|
||||
int loadIdx = vstd::find_pos(costumeLoadShortcuts, key);
|
||||
|
||||
if (saveIdx != -1)
|
||||
{
|
||||
case EShortcut::HERO_COSTUME_0:
|
||||
costumeIdx = 0;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_1:
|
||||
costumeIdx = 1;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_2:
|
||||
costumeIdx = 2;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_3:
|
||||
costumeIdx = 3;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_4:
|
||||
costumeIdx = 4;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_5:
|
||||
costumeIdx = 5;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_6:
|
||||
costumeIdx = 6;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_7:
|
||||
costumeIdx = 7;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_8:
|
||||
costumeIdx = 8;
|
||||
break;
|
||||
case EShortcut::HERO_COSTUME_9:
|
||||
costumeIdx = 9;
|
||||
break;
|
||||
default:
|
||||
shortcutPressed = true;
|
||||
LOCPLINT->cb->manageHeroCostume(getHero()->id, saveIdx, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadIdx != -1)
|
||||
{
|
||||
shortcutPressed = true;
|
||||
LOCPLINT->cb->manageHeroCostume(getHero()->id, loadIdx, false);
|
||||
return;
|
||||
}
|
||||
shortcutPressed = true;
|
||||
LOCPLINT->cb->manageHeroCostume(getHero()->id, costumeIdx, GH.isKeyboardCtrlDown());
|
||||
}
|
||||
}
|
||||
|
||||
void CArtifactsOfHeroMain::keyReleased(EShortcut key)
|
||||
{
|
||||
if(vstd::contains(costumesSwitcherHotkeys, key))
|
||||
if(vstd::contains(costumeSaveShortcuts, key) || vstd::contains(costumeLoadShortcuts, key))
|
||||
shortcutPressed = false;
|
||||
}
|
||||
|
@ -23,17 +23,29 @@ public:
|
||||
void keyReleased(EShortcut key) override;
|
||||
|
||||
private:
|
||||
const std::vector<EShortcut> costumesSwitcherHotkeys =
|
||||
{
|
||||
EShortcut::HERO_COSTUME_0,
|
||||
EShortcut::HERO_COSTUME_1,
|
||||
EShortcut::HERO_COSTUME_2,
|
||||
EShortcut::HERO_COSTUME_3,
|
||||
EShortcut::HERO_COSTUME_4,
|
||||
EShortcut::HERO_COSTUME_5,
|
||||
EShortcut::HERO_COSTUME_6,
|
||||
EShortcut::HERO_COSTUME_7,
|
||||
EShortcut::HERO_COSTUME_8,
|
||||
EShortcut::HERO_COSTUME_9
|
||||
static constexpr std::array costumeSaveShortcuts = {
|
||||
EShortcut::HERO_COSTUME_SAVE_0,
|
||||
EShortcut::HERO_COSTUME_SAVE_1,
|
||||
EShortcut::HERO_COSTUME_SAVE_2,
|
||||
EShortcut::HERO_COSTUME_SAVE_3,
|
||||
EShortcut::HERO_COSTUME_SAVE_4,
|
||||
EShortcut::HERO_COSTUME_SAVE_5,
|
||||
EShortcut::HERO_COSTUME_SAVE_6,
|
||||
EShortcut::HERO_COSTUME_SAVE_7,
|
||||
EShortcut::HERO_COSTUME_SAVE_8,
|
||||
EShortcut::HERO_COSTUME_SAVE_9
|
||||
};
|
||||
|
||||
static constexpr std::array costumeLoadShortcuts = {
|
||||
EShortcut::HERO_COSTUME_LOAD_0,
|
||||
EShortcut::HERO_COSTUME_LOAD_1,
|
||||
EShortcut::HERO_COSTUME_LOAD_2,
|
||||
EShortcut::HERO_COSTUME_LOAD_3,
|
||||
EShortcut::HERO_COSTUME_LOAD_4,
|
||||
EShortcut::HERO_COSTUME_LOAD_5,
|
||||
EShortcut::HERO_COSTUME_LOAD_6,
|
||||
EShortcut::HERO_COSTUME_LOAD_7,
|
||||
EShortcut::HERO_COSTUME_LOAD_8,
|
||||
EShortcut::HERO_COSTUME_LOAD_9
|
||||
};
|
||||
};
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
#include "../windows/CWindowWithArtifacts.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CAltarArtifacts.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -36,16 +37,16 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
|
||||
altarArtifacts = altarObj;
|
||||
|
||||
deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
|
||||
CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); });
|
||||
CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL);
|
||||
labels.emplace_back(std::make_shared<CLabel>(450, 32, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
|
||||
labels.emplace_back(std::make_shared<CLabel>(302, 424, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
|
||||
|
||||
sacrificeAllButton = std::make_shared<CButton>(Point(393, 520), AnimationPath::builtin("ALTFILL.DEF"),
|
||||
CGI->generaltexth->zelp[571], std::bind(&CExperienceAltar::sacrificeAll, this));
|
||||
CGI->generaltexth->zelp[571], std::bind(&CExperienceAltar::sacrificeAll, this), EShortcut::MARKET_SACRIFICE_ALL);
|
||||
sacrificeAllButton->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty());
|
||||
|
||||
sacrificeBackpackButton = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("ALTEMBK.DEF"),
|
||||
CGI->generaltexth->zelp[570], std::bind(&CAltarArtifacts::sacrificeBackpack, this));
|
||||
CGI->generaltexth->zelp[570], std::bind(&CAltarArtifacts::sacrificeBackpack, this), EShortcut::MARKET_SACRIFICE_BACKPACK);
|
||||
sacrificeBackpackButton->block(hero->artifactsInBackpack.empty());
|
||||
|
||||
// Hero's artifacts
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CAltarCreatures.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -33,7 +34,7 @@ CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance *
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
|
||||
|
||||
deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("ALTSACR.DEF"),
|
||||
CGI->generaltexth->zelp[584], [this]() {CAltarCreatures::makeDeal();});
|
||||
CGI->generaltexth->zelp[584], [this]() {CAltarCreatures::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
labels.emplace_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
|
||||
boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
|
||||
labels.emplace_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
|
||||
@ -44,7 +45,7 @@ CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance *
|
||||
unitsOnAltar.resize(GameConstants::ARMY_SIZE, 0);
|
||||
expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
|
||||
sacrificeAllButton = std::make_shared<CButton>(
|
||||
Point(393, 520), AnimationPath::builtin("ALTARMY.DEF"), CGI->generaltexth->zelp[579], std::bind(&CExperienceAltar::sacrificeAll, this));
|
||||
Point(393, 520), AnimationPath::builtin("ALTARMY.DEF"), CGI->generaltexth->zelp[579], std::bind(&CExperienceAltar::sacrificeAll, this), EShortcut::MARKET_SACRIFICE_ALL);
|
||||
|
||||
// Hero creatures panel
|
||||
assert(bidTradePanel);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CArtifactsBuying.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -38,7 +39,7 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance
|
||||
title = CGI->generaltexth->allTexts[349];
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
|
||||
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();});
|
||||
CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
labels.emplace_back(std::make_shared<CLabel>(445, 148, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[168]));
|
||||
|
||||
// Player's resources
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CArtifactsSelling.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -43,7 +44,7 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
|
||||
labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
|
||||
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this](){CArtifactsSelling::makeDeal();});
|
||||
CGI->generaltexth->zelp[595], [this](){CArtifactsSelling::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
bidSelectedSlot = std::make_shared<CTradeableItem>(Rect(Point(123, 470), Point(69, 66)), EType::ARTIFACT_TYPE, 0, 0);
|
||||
|
||||
// Market resources panel
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CFreelancerGuild.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -39,7 +40,7 @@ CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance
|
||||
labels.emplace_back(std::make_shared<CLabel>(155, 103, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
|
||||
boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
|
||||
deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this]() {CFreelancerGuild::makeDeal();});
|
||||
CGI->generaltexth->zelp[595], [this]() {CFreelancerGuild::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
offerSlider->moveTo(pos.topLeft() + Point(232, 489));
|
||||
|
||||
// Hero creatures panel
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "../Images.h"
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -200,7 +201,7 @@ CMarketSlider::CMarketSlider(const CSlider::SliderMovingFunctor & movingCallback
|
||||
[this]()
|
||||
{
|
||||
offerSlider->scrollToMax();
|
||||
});
|
||||
}, EShortcut::MARKET_MAX_AMOUNT);
|
||||
}
|
||||
|
||||
void CMarketSlider::deselect()
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CMarketResources.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -36,7 +37,7 @@ CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance
|
||||
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
|
||||
deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this]() {CMarketResources::makeDeal(); });
|
||||
CGI->generaltexth->zelp[595], [this]() {CMarketResources::makeDeal(); }, EShortcut::MARKET_DEAL);
|
||||
|
||||
// Player's resources
|
||||
assert(bidTradePanel);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "CTransferResources.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
|
||||
@ -34,7 +35,7 @@ CTransferResources::CTransferResources(const IMarket * market, const CGHeroInsta
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[158]));
|
||||
labels.emplace_back(std::make_shared<CLabel>(445, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[169]));
|
||||
deal = std::make_shared<CButton>(dealButtonPosWithSlider, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this](){CTransferResources::makeDeal();});
|
||||
CGI->generaltexth->zelp[595], [this](){CTransferResources::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
|
||||
// Player's resources
|
||||
assert(bidTradePanel);
|
||||
|
@ -1034,6 +1034,40 @@ void CCastleBuildings::openTownHall()
|
||||
GH.windows().createAndPushWindow<CHallInterface>(town);
|
||||
}
|
||||
|
||||
void CCastleBuildings::enterAnyThievesGuild()
|
||||
{
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::TAVERN))
|
||||
{
|
||||
LOCPLINT->showThievesGuildWindow(town);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
|
||||
}
|
||||
|
||||
void CCastleBuildings::enterAnyMarket()
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
|
||||
}
|
||||
|
||||
CCreaInfo::CCreaInfo(Point position, const CGTownInstance * Town, int Level, bool compact, bool _showAvailable):
|
||||
town(Town),
|
||||
level(Level),
|
||||
@ -1215,7 +1249,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
|
||||
exit = std::make_shared<CButton>(Point(744, 544), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN);
|
||||
exit->setImageOrder(4, 5, 6, 7);
|
||||
|
||||
auto split = std::make_shared<CButton>(Point(744, 382), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[3]), [this]() { garr->splitClick(); });
|
||||
auto split = std::make_shared<CButton>(Point(744, 382), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[3]), [this]() { garr->splitClick(); }, EShortcut::HERO_ARMY_SPLIT);
|
||||
garr->addSplitBtn(split);
|
||||
|
||||
Rect barRect(9, 182, 732, 18);
|
||||
@ -1224,8 +1258,8 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
|
||||
resdatabar = std::make_shared<CResDataBar>(ImagePath::builtin("ARESBAR"), 3, 575, 37, 3, 84, 78);
|
||||
|
||||
townlist = std::make_shared<CTownList>(3, Rect(Point(743, 414), Point(48, 128)), Point(1,16), Point(0, 32), LOCPLINT->localState->getOwnedTowns().size() );
|
||||
townlist->setScrollUpButton( std::make_shared<CButton>( Point(744, 414), AnimationPath::builtin("IAM014"), CButton::tooltipLocalized("core.help.306")));
|
||||
townlist->setScrollDownButton( std::make_shared<CButton>( Point(744, 526), AnimationPath::builtin("IAM015"), CButton::tooltipLocalized("core.help.307")));
|
||||
townlist->setScrollUpButton( std::make_shared<CButton>( Point(744, 414), AnimationPath::builtin("IAM014"), CButton::tooltipLocalized("core.help.306"), 0, EShortcut::MOVE_UP));
|
||||
townlist->setScrollDownButton( std::make_shared<CButton>( Point(744, 526), AnimationPath::builtin("IAM015"), CButton::tooltipLocalized("core.help.307"), 0, EShortcut::MOVE_DOWN));
|
||||
|
||||
if(from)
|
||||
townlist->select(from);
|
||||
@ -1326,27 +1360,14 @@ void CCastleInterface::recreateIcons()
|
||||
hall = std::make_shared<CTownInfo>(80, 413, town, true);
|
||||
fort = std::make_shared<CTownInfo>(122, 413, town, false);
|
||||
|
||||
fastTownHall = std::make_shared<CButton>(Point(80, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterTownHall(); });
|
||||
fastTownHall = std::make_shared<CButton>(Point(80, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterTownHall(); }, EShortcut::TOWN_OPEN_HALL);
|
||||
fastTownHall->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("ITMTL"), town->hallLevel()));
|
||||
|
||||
int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1;
|
||||
fastArmyPurchase = std::make_shared<CButton>(Point(122, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterToTheQuickRecruitmentWindow(); });
|
||||
fastArmyPurchase = std::make_shared<CButton>(Point(122, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterToTheQuickRecruitmentWindow(); }, EShortcut::TOWN_OPEN_RECRUITMENT);
|
||||
fastArmyPurchase->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("itmcl"), imageIndex));
|
||||
|
||||
fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [&]()
|
||||
{
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
|
||||
});
|
||||
|
||||
fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); });
|
||||
fastTavern = std::make_shared<LRClickableArea>(Rect(15, 387, 58, 64), [&]()
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::TAVERN))
|
||||
@ -1367,18 +1388,41 @@ void CCastleInterface::recreateIcons()
|
||||
|
||||
for(size_t i=0; i<4; i++)
|
||||
creainfo.push_back(std::make_shared<CCreaInfo>(Point(14 + 55 * (int)i, 507), town, (int)i + 4, compactCreatureInfo, useAvailableCreaturesForLabel));
|
||||
|
||||
}
|
||||
|
||||
void CCastleInterface::keyPressed(EShortcut key)
|
||||
{
|
||||
switch(key)
|
||||
{
|
||||
case EShortcut::MOVE_UP:
|
||||
townlist->selectPrev();
|
||||
case EShortcut::TOWN_OPEN_FORT:
|
||||
GH.windows().createAndPushWindow<CFortScreen>(town);
|
||||
break;
|
||||
case EShortcut::MOVE_DOWN:
|
||||
townlist->selectNext();
|
||||
case EShortcut::TOWN_OPEN_MARKET:
|
||||
builds->enterAnyMarket();
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_MAGE_GUILD:
|
||||
if(town->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||
builds->enterMagesGuild();
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_THIEVES_GUILD:
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_HERO_EXCHANGE:
|
||||
if (town->visitingHero && town->garrisonHero)
|
||||
LOCPLINT->showHeroExchange(town->visitingHero->id, town->garrisonHero->id);
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_HERO:
|
||||
if (town->visitingHero)
|
||||
LOCPLINT->openHeroWindow(town->visitingHero);
|
||||
else if (town->garrisonHero)
|
||||
LOCPLINT->openHeroWindow(town->garrisonHero);
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_VISITING_HERO:
|
||||
if (town->visitingHero)
|
||||
LOCPLINT->openHeroWindow(town->visitingHero);
|
||||
break;
|
||||
case EShortcut::TOWN_OPEN_GARRISONED_HERO:
|
||||
if (town->garrisonHero)
|
||||
LOCPLINT->openHeroWindow(town->garrisonHero);
|
||||
break;
|
||||
case EShortcut::TOWN_SWAP_ARMIES:
|
||||
heroes->swapArmies();
|
||||
|
@ -154,7 +154,6 @@ class CCastleBuildings : public CIntObject
|
||||
void enterBuilding(BuildingID building);//for buildings with simple description + pic left-click messages
|
||||
void enterCastleGate();
|
||||
void enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades);//Rampart's fountains
|
||||
void enterMagesGuild();
|
||||
|
||||
void openMagesGuild();
|
||||
void openTownHall();
|
||||
@ -168,6 +167,9 @@ public:
|
||||
|
||||
void enterDwelling(int level);
|
||||
void enterTownHall();
|
||||
void enterMagesGuild();
|
||||
void enterAnyMarket();
|
||||
void enterAnyThievesGuild();
|
||||
void enterToTheQuickRecruitmentWindow();
|
||||
|
||||
void buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID = BuildingSubID::NONE, BuildingID upgrades = BuildingID::NONE);
|
||||
|
376
client/windows/CExchangeWindow.cpp
Normal file
376
client/windows/CExchangeWindow.cpp
Normal file
@ -0,0 +1,376 @@
|
||||
/*
|
||||
* CExchangeWindow.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CExchangeWindow.h"
|
||||
|
||||
#include "CHeroBackpackWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/CursorHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
|
||||
#include "../widgets/CGarrisonInt.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/CAnimation.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
#include "../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../lib/CGeneralTextHandler.h"
|
||||
#include "../lib/CHeroHandler.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/CSkillHandler.h"
|
||||
#include "../lib/TextOperations.h"
|
||||
|
||||
static const std::string QUICK_EXCHANGE_BG = "quick-exchange/TRADEQE";
|
||||
|
||||
static bool isQuickExchangeLayoutAvailable()
|
||||
{
|
||||
return CResourceHandler::get()->existsResource(ImagePath::builtin("SPRITES/" + QUICK_EXCHANGE_BG));
|
||||
}
|
||||
|
||||
CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID)
|
||||
: CWindowObject(PLAYER_COLORED | BORDERED, ImagePath::builtin(isQuickExchangeLayoutAvailable() ? QUICK_EXCHANGE_BG : "TRADE2")),
|
||||
controller(hero1, hero2),
|
||||
moveStackLeftButtons(),
|
||||
moveStackRightButtons()
|
||||
{
|
||||
const bool qeLayout = isQuickExchangeLayoutAvailable();
|
||||
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
addUsedEvents(KEYBOARD);
|
||||
|
||||
heroInst[0] = LOCPLINT->cb->getHero(hero1);
|
||||
heroInst[1] = LOCPLINT->cb->getHero(hero2);
|
||||
|
||||
auto genTitle = [](const CGHeroInstance * h)
|
||||
{
|
||||
boost::format fmt(CGI->generaltexth->allTexts[138]);
|
||||
fmt % h->getNameTranslated() % h->level % h->getClassNameTranslated();
|
||||
return boost::str(fmt);
|
||||
};
|
||||
|
||||
titles[0] = std::make_shared<CLabel>(147, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[0]));
|
||||
titles[1] = std::make_shared<CLabel>(653, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[1]));
|
||||
|
||||
auto PSKIL32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("PSKIL32"));
|
||||
PSKIL32->preload();
|
||||
|
||||
auto SECSK32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("SECSK32"));
|
||||
|
||||
for(int g = 0; g < 4; ++g)
|
||||
{
|
||||
if (qeLayout)
|
||||
primSkillImages.push_back(std::make_shared<CAnimImage>(PSKIL32, g, Rect(389, 12 + 26 * g, 22, 22)));
|
||||
else
|
||||
primSkillImages.push_back(std::make_shared<CAnimImage>(PSKIL32, g, 0, 385, 19 + 36 * g));
|
||||
}
|
||||
|
||||
for(int leftRight : {0, 1})
|
||||
{
|
||||
const CGHeroInstance * hero = heroInst.at(leftRight);
|
||||
|
||||
for(int m=0; m<GameConstants::PRIMARY_SKILLS; ++m)
|
||||
primSkillValues[leftRight].push_back(std::make_shared<CLabel>(352 + (qeLayout ? 96 : 93) * leftRight, (qeLayout ? 22 : 35) + (qeLayout ? 26 : 36) * m, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE));
|
||||
|
||||
|
||||
for(int m=0; m < hero->secSkills.size(); ++m)
|
||||
secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(SECSK32, 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
|
||||
|
||||
specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
|
||||
|
||||
expImages[leftRight] = std::make_shared<CAnimImage>(PSKIL32, 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45);
|
||||
expValues[leftRight] = std::make_shared<CLabel>(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||
|
||||
manaImages[leftRight] = std::make_shared<CAnimImage>(PSKIL32, 5, 0, 139 + 490 * leftRight, qeLayout ? 41 : 45);
|
||||
manaValues[leftRight] = std::make_shared<CLabel>(155 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||
}
|
||||
|
||||
artifs[0] = std::make_shared<CArtifactsOfHeroMain>(Point(-334, 151));
|
||||
artifs[0]->setHero(heroInst[0]);
|
||||
artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 151));
|
||||
artifs[1]->setHero(heroInst[1]);
|
||||
|
||||
addSetAndCallbacks(artifs[0]);
|
||||
addSetAndCallbacks(artifs[1]);
|
||||
|
||||
for(int g=0; g<4; ++g)
|
||||
{
|
||||
primSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>());
|
||||
if (qeLayout)
|
||||
primSkillAreas[g]->pos = Rect(Point(pos.x + 324, pos.y + 12 + 26 * g), Point(152, 22));
|
||||
else
|
||||
primSkillAreas[g]->pos = Rect(Point(pos.x + 329, pos.y + 19 + 36 * g), Point(140, 32));
|
||||
primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g];
|
||||
primSkillAreas[g]->component = Component( ComponentType::PRIM_SKILL, PrimarySkill(g));
|
||||
primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1];
|
||||
boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]);
|
||||
}
|
||||
|
||||
//heroes related thing
|
||||
for(int b=0; b < heroInst.size(); b++)
|
||||
{
|
||||
const CGHeroInstance * hero = heroInst.at(b);
|
||||
|
||||
//secondary skill's clickable areas
|
||||
for(int g=0; g<hero->secSkills.size(); ++g)
|
||||
{
|
||||
SecondarySkill skill = hero->secSkills[g].first;
|
||||
int level = hero->secSkills[g].second; // <1, 3>
|
||||
secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
|
||||
secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
|
||||
secSkillAreas[b][g]->component = Component(ComponentType::SEC_SKILL, skill, level);
|
||||
secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
|
||||
|
||||
secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
|
||||
boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]);
|
||||
boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->getByIndex(skill)->getNameTranslated());
|
||||
}
|
||||
|
||||
heroAreas[b] = std::make_shared<CHeroArea>(257 + 228 * b, 13, hero);
|
||||
heroAreas[b]->addClickCallback([this, hero]() -> void
|
||||
{
|
||||
if(getPickedArtifact() == nullptr)
|
||||
LOCPLINT->openHeroWindow(hero);
|
||||
});
|
||||
|
||||
specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
|
||||
specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
|
||||
specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27];
|
||||
specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated();
|
||||
|
||||
experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
|
||||
experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
|
||||
experienceAreas[b]->hoverText = CGI->generaltexth->heroscrn[9];
|
||||
experienceAreas[b]->text = CGI->generaltexth->allTexts[2];
|
||||
boost::algorithm::replace_first(experienceAreas[b]->text, "%d", std::to_string(hero->level));
|
||||
boost::algorithm::replace_first(experienceAreas[b]->text, "%d", std::to_string(CGI->heroh->reqExp(hero->level+1)));
|
||||
boost::algorithm::replace_first(experienceAreas[b]->text, "%d", std::to_string(hero->exp));
|
||||
|
||||
spellPointsAreas[b] = std::make_shared<LRClickableAreaWText>();
|
||||
spellPointsAreas[b]->pos = Rect(Point(pos.x + 141 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
|
||||
spellPointsAreas[b]->hoverText = CGI->generaltexth->heroscrn[22];
|
||||
spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205];
|
||||
boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->getNameTranslated());
|
||||
boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", std::to_string(hero->mana));
|
||||
boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", std::to_string(hero->manaLimit()));
|
||||
|
||||
morale[b] = std::make_shared<MoraleLuckBox>(true, Rect(Point(176 + 490 * b, 39), Point(32, 32)), true);
|
||||
luck[b] = std::make_shared<MoraleLuckBox>(false, Rect(Point(212 + 490 * b, 39), Point(32, 32)), true);
|
||||
}
|
||||
|
||||
quit = std::make_shared<CButton>(Point(732, 567), AnimationPath::builtin("IOKAY.DEF"), CGI->generaltexth->zelp[600], std::bind(&CExchangeWindow::close, this), EShortcut::GLOBAL_ACCEPT);
|
||||
if(queryID.getNum() > 0)
|
||||
quit->addCallback([=](){ LOCPLINT->cb->selectionMade(0, queryID); });
|
||||
|
||||
questlogButton[0] = std::make_shared<CButton>(Point( 10, qeLayout ? 39 : 44), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(CGI->generaltexth->heroscrn[0]), std::bind(&CExchangeWindow::questLogShortcut, this), EShortcut::ADVENTURE_QUEST_LOG);
|
||||
questlogButton[1] = std::make_shared<CButton>(Point(740, qeLayout ? 39 : 44), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(CGI->generaltexth->heroscrn[0]), std::bind(&CExchangeWindow::questLogShortcut, this), EShortcut::ADVENTURE_QUEST_LOG);
|
||||
|
||||
Rect barRect(5, 578, 725, 18);
|
||||
statusbar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), barRect, 5, 578));
|
||||
|
||||
//garrison interface
|
||||
|
||||
garr = std::make_shared<CGarrisonInt>(Point(69, qeLayout ? 122 : 131), 4, Point(418,0), heroInst[0], heroInst[1], true, true);
|
||||
auto splitButtonCallback = [&](){ garr->splitClick(); };
|
||||
garr->addSplitBtn(std::make_shared<CButton>( Point( 10, qeLayout ? 122 : 132), AnimationPath::builtin("TSBTNS.DEF"), CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback, EShortcut::HERO_ARMY_SPLIT));
|
||||
garr->addSplitBtn(std::make_shared<CButton>( Point(744, qeLayout ? 122 : 132), AnimationPath::builtin("TSBTNS.DEF"), CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback, EShortcut::HERO_ARMY_SPLIT));
|
||||
|
||||
if(qeLayout)
|
||||
{
|
||||
moveAllGarrButtonLeft = std::make_shared<CButton>(Point(325, 118), AnimationPath::builtin("quick-exchange/armRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
|
||||
[this](){ this->moveUnitsShortcut(false); });
|
||||
exchangeGarrButton = std::make_shared<CButton>(Point(377, 118), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[2]),
|
||||
[this](){ controller.swapArmy(); });
|
||||
moveAllGarrButtonRight = std::make_shared<CButton>(Point(425, 118), AnimationPath::builtin("quick-exchange/armLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
|
||||
[this](){ this->moveUnitsShortcut(true); });
|
||||
moveArtifactsButtonLeft = std::make_shared<CButton>(Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]),
|
||||
[this](){ this->moveArtifactsCallback(false);});
|
||||
exchangeArtifactsButton = std::make_shared<CButton>(Point(377, 154), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[4]),
|
||||
[this](){ this->swapArtifactsCallback(); });
|
||||
moveArtifactsButtonRight = std::make_shared<CButton>(Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]),
|
||||
[this](){ this->moveArtifactsCallback(true);});
|
||||
|
||||
backpackButtonLeft = std::make_shared<CButton>(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
|
||||
[this](){ this->backpackShortcut(true); });
|
||||
backpackButtonRight = std::make_shared<CButton>(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
|
||||
[this](){ this->backpackShortcut(false); });
|
||||
backpackButtonLeft->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
|
||||
backpackButtonRight->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
|
||||
|
||||
auto leftHeroBlock = heroInst[0]->tempOwner != LOCPLINT->cb->getPlayerID();
|
||||
auto rightHeroBlock = heroInst[1]->tempOwner != LOCPLINT->cb->getPlayerID();
|
||||
moveAllGarrButtonLeft->block(leftHeroBlock);
|
||||
exchangeGarrButton->block(leftHeroBlock || rightHeroBlock);
|
||||
moveAllGarrButtonRight->block(rightHeroBlock);
|
||||
moveArtifactsButtonLeft->block(leftHeroBlock);
|
||||
exchangeArtifactsButton->block(leftHeroBlock || rightHeroBlock);
|
||||
moveArtifactsButtonRight->block(rightHeroBlock);
|
||||
backpackButtonLeft->block(leftHeroBlock);
|
||||
backpackButtonRight->block(rightHeroBlock);
|
||||
|
||||
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
|
||||
{
|
||||
moveStackLeftButtons.push_back(
|
||||
std::make_shared<CButton>(
|
||||
Point(484 + 35 * i, 154),
|
||||
AnimationPath::builtin("quick-exchange/unitLeft.DEF"),
|
||||
CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
|
||||
std::bind(&CExchangeController::moveStack, &controller, false, SlotID(i))));
|
||||
moveStackLeftButtons.back()->block(leftHeroBlock);
|
||||
|
||||
moveStackRightButtons.push_back(
|
||||
std::make_shared<CButton>(
|
||||
Point(66 + 35 * i, 154),
|
||||
AnimationPath::builtin("quick-exchange/unitRight.DEF"),
|
||||
CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
|
||||
std::bind(&CExchangeController::moveStack, &controller, true, SlotID(i))));
|
||||
moveStackLeftButtons.back()->block(rightHeroBlock);
|
||||
}
|
||||
}
|
||||
|
||||
updateWidgets();
|
||||
}
|
||||
|
||||
void CExchangeWindow::moveArtifactsCallback(bool leftToRight)
|
||||
{
|
||||
bool moveEquipped = !GH.isKeyboardShiftDown();
|
||||
bool moveBackpack = !GH.isKeyboardCmdDown();
|
||||
controller.moveArtifacts(leftToRight, moveEquipped, moveBackpack);
|
||||
};
|
||||
|
||||
void CExchangeWindow::swapArtifactsCallback()
|
||||
{
|
||||
bool moveEquipped = !GH.isKeyboardShiftDown();
|
||||
bool moveBackpack = !GH.isKeyboardCmdDown();
|
||||
controller.swapArtifacts(moveEquipped, moveBackpack);
|
||||
}
|
||||
|
||||
void CExchangeWindow::moveUnitsShortcut(bool leftToRight)
|
||||
{
|
||||
std::optional<SlotID> slotId = std::nullopt;
|
||||
if(const auto * slot = getSelectedSlotID())
|
||||
slotId = slot->getSlot();
|
||||
controller.moveArmy(leftToRight, slotId);
|
||||
};
|
||||
|
||||
void CExchangeWindow::backpackShortcut(bool leftHero)
|
||||
{
|
||||
GH.windows().createAndPushWindow<CHeroBackpackWindow>(heroInst[leftHero ? 0 : 1], artSets);
|
||||
};
|
||||
|
||||
void CExchangeWindow::keyPressed(EShortcut key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case EShortcut::EXCHANGE_ARMY_TO_LEFT:
|
||||
moveUnitsShortcut(false);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_ARMY_TO_RIGHT:
|
||||
moveUnitsShortcut(true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_ARMY_SWAP:
|
||||
controller.swapArmy();
|
||||
break;
|
||||
case EShortcut::EXCHANGE_ARTIFACTS_TO_LEFT:
|
||||
controller.moveArtifacts(false, true, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_ARTIFACTS_TO_RIGHT:
|
||||
controller.moveArtifacts(true, true, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_ARTIFACTS_SWAP:
|
||||
controller.swapArtifacts(true, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_EQUIPPED_TO_LEFT:
|
||||
controller.moveArtifacts(false, true, false);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_EQUIPPED_TO_RIGHT:
|
||||
controller.moveArtifacts(true, true, false);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_EQUIPPED_SWAP:
|
||||
controller.swapArtifacts(true, false);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_BACKPACK_TO_LEFT:
|
||||
controller.moveArtifacts(false, false, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_BACKPACK_TO_RIGHT:
|
||||
controller.moveArtifacts(true, false, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_BACKPACK_SWAP:
|
||||
controller.swapArtifacts(false, true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_BACKPACK_LEFT:
|
||||
backpackShortcut(true);
|
||||
break;
|
||||
case EShortcut::EXCHANGE_BACKPACK_RIGHT:
|
||||
backpackShortcut(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const CGarrisonSlot * CExchangeWindow::getSelectedSlotID() const
|
||||
{
|
||||
return garr->getSelection();
|
||||
}
|
||||
|
||||
void CExchangeWindow::updateGarrisons()
|
||||
{
|
||||
garr->recreateSlots();
|
||||
|
||||
updateWidgets();
|
||||
}
|
||||
|
||||
bool CExchangeWindow::holdsGarrison(const CArmedInstance * army)
|
||||
{
|
||||
return garr->upperArmy() == army || garr->lowerArmy() == army;
|
||||
}
|
||||
|
||||
void CExchangeWindow::questLogShortcut()
|
||||
{
|
||||
CCS->curh->dragAndDropCursor(nullptr);
|
||||
LOCPLINT->showQuestLog();
|
||||
}
|
||||
|
||||
void CExchangeWindow::updateWidgets()
|
||||
{
|
||||
for(size_t leftRight : {0, 1})
|
||||
{
|
||||
const CGHeroInstance * hero = heroInst.at(leftRight);
|
||||
|
||||
for(int m=0; m<GameConstants::PRIMARY_SKILLS; ++m)
|
||||
{
|
||||
auto value = heroInst[leftRight]->getPrimSkillLevel(static_cast<PrimarySkill>(m));
|
||||
primSkillValues[leftRight][m]->setText(std::to_string(value));
|
||||
}
|
||||
|
||||
for(int m=0; m < hero->secSkills.size(); ++m)
|
||||
{
|
||||
int id = hero->secSkills[m].first;
|
||||
int level = hero->secSkills[m].second;
|
||||
|
||||
secSkillIcons[leftRight][m]->setFrame(2 + id * 3 + level);
|
||||
}
|
||||
|
||||
expValues[leftRight]->setText(TextOperations::formatMetric(hero->exp, 3));
|
||||
manaValues[leftRight]->setText(TextOperations::formatMetric(hero->mana, 3));
|
||||
|
||||
morale[leftRight]->set(hero);
|
||||
luck[leftRight]->set(hero);
|
||||
}
|
||||
}
|
||||
|
78
client/windows/CExchangeWindow.h
Normal file
78
client/windows/CExchangeWindow.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* CExchangeWindow.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CWindowWithArtifacts.h"
|
||||
#include "../widgets/CExchangeController.h"
|
||||
|
||||
class CGarrisonSlot;
|
||||
|
||||
class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public CWindowWithArtifacts
|
||||
{
|
||||
std::array<std::shared_ptr<CLabel>, 2> titles;
|
||||
std::vector<std::shared_ptr<CAnimImage>> primSkillImages;//shared for both heroes
|
||||
std::array<std::vector<std::shared_ptr<CLabel>>, 2> primSkillValues;
|
||||
std::array<std::vector<std::shared_ptr<CAnimImage>>, 2> secSkillIcons;
|
||||
std::array<std::shared_ptr<CAnimImage>, 2> specImages;
|
||||
std::array<std::shared_ptr<CAnimImage>, 2> expImages;
|
||||
std::array<std::shared_ptr<CLabel>, 2> expValues;
|
||||
std::array<std::shared_ptr<CAnimImage>, 2> manaImages;
|
||||
std::array<std::shared_ptr<CLabel>, 2> manaValues;
|
||||
|
||||
std::vector<std::shared_ptr<LRClickableAreaWTextComp>> primSkillAreas;
|
||||
std::array<std::vector<std::shared_ptr<LRClickableAreaWTextComp>>, 2> secSkillAreas;
|
||||
|
||||
std::array<std::shared_ptr<CHeroArea>, 2> heroAreas;
|
||||
std::array<std::shared_ptr<LRClickableAreaWText>, 2> specialtyAreas;
|
||||
std::array<std::shared_ptr<LRClickableAreaWText>, 2> experienceAreas;
|
||||
std::array<std::shared_ptr<LRClickableAreaWText>, 2> spellPointsAreas;
|
||||
|
||||
std::array<std::shared_ptr<MoraleLuckBox>, 2> morale;
|
||||
std::array<std::shared_ptr<MoraleLuckBox>, 2> luck;
|
||||
|
||||
std::shared_ptr<CButton> quit;
|
||||
std::array<std::shared_ptr<CButton>, 2> questlogButton;
|
||||
|
||||
std::shared_ptr<CGarrisonInt> garr;
|
||||
std::shared_ptr<CButton> moveAllGarrButtonLeft;
|
||||
std::shared_ptr<CButton> exchangeGarrButton;
|
||||
std::shared_ptr<CButton> moveAllGarrButtonRight;
|
||||
std::shared_ptr<CButton> moveArtifactsButtonLeft;
|
||||
std::shared_ptr<CButton> exchangeArtifactsButton;
|
||||
std::shared_ptr<CButton> moveArtifactsButtonRight;
|
||||
std::vector<std::shared_ptr<CButton>> moveStackLeftButtons;
|
||||
std::vector<std::shared_ptr<CButton>> moveStackRightButtons;
|
||||
std::shared_ptr<CButton> backpackButtonLeft;
|
||||
std::shared_ptr<CButton> backpackButtonRight;
|
||||
CExchangeController controller;
|
||||
|
||||
void moveArtifactsCallback(bool leftToRight);
|
||||
void swapArtifactsCallback();
|
||||
void moveUnitsShortcut(bool leftToRight);
|
||||
void backpackShortcut(bool leftHero);
|
||||
void questLogShortcut();
|
||||
|
||||
std::array<const CGHeroInstance *, 2> heroInst;
|
||||
std::array<std::shared_ptr<CArtifactsOfHeroMain>, 2> artifs;
|
||||
|
||||
const CGarrisonSlot * getSelectedSlotID() const;
|
||||
|
||||
public:
|
||||
CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID);
|
||||
|
||||
void keyPressed(EShortcut key) override;
|
||||
|
||||
void updateWidgets();
|
||||
|
||||
// IGarrisonHolder impl
|
||||
void updateGarrisons() override;
|
||||
bool holdsGarrison(const CArmedInstance * army) override;
|
||||
|
||||
};
|
@ -13,7 +13,7 @@
|
||||
#include "CCreatureWindow.h"
|
||||
#include "CHeroBackpackWindow.h"
|
||||
#include "CKingdomInterface.h"
|
||||
#include "GUIClasses.h"
|
||||
#include "CExchangeWindow.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
@ -22,6 +22,7 @@
|
||||
#include "../gui/TextAlignment.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/CGarrisonInt.h"
|
||||
@ -213,7 +214,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
|
||||
boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
|
||||
|
||||
garr = std::make_shared<CGarrisonInt>(Point(15, 485), 8, Point(), curHero);
|
||||
auto split = std::make_shared<CButton>(Point(539, 519), AnimationPath::builtin("hsbtns9.def"), CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&](){ garr->splitClick(); });
|
||||
auto split = std::make_shared<CButton>(Point(539, 519), AnimationPath::builtin("hsbtns9.def"), CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&](){ garr->splitClick(); }, EShortcut::HERO_ARMY_SPLIT);
|
||||
garr->addSplitBtn(split);
|
||||
}
|
||||
if(!arts)
|
||||
@ -281,9 +282,8 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
|
||||
|
||||
for(auto cew : GH.windows().findWindows<CExchangeWindow>())
|
||||
{
|
||||
for(int g=0; g < cew->heroInst.size(); ++g)
|
||||
if(cew->heroInst[g] == curHero)
|
||||
noDismiss = true;
|
||||
if (cew->holdsGarrison(curHero))
|
||||
noDismiss = true;
|
||||
}
|
||||
|
||||
//if player only have one hero and no towns
|
||||
|
@ -866,7 +866,7 @@ class ArtSlotsTab : public CIntObject
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<CAnimImage> background;
|
||||
std::vector<std::shared_ptr<CHeroArtPlace>> arts;
|
||||
std::vector<std::shared_ptr<CArtPlace>> arts;
|
||||
|
||||
ArtSlotsTab()
|
||||
{
|
||||
@ -874,7 +874,7 @@ public:
|
||||
background = std::make_shared<CAnimImage>(AnimationPath::builtin("OVSLOT"), 4);
|
||||
pos = background->pos;
|
||||
for(int i=0; i<9; i++)
|
||||
arts.push_back(std::make_shared<CHeroArtPlace>(Point(269+i*48, 66)));
|
||||
arts.push_back(std::make_shared<CArtPlace>(Point(269+i*48, 66)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -882,7 +882,7 @@ class BackpackTab : public CIntObject
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<CAnimImage> background;
|
||||
std::vector<std::shared_ptr<CHeroArtPlace>> arts;
|
||||
std::vector<std::shared_ptr<CArtPlace>> arts;
|
||||
std::shared_ptr<CButton> btnLeft;
|
||||
std::shared_ptr<CButton> btnRight;
|
||||
|
||||
@ -894,7 +894,7 @@ public:
|
||||
btnLeft = std::make_shared<CButton>(Point(269, 66), AnimationPath::builtin("HSBTNS3"), CButton::tooltip(), 0);
|
||||
btnRight = std::make_shared<CButton>(Point(675, 66), AnimationPath::builtin("HSBTNS5"), CButton::tooltip(), 0);
|
||||
for(int i=0; i<8; i++)
|
||||
arts.push_back(std::make_shared<CHeroArtPlace>(Point(294+i*48, 66)));
|
||||
arts.push_back(std::make_shared<CArtPlace>(Point(294+i*48, 66)));
|
||||
}
|
||||
};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user