1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-04 23:17:41 +02:00

Merge branch 'vcmi/master' into 'vcmi/develop'

This commit is contained in:
Ivan Savenko 2024-05-31 09:34:21 +00:00
commit df83fa33a1
166 changed files with 2893 additions and 1363 deletions

View File

@ -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: |

View File

@ -21,7 +21,6 @@
#include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h"
#include "Engine/Nullkiller.h"

View File

@ -15,233 +15,15 @@
#include "../Goals/Composition.h"
#include "../Goals/ExecuteHeroChain.h"
#include "../Markers/ExplorationPoint.h"
#include "CaptureObjectsBehavior.h"
#include "../Goals/CaptureObject.h"
#include "../../../lib/CPlayerState.h"
#include "../Goals/ExploreNeighbourTile.h"
#include "../Helpers/ExplorationHelper.h"
namespace NKAI
{
using namespace Goals;
struct ExplorationHelper
{
const CGHeroInstance * hero;
int sightRadius;
float bestValue;
TSubgoal bestGoal;
int3 bestTile;
int bestTilesDiscovered;
const Nullkiller * ai;
CCallback * cbp;
const TeamState * ts;
int3 ourPos;
bool allowDeadEndCancellation;
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 makeComposition() const
{
Composition c;
c.addNext(ExplorationPoint(bestTile, bestTilesDiscovered));
c.addNext(bestGoal);
return sptr(c);
}
void 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);
}
}
}
}
void 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;
}
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);
}
}
void scanTile(const int3 & tile)
{
if(tile == ourPos
|| !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 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 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 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;
}
};
std::string ExplorationBehavior::toString() const
{
return "Explore";
@ -301,17 +83,13 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
{
ExplorationHelper scanResult(hero, ai);
scanResult.scanSector(1);
if(!scanResult.bestGoal->invalid())
if(scanResult.scanSector(1))
{
tasks.push_back(scanResult.makeComposition());
continue;
}
scanResult.scanSector(15);
if(!scanResult.bestGoal->invalid())
if(scanResult.scanSector(15))
{
tasks.push_back(scanResult.makeComposition());
continue;
@ -319,9 +97,7 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
if(ai->getScanDepth() == ScanDepth::ALL_FULL)
{
scanResult.scanMap();
if(!scanResult.bestGoal->invalid())
if(scanResult.scanMap())
{
tasks.push_back(scanResult.makeComposition());
}

View File

@ -40,6 +40,7 @@ set(Nullkiller_SRCS
Goals/ExchangeSwapTownHeroes.cpp
Goals/CompleteQuest.cpp
Goals/StayAtTown.cpp
Goals/ExploreNeighbourTile.cpp
Markers/ArmyUpgrade.cpp
Markers/HeroExchange.cpp
Markers/UnlockCluster.cpp
@ -62,6 +63,7 @@ set(Nullkiller_SRCS
Behaviors/StayAtTownBehavior.cpp
Behaviors/ExplorationBehavior.cpp
Helpers/ArmyFormation.cpp
Helpers/ExplorationHelper.cpp
AIGateway.cpp
)
@ -113,6 +115,7 @@ set(Nullkiller_HEADERS
Goals/CompleteQuest.h
Goals/Goals.h
Goals/StayAtTown.h
Goals/ExploreNeighbourTile.h
Markers/ArmyUpgrade.h
Markers/HeroExchange.h
Markers/UnlockCluster.h
@ -135,6 +138,7 @@ set(Nullkiller_HEADERS
Behaviors/StayAtTownBehavior.h
Behaviors/ExplorationBehavior.h
Helpers/ArmyFormation.h
Helpers/ExplorationHelper.h
AIGateway.h
)

View File

@ -75,7 +75,8 @@ namespace Goals
STAY_AT_TOWN_BEHAVIOR,
STAY_AT_TOWN,
EXPLORATION_BEHAVIOR,
EXPLORATION_POINT
EXPLORATION_POINT,
EXPLORE_NEIGHBOUR_TILE
};
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>

View File

@ -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
}
@ -64,15 +65,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)
{
}

View 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();
}
}

View 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;
};
}
}

View 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;
}
}

View 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;
};
}

View File

@ -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
}

View File

@ -23,7 +23,6 @@
#include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h"
VCMI_LIB_NAMESPACE_BEGIN

View File

@ -1,5 +1,6 @@
#!/bin/sh
sudo apt remove needrestart
sudo apt-get update
# Dependencies

View File

@ -1,5 +1,6 @@
#!/bin/sh
sudo apt remove needrestart
sudo apt-get update
# Dependencies

View File

@ -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

View File

@ -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 死于精准射击",

View File

@ -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ł!",

View File

@ -435,6 +435,8 @@ static void mainLoop()
[[noreturn]] static void quitApplication()
{
CSH->endNetwork();
if(!settings["session"]["headless"].Bool())
{
if(CSH->client)
@ -443,6 +445,8 @@ static void mainLoop()
GH.windows().clear();
}
vstd::clear_pointer(CSH);
CMM.reset();
if(!settings["session"]["headless"].Bool())
@ -463,7 +467,6 @@ static void mainLoop()
vstd::clear_pointer(graphics);
}
vstd::clear_pointer(CSH);
vstd::clear_pointer(VLC);
// sometimes leads to a hang. TODO: investigate

View File

@ -144,6 +144,7 @@ set(client_SRCS
windows/CCastleInterface.cpp
windows/CCreatureWindow.cpp
windows/CExchangeWindow.cpp
windows/CHeroOverview.cpp
windows/CHeroWindow.cpp
windows/CKingdomInterface.cpp
@ -346,6 +347,7 @@ set(client_HEADERS
windows/CCastleInterface.h
windows/CCreatureWindow.h
windows/CExchangeWindow.h
windows/CHeroOverview.h
windows/CHeroWindow.h
windows/CKingdomInterface.h
@ -377,6 +379,7 @@ set(client_HEADERS
Client.h
ClientCommandManager.h
ClientNetPackVisitors.h
ConditionalWait.h
HeroMovementController.h
GameChatHandler.h
LobbyClientNetPackVisitors.h

View File

@ -53,6 +53,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"
@ -74,7 +75,6 @@
#include "../lib/CStopWatch.h"
#include "../lib/CThreadHelper.h"
#include "../lib/CTownHandler.h"
#include "../lib/CondSh.h"
#include "../lib/GameConstants.h"
#include "../lib/RoadHandler.h"
#include "../lib/StartInfo.h"
@ -139,10 +139,10 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
LOCPLINT = this;
playerID=Player;
human=true;
battleInt = nullptr;
battleInt.reset();
castleInt = nullptr;
makingTurn = false;
showingDialog = new CondSh<bool>(false);
showingDialog = new ConditionalWait();
cingconsole = new CInGameConsole();
autosaveCount = 0;
isAutoFightOn = false;
@ -1006,7 +1006,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
{
CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
showingDialog->set(true);
showingDialog->setBusy();
movementController->requestMovementAbort(); // interrupt movement to show dialog
GH.windows().pushWindow(temp);
}
@ -1029,7 +1029,7 @@ void CPlayerInterface::showInfoDialogAndWait(std::vector<Component> & components
void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, const std::vector<std::shared_ptr<CComponent>> & components)
{
movementController->requestMovementAbort();
LOCPLINT->showingDialog->setn(true);
LOCPLINT->showingDialog->setBusy();
CInfoWindow::showYesNoDialog(text, components, onYes, onNo, playerID);
}
@ -1205,7 +1205,7 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
{
assert(h);
assert(!showingDialog->get());
assert(!showingDialog->isBusy());
assert(dialogs.empty());
LOG_TRACE(logGlobal);
@ -1215,7 +1215,7 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
return; //can't find hero
//It shouldn't be possible to move hero with open dialog (or dialog waiting in bg)
if (showingDialog->get() || !dialogs.empty())
if (showingDialog->isBusy() || !dialogs.empty())
return;
if (localState->isHeroSleeping(h))
@ -1383,9 +1383,7 @@ void CPlayerInterface::waitWhileDialog()
}
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
boost::unique_lock<boost::mutex> un(showingDialog->mx);
while(showingDialog->data)
showingDialog->cond.wait(un);
showingDialog->waitWhileBusy();
}
void CPlayerInterface::showShipyardDialog(const IShipyard *obj)
@ -1490,9 +1488,9 @@ void CPlayerInterface::update()
return;
//if there are any waiting dialogs, show them
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy())
{
showingDialog->set(true);
showingDialog->setBusy();
GH.windows().pushWindow(dialogs.front());
dialogs.pop_front();
}
@ -1504,6 +1502,11 @@ void CPlayerInterface::update()
GH.windows().simpleRedraw();
}
void CPlayerInterface::endNetwork()
{
showingDialog->requestTermination();
}
int CPlayerInterface::getLastIndex( std::string namePrefix)
{
using namespace boost::filesystem;

View File

@ -25,7 +25,7 @@ struct CGPath;
class CCreatureSet;
class CGObjectInstance;
struct UpgradeInfo;
template <typename T> struct CondSh;
class ConditionalWait;
struct CPathsInfo;
VCMI_LIB_NAMESPACE_END
@ -72,7 +72,7 @@ public: // TODO: make private
std::unique_ptr<PlayerLocalState> localState;
//minor interfaces
CondSh<bool> *showingDialog; //indicates if dialog box is displayed
ConditionalWait * showingDialog; //indicates if dialog box is displayed
bool makingTurn; //if player is already making his turn
@ -198,6 +198,7 @@ public: // public interface for use by client via LOCPLINT access
void proposeLoadingGame();
void performAutosave();
void gamePause(bool pause);
void endNetwork();
///returns true if all events are processed internally
bool capturedAllEvents();

View File

@ -29,6 +29,7 @@
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "ConditionalWait.h"
#include "../lib/CThreadHelper.h"
#include "../lib/StartInfo.h"
#include "../lib/TurnTimerInfo.h"
@ -131,6 +132,17 @@ CServerHandler::~CServerHandler()
}
}
void CServerHandler::endNetwork()
{
if (client)
client->endNetwork();
networkHandler->stop();
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
threadNetwork.join();
}
}
CServerHandler::CServerHandler()
: networkHandler(INetworkHandler::createHandler())
, lobbyClient(std::make_unique<GlobalLobbyClient>())
@ -158,7 +170,14 @@ void CServerHandler::threadRunNetwork()
{
logGlobal->info("Starting network thread");
setThreadName("runNetwork");
networkHandler->run();
try {
networkHandler->run();
}
catch (const TerminationRequestedException & e)
{
logGlobal->info("Terminating network thread");
return;
}
logGlobal->info("Ending network thread");
}

View File

@ -13,7 +13,6 @@
#include "../lib/network/NetworkInterface.h"
#include "../lib/StartInfo.h"
#include "../lib/CondSh.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -208,6 +207,7 @@ public:
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
void showHighScoresAndEndGameplay(PlayerColor player, bool victory);
void endNetwork();
void endGameplay();
void restartGameplay();
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});

View File

@ -218,6 +218,22 @@ void CClient::save(const std::string & fname)
sendRequest(&save_game, PlayerColor::NEUTRAL);
}
void CClient::endNetwork()
{
if (CGI->mh)
CGI->mh->endNetwork();
if (CPlayerInterface::battleInt)
CPlayerInterface::battleInt->endNetwork();
for(auto & i : playerint)
{
auto interface = std::dynamic_pointer_cast<CPlayerInterface>(i.second);
if (interface)
interface->endNetwork();
}
}
void CClient::endGame()
{
#if SCRIPTING_ENABLED

View File

@ -133,6 +133,7 @@ public:
void loadGame(CGameState * gameState);
void save(const std::string & fname);
void endNetwork();
void endGame();
void initMapHandler();

View File

@ -179,11 +179,6 @@ void ClientCommandManager::handleRedrawCommand()
GH.windows().totalRedraw();
}
void ClientCommandManager::handleNotDialogCommand()
{
LOCPLINT->showingDialog->setn(false);
}
void ClientCommandManager::handleTranslateGameCommand()
{
std::map<std::string, std::map<std::string, std::string>> textsByMod;
@ -584,9 +579,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
else if(commandName == "redraw")
handleRedrawCommand();
else if(commandName == "not dialog")
handleNotDialogCommand();
else if(message=="translate" || message=="translate game")
handleTranslateGameCommand();

View File

@ -45,9 +45,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
// Redraw the current screen
void handleRedrawCommand();
// Set the state indicating if dialog box is active to "no"
void handleNotDialogCommand();
// Extracts all translateable game texts into Translation directory, separating files on per-mod basis
void handleTranslateGameCommand();

77
client/ConditionalWait.h Normal file
View File

@ -0,0 +1,77 @@
/*
* ConditionalWait.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 <condition_variable>
VCMI_LIB_NAMESPACE_BEGIN
class TerminationRequestedException : public std::exception
{
public:
using exception::exception;
const char* what() const noexcept override
{
return "Thread termination requested";
}
};
class ConditionalWait
{
bool isBusyValue = false;
bool isTerminating = false;
std::condition_variable cond;
std::mutex mx;
void set(bool value)
{
boost::unique_lock<std::mutex> lock(mx);
isBusyValue = value;
}
public:
ConditionalWait() = default;
void setBusy()
{
set(true);
}
void setFree()
{
set(false);
cond.notify_all();
}
void requestTermination()
{
isTerminating = true;
setFree();
}
bool isBusy()
{
std::unique_lock<std::mutex> lock(mx);
return isBusyValue;
}
void waitWhileBusy()
{
std::unique_lock<std::mutex> un(mx);
while(isBusyValue)
cond.wait(un);
if (isTerminating)
throw TerminationRequestedException();
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -22,7 +22,7 @@
#include "../CCallback.h"
#include "../lib/CondSh.h"
#include "ConditionalWait.h"
#include "../lib/CConfigHandler.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/mapObjects/CGHeroInstance.h"
@ -238,7 +238,7 @@ void HeroMovementController::onMoveHeroApplied()
assert(currentlyMovingHero);
const auto * hero = currentlyMovingHero;
bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->get();
bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->isBusy();
bool wantStop = stoppingMovement;
bool canStop = !canMove || canHeroStopAtNode(LOCPLINT->localState->getPath(hero).currNode());

View File

@ -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)
{

View File

@ -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()

View File

@ -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);

View File

@ -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()

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -39,7 +39,6 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CondSh.h"
#include "../../lib/gameState/InfoAboutArmy.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h"
@ -96,7 +95,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
obstacleController.reset(new BattleObstacleController(*this));
adventureInt->onAudioPaused();
ongoingAnimationsState.set(true);
ongoingAnimationsState.setBusy();
GH.windows().pushWindow(windowObject);
windowObject->blockUI(true);
@ -341,7 +340,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)
@ -752,6 +751,11 @@ void BattleInterface::castThisSpell(SpellID spellID)
actionsController->castThisSpell(spellID);
}
void BattleInterface::endNetwork()
{
ongoingAnimationsState.requestTermination();
}
void BattleInterface::executeStagedAnimations()
{
EAnimationEvents earliestStage = EAnimationEvents::COUNT;
@ -783,19 +787,19 @@ void BattleInterface::executeAnimationStage(EAnimationEvents event)
void BattleInterface::onAnimationsStarted()
{
ongoingAnimationsState.setn(true);
ongoingAnimationsState.setBusy();
}
void BattleInterface::onAnimationsFinished()
{
ongoingAnimationsState.setn(false);
ongoingAnimationsState.setFree();
}
void BattleInterface::waitForAnimations()
{
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
ongoingAnimationsState.waitUntil(false);
ongoingAnimationsState.waitWhileBusy();
}
assert(!hasAnimations());
@ -810,7 +814,7 @@ void BattleInterface::waitForAnimations()
bool BattleInterface::hasAnimations()
{
return ongoingAnimationsState.get();
return ongoingAnimationsState.isBusy();
}
void BattleInterface::checkForAnimations()

View File

@ -12,7 +12,7 @@
#include "BattleConstants.h"
#include "../gui/CIntObject.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
#include "../../lib/CondSh.h"
#include "../ConditionalWait.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -99,7 +99,7 @@ class BattleInterface
};
/// Conditional variables that are set depending on ongoing animations on the battlefield
CondSh<bool> ongoingAnimationsState;
ConditionalWait ongoingAnimationsState;
/// List of events that are waiting to be triggered
std::vector<AwaitingAnimationEvents> awaitingEvents;
@ -186,6 +186,7 @@ public:
void setBattleQueueVisibility(bool visible);
void setStickyHeroWindowsVisibility(bool visible);
void endNetwork();
void executeStagedAnimations();
void executeAnimationStage( EAnimationEvents event);
void onAnimationsStarted();

View File

@ -52,7 +52,6 @@
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/CondSh.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/TextOperations.h"
@ -83,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;
@ -801,7 +807,7 @@ BattleResultResources BattleResultWindow::getResources(const BattleResult & br)
void BattleResultWindow::activate()
{
owner.showingDialog->set(true);
owner.showingDialog->setBusy();
CIntObject::activate();
}
@ -819,7 +825,7 @@ void BattleResultWindow::buttonPressed(int button)
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
//so we can be sure that there is no dialogs left on GUI stack.
intTmp.showingDialog->setn(false);
intTmp.showingDialog->setFree();
}
void BattleResultWindow::bExitf()

View File

@ -39,7 +39,6 @@
#include "../../lib/battle/BattleHex.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/CStack.h"
#include "../../lib/CondSh.h"
#include "../../lib/TextOperations.h"
static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnimation> anim)

View File

@ -26,6 +26,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"
@ -76,6 +77,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();});
@ -188,11 +191,6 @@ void BattleWindow::createTimerInfoWindows()
}
}
BattleWindow::~BattleWindow()
{
CPlayerInterface::battleInt = nullptr;
}
std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
{
auto rect = readRect(config["rect"]);
@ -754,6 +752,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);
@ -768,6 +768,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();

View File

@ -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();

View File

@ -165,6 +165,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;
@ -176,16 +177,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)
{

View File

@ -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"];

View File

@ -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();
}

View File

@ -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);

View File

@ -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);

View File

@ -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));

View File

@ -9,7 +9,6 @@
*/
#include "StdInc.h"
#include "CGuiHandler.h"
#include "../lib/CondSh.h"
#include "CIntObject.h"
#include "CursorHandler.h"

View File

@ -10,7 +10,6 @@
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
template <typename T> struct CondSh;
class Point;
class Rect;
VCMI_LIB_NAMESPACE_END

View File

@ -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;

View File

@ -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;
};

View File

@ -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,

View File

@ -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;

View File

@ -70,9 +70,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());
@ -103,8 +112,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())
@ -124,7 +136,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);
}
}

View File

@ -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)

View File

@ -32,6 +32,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"
@ -142,8 +143,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);
@ -417,7 +418,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](){
@ -425,7 +426,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](){
@ -433,7 +434,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);
}
@ -444,7 +445,7 @@ TownSelector::TownSelector(const Point & loc)
setRedrawParent(true);
int count = 0;
for(auto const & factionID : VLC->townh->getDefaultAllowed())
for(auto const & factionID : CGI->townh->getDefaultAllowed())
{
townsEnabled[factionID] = true;
count++;

View File

@ -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);

View File

@ -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();

View File

@ -24,6 +24,7 @@
#include "../windows/InfoWindows.h"
#include "../widgets/TextControls.h"
#include "../render/Canvas.h"
#include "../render/IRenderHandler.h"
#include "../CGameInfo.h"
#include "../../lib/CGeneralTextHandler.h"
@ -82,7 +83,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();
@ -110,10 +112,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()
@ -223,8 +225,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)
@ -341,7 +343,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);
}

View File

@ -16,6 +16,7 @@ class CMultiLineLabel;
class CAnimImage;
class CTextInput;
class VideoWidgetBase;
class CFilledTexture;
class TransparentFilledRectangle;
@ -62,6 +63,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;
@ -95,6 +97,7 @@ class CHighScoreInputScreen : public CWindowObject
std::shared_ptr<CHighScoreInput> input;
std::shared_ptr<TransparentFilledRectangle> background;
std::shared_ptr<VideoWidgetBase> videoPlayer;
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
bool won;
HighScoreCalculation calc;

View File

@ -58,7 +58,6 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/GameConstants.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/CondSh.h"
std::shared_ptr<CMainMenu> CMM;
ISelectionScreenInfo * SEL;
@ -265,7 +264,8 @@ CMainMenuConfig::CMainMenuConfig()
: campaignSets(JsonPath::builtin("config/campaignSets.json"))
, config(JsonPath::builtin("config/mainmenu.json"))
{
if (config["game-select"].Vector().empty())
handleFatalError("Main menu config is invalid or corrupted. Please disable any mods or reinstall VCMI", false);
}
const CMainMenuConfig & CMainMenuConfig::get()
@ -447,11 +447,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);
}

View File

@ -19,6 +19,7 @@
//#include "../gui/FramerateManager.h"
#include "../widgets/TextControls.h"
#include "../widgets/VideoWidget.h"
#include "../widgets/Images.h"
#include "../render/Canvas.h"
CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
@ -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));
//TODO: remove hardcoded paths. Some of campaigns video actually consist from 2 parts
// however, currently our campaigns format expects only a single video file

View File

@ -14,6 +14,7 @@
class CMultiLineLabel;
class VideoWidget;
class CFilledTexture;
class CPrologEpilogVideo : public CWindowObject
{
@ -27,6 +28,7 @@ class CPrologEpilogVideo : public CWindowObject
std::shared_ptr<CMultiLineLabel> text;
std::shared_ptr<VideoWidget> videoPlayer;
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
bool voiceStopped = false;

View File

@ -25,6 +25,8 @@ public:
virtual ~IMapObjectObserver();
virtual bool hasOngoingAnimations() = 0;
virtual void waitForOngoingAnimations(){};
virtual void endNetwork(){};
/// Plays fade-in animation and adds object to map
virtual void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) = 0;

View File

@ -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);

View File

@ -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()

View File

@ -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();

View File

@ -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;
}

View File

@ -25,6 +25,7 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/pathfinder/CGPathNode.h"
@ -88,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
@ -117,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);
@ -346,6 +350,7 @@ bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 &
void MapViewController::fadeOutObject(const CGObjectInstance * obj)
{
animationWait.setBusy();
logGlobal->debug("Starting fade out animation");
fadingOutContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
fadingOutContext->animationTime = adventureContext->animationTime;
@ -366,6 +371,7 @@ void MapViewController::fadeOutObject(const CGObjectInstance * obj)
void MapViewController::fadeInObject(const CGObjectInstance * obj)
{
animationWait.setBusy();
logGlobal->debug("Starting fade in animation");
fadingInContext = std::make_shared<MapRendererAdventureFadingContext>(*state);
fadingInContext->animationTime = adventureContext->animationTime;
@ -505,6 +511,7 @@ void MapViewController::onAfterHeroTeleported(const CGHeroInstance * obj, const
if(isEventVisible(obj, from, dest))
{
animationWait.setBusy();
logGlobal->debug("Starting teleport animation");
teleportContext = std::make_shared<MapRendererAdventureTransitionContext>(*state);
teleportContext->animationTime = adventureContext->animationTime;
@ -540,6 +547,7 @@ void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & fro
if(movementTime > 1)
{
animationWait.setBusy();
logGlobal->debug("Starting movement animation");
movementContext = std::make_shared<MapRendererAdventureMovingContext>(*state);
movementContext->animationTime = adventureContext->animationTime;
@ -577,6 +585,17 @@ bool MapViewController::hasOngoingAnimations()
return false;
}
void MapViewController::waitForOngoingAnimations()
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
animationWait.waitWhileBusy();
}
void MapViewController::endNetwork()
{
animationWait.requestTermination();
}
void MapViewController::activateAdventureContext(uint32_t animationTime)
{
resetContext();
@ -642,6 +661,7 @@ void MapViewController::resetContext()
worldViewContext.reset();
spellViewContext.reset();
puzzleMapContext.reset();
animationWait.setFree();
}
void MapViewController::setTerrainVisibility(bool showAllTerrain)

View File

@ -10,6 +10,7 @@
#pragma once
#include "IMapRendererObserver.h"
#include "../ConditionalWait.h"
#include "../../lib/Point.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -34,6 +35,8 @@ class MapRendererPuzzleMapContext;
/// such as its position and any animations
class MapViewController : public IMapObjectObserver
{
ConditionalWait animationWait;
std::shared_ptr<IMapRendererContext> context;
std::shared_ptr<MapRendererContextState> state;
std::shared_ptr<MapViewModel> model;
@ -52,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;
@ -68,6 +71,9 @@ private:
// IMapObjectObserver impl
bool hasOngoingAnimations() override;
void waitForOngoingAnimations() override;
void endNetwork() override;
void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) override;
void onObjectFadeOut(const CGObjectInstance * obj, const PlayerColor & initiator) override;
void onObjectInstantAdd(const CGObjectInstance * obj, const PlayerColor & initiator) override;
@ -91,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();

View File

@ -19,7 +19,6 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/ObjectTemplate.h"
@ -36,13 +35,19 @@ bool CMapHandler::hasOngoingAnimations()
void CMapHandler::waitForOngoingAnimations()
{
while(CGI->mh->hasOngoingAnimations())
for(auto * observer : observers)
{
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
if (observer->hasOngoingAnimations())
observer->waitForOngoingAnimations();
}
}
void CMapHandler::endNetwork()
{
for(auto * observer : observers)
observer->endNetwork();
}
std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const
{
const TerrainTile & t = map->getTile(pos);

View File

@ -71,6 +71,7 @@ public:
/// blocking wait until all ongoing animatins are over
void waitForOngoingAnimations();
void endNetwork();
static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b);
};

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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));

View File

@ -55,12 +55,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)
@ -256,21 +256,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
{

View File

@ -18,7 +18,7 @@ class CButton;
class CArtifactsOfHeroBase : virtual public CIntObject, public CKeyShortcut
{
protected:
using ArtPlacePtr = std::shared_ptr<CHeroArtPlace>;
using ArtPlacePtr = std::shared_ptr<CArtPlace>;
using BpackScrollFunctor = std::function<void(int)>;
public:

View File

@ -33,49 +33,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;
}

View File

@ -20,17 +20,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
};
};

View File

@ -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"

View File

@ -12,6 +12,7 @@
#include "CAltarArtifacts.h"
#include "../../gui/CGuiHandler.h"
#include "../../gui/Shortcut.h"
#include "../../widgets/Buttons.h"
#include "../../widgets/TextControls.h"
@ -35,16 +36,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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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);

View File

@ -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);

View File

@ -1036,6 +1036,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),
@ -1216,7 +1250,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);
@ -1225,8 +1259,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);
@ -1327,27 +1361,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))
@ -1368,18 +1389,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();

View File

@ -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);

View File

@ -0,0 +1,383 @@
/*
* 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]->clickPressedCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);};
artifs[0]->showPopupCallback = [this, heroArts = artifs[0]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);};
artifs[0]->gestureCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);};
artifs[0]->setHero(heroInst[0]);
artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 151));
artifs[1]->clickPressedCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);};
artifs[1]->showPopupCallback = [this, heroArts = artifs[1]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);};
artifs[1]->gestureCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);};
artifs[1]->setHero(heroInst[1]);
addSet(artifs[0]);
addSet(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);
}
}
CWindowWithArtifacts::update();
}
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();
update();
}
bool CExchangeWindow::holdsGarrison(const CArmedInstance * army)
{
return garr->upperArmy() == army || garr->lowerArmy() == army;
}
void CExchangeWindow::questLogShortcut()
{
CCS->curh->dragAndDropCursor(nullptr);
LOCPLINT->showQuestLog();
}
void CExchangeWindow::update()
{
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);
}
}

View 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 update() override;
// IGarrisonHolder impl
void updateGarrisons() override;
bool holdsGarrison(const CArmedInstance * army) override;
};

View File

@ -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"
@ -207,7 +208,7 @@ void CHeroWindow::update()
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)
@ -278,9 +279,8 @@ void CHeroWindow::update()
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

View File

@ -878,7 +878,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()
{
@ -886,7 +886,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)));
}
};
@ -894,7 +894,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;
@ -906,7 +906,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)));
}
};

View File

@ -128,35 +128,35 @@ void CMarketWindow::createChangeModeButtons(EMarketMode currentMode, const IMark
auto buttonPos = Point(18, 520);
auto addButton = [this, &buttonPos](const AnimationPath & picPath, const std::pair<std::string, std::string> & buttonHelpContainer,
const std::function<void()> & pressButtonFunctor)
const std::function<void()> & pressButtonFunctor, EShortcut shortcut)
{
changeModeButtons.emplace_back(std::make_shared<CButton>(buttonPos, picPath, buttonHelpContainer, pressButtonFunctor));
changeModeButtons.emplace_back(std::make_shared<CButton>(buttonPos, picPath, buttonHelpContainer, pressButtonFunctor, shortcut));
buttonPos -= Point(0, buttonHeightWithMargin);
};
if(isButtonVisible(EMarketMode::RESOURCE_PLAYER))
addButton(AnimationPath::builtin("TPMRKBU1.DEF"), CGI->generaltexth->zelp[612], std::bind(&CMarketWindow::createTransferResources, this, market, hero));
addButton(AnimationPath::builtin("TPMRKBU1.DEF"), CGI->generaltexth->zelp[612], std::bind(&CMarketWindow::createTransferResources, this, market, hero), EShortcut::MARKET_RESOURCE_PLAYER);
if(isButtonVisible(EMarketMode::ARTIFACT_RESOURCE))
addButton(AnimationPath::builtin("TPMRKBU3.DEF"), CGI->generaltexth->zelp[613], std::bind(&CMarketWindow::createArtifactsSelling, this, market, hero));
addButton(AnimationPath::builtin("TPMRKBU3.DEF"), CGI->generaltexth->zelp[613], std::bind(&CMarketWindow::createArtifactsSelling, this, market, hero), EShortcut::MARKET_ARTIFACT_RESOURCE);
if(isButtonVisible(EMarketMode::RESOURCE_ARTIFACT))
addButton(AnimationPath::builtin("TPMRKBU2.DEF"), CGI->generaltexth->zelp[598], std::bind(&CMarketWindow::createArtifactsBuying, this, market, hero));
addButton(AnimationPath::builtin("TPMRKBU2.DEF"), CGI->generaltexth->zelp[598], std::bind(&CMarketWindow::createArtifactsBuying, this, market, hero), EShortcut::MARKET_RESOURCE_ARTIFACT);
buttonPos = Point(516, 520 - buttonHeightWithMargin);
if(isButtonVisible(EMarketMode::CREATURE_RESOURCE))
addButton(AnimationPath::builtin("TPMRKBU4.DEF"), CGI->generaltexth->zelp[599], std::bind(&CMarketWindow::createFreelancersGuild, this, market, hero));
addButton(AnimationPath::builtin("TPMRKBU4.DEF"), CGI->generaltexth->zelp[599], std::bind(&CMarketWindow::createFreelancersGuild, this, market, hero), EShortcut::MARKET_CREATURE_RESOURCE);
if(isButtonVisible(EMarketMode::RESOURCE_RESOURCE))
addButton(AnimationPath::builtin("TPMRKBU5.DEF"), CGI->generaltexth->zelp[605], std::bind(&CMarketWindow::createMarketResources, this, market, hero));
addButton(AnimationPath::builtin("TPMRKBU5.DEF"), CGI->generaltexth->zelp[605], std::bind(&CMarketWindow::createMarketResources, this, market, hero), EShortcut::MARKET_RESOURCE_RESOURCE);
buttonPos = Point(516, 421);
if(isButtonVisible(EMarketMode::CREATURE_EXP))
{
addButton(AnimationPath::builtin("ALTSACC.DEF"), CGI->generaltexth->zelp[572], std::bind(&CMarketWindow::createAltarCreatures, this, market, hero));
addButton(AnimationPath::builtin("ALTSACC.DEF"), CGI->generaltexth->zelp[572], std::bind(&CMarketWindow::createAltarCreatures, this, market, hero), EShortcut::MARKET_CREATURE_EXPERIENCE);
if(marketWidget->hero->getAlignment() == EAlignment::GOOD)
changeModeButtons.back()->block(true);
}
if(isButtonVisible(EMarketMode::ARTIFACT_EXP))
{
addButton(AnimationPath::builtin("ALTART.DEF"), CGI->generaltexth->zelp[580], std::bind(&CMarketWindow::createAltarArtifacts, this, market, hero));
addButton(AnimationPath::builtin("ALTART.DEF"), CGI->generaltexth->zelp[580], std::bind(&CMarketWindow::createAltarArtifacts, this, market, hero), EShortcut::MARKET_ARTIFACT_EXPERIENCE);
if(marketWidget->hero->getAlignment() == EAlignment::EVIL)
changeModeButtons.back()->block(true);
}

View File

@ -128,7 +128,7 @@ CQuestLog::CQuestLog (const std::vector<QuestInfo> & Quests)
minimap = std::make_shared<CQuestMinimap>(Rect(12, 12, 169, 169));
// TextBox have it's own 4 pixel padding from top at least for English. To achieve 10px from both left and top only add 6px margin
description = std::make_shared<CTextBox>("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE);
ok = std::make_shared<CButton>(Point(539, 398), AnimationPath::builtin("IOKAY.DEF"), CGI->generaltexth->zelp[445], std::bind(&CQuestLog::close, this), EShortcut::GLOBAL_ACCEPT);
ok = std::make_shared<CButton>(Point(539, 398), AnimationPath::builtin("IOKAY.DEF"), CGI->generaltexth->zelp[445], std::bind(&CQuestLog::close, this), EShortcut::GLOBAL_RETURN);
// Both button and lable are shifted to -2px by x and y to not make them actually look like they're on same line with quests list and ok button
hideCompleteButton = std::make_shared<CToggleButton>(Point(10, 396), AnimationPath::builtin("sysopchk.def"), CButton::tooltipLocalized("vcmi.questLog.hideComplete"), std::bind(&CQuestLog::toggleComplete, this, _1));
hideCompleteLabel = std::make_shared<CLabel>(46, 398, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.questLog.hideComplete.hover"));

View File

@ -12,7 +12,7 @@
#include "../eventsSDL/InputHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CondSh.h"
#include "../ConditionalWait.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
@ -45,7 +45,7 @@ CTutorialWindow::CTutorialWindow(const TutorialMode & m)
labelTitle = std::make_shared<CLabel>(190, 15, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.tutorialWindow.title"));
labelInformation = std::make_shared<CMultiLineLabel>(Rect(5, 40, 370, 60), EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, "");
buttonOk = std::make_shared<CButton>(Point(159, 367), AnimationPath::builtin("IOKAY"), CButton::tooltip(), std::bind(&CTutorialWindow::exit, this), EShortcut::GLOBAL_ACCEPT); //62x28
buttonOk = std::make_shared<CButton>(Point(159, 367), AnimationPath::builtin("IOKAY"), CButton::tooltip(), std::bind(&CTutorialWindow::exit, this), EShortcut::GLOBAL_RETURN); //62x28
buttonLeft = std::make_shared<CButton>(Point(5, 217), AnimationPath::builtin("HSBTNS3"), CButton::tooltip(), std::bind(&CTutorialWindow::previous, this), EShortcut::MOVE_LEFT); //22x46
buttonRight = std::make_shared<CButton>(Point(352, 217), AnimationPath::builtin("HSBTNS5"), CButton::tooltip(), std::bind(&CTutorialWindow::next, this), EShortcut::MOVE_RIGHT); //22x46
@ -70,7 +70,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m)
if(GH.input().hasTouchInputDevice() && !persistentStorage["gui"]["tutorialCompleted" + std::to_string(m)].Bool())
{
if(LOCPLINT)
LOCPLINT->showingDialog->set(true);
LOCPLINT->showingDialog->setBusy();
GH.windows().pushWindow(std::make_shared<CTutorialWindow>(m));
Settings s = persistentStorage.write["gui"]["tutorialCompleted" + std::to_string(m)];
@ -81,7 +81,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m)
void CTutorialWindow::exit()
{
if(LOCPLINT)
LOCPLINT->showingDialog->setn(false);
LOCPLINT->showingDialog->setFree();
close();
}

View File

@ -10,6 +10,11 @@
#include "StdInc.h"
#include "CWindowWithArtifacts.h"
#include "CHeroWindow.h"
#include "CSpellWindow.h"
#include "CExchangeWindow.h"
#include "CHeroBackpackWindow.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/WindowHandler.h"
@ -20,8 +25,6 @@
#include "../widgets/CComponent.h"
#include "../windows/CSpellWindow.h"
#include "../windows/CHeroBackpackWindow.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"

View File

@ -45,7 +45,7 @@ void CreaturePurchaseCard::initMinButton()
void CreaturePurchaseCard::initCreatureSwitcherButton()
{
creatureSwitcher = std::make_shared<CButton>(Point(pos.x + 18, pos.y-37), AnimationPath::builtin("iDv6432.def"), CButton::tooltip(), [&](){ switchCreatureLevel(); });
creatureSwitcher = std::make_shared<CButton>(Point(pos.x + 18, pos.y-37), AnimationPath::builtin("iDv6432.def"), CButton::tooltip(), [&](){ switchCreatureLevel(); }, EShortcut::RECRUITMENT_SWITCH_LEVEL);
}
void CreaturePurchaseCard::switchCreatureLevel()

Some files were not shown because too many files have changed in this diff Show More