1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-27 21:49:10 +02:00

Merge branch 'develop' into rmg-split-enum-monster-strength

This commit is contained in:
Warzyw647 2023-05-31 19:53:19 +02:00
commit bc4755a89a
335 changed files with 12445 additions and 8462 deletions

View File

@ -50,7 +50,22 @@ int64_t AttackPossibility::calculateDamageReduce(
vstd::amin(damageDealt, defender->getAvailableHealth());
// FIXME: provide distance info for Jousting bonus
auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
auto attackerUnitForMeasurement = attacker;
if(attackerUnitForMeasurement->isTurret())
{
auto ourUnits = cb.battleGetUnitsIf([&](const battle::Unit * u) -> bool
{
return u->unitSide() == attacker->unitSide() && !u->isTurret();
});
if(ourUnits.empty())
attackerUnitForMeasurement = defender;
else
attackerUnitForMeasurement = ourUnits.front();
}
auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attackerUnitForMeasurement, 0);
auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
auto damagePerEnemy = enemyDamage / (double)defender->getCount();

View File

@ -386,9 +386,13 @@ int64_t BattleExchangeEvaluator::calculateExchange(
for(auto unit : exchangeUnits)
{
if(unit->isTurret())
continue;
bool isOur = cb->battleMatchOwner(ap.attack.attacker, unit, true);
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
if(!vstd::contains(attackerQueue, unit))
{
attackerQueue.push_back(unit);
@ -593,6 +597,9 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
for(const battle::Unit * unit : turnQueue)
{
if(unit->isTurret())
continue;
if(turnBattle.battleCanShoot(unit))
{
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)

View File

@ -255,7 +255,7 @@ bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, Pla
{
auto quest = dynamic_cast<const CGKeys *>(obj);
if(quest->passableFor(playerColor))
if(quest->wasMyColorVisited(playerColor))
return true;
}

View File

@ -114,7 +114,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
if(blockerObject)
{
blockers.push_back(blockerObject);
blockers.insert(blockers.begin(), blockerObject);
}
}

View File

@ -52,6 +52,27 @@ AISharedStorage::~AISharedStorage()
}
}
void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
{
if(!specialAction)
{
specialAction = action;
}
else
{
auto parts = specialAction->getParts();
if(parts.empty())
{
parts.push_back(specialAction);
}
parts.push_back(action);
specialAction = std::make_shared<CompositeAction>(parts);
}
}
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
{
@ -765,7 +786,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector<ExchangeCandidate>
if(exchangeNode->actor->actorAction)
{
exchangeNode->theNodeBefore = carrier;
exchangeNode->specialAction = exchangeNode->actor->actorAction;
exchangeNode->addSpecialAction(exchangeNode->actor->actorAction);
}
exchangeNode->chainOther = other;
@ -1045,7 +1066,7 @@ struct TowmPortalFinder
movementCost);
node->theNodeBefore = bestNode;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
node->addSpecialAction(std::make_shared<AIPathfinding::TownPortalAction>(targetTown));
}
return nodeOptional;

View File

@ -55,6 +55,8 @@ struct AIPathNode : public CGPathNode
return accessible == CGPathNode::EAccessibility::NOT_SET
|| accessible == CGPathNode::EAccessibility::BLOCKED;
}
void addSpecialAction(std::shared_ptr<const SpecialAction> action);
};
struct AIPathNodeInfo

View File

@ -27,4 +27,67 @@ void SpecialAction::execute(const CGHeroInstance * hero) const
throw cannotFulfillGoalException("Can not execute " + toString());
}
bool CompositeAction::canAct(const AIPathNode * source) const
{
for(auto part : parts)
{
if(!part->canAct(source)) return false;
}
return true;
}
Goals::TSubgoal CompositeAction::decompose(const CGHeroInstance * hero) const
{
for(auto part : parts)
{
auto goal = part->decompose(hero);
if(!goal->invalid()) return goal;
}
return SpecialAction::decompose(hero);
}
void CompositeAction::execute(const CGHeroInstance * hero) const
{
for(auto part : parts)
{
part->execute(hero);
}
}
void CompositeAction::applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstNode,
const AIPathNode * srcNode) const
{
for(auto part : parts)
{
part->applyOnDestination(hero, destination, source, dstNode, srcNode);
}
}
std::string CompositeAction::toString() const
{
std::string result = "";
for(auto part : parts)
{
result += ", " + part->toString();
}
return result;
}
const CGObjectInstance * CompositeAction::targetObject() const
{
if(parts.empty())
return nullptr;
return parts.front()->targetObject();
}
}

View File

@ -36,7 +36,7 @@ public:
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
AIPathNode * dstNode,
const AIPathNode * srcNode) const
{
}
@ -44,6 +44,38 @@ public:
virtual std::string toString() const = 0;
virtual const CGObjectInstance * targetObject() const { return nullptr; }
virtual std::vector<std::shared_ptr<const SpecialAction>> getParts() const
{
return {};
}
};
class CompositeAction : public SpecialAction
{
private:
std::vector<std::shared_ptr<const SpecialAction>> parts;
public:
CompositeAction(std::vector<std::shared_ptr<const SpecialAction>> parts) : parts(parts) {}
bool canAct(const AIPathNode * source) const override;
void execute(const CGHeroInstance * hero) const override;
std::string toString() const override;
const CGObjectInstance * targetObject() const override;
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
std::vector<std::shared_ptr<const SpecialAction>> getParts() const override
{
return parts;
}
void applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstNode,
const AIPathNode * srcNode) const override;
};
}

View File

@ -133,7 +133,7 @@ namespace AIPathfinding
if(boatNode->action == CGPathNode::UNKNOWN)
{
boatNode->specialAction = virtualBoat;
boatNode->addSpecialAction(virtualBoat);
destination.blocked = false;
destination.action = CGPathNode::ENodeAction::EMBARK;
destination.node = boatNode;

View File

@ -157,7 +157,7 @@ namespace AIPathfinding
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{
node->specialAction.reset(new QuestAction(questAction));
node->addSpecialAction(std::make_shared<QuestAction>(questAction));
});
}
@ -279,6 +279,11 @@ namespace AIPathfinding
if(loss < actualArmyValue)
{
if(destNode->specialAction)
{
battleNode->specialAction = destNode->specialAction;
}
destination.node = battleNode;
nodeStorage->commit(destination, source);
@ -288,7 +293,7 @@ namespace AIPathfinding
AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
battleNode->addSpecialAction(std::make_shared<BattleAction>(destination.coord));
#if NKAI_PATHFINDER_TRACE_LEVEL >= 1
logAi->trace(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 747 B

View File

@ -201,8 +201,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}%几率造成双倍基础伤害",
"core.bonus.DRAGON_NATURE.name": "龙",
"core.bonus.DRAGON_NATURE.description": "生物拥有龙的特性",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "魔法直伤免疫",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "免疫直接造成伤害的魔法",
"core.bonus.EARTH_IMMUNITY.name": "土系免疫",
"core.bonus.EARTH_IMMUNITY.description": "免疫所有土系魔法",
"core.bonus.ENCHANTER.name": "强化师",

View File

@ -30,6 +30,15 @@
"vcmi.capitalColors.6" : "Teal",
"vcmi.capitalColors.7" : "Pink",
"vcmi.mainMenu.tutorialNotImplemented" : "Sorry, tutorial is not implemented yet\n",
"vcmi.mainMenu.highscoresNotImplemented" : "Sorry, high scores menu is not implemented yet\n",
"vcmi.mainMenu.serverConnecting" : "Connecting...",
"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
"vcmi.mainMenu.serverClosing" : "Closing...",
"vcmi.mainMenu.hostTCP" : "Host TCP/IP game",
"vcmi.mainMenu.joinTCP" : "Join TCP/IP game",
"vcmi.mainMenu.playerName" : "Player",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsIncompatibility" : "The following mods are required to load the game:",
"vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?",
@ -46,13 +55,18 @@
"vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now
"vcmi.systemOptions.townsGroup" : "Town Screen",
"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
"vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\nIf selected, VCMI will run in fullscreen mode, otherwise it will run in windowed mode",
"vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)",
"vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.",
"vcmi.systemOptions.fullscreenExclusive.hover" : "Fullscreen (exclusive)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{Fullscreen}\n\nIf selected, VCMI will run in exclusive fullscreen mode. In this mode, game will change resolution of monitor to selected resolution.",
"vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution. A game restart is required to apply the new resolution.",
"vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution.",
"vcmi.systemOptions.resolutionMenu.hover" : "Select Resolution",
"vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.",
"vcmi.systemOptions.fullscreenFailed" : "{Fullscreen}\n\nFailed to switch to fullscreen mode! The current resolution is not supported by the display!",
"vcmi.systemOptions.scalingButton.hover" : "Interface Scaling: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanges scaling of in-game interface",
"vcmi.systemOptions.scalingMenu.hover" : "Select Interface Scaling",
"vcmi.systemOptions.scalingMenu.help" : "Change in-game interface scaling.",
"vcmi.systemOptions.framerateButton.hover" : "Show FPS",
"vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window",
@ -201,8 +215,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Has a ${val}% chance of dealing double base damage when attacking",
"core.bonus.DRAGON_NATURE.name": "Dragon",
"core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Direct Damage Immunity",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Immune to direct damage spells",
"core.bonus.EARTH_IMMUNITY.name": "Earth immunity",
"core.bonus.EARTH_IMMUNITY.description": "Immune to all spells from the school of Earth magic",
"core.bonus.ENCHANTER.name": "Enchanter",

View File

@ -30,6 +30,15 @@
"vcmi.capitalColors.6" : "Türkis",
"vcmi.capitalColors.7" : "Rosa",
"vcmi.mainMenu.tutorialNotImplemented" : "Das Tutorial ist aktuell noch nicht implementiert\n",
"vcmi.mainMenu.highscoresNotImplemented" : "Die Highscores sind aktuell noch nicht implementiert\n",
"vcmi.mainMenu.serverConnecting" : "Verbinde...",
"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
"vcmi.mainMenu.serverClosing" : "Trenne...",
"vcmi.mainMenu.hostTCP" : "Hoste TCP/IP Spiel",
"vcmi.mainMenu.joinTCP" : "Trete TCP/IP Spiel bei",
"vcmi.mainMenu.playerName" : "Spieler",
"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
"vcmi.server.errors.modsIncompatibility" : "Erforderliche Mods um das Spiel zu laden:",
"vcmi.server.confirmReconnect" : "Mit der letzten Sitzung verbinden?",
@ -46,13 +55,18 @@
"vcmi.systemOptions.otherGroup" : "Andere Einstellungen", // unused right now
"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
"vcmi.systemOptions.fullscreenButton.hover" : "Vollbild",
"vcmi.systemOptions.fullscreenButton.help" : "{Vollbild}\n\n Wenn ausgewählt wird VCMI im Vollbildmodus laufen, ansonsten im Fenstermodus",
"vcmi.systemOptions.fullscreenBorderless.hover" : "Vollbild (randlos)",
"vcmi.systemOptions.fullscreenBorderless.help" : "{Randloses Vollbild}\n\nWenn diese Option ausgewählt ist, wird VCMI im randlosen Vollbildmodus ausgeführt. In diesem Modus wird das Spiel immer dieselbe Auflösung wie der Desktop verwenden und die gewählte Auflösung ignorieren.",
"vcmi.systemOptions.fullscreenExclusive.hover" : "Vollbild (exklusiv)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{Vollbild}\n\nWenn diese Option ausgewählt ist, wird VCMI im exklusiven Vollbildmodus ausgeführt. In diesem Modus ändert das Spiel die Auflösung des Monitors auf die ausgewählte Auflösung.",
"vcmi.systemOptions.resolutionButton.hover" : "Auflösung: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Wähle Auflösung}\n\n Ändert die Spielauflösung. Spielneustart ist erforderlich um neue Auflösung zu übernehmen.",
"vcmi.systemOptions.resolutionButton.help" : "{Wähle Auflösung}\n\n Ändert die Spielauflösung.",
"vcmi.systemOptions.resolutionMenu.hover" : "Wähle Auflösung",
"vcmi.systemOptions.resolutionMenu.help" : "Ändere die Spielauflösung.",
"vcmi.systemOptions.fullscreenFailed" : "{Vollbild}\n\n Der Wechsel in den Vollbildmodus ist fehlgeschlagen! Die aktuelle Auflösung wird von der Anzeige nicht unterstützt!",
"vcmi.systemOptions.scalingButton.hover" : "Interface-Skalierung: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Interface-Skalierung}\n\nÄndern der Skalierung des Interfaces im Spiel",
"vcmi.systemOptions.scalingMenu.hover" : "Skalierung des Interfaces auswählen",
"vcmi.systemOptions.scalingMenu.help" : "Ändern der Skalierung des Interfaces im Spiel.",
"vcmi.systemOptions.framerateButton.hover" : "FPS anzeigen",
"vcmi.systemOptions.framerateButton.help" : "{FPS anzeigen}\n\n Schaltet die Sichtbarkeit des Zählers für die Bilder pro Sekunde in der Ecke des Spielfensters um.",
@ -88,9 +102,12 @@
"vcmi.battleOptions.animationsSpeed1.help": "Setzt die Animationsgeschwindigkeit auf sehr langsam",
"vcmi.battleOptions.animationsSpeed5.help": "Setzt die Animationsgeschwindigkeit auf sehr schnell",
"vcmi.battleOptions.animationsSpeed6.help": "Setzt die Animationsgeschwindigkeit auf sofort",
"vcmi.battleOptions.touchscreenMode.hover": "Touchscreen-Modus",
"vcmi.battleOptions.touchscreenMode.help": "{Touchscreen-Modus}\n\nFalls aktiviert, ist ein zweiter Klick erforderlich, um die Aktion zu bestätigen und auszuführen. Dies ist besser für Touchscreen-Geräte geeignet.",
"vcmi.battleOptions.movementHighlightOnHover.hover": "Hervorhebung der Bewegung bei Hover",
"vcmi.battleOptions.movementHighlightOnHover.help": "{Hervorhebung der Bewegung bei Hover}\n\nHebt die Bewegungsreichweite der Einheit hervor, wenn man mit dem Mauszeiger über sie fährt.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",
"vcmi.battleWindow.pressKeyToSkipIntro" : "Beliebige Taste drücken, um das Kampf-Intro zu überspringen",
"vcmi.battleWindow.damageEstimation.melee" : "Angriff auf %CREATURE (%DAMAGE).",
@ -103,10 +120,11 @@
"vcmi.battleWindow.damageEstimation.damage.1" : "%d Schaden",
"vcmi.battleWindow.damageEstimation.kills" : "%d werden verenden",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d werden verenden",
"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Verfügbare Kreaturen anzeigen",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Verfügbare Kreaturen anzeigen}\n\n Zeigt in der Stadtübersicht (linke untere Ecke) die zum Kauf verfügbaren Kreaturen anstelle ihres Wachstums an.",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Wöchentliches Wachstum der Kreaturen anzeigen",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Wöchentl. Wachstum der Kreaturen anz.",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Wöchentliches Wachstum der Kreaturen anzeigen}\n\n Zeigt das wöchentliche Wachstum der Kreaturen anstelle der verfügbaren Menge in der Stadtübersicht (unten links).",
"vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompakte Kreatur-Infos",
"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompakte Kreatur-Infos}\n\n Kleinere Stadt-Kreaturen Informationen in der Stadtübersicht.",
@ -196,8 +214,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% Chance auf doppelten Schaden",
"core.bonus.DRAGON_NATURE.name": "Drache",
"core.bonus.DRAGON_NATURE.description": "Kreatur hat eine Drachennatur",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Direkte Schadensimmunität",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Immun gegen Direktschadenszauber",
"core.bonus.EARTH_IMMUNITY.name": "Erdimmunität",
"core.bonus.EARTH_IMMUNITY.description": "Immun gegen alle Zauber der Erdschule",
"core.bonus.ENCHANTER.name": "Verzauberer",
@ -294,7 +310,7 @@
"core.bonus.SUMMON_GUARDIANS.description": "Beschwört bei Kampfbeginn ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name": "Synergierbar",
"core.bonus.SYNERGY_TARGET.description": "Diese Kreatur ist anfällig für Synergieeffekte",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Atem",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Atem-Angriff (2-Hex-Bereich)",
"core.bonus.THREE_HEADED_ATTACK.name": "Dreiköpfiger Angriff",
"core.bonus.THREE_HEADED_ATTACK.description": "Greift drei benachbarte Einheiten an",

View File

@ -37,13 +37,10 @@
"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
"vcmi.systemOptions.townsGroup" : "Ekran miasta",
"vcmi.systemOptions.fullscreenButton.hover" : "Pełny ekran",
"vcmi.systemOptions.fullscreenButton.help" : "{Pełny ekran}\n\n Po wybraniu VCMI uruchomi się w trybie pełnoekranowym, w przeciwnym wypadku uruchomi się w oknie",
"vcmi.systemOptions.resolutionButton.hover" : "Rozdzielczość: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Wybierz rozdzielczość}\n\n Zmień rozdzielczość ekranu w grze. Restart gry jest wymagany, by zmiany zostały uwzględnione.",
"vcmi.systemOptions.resolutionMenu.hover" : "Wybierz rozdzielczość",
"vcmi.systemOptions.resolutionMenu.help" : "Zmień rozdzielczość ekranu w grze.",
"vcmi.systemOptions.fullscreenFailed" : "{Pełny ekran}\n\n Nieudane przełączenie w tryb pełnoekranowy! Obecna rozdzielczość nie jest wspierana przez wyświetlacz!",
"vcmi.systemOptions.framerateButton.hover" : "Pokaż FPS",
"vcmi.systemOptions.framerateButton.help" : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
@ -175,8 +172,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% szans na podwójne obrażenia",
"core.bonus.DRAGON_NATURE.name": "Smok",
"core.bonus.DRAGON_NATURE.description": "Stworzenie posiada smoczą naturę",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Odporność na bezpośrednie obrażenia",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Odporny na czary zadające bezpośrednie obrażenia",
"core.bonus.EARTH_IMMUNITY.name": "Odporność na ziemię",
"core.bonus.EARTH_IMMUNITY.description": "Odporny na wszystkie czary szkoły ziemi",
"core.bonus.ENCHANTER.name": "Czarodziej",

View File

@ -37,13 +37,10 @@
"vcmi.systemOptions.otherGroup" : "Иное", // unused right now
"vcmi.systemOptions.townsGroup" : "Экран города",
"vcmi.systemOptions.fullscreenButton.hover" : "Полный экран",
"vcmi.systemOptions.fullscreenButton.help" : "{Полный экран}\n\n Если выбрано, то VCMI будет работать в полноэкранном режиме, если нет - в окне",
"vcmi.systemOptions.resolutionButton.hover" : "Разрешение %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Разрешение экрана}\n\n Изменение разрешения экрана. Для применения нового разрешения требуется перезапуск игры.",
"vcmi.systemOptions.resolutionMenu.hover" : "Выбрать разрешения экрана",
"vcmi.systemOptions.resolutionMenu.help" : "Изменение разрешения экрана в игре.",
"vcmi.systemOptions.fullscreenFailed" : "{Полный экран}\n\n Невозможно переключиться в полноэкранный режим - выбранное разрешение не поддерживается дисплеем!",
"vcmi.systemOptions.framerateButton.hover" : "Показывать частоту кадров",
"vcmi.systemOptions.framerateButton.help" : "{Показывать частоту кадров}\n\n Включить счетчик частоты кадров в углу игрового клиента",
@ -199,8 +196,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Шанс ${val}% на двойной урон",
"core.bonus.DRAGON_NATURE.name": "Дракон",
"core.bonus.DRAGON_NATURE.description": "Это существо - дракон",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Иммунитет к магии прямого урона",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Заклинания прямого урона не могут быть применены",
"core.bonus.EARTH_IMMUNITY.name": "Иммунитет к земле",
"core.bonus.EARTH_IMMUNITY.description": "Иммунитет ко всем заклинаниям Магии Земли",
"core.bonus.ENCHANTER.name": "Заклинатель (массовое)",

View File

@ -46,13 +46,10 @@
"vcmi.systemOptions.otherGroup" : "Otras configuraciones", // actualmente no utilizada
"vcmi.systemOptions.townsGroup" : "Pantalla de la ciudad",
"vcmi.systemOptions.fullscreenButton.hover" : "Pantalla completa",
"vcmi.systemOptions.fullscreenButton.help" : "{Pantalla completa}\n\n Si se selecciona, VCMI se ejecutará en modo de pantalla completa, de lo contrario se ejecutará en ventana",
"vcmi.systemOptions.resolutionButton.hover" : "Resolución: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Seleccionar resolución}\n\n Cambia la resolución de la pantalla del juego. Se requiere reiniciar el juego para aplicar la nueva resolución.",
"vcmi.systemOptions.resolutionMenu.hover" : "Seleccionar resolución",
"vcmi.systemOptions.resolutionMenu.help" : "Cambia la resolución de la pantalla del juego.",
"vcmi.systemOptions.fullscreenFailed" : "{Pantalla completa}\n\n ¡Fallo al cambiar a modo de pantalla completa! ¡La resolución actual no es compatible con la pantalla!",
"vcmi.systemOptions.framerateButton.hover" : "Mostrar FPS",
"vcmi.systemOptions.framerateButton.help" : "{Mostrar FPS}\n\n Muestra el contador de Frames Por Segundo en la esquina de la ventana del juego.",
@ -201,8 +198,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% de probabilidad de doble daño",
"core.bonus.DRAGON_NATURE.name": "Dragón",
"core.bonus.DRAGON_NATURE.description": "La criatura tiene la naturaleza de dragón",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name": "Inmunidad al Daño Directo",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description": "Inmune a hechizos de daño directo",
"core.bonus.EARTH_IMMUNITY.name": "Inmunidad a la Tierra",
"core.bonus.EARTH_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de tierra",
"core.bonus.ENCHANTER.name": "Encantador",

View File

@ -38,15 +38,18 @@
"vcmi.systemOptions.otherGroup" : "Інші налаштування",
"vcmi.systemOptions.townsGroup" : "Екран міста",
"vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
"vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
"vcmi.systemOptions.fullscreenBorderless.hover" : "На весь екран (безрамкове вікно)",
"vcmi.systemOptions.fullscreenBorderless.help" : "{На весь екран (безрамкове вікно)}\n\nЯкщо обрано, VCMI працюватиме у режимі безрамкового вікна на весь екран. У цьому режимі гра завжди використовує ту саму роздільну здатність, що й робочий стіл, ігноруючи вибрану роздільну здатність",
"vcmi.systemOptions.fullscreenExclusive.hover" : "На весь екран (ексклюзивний режим)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{На весь екран (ексклюзивний режим)}\n\nnЯкщо вибрано, VCMI працюватиме у ексклюзивному повноекранному режимі. У цьому режимі гра змінюватиме роздільну здатність монітора на вибрану роздільну здатність",
"vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
"vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
"vcmi.systemOptions.fullscreenButton.hover" : "Повноекранний режим",
"vcmi.systemOptions.fullscreenButton.help" : "{Повноекранний режим}\n\n Якщо обрано, VCMI буде запускатися в режимі на весь екран, інакше — віконний режим",
"vcmi.systemOptions.resolutionButton.hover" : "Роздільна здатність: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Роздільна здатність}\n\n Зміна розширення екрану в грі. Аби зміни набули чинності необхідно перезавантажити гру.",
"vcmi.systemOptions.resolutionMenu.hover" : "Обрати роздільну здатність",
"vcmi.systemOptions.resolutionMenu.help" : "Змінити роздільну здатність екрану в грі.",
"vcmi.systemOptions.fullscreenFailed" : "{Повноекранний режим}\n\n Не вдалося перейти в повноекранний режим! Поточна роздільна здатність не підтримується дисплеєм!",
"vcmi.systemOptions.framerateButton.hover" : "Лічильник кадрів",
"vcmi.systemOptions.framerateButton.help" : "{Лічильник кадрів}\n\n Перемикає видимість лічильника кадрів на секунду у кутку ігрового вікна",
@ -175,8 +178,6 @@
"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "${val}% шанс нанести подвійної шкоди",
"core.bonus.DRAGON_NATURE.name" : "Дракон",
"core.bonus.DRAGON_NATURE.description" : "Істота має драконячу природу",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.name" : "Імунітет до прямої шкоди",
"core.bonus.DIRECT_DAMAGE_IMMUNITY.description" : "Імунітет до заклять, що завдають прямої шкоди",
"core.bonus.EARTH_IMMUNITY.name" : "Імунітет Землі",
"core.bonus.EARTH_IMMUNITY.description" : "Імунітет до всіх заклять школи Землі",
"core.bonus.ENCHANTER.name" : "Чарівник",

View File

@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse;
public class ActivityMods extends ActivityWithToolbar
{
private static final boolean ENABLE_REPO_DOWNLOADING = true;
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.2.json";
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.3.json";
private VCMIModsRepo mRepo;
private RecyclerView mRecycler;

View File

@ -7,61 +7,42 @@
* Full text of license available in license.txt file, in main folder
*
*/
// CMT.cpp : Defines the entry point for the console application.
//
#include "StdInc.h"
#include "CMT.h"
#include "CGameInfo.h"
#include "mainmenu/CMainMenu.h"
#include "lobby/CSelectionBase.h"
#include "windows/CCastleInterface.h"
#include "gui/CursorHandler.h"
#include "eventsSDL/InputHandler.h"
#include "CPlayerInterface.h"
#include "CVideoHandler.h"
#include "CMusicHandler.h"
#include "Client.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "CServerHandler.h"
#include "gui/NotificationHandler.h"
#include "ClientCommandManager.h"
#include "windows/CMessage.h"
#include "renderSDL/SDL_Extensions.h"
#include "render/IScreenHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileStream.h"
#include "../lib/CConsoleHandler.h"
#include "../lib/CGameState.h"
#include "../lib/CBuildingHandler.h"
#include "../CCallback.h"
#include "../lib/CHeroHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/VCMIDirs.h"
#include "../lib/NetPacks.h"
#include "../lib/CModHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CConfigHandler.h"
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CPlayerState.h"
#include "../lib/serializer/Connection.h"
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include "mainmenu/CPrologEpilogVideo.h"
#include <vstd/StringUtils.h>
#include <SDL.h>
#ifdef VCMI_WINDOWS
#include <SDL_syswm.h>
#endif
#include <SDL_main.h>
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"
#include <SDL_system.h>
#endif
#include "CMT.h"
#if __MINGW32__
#undef main
#endif
@ -70,44 +51,18 @@ namespace po = boost::program_options;
namespace po_style = boost::program_options::command_line_style;
namespace bfs = boost::filesystem;
std::string NAME_AFFIX = "client";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CGuiHandler GH;
int preferredDriverIndex = -1;
SDL_Window * mainWindow = nullptr;
SDL_Renderer * mainRenderer = nullptr;
SDL_Texture * screenTexture = nullptr;
extern boost::thread_specific_ptr<bool> inGuiThread;
SDL_Surface *screen = nullptr, //main screen surface
*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
std::queue<SDL_Event> SDLEventsQueue;
boost::mutex eventsM;
static po::variables_map vm;
//static bool setResolution = false; //set by event handling thread after resolution is adjusted
#ifndef VCMI_IOS
void processCommand(const std::string &message);
#endif
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
void playIntro();
static void mainLoop();
static CBasicLogConfigurator *logConfig;
#ifndef VCMI_WINDOWS
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <getopt.h>
#endif
void init()
{
CStopWatch tmh;
@ -139,17 +94,6 @@ static void prog_help(const po::options_description &opts)
std::cout << opts;
}
static void SDLLogCallback(void* userdata,
int category,
SDL_LogPriority priority,
const char* message)
{
//todo: convert SDL log priority to vcmi log priority
//todo: make separate log domain for SDL
logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
}
#if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
int wmain(int argc, wchar_t* argv[])
#elif defined(VCMI_MOBILE)
@ -255,7 +199,7 @@ int main(int argc, char * argv[])
const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
logConfig = new CBasicLogConfigurator(logPath, console);
logConfig->configureDefault();
logGlobal->info(NAME);
logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION);
logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
logGlobal->info("The log file will be saved to %s", logPath);
@ -338,73 +282,11 @@ int main(int argc, char * argv[])
testFile("VIDEO/GOOD1A.SMK", "campaign movies");
testFile("SOUNDS/G1A.WAV", "campaign music"); //technically not a music but voiced intro sounds
conf.init();
logGlobal->info("Loading settings: %d ms", pomtime.getDiff());
srand ( (unsigned int)time(nullptr) );
const JsonNode& video = settings["video"];
const JsonNode& res = video["screenRes"];
//something is really wrong...
if (res["width"].Float() < 100 || res["height"].Float() < 100)
{
logGlobal->error("Fatal error: failed to load settings!");
logGlobal->error("Possible reasons:");
logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("VCMI will now exit...");
exit(EXIT_FAILURE);
}
if(!settings["session"]["headless"].Bool())
{
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
{
logGlobal->error("Something was wrong: %s", SDL_GetError());
exit(-1);
}
#ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
//(!)init here AFTER SDL_Init() while using SDL for FPS management
GH.init();
SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
int driversCount = SDL_GetNumRenderDrivers();
std::string preferredDriverName = video["driver"].String();
logGlobal->info("Found %d render drivers", driversCount);
for(int it = 0; it < driversCount; it++)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(it,&info);
std::string driverName(info.name);
if(!preferredDriverName.empty() && driverName == preferredDriverName)
{
preferredDriverIndex = it;
logGlobal->info("\t%s (active)", driverName);
}
else
logGlobal->info("\t%s", driverName);
}
setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float());
logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff());
}
CCS = new CClientState();
CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
CSH = new CServerHandler();
@ -433,19 +315,6 @@ int main(int argc, char * argv[])
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
}
#ifdef VCMI_MAC
// Ctrl+click should be treated as a right click on Mac OS X
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
#endif
#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
if(GH.isPointerRelativeMode)
{
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
}
#endif
#ifndef VCMI_NO_THREADED_LOAD
//we can properly play intro only in the main thread, so we have to move loading to the separate thread
boost::thread loading(init);
@ -457,9 +326,7 @@ int main(int argc, char * argv[])
{
if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
playIntro();
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
GH.screenHandler().clearScreen();
}
@ -490,7 +357,6 @@ int main(int argc, char * argv[])
CCS->curh->show();
}
logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
session["autoSkip"].Bool() = vm.count("autoSkip");
@ -580,490 +446,20 @@ void playIntro()
}
}
#if !defined(VCMI_MOBILE)
static bool checkVideoMode(int monitorIndex, int w, int h)
{
//we only check that our desired window size fits on screen
SDL_DisplayMode mode;
if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode))
{
logGlobal->error("SDL_GetDesktopDisplayMode failed");
logGlobal->error(SDL_GetError());
return false;
}
logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h);
if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h))
{
return true;
}
return false;
}
#endif
static void cleanupRenderer()
{
screenBuf = nullptr; //it`s a link - just nullify
if(nullptr != screen2)
{
SDL_FreeSurface(screen2);
screen2 = nullptr;
}
if(nullptr != screen)
{
SDL_FreeSurface(screen);
screen = nullptr;
}
if(nullptr != screenTexture)
{
SDL_DestroyTexture(screenTexture);
screenTexture = nullptr;
}
}
static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex)
{
// VCMI will only work with 2 or 4 bytes per pixel
vstd::amax(bpp, 16);
vstd::amin(bpp, 32);
if(bpp>16)
bpp = 32;
if(displayIndex < 0)
{
if (mainWindow != nullptr)
displayIndex = SDL_GetWindowDisplayIndex(mainWindow);
if (displayIndex < 0)
displayIndex = 0;
}
#if defined(VCMI_MOBILE)
SDL_GetWindowSize(mainWindow, &w, &h);
#else
if(!checkVideoMode(displayIndex, w, h))
{
logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
}
#endif
bool bufOnScreen = (screenBuf == screen);
bool realFullscreen = settings["video"]["realFullscreen"].Bool();
/* match best rendering resolution */
int renderWidth = 0, renderHeight = 0;
auto aspectRatio = (float)w / (float)h;
auto minDiff = 10.f;
for (const auto& pair : conf.guiOptions)
{
int pWidth, pHeight;
std::tie(pWidth, pHeight) = pair.first;
/* filter out resolution which is larger than window */
if (pWidth > w || pHeight > h)
{
continue;
}
auto ratio = (float)pWidth / (float)pHeight;
auto diff = fabs(aspectRatio - ratio);
/* select closest aspect ratio */
if (diff < minDiff)
{
renderWidth = pWidth;
renderHeight = pHeight;
minDiff = diff;
}
/* select largest resolution meets prior conditions.
* since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */
else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth)
{
renderWidth = pWidth;
renderHeight = pHeight;
}
}
if (renderWidth == 0)
{
// no matching resolution for upscaling - complain & fallback to default resolution.
logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h);
Settings newRes = settings.write["video"]["screenRes"];
std::tie(w, h) = conf.guiOptions.begin()->first;
newRes["width"].Float() = w;
newRes["height"].Float() = h;
conf.SetResolution(w, h);
logGlobal->error("Falling back to %dx%d", w, h);
renderWidth = w;
renderHeight = h;
}
else
{
logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight);
}
cleanupRenderer();
if(nullptr == mainWindow)
{
#if defined(VCMI_MOBILE)
auto createWindow = [displayIndex](uint32_t extraFlags) -> bool {
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags);
return mainWindow != nullptr;
};
# ifdef VCMI_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
if(!createWindow(windowFlags | SDL_WINDOW_METAL))
{
logGlobal->warn("Metal unavailable, using OpenGLES");
createWindow(windowFlags);
}
# else
createWindow(0);
# endif // VCMI_IOS
// SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate
// That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate
SDL_Rect screenRect;
if(SDL_GetDisplayBounds(0, &screenRect) == 0)
{
const auto screenWidth = screenRect.w;
const auto screenHeight = screenRect.h;
const auto aspect = static_cast<double>(screenWidth) / screenHeight;
logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect);
if((double)w / aspect > (double)h)
{
h = (int)round((double)w / aspect);
}
else
{
w = (int)round((double)h * aspect);
}
logGlobal->info("Changing logical screen size to %dx%d", w, h);
}
else
{
logGlobal->error("Can't fix aspect ratio for screen");
}
#else
if(fullscreen)
{
if(realFullscreen)
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN);
else //in windowed full-screen mode use desktop resolution
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
}
#endif // defined(VCMI_MOBILE)
if(nullptr == mainWindow)
{
throw std::runtime_error("Unable to create window\n");
}
//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
if(nullptr == mainRenderer)
{
throw std::runtime_error("Unable to create renderer\n");
}
SDL_RendererInfo info;
SDL_GetRendererInfo(mainRenderer, &info);
logGlobal->info("Created renderer %s", info.name);
}
else
{
#if !defined(VCMI_MOBILE)
if(fullscreen)
{
if(realFullscreen)
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
mode.w = renderWidth;
mode.h = renderHeight;
SDL_SetWindowDisplayMode(mainWindow, &mode);
}
else
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, w, h);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
}
#endif
}
if(!(fullscreen && realFullscreen))
{
SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight);
//following line is bugged not only on android, do not re-enable without checking
//#ifndef VCMI_ANDROID
// // on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
// SDL_RenderSetViewport(mainRenderer, nullptr);
//#endif
}
#ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000;
int gmask = 0x00ff0000;
int rmask = 0x0000ff00;
int amask = 0x000000ff;
#else
int bmask = 0x000000ff;
int gmask = 0x0000ff00;
int rmask = 0x00ff0000;
int amask = 0xFF000000;
#endif
screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask);
if(nullptr == screen)
{
logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
screenTexture = SDL_CreateTexture(mainRenderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
renderWidth, renderHeight);
if(nullptr == screenTexture)
{
logGlobal->error("Unable to create screen texture");
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create screen texture");
}
screen2 = CSDL_Ext::copySurface(screen);
if(nullptr == screen2)
{
throw std::runtime_error("Unable to copy surface\n");
}
screenBuf = bufOnScreen ? screen : screen2;
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::init(mainWindow);
}
return true;
}
//used only once during initialization
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo)
{
if(!recreateWindow(w, h, bpp, fullscreen, displayIndex))
{
throw std::runtime_error("Requested screen resolution is not available\n");
}
}
static void fullScreenChanged()
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
Settings full = settings.write["video"]["fullscreen"];
const bool toFullscreen = full->Bool();
auto bitsPerPixel = screen->format->BitsPerPixel;
auto w = screen->w;
auto h = screen->h;
if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1))
{
//will return false and report error if video mode is not supported
return;
}
GH.totalRedraw();
}
static void handleEvent(SDL_Event & ev)
{
if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
{
#ifdef VCMI_ANDROID
handleQuit(false);
#else
handleQuit();
#endif
return;
}
#ifdef VCMI_ANDROID
else if (ev.type == SDL_KEYDOWN && ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK)
{
handleQuit(true);
}
#endif
else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
{
Settings full = settings.write["video"]["fullscreen"];
full->Bool() = !full->Bool();
return;
}
else if(ev.type == SDL_USEREVENT)
{
switch(static_cast<EUserEvent>(ev.user.code))
{
case EUserEvent::FORCE_QUIT:
{
handleQuit(false);
return;
}
break;
case EUserEvent::RETURN_TO_MAIN_MENU:
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
}
break;
case EUserEvent::RESTART_GAME:
{
CSH->sendRestartGame();
}
break;
case EUserEvent::CAMPAIGN_START_SCENARIO:
{
CSH->campaignServerRestartLock.set(true);
CSH->endGameplay();
auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(ev.user.data1));
auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
auto finisher = [=]()
{
if(ourCampaign->mapsRemaining.size())
{
GH.pushInt(CMM);
GH.pushInt(CMM->menu);
CMM->openCampaignLobby(ourCampaign);
}
};
if(epilogue.hasPrologEpilog)
{
GH.pushIntT<CPrologEpilogVideo>(epilogue, finisher);
}
else
{
CSH->campaignServerRestartLock.waitUntil(false);
finisher();
}
}
break;
case EUserEvent::RETURN_TO_MENU_LOAD:
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("load");
break;
case EUserEvent::FULLSCREEN_TOGGLED:
fullScreenChanged();
break;
default:
logGlobal->error("Unknown user event. Code %d", ev.user.code);
break;
}
return;
}
else if(ev.type == SDL_WINDOWEVENT)
{
switch (ev.window.event) {
case SDL_WINDOWEVENT_RESTORED:
#ifndef VCMI_IOS
fullScreenChanged();
#endif
break;
}
return;
}
else if(ev.type == SDL_SYSWMEVENT)
{
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::handleSdlEvent(ev);
}
}
//preprocessing
if(ev.type == SDL_MOUSEMOTION)
{
CCS->curh->cursorMove(ev.motion.x, ev.motion.y);
}
{
boost::unique_lock<boost::mutex> lock(eventsM);
SDLEventsQueue.push(ev);
}
}
static void mainLoop()
{
SettingsListener resChanged = settings.listen["video"]["fullscreen"];
resChanged([](const JsonNode &newState){ CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
SettingsListener resChanged = settings.listen["video"]["resolution"];
SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
resChanged([](const JsonNode &newState){ GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
fsChanged([](const JsonNode &newState){ GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
inGuiThread.reset(new bool(true));
assert(GH.mainFPSmng);
GH.mainFPSmng->init(settings["video"]["targetfps"].Integer());
while(1) //main SDL events loop
{
SDL_Event ev;
while(1 == SDL_PollEvent(&ev))
{
handleEvent(ev);
}
GH.input().fetchEvents();
CSH->applyPacksOnLobbyScreen();
GH.renderFrame();
}
}
@ -1075,8 +471,7 @@ static void quitApplication()
CSH->endGameplay();
}
GH.listInt.clear();
GH.objsToBlit.clear();
GH.windows().clear();
CMM.reset();
@ -1100,29 +495,9 @@ static void quitApplication()
vstd::clear_pointer(console);// should be removed after everything else since used by logging
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
if(!settings["session"]["headless"].Bool())
{
if(settings["general"]["notifications"].Bool())
{
NotificationHandler::destroy();
}
cleanupRenderer();
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
SDL_Quit();
}
GH.screenHandler().close();
if(logConfig != nullptr)
{
@ -1137,19 +512,10 @@ static void quitApplication()
void handleQuit(bool ask)
{
if(CSH->client && LOCPLINT && ask)
{
CCS->curh->set(Cursor::Map::POINTER);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], [](){
// Workaround for assertion failure on exit:
// handleQuit() is alway called during SDL event processing
// during which, eventsM is kept locked
// this leads to assertion failure if boost::mutex is in locked state
eventsM.unlock();
quitApplication();
}, nullptr);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
}
else
{

View File

@ -2,9 +2,10 @@ set(client_SRCS
StdInc.cpp
../CCallback.cpp
adventureMap/CAdvMapPanel.cpp
adventureMap/CAdventureMapInterface.cpp
adventureMap/CAdventureOptions.cpp
adventureMap/AdventureMapInterface.cpp
adventureMap/AdventureMapShortcuts.cpp
adventureMap/AdventureMapWidget.cpp
adventureMap/AdventureOptions.cpp
adventureMap/CInGameConsole.cpp
adventureMap/CInfoBar.cpp
adventureMap/CList.cpp
@ -26,12 +27,23 @@ set(client_SRCS
battle/BattleWindow.cpp
battle/CreatureAnimation.cpp
eventsSDL/NotificationHandler.cpp
eventsSDL/InputHandler.cpp
eventsSDL/UserEventHandler.cpp
eventsSDL/InputSourceKeyboard.cpp
eventsSDL/InputSourceMouse.cpp
eventsSDL/InputSourceText.cpp
eventsSDL/InputSourceTouch.cpp
gui/CGuiHandler.cpp
gui/CIntObject.cpp
gui/CursorHandler.cpp
gui/EventDispatcher.cpp
gui/EventsReceiver.cpp
gui/InterfaceObjectConfigurable.cpp
gui/NotificationHandler.cpp
gui/FramerateManager.cpp
gui/ShortcutHandler.cpp
gui/WindowHandler.cpp
lobby/CBonusSelection.cpp
lobby/CCampaignInfoScreen.cpp
@ -75,6 +87,7 @@ set(client_SRCS
renderSDL/SDLImage.cpp
renderSDL/SDLImageLoader.cpp
renderSDL/SDLRWwrapper.cpp
renderSDL/ScreenHandler.cpp
renderSDL/SDL_Extensions.cpp
widgets/Buttons.cpp
@ -129,9 +142,11 @@ set(client_SRCS
set(client_HEADERS
StdInc.h
adventureMap/CAdvMapPanel.h
adventureMap/CAdventureMapInterface.h
adventureMap/CAdventureOptions.h
adventureMap/AdventureMapInterface.h
adventureMap/AdventureMapShortcuts.h
adventureMap/AdventureMapWidget.h
adventureMap/AdventureState.h
adventureMap/AdventureOptions.h
adventureMap/CInGameConsole.h
adventureMap/CInfoBar.h
adventureMap/CList.h
@ -154,15 +169,26 @@ set(client_HEADERS
battle/BattleWindow.h
battle/CreatureAnimation.h
eventsSDL/NotificationHandler.h
eventsSDL/InputHandler.h
eventsSDL/UserEventHandler.h
eventsSDL/InputSourceKeyboard.h
eventsSDL/InputSourceMouse.h
eventsSDL/InputSourceText.h
eventsSDL/InputSourceTouch.h
gui/CGuiHandler.h
gui/CIntObject.h
gui/CursorHandler.h
gui/EventDispatcher.h
gui/EventsReceiver.h
gui/InterfaceObjectConfigurable.h
gui/FramerateManager.h
gui/MouseButton.h
gui/NotificationHandler.h
gui/Shortcut.h
gui/ShortcutHandler.h
gui/TextAlignment.h
gui/WindowHandler.h
lobby/CBonusSelection.h
lobby/CCampaignInfoScreen.h
@ -202,6 +228,7 @@ set(client_HEADERS
render/IFont.h
render/IImage.h
render/IImageLoader.h
render/IScreenHandler.h
renderSDL/CBitmapFont.h
renderSDL/CBitmapHanFont.h
@ -211,6 +238,7 @@ set(client_HEADERS
renderSDL/SDLImage.h
renderSDL/SDLImageLoader.h
renderSDL/SDLRWwrapper.h
renderSDL/ScreenHandler.h
renderSDL/SDL_Extensions.h
renderSDL/SDL_PixelAccess.h
@ -323,7 +351,7 @@ if(WIN32)
endif()
target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
# TODO: very hacky, find proper solution to copy AI dlls into bin dir
# TODO: very hacky, find proper solution to copy AI dlls into bin dir
if(MSVC)
add_custom_command(TARGET vcmiclient POST_BUILD
WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"

View File

@ -335,7 +335,7 @@ CMusicHandler::CMusicHandler():
auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourceID & id) -> bool
{
if(id.getType() != EResType::MUSIC)
if(id.getType() != EResType::SOUND)
return false;
if(!boost::algorithm::istarts_with(id.getName(), "MUSIC/"))
@ -561,7 +561,7 @@ void MusicEntry::load(std::string musicURI)
try
{
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::MUSIC)));
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::SOUND)));
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
}
catch(std::exception &e)

View File

@ -12,10 +12,9 @@
#include <vcmi/Artifact.h>
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
#include "mapView/mapHandler.h"
#include "adventureMap/CList.h"
#include "adventureMap/CInfoBar.h"
#include "battle/BattleInterface.h"
#include "battle/BattleEffectsController.h"
#include "battle/BattleFieldController.h"
@ -23,6 +22,7 @@
#include "battle/BattleWindow.h"
#include "../CCallback.h"
#include "windows/CCastleInterface.h"
#include "eventsSDL/InputHandler.h"
#include "gui/CursorHandler.h"
#include "windows/CKingdomInterface.h"
#include "CGameInfo.h"
@ -66,6 +66,7 @@
#include "../lib/CPlayerState.h"
#include "../lib/GameConstants.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "windows/InfoWindows.h"
#include "../lib/UnlockGuard.h"
#include "../lib/CPathfinder.h"
@ -74,11 +75,9 @@
#include "CServerHandler.h"
// FIXME: only needed for CGameState::mutex
#include "../lib/CGameState.h"
#include "gui/NotificationHandler.h"
#include "eventsSDL/NotificationHandler.h"
#include "adventureMap/CInGameConsole.h"
#include <SDL_events.h>
// The macro below is used to mark functions that are called by client when game state changes.
// They all assume that CPlayerInterface::pim mutex is locked.
#define EVENT_HANDLER_CALLED_BY_CLIENT
@ -96,8 +95,6 @@
return; \
RETURN_IF_QUICK_COMBAT
extern std::queue<SDL_Event> SDLEventsQueue;
extern boost::mutex eventsM;
boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
CPlayerInterface * LOCPLINT;
@ -163,22 +160,23 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
initializeHeroTownList();
// always recreate advmap interface to avoid possible memory-corruption bugs
adventureInt.reset(new CAdventureMapInterface());
adventureInt.reset(new AdventureMapInterface());
}
void CPlayerInterface::playerStartsTurn(PlayerColor player)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if (!vstd::contains (GH.listInt, adventureInt))
if(GH.windows().findWindows<AdventureMapInterface>().empty())
{
// after map load - remove all active windows and replace them with adventure map
GH.popInts ((int)GH.listInt.size());
GH.pushInt (adventureInt);
GH.windows().clear();
GH.windows().pushWindow(adventureInt);
}
// remove all dialogs that do not expect query answer
while (GH.listInt.front() != adventureInt && !dynamic_cast<CInfoWindow*>(GH.listInt.front().get()))
GH.popInts(1);
while (!GH.windows().topWindow<AdventureMapInterface>() && !GH.windows().topWindow<CInfoWindow>())
GH.windows().popWindows(1);
if (player != playerID && LOCPLINT == this)
{
@ -205,7 +203,7 @@ void CPlayerInterface::performAutosave()
}
else if(frequency > 0 && cb->getDate() % frequency == 0)
{
LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + std::to_string(autosaveCount++ + 1));
cb->save("Saves/" + prefix + "Autosave_" + std::to_string(autosaveCount++ + 1));
autosaveCount %= 5;
}
}
@ -214,8 +212,6 @@ void CPlayerInterface::yourTurn()
{
EVENT_HANDLER_CALLED_BY_CLIENT;
{
boost::unique_lock<boost::mutex> lock(eventsM); //block handling events until interface is ready
LOCPLINT = this;
GH.curInt = this;
@ -246,7 +242,7 @@ void CPlayerInterface::acceptTurn()
{
if (settings["session"]["autoSkip"].Bool())
{
while(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
while(auto iw = GH.windows().topWindow<CInfoWindow>())
iw->close();
}
@ -371,22 +367,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
//check if user cancelled movement
{
boost::unique_lock<boost::mutex> un(eventsM);
while(!SDLEventsQueue.empty())
{
SDL_Event ev = SDLEventsQueue.front();
SDLEventsQueue.pop();
switch(ev.type)
{
case SDL_MOUSEBUTTONDOWN:
stillMoveHero.setn(STOP_MOVE);
break;
case SDL_KEYDOWN:
if (ev.key.keysym.sym < SDLK_F1 || ev.key.keysym.sym > SDLK_F15)
stillMoveHero.setn(STOP_MOVE);
break;
}
}
if (GH.input().ignoreEventsUntilInput())
stillMoveHero.setn(STOP_MOVE);
}
if (stillMoveHero.get() == WAITING_MOVE)
@ -440,7 +422,7 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
auto newCastleInt = std::make_shared<CCastleInterface>(town);
GH.pushInt(newCastleInt);
GH.windows().pushWindow(newCastleInt);
}
void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val)
@ -448,7 +430,7 @@ void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int
EVENT_HANDLER_CALLED_BY_CLIENT;
if (which == 4)
{
if (CAltarWindow *ctw = dynamic_cast<CAltarWindow *>(GH.topInt().get()))
for (auto ctw : GH.windows().findWindows<CAltarWindow>())
ctw->setExpToLevel();
}
else
@ -458,11 +440,8 @@ void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int
void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
CUniversityWindow* cuw = dynamic_cast<CUniversityWindow*>(GH.topInt().get());
if (cuw) //university window is open
{
GH.totalRedraw();
}
for (auto cuw : GH.windows().findWindows<CUniversityWindow>())
cuw->redraw();
}
void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero)
@ -481,10 +460,10 @@ void CPlayerInterface::heroMovePointsChanged(const CGHeroInstance * hero)
void CPlayerInterface::receivedResource()
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if (CMarketplaceWindow *mw = dynamic_cast<CMarketplaceWindow *>(GH.topInt().get()))
for (auto mw : GH.windows().findWindows<CMarketplaceWindow>())
mw->resourceChanged();
GH.totalRedraw();
GH.windows().totalRedraw();
}
void CPlayerInterface::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill>& skills, QueryID queryID)
@ -492,7 +471,7 @@ void CPlayerInterface::heroGotLevel(const CGHeroInstance *hero, PrimarySkill::Pr
EVENT_HANDLER_CALLED_BY_CLIENT;
waitWhileDialog();
CCS->soundh->playSound(soundBase::heroNewLevel);
GH.pushIntT<CLevelWindow>(hero, pskill, skills, [=](ui32 selection)
GH.windows().createAndPushWindow<CLevelWindow>(hero, pskill, skills, [=](ui32 selection)
{
cb->selectionMade(selection, queryID);
});
@ -503,7 +482,7 @@ void CPlayerInterface::commanderGotLevel (const CCommanderInstance * commander,
EVENT_HANDLER_CALLED_BY_CLIENT;
waitWhileDialog();
CCS->soundh->playSound(soundBase::heroNewLevel);
GH.pushIntT<CStackWindow>(commander, skills, [=](ui32 selection)
GH.windows().createAndPushWindow<CStackWindow>(commander, skills, [=](ui32 selection)
{
cb->selectionMade(selection, queryID);
});
@ -538,17 +517,15 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
castleInt->heroes->update();
castleInt->redraw();
}
for (auto isa : GH.listInt)
for (auto ki : GH.windows().findWindows<CKingdomInterface>())
{
CKingdomInterface *ki = dynamic_cast<CKingdomInterface*>(isa.get());
if (ki)
{
ki->townChanged(town);
ki->updateGarrisons();
ki->redraw();
}
ki->townChanged(town);
ki->updateGarrisons();
ki->redraw();
}
}
void CPlayerInterface::heroVisitsTown(const CGHeroInstance* hero, const CGTownInstance * town)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
@ -590,20 +567,16 @@ void CPlayerInterface::garrisonsChanged(std::vector<const CGObjectInstance *> ob
adventureInt->onTownChanged(town);
}
for (auto & elem : GH.listInt)
{
CGarrisonHolder *cgh = dynamic_cast<CGarrisonHolder*>(elem.get());
if (cgh)
cgh->updateGarrisons();
for (auto cgh : GH.windows().findWindows<CGarrisonHolder>())
cgh->updateGarrisons();
if (CTradeWindow *cmw = dynamic_cast<CTradeWindow*>(elem.get()))
{
if (vstd::contains(objs, cmw->hero))
cmw->garrisonChanged();
}
for (auto cmw : GH.windows().findWindows<CTradeWindow>())
{
if (vstd::contains(objs, cmw->hero))
cmw->garrisonChanged();
}
GH.totalRedraw();
GH.windows().totalRedraw();
}
void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) //what: 1 - built, 2 - demolished
@ -862,7 +835,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br, QueryID queryID)
{
cb->selectionMade(selection, queryID);
};
GH.pushInt(wnd);
GH.windows().pushWindow(wnd);
// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
// Otherwise NewTurn causes freeze.
waitWhileDialog();
@ -1016,7 +989,7 @@ void CPlayerInterface::showInfoDialog(EInfoWindowMode type, const std::string &t
waitWhileDialog(); //Fix for mantis #98
adventureInt->showInfoBoxMessage(components, text, timer);
if (makingTurn && GH.listInt.size() && LOCPLINT == this)
if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
return;
}
@ -1057,12 +1030,12 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
}
std::shared_ptr<CInfoWindow> temp = CInfoWindow::create(text, playerID, components);
if (makingTurn && GH.listInt.size() && LOCPLINT == this)
if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
{
CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
showingDialog->set(true);
stopMovement(); // interrupt movement to show dialog
GH.pushInt(temp);
GH.windows().pushWindow(temp);
}
else
{
@ -1122,7 +1095,7 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v
int charperline = 35;
if (pom.size() > 1)
charperline = 50;
GH.pushIntT<CSelWindow>(text, playerID, charperline, intComps, pom, askID);
GH.windows().createAndPushWindow<CSelWindow>(text, playerID, charperline, intComps, pom, askID);
intComps[0]->clickLeft(true, false);
}
}
@ -1171,7 +1144,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
std::shared_ptr<CObjectListWindow> wnd = std::make_shared<CObjectListWindow>(tempList, localIcon, localTitle, localDescription, selectCallback);
wnd->onExit = cancelCallback;
GH.pushInt(wnd);
GH.windows().pushWindow(wnd);
}
void CPlayerInterface::tileRevealed(const std::unordered_set<int3> &pos)
@ -1190,7 +1163,7 @@ void CPlayerInterface::tileHidden(const std::unordered_set<int3> &pos)
void CPlayerInterface::openHeroWindow(const CGHeroInstance *hero)
{
boost::unique_lock<boost::recursive_mutex> un(*pim);
GH.pushIntT<CHeroWindow>(hero);
GH.windows().createAndPushWindow<CHeroWindow>(hero);
}
void CPlayerInterface::availableCreaturesChanged( const CGDwelling *town )
@ -1198,27 +1171,22 @@ void CPlayerInterface::availableCreaturesChanged( const CGDwelling *town )
EVENT_HANDLER_CALLED_BY_CLIENT;
if (const CGTownInstance * townObj = dynamic_cast<const CGTownInstance*>(town))
{
CFortScreen * fortScreen = dynamic_cast<CFortScreen*>(GH.topInt().get());
CCastleInterface * castleInterface = dynamic_cast<CCastleInterface*>(GH.topInt().get());
if (fortScreen)
for (auto fortScreen : GH.windows().findWindows<CFortScreen>())
fortScreen->creaturesChangedEventHandler();
else if(castleInterface)
for (auto castleInterface : GH.windows().findWindows<CCastleInterface>())
castleInterface->creaturesChangedEventHandler();
for(auto isa : GH.listInt)
{
CKingdomInterface *ki = dynamic_cast<CKingdomInterface*>(isa.get());
if (ki && townObj)
if (townObj)
for (auto ki : GH.windows().findWindows<CKingdomInterface>())
ki->townChanged(townObj);
}
}
else if(town && GH.listInt.size() && (town->ID == Obj::CREATURE_GENERATOR1
else if(town && GH.windows().count() > 0 && (town->ID == Obj::CREATURE_GENERATOR1
|| town->ID == Obj::CREATURE_GENERATOR4 || town->ID == Obj::WAR_MACHINE_FACTORY))
{
CRecruitmentWindow *crw = dynamic_cast<CRecruitmentWindow*>(GH.topInt().get());
if (crw && crw->dwelling == town)
crw->availableCreaturesChanged();
for (auto crw : GH.windows().findWindows<CRecruitmentWindow>())
if (crw->dwelling == town)
crw->availableCreaturesChanged();
}
}
@ -1284,7 +1252,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
auto cgw = std::make_shared<CGarrisonWindow>(up, down, removableUnits);
cgw->quit->addCallback(onEnd);
GH.pushInt(cgw);
GH.windows().pushWindow(cgw);
}
/**
@ -1342,7 +1310,7 @@ void CPlayerInterface::showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID
void CPlayerInterface::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CExchangeWindow>(hero1, hero2, query);
GH.windows().createAndPushWindow<CExchangeWindow>(hero1, hero2, query);
}
void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
@ -1403,7 +1371,7 @@ void CPlayerInterface::showRecruitmentDialog(const CGDwelling *dwelling, const C
{
LOCPLINT->cb->recruitCreatures(dwelling, dst, id, count, -1);
};
GH.pushIntT<CRecruitmentWindow>(dwelling, level, dst, recruitCb);
GH.windows().createAndPushWindow<CRecruitmentWindow>(dwelling, level, dst, recruitCb);
}
void CPlayerInterface::waitWhileDialog(bool unlockPim)
@ -1426,7 +1394,7 @@ void CPlayerInterface::showShipyardDialog(const IShipyard *obj)
auto state = obj->shipyardStatus();
TResources cost;
obj->getBoatCost(cost);
GH.pushIntT<CShipyardWindow>(cost, state, obj->getBoatType(), [=](){ cb->buildBoat(obj); });
GH.windows().createAndPushWindow<CShipyardWindow>(cost, state, obj->getBoatType(), [=](){ cb->buildBoat(obj); });
}
void CPlayerInterface::newObject( const CGObjectInstance * obj )
@ -1450,7 +1418,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
adventureInt->centerOnTile(pos);
if (focusTime)
{
GH.totalRedraw();
GH.windows().totalRedraw();
{
auto unlockPim = vstd::makeUnlockGuard(*pim);
IgnoreEvents ignore(*this);
@ -1491,7 +1459,6 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
if(CSH->howManyPlayerInterfaces() > 1 && LOCPLINT != this && LOCPLINT->makingTurn == false)
{
//one of our players who isn't last in order got attacked not by our another player (happens for example in hotseat mode)
boost::unique_lock<boost::mutex> lock(eventsM); //TODO: copied from yourTurn, no idea if it's needed
LOCPLINT = this;
GH.curInt = this;
adventureInt->onCurrentPlayerChanged(playerID);
@ -1519,16 +1486,15 @@ void CPlayerInterface::update()
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
{
showingDialog->set(true);
GH.pushInt(dialogs.front());
GH.windows().pushWindow(dialogs.front());
dialogs.pop_front();
}
assert(adventureInt);
// Handles mouse and key input
GH.updateTime();
GH.handleEvents();
GH.simpleRedraw();
GH.windows().simpleRedraw();
}
int CPlayerInterface::getLastIndex( std::string namePrefix)
@ -1594,7 +1560,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
if(adventureInt)
{
GH.terminate_cond->setn(true);
GH.popInts(GH.listInt.size());
GH.windows().popWindows(GH.windows().count());
adventureInt.reset();
}
}
@ -1640,7 +1606,7 @@ void CPlayerInterface::showPuzzleMap()
double ratio = 0;
int3 grailPos = cb->getGrailPos(&ratio);
GH.pushIntT<CPuzzleWindow>(grailPos, ratio);
GH.windows().createAndPushWindow<CPuzzleWindow>(grailPos, ratio);
}
void CPlayerInterface::viewWorldMap()
@ -1652,8 +1618,8 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if(dynamic_cast<CSpellWindow *>(GH.topInt().get()))
GH.popInts(1);
if(GH.windows().topWindow<CSpellWindow>())
GH.windows().popWindows(1);
if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
localState->erasePath(caster);
@ -1715,50 +1681,50 @@ void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInsta
EVENT_HANDLER_CALLED_BY_CLIENT;
if(market->allowsTrade(EMarketMode::ARTIFACT_EXP) && visitor->getAlignment() != EAlignment::EVIL)
GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::ARTIFACT_EXP);
GH.windows().createAndPushWindow<CAltarWindow>(market, visitor, EMarketMode::ARTIFACT_EXP);
else if(market->allowsTrade(EMarketMode::CREATURE_EXP) && visitor->getAlignment() != EAlignment::GOOD)
GH.pushIntT<CAltarWindow>(market, visitor, EMarketMode::CREATURE_EXP);
GH.windows().createAndPushWindow<CAltarWindow>(market, visitor, EMarketMode::CREATURE_EXP);
else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
GH.pushIntT<CTransformerWindow>(market, visitor);
GH.windows().createAndPushWindow<CTransformerWindow>(market, visitor);
else if(!market->availableModes().empty())
GH.pushIntT<CMarketplaceWindow>(market, visitor, market->availableModes().front());
GH.windows().createAndPushWindow<CMarketplaceWindow>(market, visitor, market->availableModes().front());
}
void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CUniversityWindow>(visitor, market);
GH.windows().createAndPushWindow<CUniversityWindow>(visitor, market);
}
void CPlayerInterface::showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CHillFortWindow>(visitor, object);
GH.windows().createAndPushWindow<CHillFortWindow>(visitor, object);
}
void CPlayerInterface::availableArtifactsChanged(const CGBlackMarket * bm)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
if (CMarketplaceWindow *cmw = dynamic_cast<CMarketplaceWindow*>(GH.topInt().get()))
for (auto cmw : GH.windows().findWindows<CMarketplaceWindow>())
cmw->artifactsChanged(false);
}
void CPlayerInterface::showTavernWindow(const CGObjectInstance *townOrTavern)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CTavernWindow>(townOrTavern);
GH.windows().createAndPushWindow<CTavernWindow>(townOrTavern);
}
void CPlayerInterface::showThievesGuildWindow (const CGObjectInstance * obj)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CThievesGuildWindow>(obj);
GH.windows().createAndPushWindow<CThievesGuildWindow>(obj);
}
void CPlayerInterface::showQuestLog()
{
EVENT_HANDLER_CALLED_BY_CLIENT;
GH.pushIntT<CQuestLog>(LOCPLINT->cb->getMyQuests());
GH.windows().createAndPushWindow<CQuestLog>(LOCPLINT->cb->getMyQuests());
}
void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
@ -1809,12 +1775,9 @@ void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
EVENT_HANDLER_CALLED_BY_CLIENT;
auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
adventureInt->onHeroChanged(hero);
for(auto isa : GH.listInt)
{
auto artWin = dynamic_cast<CArtifactHolder*>(isa.get());
if (artWin)
artWin->artifactRemoved(al);
}
for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
artWin->artifactRemoved(al);
waitWhileDialog();
}
@ -1834,12 +1797,9 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact
redraw = false;
}
for(auto isa : GH.listInt)
{
auto artWin = dynamic_cast<CArtifactHolder*>(isa.get());
if (artWin)
artWin->artifactMoved(src, dst, redraw);
}
for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
artWin->artifactMoved(src, dst, redraw);
waitWhileDialog();
}
@ -1853,12 +1813,9 @@ void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
EVENT_HANDLER_CALLED_BY_CLIENT;
auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
adventureInt->onHeroChanged(hero);
for(auto isa : GH.listInt)
{
auto artWin = dynamic_cast<CArtifactHolder*>(isa.get());
if (artWin)
artWin->artifactAssembled(al);
}
for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
artWin->artifactAssembled(al);
}
void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
@ -1866,12 +1823,9 @@ void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
EVENT_HANDLER_CALLED_BY_CLIENT;
auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
adventureInt->onHeroChanged(hero);
for(auto isa : GH.listInt)
{
auto artWin = dynamic_cast<CArtifactHolder*>(isa.get());
if (artWin)
artWin->artifactDisassembled(al);
}
for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
artWin->artifactDisassembled(al);
}
void CPlayerInterface::waitForAllDialogs(bool unlockPim)
@ -1897,15 +1851,11 @@ bool CPlayerInterface::capturedAllEvents()
return true;
}
bool needToLockAdventureMap = adventureInt && adventureInt->active && CGI->mh->hasOngoingAnimations();
bool needToLockAdventureMap = adventureInt && adventureInt->isActive() && CGI->mh->hasOngoingAnimations();
if (ignoreEvents || needToLockAdventureMap || isAutoFightOn)
{
boost::unique_lock<boost::mutex> un(eventsM);
while(!SDLEventsQueue.empty())
{
SDLEventsQueue.pop();
}
GH.input().ignoreEventsUntilInput();
return true;
}

View File

@ -31,7 +31,7 @@ struct CPathsInfo;
VCMI_LIB_NAMESPACE_END
class CButton;
class CAdventureMapInterface;
class AdventureMapInterface;
class CCastleInterface;
class BattleInterface;
class CComponent;
@ -47,7 +47,6 @@ class KeyInterested;
class MotionInterested;
class PlayerLocalState;
class TimeInterested;
class IShowable;
namespace boost
{

View File

@ -14,6 +14,7 @@
#include "CGameInfo.h"
#include "CPlayerInterface.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h"
@ -123,7 +124,8 @@ public:
}
};
extern std::string NAME;
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
@ -324,7 +326,7 @@ void CServerHandler::applyPacksOnLobbyScreen()
packsForLobbyScreen.pop_front();
CBaseForLobbyApply * apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
apply->applyOnLobbyScreen(static_cast<CLobbyScreen *>(SEL), this, pack);
GH.totalRedraw();
GH.windows().totalRedraw();
delete pack;
}
}
@ -748,8 +750,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
while(!settings["session"]["headless"].Bool() && !dynamic_cast<CLobbyScreen *>(GH.topInt().get()))
while(!settings["session"]["headless"].Bool() && !GH.windows().topWindow<CLobbyScreen>())
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
while(!mi || mapInfo->fileURI != CSH->mi->fileURI)
{
setMapInfo(mapInfo);
@ -840,7 +843,7 @@ void CServerHandler::threadHandleConnection()
if(client)
{
state = EClientState::DISCONNECTING;
CGuiHandler::pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
}
else
{

View File

@ -12,14 +12,13 @@
#include "CMT.h"
#include "gui/CGuiHandler.h"
#include "eventsSDL/InputHandler.h"
#include "gui/FramerateManager.h"
#include "renderSDL/SDL_Extensions.h"
#include "CPlayerInterface.h"
#include "../lib/filesystem/Filesystem.h"
#include <SDL_render.h>
#include <SDL_events.h>
extern CGuiHandler GH; //global gui handler
#ifndef DISABLE_VIDEO
@ -30,18 +29,6 @@ extern "C" {
#include <libswscale/swscale.h>
}
//reads events and returns true on key down
static bool keyDown()
{
SDL_Event ev;
while(SDL_PollEvent(&ev))
{
if(ev.type == SDL_KEYDOWN || ev.type == SDL_MOUSEBUTTONDOWN)
return true;
}
return false;
}
#ifdef _MSC_VER
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
@ -370,7 +357,7 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
auto packet_duration = frame->duration;
#endif
double frameEndTime = (frame->pts + packet_duration) * av_q2d(format->streams[stream]->time_base);
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.0;
frameTime += GH.framerate().getElapsedMilliseconds() / 1000.0;
if (frameTime >= frameEndTime )
{
@ -450,21 +437,31 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
pos.x = x;
pos.y = y;
frameTime = 0.0;
while(nextFrame())
{
if(stopOnKey && keyDown())
return false;
if(stopOnKey)
{
GH.input().fetchEvents();
if(GH.input().ignoreEventsUntilInput())
return false;
}
SDL_Rect rect = CSDL_Ext::toSDL(pos);
SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
SDL_RenderPresent(mainRenderer);
// Wait 3 frames
GH.mainFPSmng->framerateDelay();
GH.mainFPSmng->framerateDelay();
GH.mainFPSmng->framerateDelay();
#if (LIBAVUTIL_VERSION_MAJOR < 58)
auto packet_duration = frame->pkt_duration;
#else
auto packet_duration = frame->duration;
#endif
double frameDurationSec = packet_duration * av_q2d(format->streams[stream]->time_base);
uint32_t timeToSleepMillisec = 1000 * (frameDurationSec);
boost::this_thread::sleep(boost::posix_time::millisec(timeToSleepMillisec));
}
return true;

View File

@ -15,9 +15,10 @@
#include "CPlayerInterface.h"
#include "CServerHandler.h"
#include "ClientNetPackVisitors.h"
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
#include "battle/BattleInterface.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "mapView/mapHandler.h"
#include "../CCallback.h"
@ -766,12 +767,8 @@ void CClient::removeGUI()
{
// CClient::endGame
GH.curInt = nullptr;
if(GH.topInt())
GH.topInt()->deactivate();
GH.windows().clear();
adventureInt.reset();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar.reset();
logGlobal->info("Removed GUI.");
LOCPLINT = nullptr;

View File

@ -16,6 +16,7 @@
#include "PlayerLocalState.h"
#include "CServerHandler.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "../lib/NetPacks.h"
#include "ClientNetPackVisitors.h"
#include "../lib/CConfigHandler.h"
@ -100,7 +101,7 @@ void ClientCommandManager::handleGoSoloCommand()
CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
}
}
GH.totalRedraw();
GH.windows().totalRedraw();
giveTurn(color);
}
session["aiSolo"].Bool() = !session["aiSolo"].Bool();
@ -141,7 +142,7 @@ void ClientCommandManager::handleControlaiCommand(std::istringstream& singleWord
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
GH.totalRedraw();
GH.windows().totalRedraw();
if(color != PlayerColor::NEUTRAL)
giveTurn(color);
}
@ -170,7 +171,7 @@ void ClientCommandManager::handleSetBattleAICommand(std::istringstream& singleWo
void ClientCommandManager::handleRedrawCommand()
{
GH.totalRedraw();
GH.windows().totalRedraw();
}
void ClientCommandManager::handleScreenCommand()
@ -193,18 +194,6 @@ void ClientCommandManager::handleNotDialogCommand()
LOCPLINT->showingDialog->setn(false);
}
void ClientCommandManager::handleGuiCommand()
{
for(const auto & child : GH.listInt)
{
const auto childPtr = child.get();
if(const CIntObject * obj = dynamic_cast<const CIntObject*>(childPtr))
printInfoAboutInterfaceObject(obj, 0);
else
printCommandMessage(std::string(typeid(childPtr).name()) + "\n");
}
}
void ClientCommandManager::handleConvertTextCommand()
{
logGlobal->info("Searching for available maps");
@ -387,7 +376,7 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
return ss.str();
};
printCommandMessage("Bonuses of " + LOCPLINT->localState->getCurrentArmy()->getObjectName() + "\n");
printCommandMessage(format(LOCPLINT->localState->getCurrentArmy()->getBonusList()) + "\n");
printCommandMessage(format(*LOCPLINT->localState->getCurrentArmy()->getAllBonuses(Selector::all, Selector::all)) + "\n");
printCommandMessage("\nInherited bonuses:\n");
TCNodes parents;
@ -492,36 +481,6 @@ void ClientCommandManager::printCommandMessage(const std::string &commandMessage
}
}
void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level)
{
std::stringstream sbuffer;
sbuffer << std::string(level, '\t');
sbuffer << typeid(*obj).name() << " *** ";
if (obj->active)
{
#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
PRINT(LCLICK, 'L');
PRINT(RCLICK, 'R');
PRINT(HOVER, 'H');
PRINT(MOVE, 'M');
PRINT(KEYBOARD, 'K');
PRINT(TIME, 'T');
PRINT(GENERAL, 'A');
PRINT(WHEEL, 'W');
PRINT(DOUBLECLICK, 'D');
#undef PRINT
}
else
sbuffer << "inactive";
sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
printCommandMessage(sbuffer.str(), ELogLevel::INFO);
for(const CIntObject *child : obj->children)
printInfoAboutInterfaceObject(child, level+1);
}
void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier)
{
YourTurn yt;
@ -574,9 +533,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
else if(commandName == "not dialog")
handleNotDialogCommand();
else if(commandName == "gui")
handleGuiCommand();
else if(message=="convert txt")
handleConvertTextCommand();

View File

@ -51,9 +51,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
// Set the state indicating if dialog box is active to "no"
void handleNotDialogCommand();
// Displays tree view of currently present VCMI common GUI elements
void handleGuiCommand();
// Dumps all game text, maps text and campaign maps text into Client log between BEGIN TEXT EXPORT and END TEXT EXPORT
void handleConvertTextCommand();
@ -92,7 +89,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
// Prints in Chat the given message
void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET);
void printInfoAboutInterfaceObject(const CIntObject *obj, int level);
void giveTurn(const PlayerColor &color);
public:

View File

@ -1,9 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@ -329,13 +329,12 @@ void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
case GiveBonus::ETarget::HERO:
{
const CGHeroInstance *h = gs.getHero(ObjectInstanceID(pack.id));
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, *h->getBonusList().back(), true);
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, true);
}
break;
case GiveBonus::ETarget::PLAYER:
{
const PlayerState *p = gs.getPlayerState(PlayerColor(pack.id));
callInterfaceIfPresent(cl, PlayerColor(pack.id), &IGameEventsReceiver::playerBonusChanged, *p->getBonusList().back(), true);
callInterfaceIfPresent(cl, PlayerColor(pack.id), &IGameEventsReceiver::playerBonusChanged, pack.bonus, true);
}
break;
}

View File

@ -21,6 +21,7 @@
#include "CServerHandler.h"
#include "CGameInfo.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "widgets/Buttons.h"
#include "widgets/TextControls.h"
@ -38,7 +39,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
{
handler.c->connectionID = pack.clientId;
if(!settings["session"]["headless"].Bool())
GH.pushIntT<CLobbyScreen>(static_cast<ESelectionScreen>(handler.screenType));
GH.windows().createAndPushWindow<CLobbyScreen>(static_cast<ESelectionScreen>(handler.screenType));
handler.state = EClientState::LOBBY;
}
}
@ -56,8 +57,8 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{
if(GH.listInt.size())
GH.popInts(1);
if(GH.windows().count() > 0)
GH.windows().popWindows(1);
}
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & pack)
@ -128,7 +129,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack
if(pack.clientId != -1 && pack.clientId != handler.c->connectionID)
return;
GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState));
GH.windows().createAndPushWindow<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState));
}
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & pack)
@ -145,7 +146,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
if(!lobby->bonusSel && handler.si->campState && handler.state == EClientState::LOBBY_CAMPAIGN)
{
lobby->bonusSel = std::make_shared<CBonusSelection>();
GH.pushInt(lobby->bonusSel);
GH.windows().pushWindow(lobby->bonusSel);
}
if(lobby->bonusSel)

View File

@ -15,7 +15,7 @@
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "CPlayerInterface.h"
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
: owner(owner)

View File

@ -0,0 +1,827 @@
/*
* AdventureMapInterface.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 "AdventureMapInterface.h"
#include "AdventureOptions.h"
#include "AdventureState.h"
#include "CInGameConsole.h"
#include "CMinimap.h"
#include "CList.h"
#include "CInfoBar.h"
#include "MapAudioPlayer.h"
#include "AdventureMapWidget.h"
#include "AdventureMapShortcuts.h"
#include "../mapView/mapHandler.h"
#include "../mapView/MapView.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../CMT.h"
#include "../PlayerLocalState.h"
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/CPathfinder.h"
#include "../../lib/mapping/CMap.h"
std::shared_ptr<AdventureMapInterface> adventureInt;
AdventureMapInterface::AdventureMapInterface():
mapAudio(new MapAudioPlayer()),
spellBeingCasted(nullptr),
scrollingWasActive(false),
scrollingWasBlocked(false)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
setMoveEventStrongInterest(true); // handle all mouse move events to prevent dead mouse move space in fullscreen mode
shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
widget = std::make_shared<AdventureMapWidget>(shortcuts);
shortcuts->setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
addUsedEvents(KEYBOARD | TIME);
}
void AdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
{
shortcuts->onMapViewMoved(visibleArea, mapLevel);
widget->getMinimap()->onMapViewMoved(visibleArea, mapLevel);
widget->onMapViewMoved(visibleArea, mapLevel);
}
void AdventureMapInterface::onAudioResumed()
{
mapAudio->onAudioResumed();
}
void AdventureMapInterface::onAudioPaused()
{
mapAudio->onAudioPaused();
}
void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
{
widget->getInfoBar()->popAll();
widget->getInfoBar()->showSelection();
}
void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
{
widget->getHeroList()->update(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
widget->updateActiveState();
}
void AdventureMapInterface::onTownChanged(const CGTownInstance * town)
{
widget->getTownList()->update(town);
if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
}
void AdventureMapInterface::showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer)
{
widget->getInfoBar()->pushComponents(components, message, timer);
}
void AdventureMapInterface::activate()
{
CIntObject::activate();
adjustActiveness();
screenBuf = screen;
if(LOCPLINT)
{
LOCPLINT->cingconsole->activate();
LOCPLINT->cingconsole->pos = this->pos;
}
GH.fakeMouseMove(); //to restore the cursor
}
void AdventureMapInterface::deactivate()
{
CIntObject::deactivate();
CCS->curh->set(Cursor::Map::POINTER);
}
void AdventureMapInterface::showAll(SDL_Surface * to)
{
CIntObject::showAll(to);
LOCPLINT->cingconsole->show(to);
}
void AdventureMapInterface::show(SDL_Surface * to)
{
CIntObject::show(to);
LOCPLINT->cingconsole->show(to);
}
void AdventureMapInterface::tick(uint32_t msPassed)
{
handleMapScrollingUpdate(msPassed);
// we want animations to be active during enemy turn but map itself to be non-interactive
// so call timer update directly on inactive element
widget->getMapView()->tick(msPassed);
}
void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed)
{
/// Width of window border, in pixels, that triggers map scrolling
static constexpr uint32_t borderScrollWidth = 15;
uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000;
Point cursorPosition = GH.getCursorPosition();
Point scrollDirection;
if (cursorPosition.x < borderScrollWidth)
scrollDirection.x = -1;
if (cursorPosition.x > GH.screenDimensions().x - borderScrollWidth)
scrollDirection.x = +1;
if (cursorPosition.y < borderScrollWidth)
scrollDirection.y = -1;
if (cursorPosition.y > GH.screenDimensions().y - borderScrollWidth)
scrollDirection.y = +1;
Point scrollDelta = scrollDirection * scrollDistance;
bool cursorInScrollArea = scrollDelta != Point(0,0);
bool scrollingActive = cursorInScrollArea && isActive() && shortcuts->optionSidePanelActive() && !scrollingWasBlocked;
bool scrollingBlocked = GH.isKeyboardCtrlDown();
if (!scrollingWasActive && scrollingBlocked)
{
scrollingWasBlocked = true;
return;
}
if (!cursorInScrollArea && scrollingWasBlocked)
{
scrollingWasBlocked = false;
return;
}
if (scrollingActive)
widget->getMapView()->onMapScrolled(scrollDelta);
if (!scrollingActive && !scrollingWasActive)
return;
if(scrollDelta.x > 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::SCROLL_EAST);
}
if(scrollDelta.x < 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::SCROLL_WEST);
}
if (scrollDelta.x == 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTH);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::POINTER);
}
scrollingWasActive = scrollingActive;
}
void AdventureMapInterface::centerOnTile(int3 on)
{
widget->getMapView()->onCenteredTile(on);
}
void AdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
{
widget->getMapView()->onCenteredObject(obj);
}
void AdventureMapInterface::keyPressed(EShortcut key)
{
if (key == EShortcut::GLOBAL_CANCEL && spellBeingCasted)
hotkeyAbortCastingMode();
//fake mouse use to trigger onTileHovered()
GH.fakeMouseMove();
}
void AdventureMapInterface::onSelectionChanged(const CArmedInstance *sel)
{
assert(sel);
widget->getInfoBar()->popAll();
mapAudio->onSelectionChanged(sel);
bool centerView = !settings["session"]["autoSkip"].Bool();
if (centerView)
centerOnObject(sel);
if(sel->ID==Obj::TOWN)
{
auto town = dynamic_cast<const CGTownInstance*>(sel);
widget->getInfoBar()->showTownSelection(town);
widget->getTownList()->select(town);
widget->getHeroList()->select(nullptr);
onHeroChanged(nullptr);
}
else //hero selected
{
auto hero = dynamic_cast<const CGHeroInstance*>(sel);
widget->getInfoBar()->showHeroSelection(hero);
widget->getHeroList()->select(hero);
widget->getTownList()->select(nullptr);
LOCPLINT->localState->verifyPath(hero);
onHeroChanged(hero);
}
widget->updateActiveState();
widget->getHeroList()->redraw();
widget->getTownList()->redraw();
}
void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
{
if (positions)
widget->getMinimap()->updateTiles(*positions);
else
widget->getMinimap()->update();
}
void AdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
{
onCurrentPlayerChanged(playerID);
setState(EAdventureState::HOTSEAT_WAIT);
}
void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
{
if(settings["session"]["spectate"].Bool())
return;
mapAudio->onEnemyTurnStarted();
widget->getMinimap()->setAIRadar(true);
widget->getInfoBar()->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
setState(EAdventureState::ENEMY_TURN);
}
void AdventureMapInterface::setState(EAdventureState state)
{
shortcuts->setState(state);
adjustActiveness();
widget->updateActiveState();
}
void AdventureMapInterface::adjustActiveness()
{
bool widgetMustBeActive = isActive() && shortcuts->optionSidePanelActive();
bool mapViewMustBeActive = isActive() && (shortcuts->optionMapViewActive());
if (widgetMustBeActive && !widget->isActive())
widget->activate();
if (!widgetMustBeActive && widget->isActive())
widget->deactivate();
if (mapViewMustBeActive && !widget->getMapView()->isActive())
widget->getMapView()->activate();
if (!mapViewMustBeActive && widget->getMapView()->isActive())
widget->getMapView()->deactivate();
}
void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
{
LOCPLINT->localState->setSelection(nullptr);
if (playerID == currentPlayerID)
return;
currentPlayerID = playerID;
widget->setPlayer(playerID);
}
void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
{
onCurrentPlayerChanged(playerID);
setState(EAdventureState::MAKING_TURN);
if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
|| settings["session"]["spectate"].Bool())
{
widget->getMinimap()->setAIRadar(false);
widget->getInfoBar()->showSelection();
}
widget->getHeroList()->update();
widget->getTownList()->update();
const CGHeroInstance * heroToSelect = nullptr;
// find first non-sleeping hero
for (auto hero : LOCPLINT->localState->getWanderingHeroes())
{
if (!LOCPLINT->localState->isHeroSleeping(hero))
{
heroToSelect = hero;
break;
}
}
//select first hero if available.
if (heroToSelect != nullptr)
{
LOCPLINT->localState->setSelection(heroToSelect);
}
else if (LOCPLINT->localState->getOwnedTowns().size())
{
LOCPLINT->localState->setSelection(LOCPLINT->localState->getOwnedTown(0));
}
else
{
LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
}
//show new day animation and sound on infobar
widget->getInfoBar()->showDate();
onHeroChanged(nullptr);
showAll(screen);
mapAudio->onPlayerTurnStarted();
if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
{
if(auto iw = GH.windows().topWindow<CInfoWindow>())
iw->close();
hotkeyEndingTurn();
}
}
void AdventureMapInterface::hotkeyEndingTurn()
{
if(settings["session"]["spectate"].Bool())
return;
LOCPLINT->makingTurn = false;
LOCPLINT->cb->endTurn();
mapAudio->onPlayerTurnEnded();
}
const CGObjectInstance* AdventureMapInterface::getActiveObject(const int3 &mapPos)
{
std::vector < const CGObjectInstance * > bobjs = LOCPLINT->cb->getBlockingObjs(mapPos); //blocking objects at tile
if (bobjs.empty())
return nullptr;
return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
}
void AdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
//FIXME: this line breaks H3 behavior for Dimension Door
if(!LOCPLINT->cb->isVisible(mapPos))
return;
if(!LOCPLINT->makingTurn)
return;
const TerrainTile *tile = LOCPLINT->cb->getTile(mapPos);
const CGObjectInstance *topBlocking = getActiveObject(mapPos);
int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(spellBeingCasted)
{
assert(shortcuts->optionSpellcasting());
if (!isInScreenRange(selPos, mapPos))
return;
const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT: //Scuttle Boat
if(topBlocking && topBlocking->ID == Obj::BOAT)
performSpellcasting(mapPos);
break;
case SpellID::DIMENSION_DOOR:
if(!tile || tile->isClear(heroTile))
performSpellcasting(mapPos);
break;
}
return;
}
//check if we can select this object
bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
bool isHero = false;
if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town)
{
if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked
LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
else if(canSelect)
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
}
else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected
{
isHero = true;
const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
if(currentHero == topBlocking) //clicked selected hero
{
LOCPLINT->openHeroWindow(currentHero);
return;
}
else if(canSelect && pn->turns == 255 ) //selectable object at inaccessible tile
{
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
return;
}
else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
{
if(LOCPLINT->localState->hasPath(currentHero) &&
LOCPLINT->localState->getPath(currentHero).endPos() == mapPos)//we'll be moving
{
if(!CGI->mh->hasOngoingAnimations())
LOCPLINT->moveHero(currentHero, LOCPLINT->localState->getPath(currentHero));
return;
}
else //remove old path and find a new one if we clicked on accessible tile
{
LOCPLINT->localState->setPath(currentHero, mapPos);
onHeroChanged(currentHero);
}
}
} //end of hero is selected "case"
else
{
throw std::runtime_error("Nothing is selected...");
}
const auto shipyard = ourInaccessibleShipyard(topBlocking);
if(isHero && shipyard != nullptr)
{
LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
}
}
void AdventureMapInterface::onTileHovered(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
//may occur just at the start of game (fake move before full intiialization)
if(!LOCPLINT->localState->getCurrentArmy())
return;
if(!LOCPLINT->cb->isVisible(mapPos))
{
CCS->curh->set(Cursor::Map::POINTER);
GH.statusbar()->clear();
return;
}
auto objRelations = PlayerRelations::ALLIES;
const CGObjectInstance *objAtTile = getActiveObject(mapPos);
if(objAtTile)
{
objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
boost::replace_all(text,"\n"," ");
GH.statusbar()->write(text);
}
else
{
std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
GH.statusbar()->write(hlp);
}
if(spellBeingCasted)
{
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT:
{
int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos))
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
else
CCS->curh->set(Cursor::Map::POINTER);
return;
}
case SpellID::DIMENSION_DOOR:
{
const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
CCS->curh->set(Cursor::Map::TELEPORT);
else
CCS->curh->set(Cursor::Map::POINTER);
return;
}
}
}
if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN)
{
if(objAtTile)
{
if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->set(Cursor::Map::POINTER);
}
else if(const CGHeroInstance * hero = LOCPLINT->localState->getCurrentHero())
{
std::array<Cursor::Map, 4> cursorMove = { Cursor::Map::T1_MOVE, Cursor::Map::T2_MOVE, Cursor::Map::T3_MOVE, Cursor::Map::T4_MOVE, };
std::array<Cursor::Map, 4> cursorAttack = { Cursor::Map::T1_ATTACK, Cursor::Map::T2_ATTACK, Cursor::Map::T3_ATTACK, Cursor::Map::T4_ATTACK, };
std::array<Cursor::Map, 4> cursorSail = { Cursor::Map::T1_SAIL, Cursor::Map::T2_SAIL, Cursor::Map::T3_SAIL, Cursor::Map::T4_SAIL, };
std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK, Cursor::Map::T2_DISEMBARK, Cursor::Map::T3_DISEMBARK, Cursor::Map::T4_DISEMBARK, };
std::array<Cursor::Map, 4> cursorExchange = { Cursor::Map::T1_EXCHANGE, Cursor::Map::T2_EXCHANGE, Cursor::Map::T3_EXCHANGE, Cursor::Map::T4_EXCHANGE, };
std::array<Cursor::Map, 4> cursorVisit = { Cursor::Map::T1_VISIT, Cursor::Map::T2_VISIT, Cursor::Map::T3_VISIT, Cursor::Map::T4_VISIT, };
std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
assert(pathNode);
if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info
{
showMoveDetailsInStatusbar(*hero, *pathNode);
}
int turns = pathNode->turns;
vstd::amin(turns, 3);
switch(pathNode->action)
{
case CGPathNode::NORMAL:
case CGPathNode::TELEPORT_NORMAL:
if(pathNode->layer == EPathfindingLayer::LAND)
CCS->curh->set(cursorMove[turns]);
else
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::VISIT:
case CGPathNode::BLOCKING_VISIT:
case CGPathNode::TELEPORT_BLOCKING_VISIT:
if(objAtTile && objAtTile->ID == Obj::HERO)
{
if(LOCPLINT->localState->getCurrentArmy() == objAtTile)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(cursorExchange[turns]);
}
else if(pathNode->layer == EPathfindingLayer::LAND)
CCS->curh->set(cursorVisit[turns]);
else
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::BATTLE:
case CGPathNode::TELEPORT_BATTLE:
CCS->curh->set(cursorAttack[turns]);
break;
case CGPathNode::EMBARK:
CCS->curh->set(cursorSail[turns]);
break;
case CGPathNode::DISEMBARK:
CCS->curh->set(cursorDisembark[turns]);
break;
default:
if(objAtTile && objRelations != PlayerRelations::ENEMIES)
{
if(objAtTile->ID == Obj::TOWN)
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->set(Cursor::Map::POINTER);
break;
}
}
if(ourInaccessibleShipyard(objAtTile))
{
CCS->curh->set(Cursor::Map::T1_SAIL);
}
}
void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
{
const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
GH.statusbar()->write(result);
}
void AdventureMapInterface::onTileRightClicked(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
if(spellBeingCasted)
{
hotkeyAbortCastingMode();
return;
}
if(!LOCPLINT->cb->isVisible(mapPos))
{
CRClickPopup::createAndPush(VLC->generaltexth->allTexts[61]); //Uncharted Territory
return;
}
const CGObjectInstance * obj = getActiveObject(mapPos);
if(!obj)
{
// Bare or undiscovered terrain
const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
if(tile)
{
std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
CRClickPopup::createAndPush(hlp);
}
return;
}
CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
}
void AdventureMapInterface::enterCastingMode(const CSpell * sp)
{
assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR);
spellBeingCasted = sp;
Settings config = settings.write["session"]["showSpellRange"];
config->Bool() = true;
setState(EAdventureState::CASTING_SPELL);
}
void AdventureMapInterface::exitCastingMode()
{
assert(spellBeingCasted);
spellBeingCasted = nullptr;
setState(EAdventureState::MAKING_TURN);
Settings config = settings.write["session"]["showSpellRange"];
config->Bool() = false;
}
void AdventureMapInterface::hotkeyAbortCastingMode()
{
exitCastingMode();
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
}
void AdventureMapInterface::performSpellcasting(const int3 & dest)
{
SpellID id = spellBeingCasted->id;
exitCastingMode();
LOCPLINT->cb->castSpell(LOCPLINT->localState->getCurrentHero(), id, dest);
}
Rect AdventureMapInterface::terrainAreaPixels() const
{
return widget->getMapView()->pos;
}
const IShipyard * AdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
{
const IShipyard *ret = IShipyard::castFrom(obj);
if(!ret ||
obj->tempOwner != currentPlayerID ||
(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
return nullptr;
return ret;
}
void AdventureMapInterface::hotkeyExitWorldView()
{
setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
}
void AdventureMapInterface::openWorldView(int tileSize)
{
setState(EAdventureState::WORLD_VIEW);
widget->getMapView()->onViewWorldActivated(tileSize);
}
void AdventureMapInterface::openWorldView()
{
openWorldView(11);
}
void AdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
{
openWorldView(11);
widget->getMapView()->onViewSpellActivated(11, objectPositions, showTerrain);
}
void AdventureMapInterface::hotkeyNextTown()
{
widget->getTownList()->selectNext();
}
void AdventureMapInterface::hotkeySwitchMapLevel()
{
widget->getMapView()->onMapLevelSwitched();
}
void AdventureMapInterface::hotkeyZoom(int delta)
{
widget->getMapView()->onMapZoomLevelChanged(delta);
}
void AdventureMapInterface::onScreenResize()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
widget.reset();
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
widget = std::make_shared<AdventureMapWidget>(shortcuts);
widget->getMapView()->onViewMapActivated();
widget->setPlayer(currentPlayerID);
widget->updateActiveState();
widget->getMinimap()->update();
widget->getInfoBar()->showSelection();
adjustActiveness();
}

View File

@ -1,5 +1,5 @@
/*
* CAdvMapInt.h, part of VCMI engine
* AdventureMapInterface.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -29,8 +29,8 @@ class CButton;
class IImage;
class CAnimImage;
class CGStatusBar;
class CAdvMapPanel;
class CAdvMapWorldViewPanel;
class AdventureMapWidget;
class AdventureMapShortcuts;
class CAnimation;
class MapView;
class CResDataBar;
@ -39,122 +39,77 @@ class CTownList;
class CInfoBar;
class CMinimap;
class MapAudioPlayer;
enum class EAdventureState;
struct MapDrawingInfo;
/// That's a huge class which handles general adventure map actions and
/// shows the right menu(questlog, spellbook, end turn,..) from where you
/// can get to the towns and heroes.
class CAdventureMapInterface : public CIntObject
class AdventureMapInterface : public CIntObject
{
private:
enum class EGameState
{
NOT_INITIALIZED,
HOTSEAT_WAIT,
MAKING_TURN,
ENEMY_TURN,
WORLD_VIEW
};
EGameState state;
/// currently acting player
PlayerColor currentPlayerID;
/// uses EDirections enum
bool scrollingCursorSet;
/// if true, cursor was changed to scrolling and must be reset back once scroll is over
bool scrollingWasActive;
const CSpell *spellBeingCasted; //nullptr if none
/// if true, then scrolling was blocked via ctrl and should not restart until player move cursor outside scrolling area
bool scrollingWasBlocked;
std::vector<std::shared_ptr<CAnimImage>> gems;
std::shared_ptr<IImage> bg;
std::shared_ptr<IImage> bgWorldView;
std::shared_ptr<CButton> kingOverview;
std::shared_ptr<CButton> sleepWake;
std::shared_ptr<CButton> underground;
std::shared_ptr<CButton> questlog;
std::shared_ptr<CButton> moveHero;
std::shared_ptr<CButton> spellbook;
std::shared_ptr<CButton> advOptions;
std::shared_ptr<CButton> sysOptions;
std::shared_ptr<CButton> nextHero;
std::shared_ptr<CButton> endTurn;
std::shared_ptr<CButton> worldViewUnderground;
std::shared_ptr<MapView> terrain;
std::shared_ptr<CMinimap> minimap;
std::shared_ptr<CHeroList> heroList;
std::shared_ptr<CTownList> townList;
std::shared_ptr<CInfoBar> infoBar;
std::shared_ptr<CGStatusBar> statusbar;
std::shared_ptr<CResDataBar> resdatabar;
std::shared_ptr<CAdvMapPanel> panelMain; // panel that holds all right-side buttons in normal view
std::shared_ptr<CAdvMapWorldViewPanel> panelWorldView; // panel that holds all buttons and other ui in world view
std::shared_ptr<CAdvMapPanel> activeMapPanel; // currently active panel (either main or world view, depending on current mode)
std::shared_ptr<CAnimation> worldViewIcons;// images for world view overlay
/// spell for which player is selecting target, or nullptr if none
const CSpell *spellBeingCasted;
std::shared_ptr<MapAudioPlayer> mapAudio;
std::shared_ptr<AdventureMapWidget> widget;
std::shared_ptr<AdventureMapShortcuts> shortcuts;
private:
//functions bound to buttons
void fshowOverview();
void fworldViewBack();
void fworldViewScale1x();
void fworldViewScale2x();
void fworldViewScale4x();
void fswitchLevel();
void fshowQuestlog();
void fsleepWake();
void fmoveHero();
void fshowSpellbok();
void fadventureOPtions();
void fsystemOptions();
void fnextHero();
void fendTurn();
void setState(EAdventureState state);
void hotkeyMoveHeroDirectional(Point direction);
/// updates active state of game window whenever game state changes
void adjustActiveness();
bool isActive();
void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
/// checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const;
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const; //checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
// update locked state of buttons
void updateButtons();
void handleMapScrollingUpdate();
/// check and if necessary reacts on scrolling by moving cursor to screen edge
void handleMapScrollingUpdate(uint32_t msPassed);
void showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode);
const CGObjectInstance *getActiveObject(const int3 &tile);
std::optional<Point> keyToMoveDirection(EShortcut key);
void endingTurn();
/// exits currently opened world view mode and returns to normal map
void exitWorldView();
void exitCastingMode();
void leaveCastingMode(const int3 & castTarget);
void abortCastingMode();
/// casts current spell at specified location
void performSpellcasting(const int3 & castTarget);
protected:
// CIntObject interface implementation
/// CIntObject interface implementation
void activate() override;
void deactivate() override;
void tick(uint32_t msPassed) override;
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
void keyPressed(EShortcut key) override;
void onScreenResize() override;
public:
CAdventureMapInterface();
AdventureMapInterface();
void hotkeyAbortCastingMode();
void hotkeyExitWorldView();
void hotkeyEndingTurn();
void hotkeyNextTown();
void hotkeySwitchMapLevel();
void hotkeyZoom(int delta);
/// Called by PlayerInterface when specified player is ready to start his turn
void onHotseatWaitStarted(PlayerColor playerID);
@ -225,4 +180,4 @@ public:
void openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain);
};
extern std::shared_ptr<CAdventureMapInterface> adventureInt;
extern std::shared_ptr<AdventureMapInterface> adventureInt;

View File

@ -0,0 +1,455 @@
/*
* AdventureMapShortcuts.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 "AdventureMapShortcuts.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../lobby/CSavingScreen.h"
#include "../mapView/mapHandler.h"
#include "../windows/CKingdomInterface.h"
#include "../windows/CSpellWindow.h"
#include "../windows/CTradeWindow.h"
#include "../windows/settings/SettingsMainWindow.h"
#include "AdventureMapInterface.h"
#include "AdventureOptions.h"
#include "AdventureState.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CPathfinder.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapping/CMap.h"
AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner)
: owner(owner)
, state(EAdventureState::NOT_INITIALIZED)
, mapLevel(0)
{}
void AdventureMapShortcuts::setState(EAdventureState newState)
{
state = newState;
}
void AdventureMapShortcuts::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
{
mapLevel = newMapLevel;
}
std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts()
{
std::vector<AdventureMapShortcutState> result = {
{ EShortcut::ADVENTURE_KINGDOM_OVERVIEW, optionInMapView(), [this]() { this->showOverview(); } },
{ EShortcut::ADVENTURE_EXIT_WORLD_VIEW, optionInWorldView(), [this]() { this->worldViewBack(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD, optionInMapView(), [this]() { this->worldViewScale1x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X1, optionInWorldView(), [this]() { this->worldViewScale1x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X2, optionInWorldView(), [this]() { this->worldViewScale2x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X4, optionInWorldView(), [this]() { this->worldViewScale4x(); } },
{ EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, optionCanToggleLevel(), [this]() { this->switchMapLevel(); } },
{ EShortcut::ADVENTURE_QUEST_LOG, optionCanViewQuests(), [this]() { this->showQuestlog(); } },
{ EShortcut::ADVENTURE_TOGGLE_SLEEP, optionHeroSelected(), [this]() { this->toggleSleepWake(); } },
{ EShortcut::ADVENTURE_SET_HERO_ASLEEP, optionHeroAwake(), [this]() { this->setHeroSleeping(); } },
{ EShortcut::ADVENTURE_SET_HERO_AWAKE, optionHeroSleeping(), [this]() { this->setHeroAwake(); } },
{ EShortcut::ADVENTURE_MOVE_HERO, optionHeroCanMove(), [this]() { this->moveHeroAlongPath(); } },
{ 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_NEXT_HERO, optionHasNextHero(), [this]() { this->nextHero(); } },
{ EShortcut::GAME_END_TURN, optionInMapView(), [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_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, optionHeroSelected(), [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_ZOOM_RESET, optionSidePanelActive(),[this]() { this->zoom( 0); } },
{ 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}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_SS, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, +1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_SE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, +1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_WW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, 0}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_EE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, 0}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, -1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NN, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, -1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } }
};
return result;
}
void AdventureMapShortcuts::showOverview()
{
GH.windows().createAndPushWindow<CKingdomInterface>();
}
void AdventureMapShortcuts::worldViewBack()
{
owner.hotkeyExitWorldView();
auto hero = LOCPLINT->localState->getCurrentHero();
if (hero)
owner.centerOnObject(hero);
}
void AdventureMapShortcuts::worldViewScale1x()
{
// TODO set corresponding scale button to "selected" mode
owner.openWorldView(7);
}
void AdventureMapShortcuts::worldViewScale2x()
{
owner.openWorldView(11);
}
void AdventureMapShortcuts::worldViewScale4x()
{
owner.openWorldView(16);
}
void AdventureMapShortcuts::switchMapLevel()
{
int maxLevels = LOCPLINT->cb->getMapSize().z;
if (maxLevels < 2)
return;
owner.hotkeySwitchMapLevel();
}
void AdventureMapShortcuts::showQuestlog()
{
LOCPLINT->showQuestLog();
}
void AdventureMapShortcuts::toggleSleepWake()
{
if (!optionHeroSelected())
return;
if (optionHeroAwake())
setHeroSleeping();
else
setHeroAwake();
}
void AdventureMapShortcuts::setHeroSleeping()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (h)
{
LOCPLINT->localState->setHeroAsleep(h);
owner.onHeroChanged(h);
nextHero();
}
}
void AdventureMapShortcuts::setHeroAwake()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (h)
{
LOCPLINT->localState->setHeroAwaken(h);
owner.onHeroChanged(h);
}
}
void AdventureMapShortcuts::moveHeroAlongPath()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (!h || !LOCPLINT->localState->hasPath(h))
return;
LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
}
void AdventureMapShortcuts::showSpellbook()
{
if (!LOCPLINT->localState->getCurrentHero())
return;
owner.centerOnObject(LOCPLINT->localState->getCurrentHero());
GH.windows().createAndPushWindow<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
}
void AdventureMapShortcuts::adventureOptions()
{
GH.windows().createAndPushWindow<AdventureOptions>();
}
void AdventureMapShortcuts::systemOptions()
{
GH.windows().createAndPushWindow<SettingsMainWindow>();
}
void AdventureMapShortcuts::nextHero()
{
const auto * currHero = LOCPLINT->localState->getCurrentHero();
const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
if (nextHero)
{
LOCPLINT->localState->setSelection(nextHero);
owner.centerOnObject(nextHero);
}
}
void AdventureMapShortcuts::endTurn()
{
if(!LOCPLINT->makingTurn)
return;
if(settings["adventure"]["heroReminder"].Bool())
{
for(auto hero : LOCPLINT->localState->getWanderingHeroes())
{
if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
{
// Only show hero reminder if conditions met:
// - There still movement points
// - Hero don't have a path or there not points for first step on path
LOCPLINT->localState->verifyPath(hero);
if(!LOCPLINT->localState->hasPath(hero))
{
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
return;
}
auto path = LOCPLINT->localState->getPath(hero);
if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
{
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
return;
}
}
}
}
owner.hotkeyEndingTurn();
}
void AdventureMapShortcuts::showThievesGuild()
{
//find first town with tavern
auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
{
return town->hasBuilt(BuildingID::TAVERN);
});
if(itr != LOCPLINT->localState->getOwnedTowns().end())
LOCPLINT->showThievesGuildWindow(*itr);
else
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
}
void AdventureMapShortcuts::showScenarioInfo()
{
AdventureOptions::showScenarioInfo();
}
void AdventureMapShortcuts::saveGame()
{
GH.windows().createAndPushWindow<CSavingScreen>();
}
void AdventureMapShortcuts::loadGame()
{
LOCPLINT->proposeLoadingGame();
}
void AdventureMapShortcuts::digGrail()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if(h && LOCPLINT->makingTurn)
LOCPLINT->tryDiggging(h);
}
void AdventureMapShortcuts::viewPuzzleMap()
{
LOCPLINT->showPuzzleMap();
}
void AdventureMapShortcuts::restartGame()
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
}
void AdventureMapShortcuts::visitObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if(h)
LOCPLINT->cb->moveHero(h,h->pos);
}
void AdventureMapShortcuts::openObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
if(h)
LOCPLINT->openHeroWindow(h);
if(t)
LOCPLINT->openTownWindow(t);
}
void AdventureMapShortcuts::showMarketplace()
{
//check if we have any marketplace
const CGTownInstance *townWithMarket = nullptr;
for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
{
if(t->hasBuilt(BuildingID::MARKETPLACE))
{
townWithMarket = t;
break;
}
}
if(townWithMarket) //if any town has marketplace, open window
GH.windows().createAndPushWindow<CMarketplaceWindow>(townWithMarket);
else //if not - complain
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
}
void AdventureMapShortcuts::nextTown()
{
owner.hotkeyNextTown();
}
void AdventureMapShortcuts::zoom( int distance)
{
owner.hotkeyZoom(distance);
}
void AdventureMapShortcuts::nextObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
if(h)
nextHero();
if(t)
nextTown();
}
void AdventureMapShortcuts::moveHeroDirectional(const Point & direction)
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
if(!h)
return;
if (CGI->mh->hasOngoingAnimations())
return;
int3 dst = h->visitablePos() + int3(direction.x, direction.y, 0);
if (!CGI->mh->isInMap((dst)))
return;
if ( !LOCPLINT->localState->setPath(h, dst))
return;
const CGPath & path = LOCPLINT->localState->getPath(h);
if (path.nodes.size() > 2)
owner.onHeroChanged(h);
else
if(path.nodes[0].turns == 0)
LOCPLINT->moveHero(h, path);
}
bool AdventureMapShortcuts::optionCanViewQuests()
{
return optionInMapView() && CGI->mh->getMap()->quests.empty();
}
bool AdventureMapShortcuts::optionCanToggleLevel()
{
return optionInMapView() && LOCPLINT->cb->getMapSize().z > 0;
}
bool AdventureMapShortcuts::optionMapLevelSurface()
{
return mapLevel == 0;
}
bool AdventureMapShortcuts::optionHeroSleeping()
{
const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && LOCPLINT->localState->isHeroSleeping(hero);
}
bool AdventureMapShortcuts::optionHeroAwake()
{
const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && !LOCPLINT->localState->isHeroSleeping(hero);
}
bool AdventureMapShortcuts::optionHeroSelected()
{
return optionInMapView() && LOCPLINT->localState->getCurrentHero() != nullptr;
}
bool AdventureMapShortcuts::optionHeroCanMove()
{
const auto * hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && hero->movement != 0 && LOCPLINT->localState->hasPath(hero);
}
bool AdventureMapShortcuts::optionHasNextHero()
{
const auto * hero = LOCPLINT->localState->getCurrentHero();
const auto * nextSuitableHero = LOCPLINT->localState->getNextWanderingHero(hero);
return optionInMapView() && nextSuitableHero != nullptr;
}
bool AdventureMapShortcuts::optionSpellcasting()
{
return state == EAdventureState::CASTING_SPELL;
}
bool AdventureMapShortcuts::optionInMapView()
{
return state == EAdventureState::MAKING_TURN;
}
bool AdventureMapShortcuts::optionInWorldView()
{
return state == EAdventureState::WORLD_VIEW;
}
bool AdventureMapShortcuts::optionSidePanelActive()
{
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
}
bool AdventureMapShortcuts::optionMapViewActive()
{
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL;
}

View File

@ -0,0 +1,88 @@
/*
* AdventureMapShortcuts.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
VCMI_LIB_NAMESPACE_BEGIN
class Point;
class Rect;
VCMI_LIB_NAMESPACE_END
enum class EShortcut;
class AdventureMapInterface;
enum class EAdventureState;
struct AdventureMapShortcutState
{
EShortcut shortcut;
bool isEnabled;
std::function<void()> callback;
};
/// Class that contains list of functions for shortcuts available from adventure map
class AdventureMapShortcuts
{
AdventureMapInterface & owner;
EAdventureState state;
int mapLevel;
void showOverview();
void worldViewBack();
void worldViewScale1x();
void worldViewScale2x();
void worldViewScale4x();
void switchMapLevel();
void showQuestlog();
void toggleSleepWake();
void setHeroSleeping();
void setHeroAwake();
void moveHeroAlongPath();
void showSpellbook();
void adventureOptions();
void systemOptions();
void nextHero();
void endTurn();
void showThievesGuild();
void showScenarioInfo();
void saveGame();
void loadGame();
void digGrail();
void viewPuzzleMap();
void restartGame();
void visitObject();
void openObject();
void showMarketplace();
void nextTown();
void nextObject();
void zoom( int distance);
void moveHeroDirectional(const Point & direction);
public:
explicit AdventureMapShortcuts(AdventureMapInterface & owner);
std::vector<AdventureMapShortcutState> getShortcuts();
bool optionCanViewQuests();
bool optionCanToggleLevel();
bool optionMapLevelSurface();
bool optionHeroSleeping();
bool optionHeroAwake();
bool optionHeroSelected();
bool optionHeroCanMove();
bool optionHasNextHero();
bool optionSpellcasting();
bool optionInMapView();
bool optionInWorldView();
bool optionSidePanelActive();
bool optionMapViewActive();
void setState(EAdventureState newState);
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
};

View File

@ -0,0 +1,455 @@
/*
* CAdventureMapWidget.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 "AdventureMapWidget.h"
#include "AdventureMapShortcuts.h"
#include "CInfoBar.h"
#include "CList.h"
#include "CMinimap.h"
#include "CResDataBar.h"
#include "AdventureState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../mapView/MapView.h"
#include "../render/CAnimation.h"
#include "../render/IImage.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../../lib/StringConstants.h"
#include "../../lib/filesystem/ResourceID.h"
AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
: shortcuts(shortcuts)
, mapLevel(0)
{
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
REGISTER_BUILDER("adventureInfobar", &AdventureMapWidget::buildInfobox );
REGISTER_BUILDER("adventureMapImage", &AdventureMapWidget::buildMapImage );
REGISTER_BUILDER("adventureMapButton", &AdventureMapWidget::buildMapButton );
REGISTER_BUILDER("adventureMapContainer", &AdventureMapWidget::buildMapContainer );
REGISTER_BUILDER("adventureMapGameArea", &AdventureMapWidget::buildMapGameArea );
REGISTER_BUILDER("adventureMapHeroList", &AdventureMapWidget::buildMapHeroList );
REGISTER_BUILDER("adventureMapIcon", &AdventureMapWidget::buildMapIcon );
REGISTER_BUILDER("adventureMapTownList", &AdventureMapWidget::buildMapTownList );
REGISTER_BUILDER("adventureMinimap", &AdventureMapWidget::buildMinimap );
REGISTER_BUILDER("adventureResourceDateBar", &AdventureMapWidget::buildResourceDateBar );
REGISTER_BUILDER("adventureStatusBar", &AdventureMapWidget::buildStatusBar );
REGISTER_BUILDER("adventurePlayerTexture", &AdventureMapWidget::buildTexturePlayerColored);
for (const auto & entry : shortcuts->getShortcuts())
addShortcut(entry.shortcut, entry.callback);
const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
{
ResourceID resourceName(entry.String(), EResType::IMAGE);
playerColorerImages.push_back(resourceName.getName());
}
build(config);
addUsedEvents(KEYBOARD);
}
void AdventureMapWidget::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
{
if(mapLevel == newMapLevel)
return;
mapLevel = newMapLevel;
updateActiveState();
}
Rect AdventureMapWidget::readSourceArea(const JsonNode & source, const JsonNode & sourceCommon)
{
const auto & input = source.isNull() ? sourceCommon : source;
return readArea(input, Rect(Point(0, 0), Point(800, 600)));
}
Rect AdventureMapWidget::readTargetArea(const JsonNode & source)
{
if(subwidgetSizes.empty())
return readArea(source, pos);
return readArea(source, subwidgetSizes.back());
}
Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & boundingBox)
{
const auto & object = source.Struct();
if(object.count("left") + object.count("width") + object.count("right") != 2)
logGlobal->error("Invalid area definition in widget! Unable to load width!");
if(object.count("top") + object.count("height") + object.count("bottom") != 2)
logGlobal->error("Invalid area definition in widget! Unable to load height!");
int left = source["left"].Integer();
int width = source["width"].Integer();
int right = source["right"].Integer();
int top = source["top"].Integer();
int height = source["height"].Integer();
int bottom = source["bottom"].Integer();
Point topLeft(left, top);
Point dimensions(width, height);
if(source["left"].isNull())
topLeft.x = boundingBox.w - right - width;
if(source["width"].isNull())
dimensions.x = boundingBox.w - right - left;
if(source["top"].isNull())
topLeft.y = boundingBox.h - bottom - height;
if(source["height"].isNull())
dimensions.y = boundingBox.h - bottom - top;
return Rect(topLeft + boundingBox.topLeft(), dimensions);
}
std::shared_ptr<IImage> AdventureMapWidget::loadImage(const std::string & name)
{
ResourceID resource(name, EResType::IMAGE);
if(images.count(resource.getName()) == 0)
images[resource.getName()] = IImage::createFromFile(resource.getName());
return images[resource.getName()];
}
std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const std::string & name)
{
ResourceID resource(name, EResType::ANIMATION);
if(animations.count(resource.getName()) == 0)
animations[resource.getName()] = std::make_shared<CAnimation>(resource.getName());
return animations[resource.getName()];
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
infoBar = std::make_shared<CInfoBar>(area);
return infoBar;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & input)
{
Rect targetArea = readTargetArea(input["area"]);
Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
std::string image = input["image"].String();
return std::make_shared<CFilledTexture>(loadImage(image), targetArea, sourceArea);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
{
auto position = readTargetArea(input["area"]);
auto image = input["image"].String();
auto help = readHintText(input["help"]);
bool playerColored = input["playerColored"].Bool();
auto button = std::make_shared<CButton>(position.topLeft(), image, help, 0, EShortcut::NONE, playerColored);
loadButtonBorderColor(button, input["borderColor"]);
loadButtonHotkey(button, input["hotkey"]);
return button;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapContainer(const JsonNode & input)
{
auto position = readTargetArea(input["area"]);
std::shared_ptr<CAdventureMapContainerWidget> result;
if (!input["exists"].isNull())
{
if (!input["exists"]["heightMin"].isNull() && input["exists"]["heightMin"].Integer() >= pos.h)
return nullptr;
if (!input["exists"]["heightMax"].isNull() && input["exists"]["heightMax"].Integer() < pos.h)
return nullptr;
if (!input["exists"]["widthMin"].isNull() && input["exists"]["widthMin"].Integer() >= pos.w)
return nullptr;
if (!input["exists"]["widthMax"].isNull() && input["exists"]["widthMax"].Integer() < pos.w)
return nullptr;
}
if (input["overlay"].Bool())
result = std::make_shared<CAdventureMapOverlayWidget>();
else
result = std::make_shared<CAdventureMapContainerWidget>();
result->disableCondition = input["hideWhen"].String();
result->moveBy(position.topLeft());
subwidgetSizes.push_back(position);
for(const auto & entry : input["items"].Vector())
{
auto widget = buildWidget(entry);
addWidget(entry["name"].String(), widget);
result->ownedChildren.push_back(widget);
// FIXME: remove cast and replace it with better check
if (std::dynamic_pointer_cast<CLabel>(widget) || std::dynamic_pointer_cast<CLabelGroup>(widget))
result->addChild(widget.get(), true);
else
result->addChild(widget.get(), false);
}
subwidgetSizes.pop_back();
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapGameArea(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
mapView = std::make_shared<MapView>(area.topLeft(), area.dimensions());
return mapView;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapHeroList(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
subwidgetSizes.push_back(area);
Rect item = readTargetArea(input["item"]);
Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
int itemsCount = input["itemsCount"].Integer();
auto result = std::make_shared<CHeroList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getWanderingHeroes().size());
if(!input["scrollUp"].isNull())
result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
if(!input["scrollDown"].isNull())
result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
subwidgetSizes.pop_back();
heroList = result;
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
size_t index = input["index"].Integer();
size_t perPlayer = input["perPlayer"].Integer();
std::string image = input["image"].String();
return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(image), index, perPlayer);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
subwidgetSizes.push_back(area);
Rect item = readTargetArea(input["item"]);
Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
int itemsCount = input["itemsCount"].Integer();
auto result = std::make_shared<CTownList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getOwnedTowns().size());
if(!input["scrollUp"].isNull())
result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
if(!input["scrollDown"].isNull())
result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
subwidgetSizes.pop_back();
townList = result;
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMinimap(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
minimap = std::make_shared<CMinimap>(area);
return minimap;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
std::string image = input["image"].String();
auto result = std::make_shared<CResDataBar>(image, area.topLeft());
for(auto i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
{
const auto & node = input[GameConstants::RESOURCE_NAMES[i]];
if(node.isNull())
continue;
result->setResourcePosition(GameResID(i), Point(node["x"].Integer(), node["y"].Integer()));
}
result->setDatePosition(Point(input["date"]["x"].Integer(), input["date"]["y"].Integer()));
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
std::string image = input["image"].String();
auto background = std::make_shared<CFilledTexture>(image, area);
return CGStatusBar::create(background);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
{
logGlobal->debug("Building widget CFilledTexture");
auto image = input["image"].String();
Rect area = readTargetArea(input["area"]);
return std::make_shared<FilledTexturePlayerColored>(image, area);
}
std::shared_ptr<CHeroList> AdventureMapWidget::getHeroList()
{
return heroList;
}
std::shared_ptr<CTownList> AdventureMapWidget::getTownList()
{
return townList;
}
std::shared_ptr<CMinimap> AdventureMapWidget::getMinimap()
{
return minimap;
}
std::shared_ptr<MapView> AdventureMapWidget::getMapView()
{
return mapView;
}
std::shared_ptr<CInfoBar> AdventureMapWidget::getInfoBar()
{
return infoBar;
}
void AdventureMapWidget::setPlayer(const PlayerColor & player)
{
setPlayerChildren(this, player);
}
void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColor & player)
{
for(auto & entry : widget->children)
{
auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
auto icon = dynamic_cast<CAdventureMapIcon *>(entry);
auto button = dynamic_cast<CButton *>(entry);
auto texture = dynamic_cast<FilledTexturePlayerColored *>(entry);
if(button)
button->setPlayerColor(player);
if(icon)
icon->setPlayer(player);
if(container)
setPlayerChildren(container, player);
if (texture)
texture->playerColored(player);
}
for(const auto & entry : playerColorerImages)
{
if(images.count(entry))
images[entry]->playerColored(player);
}
redraw();
}
CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> animation, size_t index, size_t iconsPerPlayer)
: index(index)
, iconsPerPlayer(iconsPerPlayer)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos += position;
image = std::make_shared<CAnimImage>(animation, index);
}
void CAdventureMapIcon::setPlayer(const PlayerColor & player)
{
image->setFrame(index + player.getNum() * iconsPerPlayer);
}
void CAdventureMapOverlayWidget::show(SDL_Surface * to)
{
CIntObject::showAll(to);
}
void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget)
{
for(auto & entry : widget->children)
{
auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
if (container)
{
if (container->disableCondition == "heroAwake")
container->setEnabled(!shortcuts->optionHeroSleeping());
if (container->disableCondition == "heroSleeping")
container->setEnabled(shortcuts->optionHeroSleeping());
if (container->disableCondition == "mapLayerSurface")
container->setEnabled(shortcuts->optionMapLevelSurface());
if (container->disableCondition == "mapLayerUnderground")
container->setEnabled(!shortcuts->optionMapLevelSurface());
if (container->disableCondition == "mapViewMode")
container->setEnabled(shortcuts->optionInWorldView());
if (container->disableCondition == "worldViewMode")
container->setEnabled(!shortcuts->optionInWorldView());
updateActiveStateChildden(container);
}
}
}
void AdventureMapWidget::updateActiveState()
{
updateActiveStateChildden(this);
for (auto entry: shortcuts->getShortcuts())
setShortcutBlocked(entry.shortcut, !entry.isEnabled);
}

View File

@ -0,0 +1,109 @@
/*
* CAdventureMapWidget.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 "../gui/InterfaceObjectConfigurable.h"
class CHeroList;
class CTownList;
class CMinimap;
class MapView;
class CInfoBar;
class IImage;
class AdventureMapShortcuts;
enum class EAdventureState;
/// Internal class of AdventureMapInterface that contains actual UI elements
class AdventureMapWidget : public InterfaceObjectConfigurable
{
int mapLevel;
/// temporary stack of sizes of currently building widgets
std::vector<Rect> subwidgetSizes;
/// list of images on which player-colored palette will be applied
std::vector<std::string> playerColorerImages;
/// list of named images shared between widgets
std::map<std::string, std::shared_ptr<IImage>> images;
std::map<std::string, std::shared_ptr<CAnimation>> animations;
/// Widgets that require access from adventure map
std::shared_ptr<CHeroList> heroList;
std::shared_ptr<CTownList> townList;
std::shared_ptr<CMinimap> minimap;
std::shared_ptr<MapView> mapView;
std::shared_ptr<CInfoBar> infoBar;
std::shared_ptr<AdventureMapShortcuts> shortcuts;
Rect readTargetArea(const JsonNode & source);
Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
Rect readArea(const JsonNode & source, const Rect & boundingBox);
std::shared_ptr<IImage> loadImage(const std::string & name);
std::shared_ptr<CAnimation> loadAnimation(const std::string & name);
std::shared_ptr<CIntObject> buildInfobox(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapImage(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapButton(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapContainer(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapGameArea(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapHeroList(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapIcon(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapTownList(const JsonNode & input);
std::shared_ptr<CIntObject> buildMinimap(const JsonNode & input);
std::shared_ptr<CIntObject> buildResourceDateBar(const JsonNode & input);
std::shared_ptr<CIntObject> buildStatusBar(const JsonNode & input);
std::shared_ptr<CIntObject> buildTexturePlayerColored(const JsonNode &);
void setPlayerChildren(CIntObject * widget, const PlayerColor & player);
void updateActiveStateChildden(CIntObject * widget);
public:
explicit AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts );
std::shared_ptr<CHeroList> getHeroList();
std::shared_ptr<CTownList> getTownList();
std::shared_ptr<CMinimap> getMinimap();
std::shared_ptr<MapView> getMapView();
std::shared_ptr<CInfoBar> getInfoBar();
void setPlayer(const PlayerColor & player);
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
void updateActiveState();
};
/// Small helper class that provides ownership for shared_ptr's of child elements
class CAdventureMapContainerWidget : public CIntObject
{
friend class AdventureMapWidget;
std::vector<std::shared_ptr<CIntObject>> ownedChildren;
std::string disableCondition;
};
class CAdventureMapOverlayWidget : public CAdventureMapContainerWidget
{
public:
void show(SDL_Surface * to) override;
};
/// Small helper class that provides player-colorable icon using animation file
class CAdventureMapIcon : public CIntObject
{
std::shared_ptr<CAnimImage> image;
size_t index;
size_t iconsPerPlayer;
public:
CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> image, size_t index, size_t iconsPerPlayer);
void setPlayer(const PlayerColor & player);
};

View File

@ -9,7 +9,7 @@
*/
#include "StdInc.h"
#include "CAdventureOptions.h"
#include "AdventureOptions.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
@ -17,13 +17,14 @@
#include "../lobby/CCampaignInfoScreen.h"
#include "../lobby/CScenarioInfoScreen.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../gui/Shortcut.h"
#include "../widgets/Buttons.h"
#include "../../CCallback.h"
#include "../../lib/StartInfo.h"
CAdventureOptions::CAdventureOptions()
AdventureOptions::AdventureOptions()
: CWindowObject(PLAYER_COLORED, "ADVOPTS")
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -31,10 +32,10 @@ CAdventureOptions::CAdventureOptions()
viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
scenInfo->addCallback(AdventureOptions::showScenarioInfo);
puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
@ -46,15 +47,15 @@ CAdventureOptions::CAdventureOptions()
dig->block(true);
}
void CAdventureOptions::showScenarioInfo()
void AdventureOptions::showScenarioInfo()
{
if(LOCPLINT->cb->getStartInfo()->campState)
{
GH.pushIntT<CCampaignInfoScreen>();
GH.windows().createAndPushWindow<CCampaignInfoScreen>();
}
else
{
GH.pushIntT<CScenarioInfoScreen>();
GH.windows().createAndPushWindow<CScenarioInfoScreen>();
}
}

View File

@ -14,7 +14,7 @@
class CButton;
/// Adventure options dialog where you can view the world, dig, play the replay of the last turn,...
class CAdventureOptions : public CWindowObject
class AdventureOptions : public CWindowObject
{
std::shared_ptr<CButton> exit;
std::shared_ptr<CButton> viewWorld;
@ -24,7 +24,7 @@ class CAdventureOptions : public CWindowObject
/*std::shared_ptr<CButton> replay*/
public:
CAdventureOptions();
AdventureOptions();
static void showScenarioInfo();
};

View File

@ -0,0 +1,20 @@
/*
* AdventureState.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
enum class EAdventureState
{
NOT_INITIALIZED,
HOTSEAT_WAIT,
MAKING_TURN,
ENEMY_TURN,
CASTING_SPELL,
WORLD_VIEW
};

View File

@ -1,94 +0,0 @@
/*
* CAdvMapPanel.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 "CAdvMapPanel.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../render/CAnimation.h"
#include "../render/IImage.h"
#include "../gui/CGuiHandler.h"
CAdvMapPanel::CAdvMapPanel(std::shared_ptr<IImage> bg, Point position)
: CIntObject()
, background(bg)
{
defActions = 255;
recActions = 255;
pos.x += position.x;
pos.y += position.y;
if (bg)
{
pos.w = bg->width();
pos.h = bg->height();
}
}
void CAdvMapPanel::addChildColorableButton(std::shared_ptr<CButton> button)
{
colorableButtons.push_back(button);
addChildToPanel(button, ACTIVATE | DEACTIVATE);
}
void CAdvMapPanel::setPlayerColor(const PlayerColor & clr)
{
for(auto & button : colorableButtons)
{
button->setPlayerColor(clr);
}
}
void CAdvMapPanel::showAll(SDL_Surface * to)
{
if(background)
background->draw(to, pos.x, pos.y);
CIntObject::showAll(to);
}
void CAdvMapPanel::addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions)
{
otherObjects.push_back(obj);
obj->recActions |= actions | SHOWALL;
obj->recActions &= ~DISPOSE;
addChild(obj.get(), false);
}
CAdvMapWorldViewPanel::CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color)
: CAdvMapPanel(bg, position), icons(_icons)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
int fillerHeight = bg ? spaceBottom - pos.y - pos.h : 0;
if(fillerHeight > 0)
{
backgroundFiller = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, pos.h, pos.w, fillerHeight));
}
}
void CAdvMapWorldViewPanel::recolorIcons(const PlayerColor & color, int indexOffset)
{
assert(iconsData.size() == currentIcons.size());
for(size_t idx = 0; idx < iconsData.size(); idx++)
{
const auto & data = iconsData.at(idx);
currentIcons[idx]->setFrame(data.first + indexOffset);
}
}
void CAdvMapWorldViewPanel::addChildIcon(std::pair<int, Point> data, int indexOffset)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
iconsData.push_back(data);
currentIcons.push_back(std::make_shared<CAnimImage>(icons, data.first + indexOffset, 0, data.second.x, data.second.y));
}

View File

@ -1,60 +0,0 @@
/*
* CAdvMapPanel.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 "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class PlayerColor;
VCMI_LIB_NAMESPACE_END
class CAnimation;
class CAnimImage;
class CFilledTexture;
class CButton;
class IImage;
/// simple panel that contains other displayable elements; used to separate groups of controls
class CAdvMapPanel : public CIntObject
{
std::vector<std::shared_ptr<CButton>> colorableButtons;
std::vector<std::shared_ptr<CIntObject>> otherObjects;
/// the surface passed to this obj will be freed in dtor
std::shared_ptr<IImage> background;
public:
CAdvMapPanel(std::shared_ptr<IImage> bg, Point position);
void addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions = 0);
void addChildColorableButton(std::shared_ptr<CButton> button);
/// recolors all buttons to given player color
void setPlayerColor(const PlayerColor & clr);
void showAll(SDL_Surface * to) override;
};
/// specialized version of CAdvMapPanel that handles recolorable def-based pictures for world view info panel
class CAdvMapWorldViewPanel : public CAdvMapPanel
{
/// data that allows reconstruction of panel info icons
std::vector<std::pair<int, Point>> iconsData;
/// ptrs to child-pictures constructed from iconsData
std::vector<std::shared_ptr<CAnimImage>> currentIcons;
/// surface drawn below world view panel on higher resolutions (won't be needed when world view panel is configured for extraResolutions mod)
std::shared_ptr<CFilledTexture> backgroundFiller;
std::shared_ptr<CAnimation> icons;
public:
CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color);
void addChildIcon(std::pair<int, Point> data, int indexOffset);
/// recreates all pictures from given def to recolor them according to current player color
void recolorIcons(const PlayerColor & color, int indexOffset);
};

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,11 @@
#include "../PlayerLocalState.h"
#include "../ClientCommandManager.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../gui/Shortcut.h"
#include "../render/Colors.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../windows/CMessage.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
@ -50,7 +53,7 @@ void CInGameConsole::show(SDL_Surface * to)
Point leftBottomCorner(0, pos.h);
Point textPosition(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number * 20);
graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, text.text, Colors::GREEN, textPosition );
graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, text.text, Colors::GREEN, pos.topLeft() + textPosition );
number++;
}
@ -75,7 +78,7 @@ void CInGameConsole::tick(uint32_t msPassed)
}
if(sizeBefore != texts.size())
GH.totalRedraw(); // FIXME: ingame console has no parent widget set
GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
}
void CInGameConsole::print(const std::string & txt)
@ -83,30 +86,23 @@ void CInGameConsole::print(const std::string & txt)
// boost::unique_lock scope
{
boost::unique_lock<boost::mutex> lock(texts_mx);
int lineLen = conf.go()->ac.outputLineLength;
if(txt.size() < lineLen)
{
texts.push_back({txt, 0});
}
else
{
assert(lineLen);
for(int g = 0; g < txt.size() / lineLen + 1; ++g)
{
std::string part = txt.substr(g * lineLen, lineLen);
if(part.empty())
break;
// Maximum width for a text line is limited by:
// 1) width of adventure map terrain area, for when in-game console is on top of advmap
// 2) width of castle/battle window (fixed to 800) when this window is open
// 3) arbitrary selected left and right margins
int maxWidth = std::min( 800, adventureInt->terrainAreaPixels().w) - 100;
texts.push_back({part, 0});
}
}
auto splitText = CMessage::breakText(txt, maxWidth, FONT_MEDIUM);
for (auto const & entry : splitText)
texts.push_back({entry, 0});
while(texts.size() > maxDisplayedTexts)
texts.erase(texts.begin());
}
GH.totalRedraw(); // FIXME: ingame console has no parent widget set
GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
}
void CInGameConsole::keyPressed (EShortcut key)
@ -215,22 +211,21 @@ void CInGameConsole::textEdited(const std::string & inputtedText)
void CInGameConsole::startEnteringText()
{
if (!active)
if (!isActive())
return;
if (captureAllKeys)
return;
assert(GH.statusbar);
assert(currentStatusBar.expired());//effectively, nullptr check
currentStatusBar = GH.statusbar;
currentStatusBar = GH.statusbar();
captureAllKeys = true;
enteredText = "_";
GH.statusbar->setEnteringMode(true);
GH.statusbar->setEnteredText(enteredText);
GH.statusbar()->setEnteringMode(true);
GH.statusbar()->setEnteredText(enteredText);
}
void CInGameConsole::endEnteringText(bool processEnteredText)

View File

@ -11,7 +11,7 @@
#include "StdInc.h"
#include "CInfoBar.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/CComponent.h"
#include "../widgets/Images.h"
@ -24,6 +24,7 @@
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../../CCallback.h"
#include "../../lib/CGeneralTextHandler.h"
@ -261,7 +262,7 @@ void CInfoBar::tick(uint32_t msPassed)
{
timerCounter = 0;
removeUsedEvents(TIME);
if(GH.topInt() == adventureInt)
if(GH.windows().isTopWindow(adventureInt))
popComponents(true);
}
else
@ -292,9 +293,9 @@ void CInfoBar::clickRight(tribool down, bool previousState)
void CInfoBar::hover(bool on)
{
if(on)
GH.statusbar->write(CGI->generaltexth->zelp[292].first);
GH.statusbar()->write(CGI->generaltexth->zelp[292].first);
else
GH.statusbar->clear();
GH.statusbar()->clear();
}
CInfoBar::CInfoBar(const Rect & position)
@ -315,8 +316,7 @@ CInfoBar::CInfoBar(const Point & position): CInfoBar(Rect(position.x, position.y
void CInfoBar::setTimer(uint32_t msToTrigger)
{
if (!(active & TIME))
addUsedEvents(TIME);
addUsedEvents(TIME);
timerCounter = msToTrigger;
}

View File

@ -11,15 +11,17 @@
#include "StdInc.h"
#include "CList.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../widgets/Buttons.h"
#include "../widgets/ObjectLists.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
@ -66,9 +68,9 @@ void CList::CListItem::clickLeft(tribool down, bool previousState)
void CList::CListItem::hover(bool on)
{
if (on)
GH.statusbar->write(getHoverText());
GH.statusbar()->write(getHoverText());
else
GH.statusbar->clear();
GH.statusbar()->clear();
}
void CList::CListItem::onSelect(bool on)
@ -81,24 +83,44 @@ void CList::CListItem::onSelect(bool on)
redraw();
}
CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create)
: CIntObject(0, position),
CList::CList(int Size, Rect widgetDimensions)
: CIntObject(0, widgetDimensions.topLeft()),
size(Size),
selected(nullptr)
{
pos.w = widgetDimensions.w;
pos.h = widgetDimensions.h;
}
void CList::showAll(SDL_Surface * to)
{
CSDL_Ext::fillRect(to, pos, Colors::BLACK);
CIntObject::showAll(to);
}
void CList::createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
scrollUp = std::make_shared<CButton>(Point(0, 0), btnUp, CGI->generaltexth->zelp[helpUp]);
scrollDown = std::make_shared<CButton>(Point(0, scrollUp->pos.h + 32*(int)size), btnDown, CGI->generaltexth->zelp[helpDown]);
listBox = std::make_shared<CListBox>(std::bind(&CList::createItem, this, _1), firstItemPosition, itemPositionDelta, size, listAmount);
}
listBox = std::make_shared<CListBox>(create, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount);
void CList::setScrollUpButton(std::shared_ptr<CButton> button)
{
addChild(button.get());
//assign callback only after list was created
scrollUp = button;
scrollUp->addCallback(std::bind(&CListBox::moveToPrev, listBox));
scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
scrollUp->addCallback(std::bind(&CList::update, this));
scrollDown->addCallback(std::bind(&CList::update, this));
update();
}
void CList::setScrollDownButton(std::shared_ptr<CButton> button)
{
addChild(button.get());
scrollDown = button;
scrollDown->addCallback(std::bind(&CList::update, this));
scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
update();
}
@ -107,8 +129,11 @@ void CList::update()
bool onTop = listBox->getPos() == 0;
bool onBottom = listBox->getPos() + size >= listBox->size();
scrollUp->block(onTop);
scrollDown->block(onBottom);
if (scrollUp)
scrollUp->block(onTop);
if (scrollDown)
scrollDown->block(onBottom);
}
void CList::select(std::shared_ptr<CListItem> which)
@ -223,16 +248,17 @@ std::string CHeroList::CHeroItem::getHoverText()
return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated());
}
std::shared_ptr<CIntObject> CHeroList::createHeroItem(size_t index)
std::shared_ptr<CIntObject> CHeroList::createItem(size_t index)
{
if (LOCPLINT->localState->getWanderingHeroes().size() > index)
return std::make_shared<CHeroItem>(this, LOCPLINT->localState->getWanderingHero(index));
return std::make_shared<CEmptyHeroItem>();
}
CHeroList::CHeroList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getWanderingHeroes().size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
CHeroList::CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
: CList(visibleItemsCount, widgetPosition)
{
createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
}
void CHeroList::select(const CGHeroInstance * hero)
@ -261,7 +287,7 @@ void CHeroList::update(const CGHeroInstance * hero)
CList::update();
}
std::shared_ptr<CIntObject> CTownList::createTownItem(size_t index)
std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
{
if (LOCPLINT->localState->getOwnedTowns().size() > index)
return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
@ -312,9 +338,10 @@ std::string CTownList::CTownItem::getHoverText()
return town->getObjectName();
}
CTownList::CTownList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getOwnedTowns().size(), 306, 307, std::bind(&CTownList::createTownItem, this, _1))
CTownList::CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
: CList(visibleItemsCount, widgetPosition)
{
createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
}
void CTownList::select(const CGTownInstance * town)

View File

@ -10,8 +10,6 @@
#pragma once
#include "../gui/CIntObject.h"
#include "../widgets/ObjectLists.h"
#include "../../lib/FunctionList.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -21,7 +19,9 @@ class CGTownInstance;
VCMI_LIB_NAMESPACE_END
class CListBox;
class CButton;
class CAnimImage;
/// Base UI Element for hero\town lists
class CList : public CIntObject
@ -53,23 +53,9 @@ protected:
virtual std::string getHoverText()=0;
};
std::shared_ptr<CListBox> listBox;
private:
const size_t size;
/**
* @brief CList - protected constructor
* @param size - maximal amount of visible at once items
* @param position - cordinates
* @param btnUp - path to image to use as top button
* @param btnDown - path to image to use as bottom button
* @param listAmount - amount of items in the list
* @param helpUp - index in zelp.txt for button help tooltip
* @param helpDown - index in zelp.txt for button help tooltip
* @param create - function for creating items in listbox
* @param destroy - function for deleting items in listbox
*/
CList(int size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create);
//for selection\deselection
std::shared_ptr<CListItem> selected;
void select(std::shared_ptr<CListItem> which);
@ -78,8 +64,14 @@ protected:
std::shared_ptr<CButton> scrollUp;
std::shared_ptr<CButton> scrollDown;
/// should be called when list is invalidated
void update();
protected:
std::shared_ptr<CListBox> listBox;
CList(int size, Rect widgetDimensions);
void createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount);
virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0;
public:
/// functions that will be called when selection changes
@ -88,10 +80,18 @@ public:
/// return index of currently selected element
int getSelectedIndex();
void setScrollUpButton(std::shared_ptr<CButton> button);
void setScrollDownButton(std::shared_ptr<CButton> button);
/// should be called when list is invalidated
void update();
/// set of methods to switch selection
void selectIndex(int which);
void selectNext();
void selectPrev();
void showAll(SDL_Surface * to) override;
};
/// List of heroes which is shown at the right of the adventure map screen
@ -125,13 +125,9 @@ class CHeroList : public CList
std::string getHoverText() override;
};
std::shared_ptr<CIntObject> createHeroItem(size_t index);
std::shared_ptr<CIntObject> createItem(size_t index);
public:
/**
* @brief CHeroList
* @param size, position, btnUp, btnDown @see CList::CList
*/
CHeroList(int size, Point position, std::string btnUp, std::string btnDown);
CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
/// Select specific hero and scroll if needed
void select(const CGHeroInstance * hero = nullptr);
@ -159,13 +155,9 @@ class CTownList : public CList
std::string getHoverText() override;
};
std::shared_ptr<CIntObject> createTownItem(size_t index);
std::shared_ptr<CIntObject> createItem(size_t index) override;
public:
/**
* @brief CTownList
* @param size, position, btnUp, btnDown @see CList::CList
*/
CTownList(int size, Point position, std::string btnUp, std::string btnDown);
CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
/// Select specific town and scroll if needed
void select(const CGTownInstance * town = nullptr);

View File

@ -11,12 +11,14 @@
#include "StdInc.h"
#include "CMinimap.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/MouseButton.h"
#include "../gui/WindowHandler.h"
#include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/Canvas.h"
@ -130,8 +132,8 @@ void CMinimap::moveAdvMapSelection()
int3 newLocation = pixelToTile(GH.getCursorPosition() - pos.topLeft());
adventureInt->centerOnTile(newLocation);
if (!(adventureInt->active & GENERAL))
GH.totalRedraw(); //redraw this as well as inactive adventure map
if (!(adventureInt->isActive()))
GH.windows().totalRedraw(); //redraw this as well as inactive adventure map
else
redraw();//redraw only this
}
@ -151,14 +153,14 @@ void CMinimap::clickRight(tribool down, bool previousState)
void CMinimap::hover(bool on)
{
if(on)
GH.statusbar->write(CGI->generaltexth->zelp[291].first);
GH.statusbar()->write(CGI->generaltexth->zelp[291].first);
else
GH.statusbar->clear();
GH.statusbar()->clear();
}
void CMinimap::mouseMoved(const Point & cursorPosition)
{
if(mouseState(MouseButton::LEFT))
if(isMouseButtonPressed(MouseButton::LEFT))
moveAdvMapSelection();
}

View File

@ -19,50 +19,40 @@
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/ResourceSet.h"
#define ADVOPT (conf.go()->ac)
CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist)
CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
{
pos.x += x;
pos.y += y;
pos.x += position.x;
pos.y += position.y;
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CPicture>(defname, 0, 0);
background = std::make_shared<CPicture>(imageName, 0, 0);
background->colorize(LOCPLINT->playerID);
pos.w = background->pos.w;
pos.h = background->pos.h;
txtpos.resize(8);
for (int i = 0; i < 8 ; i++)
{
txtpos[i].first = pos.x + offx + resdist*i;
txtpos[i].second = pos.y + offy;
}
txtpos[7].first = txtpos[6].first + datedist;
addUsedEvents(RCLICK);
}
CResDataBar::CResDataBar()
CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist):
CResDataBar(defname, Point(x,y))
{
pos.x += ADVOPT.resdatabarX;
pos.y += ADVOPT.resdatabarY;
for (int i = 0; i < 7 ; i++)
resourcePositions[GameResID(i)] = Point( offx + resdist*i, offy );
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CPicture>(ADVOPT.resdatabarG, 0, 0);
background->colorize(LOCPLINT->playerID);
datePosition = resourcePositions[EGameResID::GOLD] + Point(datedist, 0);
}
pos.w = background->pos.w;
pos.h = background->pos.h;
txtpos.resize(8);
for (int i = 0; i < 8 ; i++)
{
txtpos[i].first = pos.x + ADVOPT.resOffsetX + ADVOPT.resDist*i;
txtpos[i].second = pos.y + ADVOPT.resOffsetY;
}
txtpos[7].first = txtpos[6].first + ADVOPT.resDateDist;
void CResDataBar::setDatePosition(const Point & position)
{
datePosition = position;
}
void CResDataBar::setResourcePosition(const GameResID & resource, const Point & position)
{
resourcePositions[resource] = position;
}
std::string CResDataBar::buildDateString()
@ -80,13 +70,15 @@ std::string CResDataBar::buildDateString()
void CResDataBar::draw(SDL_Surface * to)
{
//TODO: all this should be labels, but they require proper text update on change
for (GameResID i=EGameResID::WOOD; i <= GameResID(EGameResID::GOLD); ++i)
for (auto & entry : resourcePositions)
{
std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));
std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(entry.first));
graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first, txtpos[i].second));
graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, pos.topLeft() + entry.second);
}
graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, Point(txtpos[7].first, txtpos[7].second));
if (datePosition)
graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, pos.topLeft() + *datePosition);
}
void CResDataBar::showAll(SDL_Surface * to)

View File

@ -11,6 +11,11 @@
#include "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
enum class EGameResID : int8_t;
using GameResID = Identifier<EGameResID>;
VCMI_LIB_NAMESPACE_END
/// Resources bar which shows information about how many gold, crystals,... you have
/// Current date is displayed too
class CResDataBar : public CIntObject
@ -19,14 +24,21 @@ class CResDataBar : public CIntObject
std::shared_ptr<CPicture> background;
std::vector<std::pair<int,int> > txtpos;
std::map<GameResID, Point> resourcePositions;
std::optional<Point> datePosition;
void draw(SDL_Surface * to);
public:
CResDataBar();
/// For dynamically-sized UI windows, e.g. adventure map interface
CResDataBar(const std::string & imageName, const Point & position);
/// For fixed-size UI windows, e.g. CastleInterface
CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
void setDatePosition(const Point & position);
void setResourcePosition(const GameResID & resource, const Point & position);
void colorize(PlayerColor player);
void showAll(SDL_Surface * to) override;
};

View File

@ -22,6 +22,7 @@
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CIntObject.h"
#include "../gui/WindowHandler.h"
#include "../windows/CCreatureWindow.h"
#include "../../CCallback.h"
@ -668,7 +669,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
case PossiblePlayerBattleAction::CREATURE_INFO:
{
GH.pushIntT<CStackWindow>(targetStack, false);
GH.windows().createAndPushWindow<CStackWindow>(targetStack, false);
return;
}
@ -772,7 +773,7 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex)
if (owner.openingPlaying())
{
currentConsoleMsg = VLC->generaltexth->translate("vcmi.battleWindow.pressKeyToSkipIntro");
GH.statusbar->write(currentConsoleMsg);
GH.statusbar()->write(currentConsoleMsg);
return;
}
@ -782,7 +783,7 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex)
if (hoveredHex == BattleHex::INVALID)
{
if (!currentConsoleMsg.empty())
GH.statusbar->clearIfMatching(currentConsoleMsg);
GH.statusbar()->clearIfMatching(currentConsoleMsg);
currentConsoleMsg.clear();
CCS->curh->set(Cursor::Combat::BLOCKED);
@ -805,10 +806,10 @@ void BattleActionsController::onHexHovered(BattleHex hoveredHex)
}
if (!currentConsoleMsg.empty())
GH.statusbar->clearIfMatching(currentConsoleMsg);
GH.statusbar()->clearIfMatching(currentConsoleMsg);
if (!newConsoleMsg.empty())
GH.statusbar->write(newConsoleMsg);
GH.statusbar()->write(newConsoleMsg);
currentConsoleMsg = newConsoleMsg;
}
@ -818,7 +819,7 @@ void BattleActionsController::onHoverEnded()
CCS->curh->set(Cursor::Combat::POINTER);
if (!currentConsoleMsg.empty())
GH.statusbar->clearIfMatching(currentConsoleMsg);
GH.statusbar()->clearIfMatching(currentConsoleMsg);
currentConsoleMsg.clear();
}
@ -849,7 +850,7 @@ void BattleActionsController::onHexLeftClicked(BattleHex clickedHex)
{
actionRealize(action, clickedHex);
GH.statusbar->clear();
GH.statusbar()->clear();
}
else
{
@ -973,7 +974,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
auto selectedStack = owner.curInt->cb->battleGetStackByPos(clickedHex, true);
if (selectedStack != nullptr)
GH.pushIntT<CStackWindow>(selectedStack, true);
GH.windows().createAndPushWindow<CStackWindow>(selectedStack, true);
if (clickedHex == BattleHex::HERO_ATTACKER && owner.attackingHero)
owner.attackingHero->heroRightClicked();

View File

@ -222,7 +222,7 @@ bool DummyAnimation::init()
return true;
}
void DummyAnimation::nextFrame()
void DummyAnimation::tick(uint32_t msPassed)
{
counter++;
if(counter > howMany)
@ -300,7 +300,7 @@ ECreatureAnimType MeleeAttackAnimation::selectGroup(bool multiAttack)
return mutPosToGroup[mutPos];
}
void MeleeAttackAnimation::nextFrame()
void MeleeAttackAnimation::tick(uint32_t msPassed)
{
size_t currentFrame = stackAnimation(attackingStack)->getCurrentFrame();
size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(getGroup());
@ -308,7 +308,7 @@ void MeleeAttackAnimation::nextFrame()
if ( currentFrame * 2 >= totalFrames )
owner.executeAnimationStage(EAnimationEvents::HIT);
AttackAnimation::nextFrame();
AttackAnimation::tick(msPassed);
}
MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack)
@ -379,15 +379,15 @@ bool MovementAnimation::init()
return true;
}
void MovementAnimation::nextFrame()
void MovementAnimation::tick(uint32_t msPassed)
{
progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * progressPerSecond;
progress += float(msPassed) / 1000 * progressPerSecond;
//moving instructions
myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress );
myAnim->pos.y = static_cast<Sint16>(begY + distanceY * progress );
BattleAnimation::nextFrame();
BattleAnimation::tick(msPassed);
if(progress >= 1.0)
{
@ -577,9 +577,9 @@ bool ColorTransformAnimation::init()
return true;
}
void ColorTransformAnimation::nextFrame()
void ColorTransformAnimation::tick(uint32_t msPassed)
{
float elapsed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
float elapsed = msPassed / 1000.f;
float fullTime = AnimationControls::getFadeInDuration();
float delta = elapsed / fullTime;
totalProgress += delta;
@ -699,7 +699,7 @@ void RangedAttackAnimation::emitProjectile()
projectileEmitted = true;
}
void RangedAttackAnimation::nextFrame()
void RangedAttackAnimation::tick(uint32_t msPassed)
{
// animation should be paused if there is an active projectile
if (projectileEmitted)
@ -716,7 +716,7 @@ void RangedAttackAnimation::nextFrame()
else
stackAnimation(attackingStack)->playUntil(static_cast<size_t>(-1));
AttackAnimation::nextFrame();
AttackAnimation::tick(msPassed);
if (!projectileEmitted)
{
@ -790,9 +790,9 @@ CatapultAnimation::CatapultAnimation(BattleInterface & owner, const CStack * att
logAnim->debug("Created shooting anim for %s", stack->getName());
}
void CatapultAnimation::nextFrame()
void CatapultAnimation::tick(uint32_t msPassed)
{
ShootingAnimation::nextFrame();
ShootingAnimation::tick(msPassed);
if ( explosionEmitted)
return;
@ -988,9 +988,9 @@ bool EffectAnimation::init()
return true;
}
void EffectAnimation::nextFrame()
void EffectAnimation::tick(uint32_t msPassed)
{
playEffect();
playEffect(msPassed);
if (effectFinished)
{
@ -1020,7 +1020,7 @@ void EffectAnimation::onEffectFinished()
effectFinished = true;
}
void EffectAnimation::playEffect()
void EffectAnimation::playEffect(uint32_t msPassed)
{
if ( effectFinished )
return;
@ -1029,7 +1029,7 @@ void EffectAnimation::playEffect()
{
if(elem.effectID == ID)
{
elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
elem.currentFrame += AnimationControls::getSpellEffectSpeed() * msPassed / 1000;
if(elem.currentFrame >= elem.animation->size())
{
@ -1113,7 +1113,7 @@ void HeroCastAnimation::emitAnimationEvent()
owner.executeAnimationStage(EAnimationEvents::HIT);
}
void HeroCastAnimation::nextFrame()
void HeroCastAnimation::tick(uint32_t msPassed)
{
float frame = hero->getFrame();

View File

@ -48,7 +48,7 @@ public:
bool isInitialized();
bool tryInitialize();
virtual void nextFrame() {} //call every new frame
virtual void tick(uint32_t msPassed) {} //call every new frame
virtual ~BattleAnimation();
BattleAnimation(BattleInterface & owner);
@ -120,7 +120,7 @@ class ColorTransformAnimation : public BattleStackAnimation
float totalProgress;
bool init() override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
public:
ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell);
@ -157,7 +157,7 @@ private:
public:
bool init() override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
~MovementAnimation();
@ -220,7 +220,7 @@ class MeleeAttackAnimation : public AttackAnimation
public:
MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack);
void nextFrame() override;
void tick(uint32_t msPassed) override;
};
@ -246,7 +246,7 @@ public:
~RangedAttackAnimation();
bool init() override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
};
/// Shooting attack
@ -275,7 +275,7 @@ public:
CatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
void createProjectile(const Point & from, const Point & dest) const override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
};
class CastAnimation : public RangedAttackAnimation
@ -300,7 +300,7 @@ private:
int howMany;
public:
bool init() override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
DummyAnimation(BattleInterface & owner, int howManyFrames);
};
@ -324,7 +324,7 @@ class EffectAnimation : public BattleAnimation
void onEffectFinished();
void clearEffect();
void playEffect();
void playEffect(uint32_t msPassed);
public:
enum EEffectFlags
@ -349,7 +349,7 @@ public:
~EffectAnimation();
bool init() override;
void nextFrame() override;
void tick(uint32_t msPassed) override;
};
class HeroCastAnimation : public BattleAnimation
@ -367,6 +367,6 @@ class HeroCastAnimation : public BattleAnimation
public:
HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell);
void nextFrame() override;
void tick(uint32_t msPassed) override;
bool init() override;
};

View File

@ -39,7 +39,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
owner(owner)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
strongInterest = true;
setMoveEventStrongInterest(true);
//preparing cells and hexes
cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
@ -68,8 +68,13 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
updateAccessibleHexes();
addUsedEvents(LCLICK | RCLICK | MOVE);
addUsedEvents(LCLICK | RCLICK | MOVE | TIME);
}
void BattleFieldController::activate()
{
LOCPLINT->cingconsole->pos = this->pos;
CIntObject::activate();
}
void BattleFieldController::createHeroes()
@ -129,7 +134,7 @@ void BattleFieldController::renderBattlefield(Canvas & canvas)
renderer.execute(clippedCanvas);
owner.projectilesController->showProjectiles(clippedCanvas);
owner.projectilesController->render(clippedCanvas);
}
void BattleFieldController::showBackground(Canvas & canvas)
@ -606,12 +611,16 @@ void BattleFieldController::showAll(SDL_Surface * to)
show(to);
}
void BattleFieldController::show(SDL_Surface * to)
void BattleFieldController::tick(uint32_t msPassed)
{
updateAccessibleHexes();
owner.stacksController->update();
owner.obstacleController->update();
owner.stacksController->tick(msPassed);
owner.obstacleController->tick(msPassed);
owner.projectilesController->tick(msPassed);
}
void BattleFieldController::show(SDL_Surface * to)
{
Canvas canvas(to);
CSDL_Ext::CClipRectGuard guard(to, pos);

View File

@ -66,9 +66,11 @@ class BattleFieldController : public CIntObject
void mouseMoved(const Point & cursorPosition) override;
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
void activate() override;
void showAll(SDL_Surface * to) override;
void show(SDL_Surface * to) override;
void tick(uint32_t msPassed) override;
public:
BattleFieldController(BattleInterface & owner);

View File

@ -28,8 +28,9 @@
#include "../CPlayerInterface.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../render/Canvas.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
@ -95,7 +96,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
adventureInt->onAudioPaused();
ongoingAnimationsState.set(true);
GH.pushInt(windowObject);
GH.windows().pushWindow(windowObject);
windowObject->blockUI(true);
windowObject->updateQueue();
@ -167,7 +168,7 @@ BattleInterface::~BattleInterface()
void BattleInterface::redrawBattlefield()
{
fieldController->redrawBackgroundWithHexes();
GH.totalRedraw();
GH.windows().totalRedraw();
}
void BattleInterface::stackReset(const CStack * stack)
@ -328,7 +329,7 @@ void BattleInterface::battleFinished(const BattleResult& br, QueryID queryID)
{
curInt->cb->selectionMade(selection, queryID);
};
GH.pushInt(wnd);
GH.windows().pushWindow(wnd);
curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
CPlayerInterface::battleInt = nullptr;

View File

@ -25,6 +25,8 @@
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/MouseButton.h"
#include "../gui/WindowHandler.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../widgets/Buttons.h"
@ -202,6 +204,25 @@ const CGHeroInstance * BattleHero::instance()
return hero;
}
void BattleHero::tick(uint32_t msPassed)
{
size_t groupIndex = static_cast<size_t>(phase);
float timePassed = msPassed / 1000.f;
flagCurrentFrame += currentSpeed * timePassed;
currentFrame += currentSpeed * timePassed;
if(flagCurrentFrame >= flagAnimation->size(0))
flagCurrentFrame -= flagAnimation->size(0);
if(currentFrame >= animation->size(groupIndex))
{
currentFrame -= animation->size(groupIndex);
switchToNextPhase();
}
}
void BattleHero::render(Canvas & canvas)
{
size_t groupIndex = static_cast<size_t>(phase);
@ -219,20 +240,6 @@ void BattleHero::render(Canvas & canvas)
canvas.draw(flagFrame, flagPosition);
canvas.draw(heroFrame, heroPosition);
float timePassed = float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000.f;
flagCurrentFrame += currentSpeed * timePassed;
currentFrame += currentSpeed * timePassed;
if(flagCurrentFrame >= flagAnimation->size(0))
flagCurrentFrame -= flagAnimation->size(0);
if(currentFrame >= animation->size(groupIndex))
{
currentFrame -= animation->size(groupIndex);
switchToNextPhase();
}
}
void BattleHero::pause()
@ -284,7 +291,7 @@ void BattleHero::heroLeftClicked()
if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
{
CCS->curh->set(Cursor::Map::POINTER);
GH.pushIntT<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
GH.windows().createAndPushWindow<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
}
}
@ -299,7 +306,7 @@ void BattleHero::heroRightClicked()
{
auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance;
targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
GH.pushIntT<HeroInfoWindow>(targetHero, &windowPosition);
GH.windows().createAndPushWindow<HeroInfoWindow>(targetHero, &windowPosition);
}
}
@ -354,6 +361,8 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
switchToNextPhase();
play();
addUsedEvents(TIME);
}
HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
@ -584,8 +593,8 @@ void BattleResultWindow::buttonPressed(int button)
close();
if(dynamic_cast<BattleWindow*>(GH.topInt().get()))
GH.popInts(1); //pop battle interface if present
if(GH.windows().topWindow<BattleWindow>())
GH.windows().popWindows(1); //pop battle interface if present
//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.
@ -684,7 +693,7 @@ std::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const
{
for(const auto & stackBox : stackBoxes)
{
if(stackBox->hovered || stackBox->mouseState(MouseButton::RIGHT))
if(stackBox->isHovered() || stackBox->isMouseButtonPressed(MouseButton::RIGHT))
{
return stackBox->getBoundUnitID();
}

View File

@ -114,6 +114,7 @@ public:
void setPhase(EHeroAnimType newPhase); //sets phase of hero animation
void collectRenderableObjects(BattleRenderer & renderer);
void tick(uint32_t msPassed) override;
float getFrame() const;
void onPhaseFinished(const std::function<void()> &);

View File

@ -159,9 +159,9 @@ void BattleObstacleController::collectRenderableObjects(BattleRenderer & rendere
}
}
void BattleObstacleController::update()
void BattleObstacleController::tick(uint32_t msPassed)
{
timePassed += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
timePassed += msPassed / 1000.f;
}
std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)

View File

@ -50,7 +50,7 @@ public:
BattleObstacleController(BattleInterface & owner);
/// called every frame
void update();
void tick(uint32_t msPassed);
/// call-in from network pack, add newly placed obstacles with any required animations
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);

View File

@ -58,15 +58,18 @@ void ProjectileMissile::show(Canvas & canvas)
canvas.draw(image, pos);
}
}
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
void ProjectileMissile::tick(uint32_t msPassed)
{
float timePassed = msPassed / 1000.f;
progress += timePassed * speed;
}
void ProjectileAnimatedMissile::show(Canvas & canvas)
void ProjectileAnimatedMissile::tick(uint32_t msPassed)
{
ProjectileMissile::show(canvas);
frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
ProjectileMissile::tick(msPassed);
frameProgress += AnimationControls::getSpellEffectSpeed() * msPassed / 1000;
size_t animationSize = animation->size(reverse ? 1 : 0);
while (frameProgress > animationSize)
frameProgress -= animationSize;
@ -74,9 +77,15 @@ void ProjectileAnimatedMissile::show(Canvas & canvas)
frameNum = std::floor(frameProgress);
}
void ProjectileCatapult::tick(uint32_t msPassed)
{
frameProgress += AnimationControls::getSpellEffectSpeed() * msPassed / 1000;
float timePassed = msPassed / 1000.f;
progress += timePassed * speed;
}
void ProjectileCatapult::show(Canvas & canvas)
{
frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
int frameCounter = std::floor(frameProgress);
int frameIndex = (frameCounter + 1) % animation->size(0);
@ -90,9 +99,6 @@ void ProjectileCatapult::show(Canvas & canvas)
canvas.draw(image, pos);
}
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
progress += timePassed * speed;
}
void ProjectileRay::show(Canvas & canvas)
@ -135,8 +141,11 @@ void ProjectileRay::show(Canvas & canvas)
canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), ray.start, ray.end);
}
}
}
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
void ProjectileRay::tick(uint32_t msPassed)
{
float timePassed = msPassed / 1000.f;
progress += timePassed * speed;
}
@ -217,13 +226,22 @@ void BattleProjectileController::emitStackProjectile(const CStack * stack)
}
}
void BattleProjectileController::showProjectiles(Canvas & canvas)
void BattleProjectileController::render(Canvas & canvas)
{
for ( auto projectile: projectiles)
{
if ( projectile->playing )
projectile->show(canvas);
}
}
void BattleProjectileController::tick(uint32_t msPassed)
{
for ( auto projectile: projectiles)
{
if ( projectile->playing )
projectile->tick(msPassed);
}
vstd::erase_if(projectiles, [&](const std::shared_ptr<ProjectileBase> & projectile){
return projectile->progress > 1.0f;

View File

@ -28,6 +28,7 @@ struct ProjectileBase
{
virtual ~ProjectileBase() = default;
virtual void show(Canvas & canvas) = 0;
virtual void tick(uint32_t msPassed) = 0;
Point from; // initial position on the screen
Point dest; // target position on the screen
@ -42,6 +43,7 @@ struct ProjectileBase
struct ProjectileMissile : ProjectileBase
{
void show(Canvas & canvas) override;
void tick(uint32_t msPassed) override;
std::shared_ptr<CAnimation> animation;
int frameNum; // frame to display from projectile animation
@ -51,7 +53,7 @@ struct ProjectileMissile : ProjectileBase
/// Projectile for spell - render animation moving in straight line from origin to destination
struct ProjectileAnimatedMissile : ProjectileMissile
{
void show(Canvas & canvas) override;
void tick(uint32_t msPassed) override;
float frameProgress;
};
@ -59,6 +61,7 @@ struct ProjectileAnimatedMissile : ProjectileMissile
struct ProjectileCatapult : ProjectileBase
{
void show(Canvas & canvas) override;
void tick(uint32_t msPassed) override;
std::shared_ptr<CAnimation> animation;
float frameProgress;
@ -68,6 +71,7 @@ struct ProjectileCatapult : ProjectileBase
struct ProjectileRay : ProjectileBase
{
void show(Canvas & canvas) override;
void tick(uint32_t msPassed) override;
std::vector<CCreature::CreatureAnimation::RayColor> rayConfig;
};
@ -102,7 +106,10 @@ public:
BattleProjectileController(BattleInterface & owner);
/// renders all currently active projectiles
void showProjectiles(Canvas & canvas);
void render(Canvas & canvas);
/// updates positioning / animations of all projectiles
void tick(uint32_t msPassed);
/// returns true if stack has projectile that is yet to hit target
bool hasActiveProjectile(const CStack * stack, bool emittedOnly) const;

View File

@ -335,13 +335,12 @@ void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
}
stackAnimation[stack->unitId()]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
stackAnimation[stack->unitId()]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
}
void BattleStacksController::update()
void BattleStacksController::tick(uint32_t msPassed)
{
updateHoveredStacks();
updateBattleAnimations();
updateBattleAnimations(msPassed);
}
void BattleStacksController::initializeBattleAnimations()
@ -352,21 +351,30 @@ void BattleStacksController::initializeBattleAnimations()
elem->tryInitialize();
}
void BattleStacksController::stepFrameBattleAnimations()
void BattleStacksController::tickFrameBattleAnimations(uint32_t msPassed)
{
for (auto stack : owner.curInt->cb->battleGetAllStacks(true))
{
if (stackAnimation.find(stack->unitId()) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
continue;
stackAnimation[stack->unitId()]->incrementFrame(msPassed / 1000.f);
}
// operate on copy - to prevent potential iterator invalidation due to push_back's
// FIXME? : remove remaining calls to addNewAnim from BattleAnimation::nextFrame (only Catapult explosion at the time of writing)
auto copiedVector = currentAnimations;
for (auto & elem : copiedVector)
if (elem && elem->isInitialized())
elem->nextFrame();
elem->tick(msPassed);
}
void BattleStacksController::updateBattleAnimations()
void BattleStacksController::updateBattleAnimations(uint32_t msPassed)
{
bool hadAnimations = !currentAnimations.empty();
initializeBattleAnimations();
stepFrameBattleAnimations();
tickFrameBattleAnimations(msPassed);
vstd::erase(currentAnimations, nullptr);
if (hadAnimations && currentAnimations.empty())

View File

@ -91,9 +91,9 @@ class BattleStacksController
void removeExpiredColorFilters();
void initializeBattleAnimations();
void stepFrameBattleAnimations();
void tickFrameBattleAnimations(uint32_t msPassed);
void updateBattleAnimations();
void updateBattleAnimations(uint32_t msPassed);
void updateHoveredStacks();
std::vector<const CStack *> selectHoveredStacks();
@ -138,7 +138,7 @@ public:
const CStack* getActiveStack() const;
const std::vector<uint32_t> getHoveredStacksUnitIds() const;
void update();
void tick(uint32_t msPassed);
/// returns position of animation needed to place stack in specific hex
Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;

View File

@ -22,6 +22,7 @@
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../windows/CSpellWindow.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
@ -72,7 +73,6 @@ BattleWindow::BattleWindow(BattleInterface & owner):
console = widget<BattleConsole>("console");
GH.statusbar = console;
owner.console = console;
owner.fieldController.reset( new BattleFieldController(owner));
@ -153,7 +153,7 @@ void BattleWindow::hideQueue()
pos.h -= queue->pos.h;
pos = center();
}
GH.totalRedraw();
GH.windows().totalRedraw();
}
void BattleWindow::showQueue()
@ -166,7 +166,7 @@ void BattleWindow::showQueue()
createQueue();
updateQueue();
GH.totalRedraw();
GH.windows().totalRedraw();
}
void BattleWindow::updateQueue()
@ -176,17 +176,23 @@ void BattleWindow::updateQueue()
void BattleWindow::activate()
{
GH.statusbar = console;
GH.setStatusbar(console);
CIntObject::activate();
LOCPLINT->cingconsole->activate();
}
void BattleWindow::deactivate()
{
GH.setStatusbar(nullptr);
CIntObject::deactivate();
LOCPLINT->cingconsole->deactivate();
}
bool BattleWindow::captureThisKey(EShortcut key)
{
return owner.openingPlaying();
}
void BattleWindow::keyPressed(EShortcut key)
{
if (owner.openingPlaying())
@ -252,7 +258,7 @@ void BattleWindow::bOptionsf()
CCS->curh->set(Cursor::Map::POINTER);
GH.pushIntT<SettingsMainWindow>(&owner);
GH.windows().createAndPushWindow<SettingsMainWindow>(&owner);
}
void BattleWindow::bSurrenderf()
@ -359,7 +365,7 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
}
auto anim = std::make_shared<CAnimation>(iconName);
w->setImage(anim, false);
w->setImage(anim);
w->redraw();
}
@ -420,7 +426,7 @@ void BattleWindow::bSpellf()
if(spellCastProblem == ESpellCastProblem::OK)
{
GH.pushIntT<CSpellWindow>(myHero, owner.curInt.get());
GH.windows().createAndPushWindow<CSpellWindow>(myHero, owner.curInt.get());
}
else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
{
@ -565,7 +571,7 @@ void BattleWindow::show(SDL_Surface *to)
void BattleWindow::close()
{
if(GH.topInt().get() != this)
if(!GH.windows().isTopWindow(this))
logGlobal->error("Only top interface must be closed");
GH.popInts(1);
GH.windows().popWindows(1);
}

View File

@ -85,6 +85,7 @@ public:
void activate() override;
void deactivate() override;
void keyPressed(EShortcut key) override;
bool captureThisKey(EShortcut key) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;

View File

@ -0,0 +1,269 @@
/*
* InputHandler.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 "InputHandler.h"
#include "NotificationHandler.h"
#include "InputSourceMouse.h"
#include "InputSourceKeyboard.h"
#include "InputSourceTouch.h"
#include "InputSourceText.h"
#include "UserEventHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/MouseButton.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
#include "../../lib/CConfigHandler.h"
#include <SDL_events.h>
#include <SDL_hints.h>
InputHandler::InputHandler()
: mouseHandler(std::make_unique<InputSourceMouse>())
, keyboardHandler(std::make_unique<InputSourceKeyboard>())
, fingerHandler(std::make_unique<InputSourceTouch>())
, textHandler(std::make_unique<InputSourceText>())
, userHandler(std::make_unique<UserEventHandler>())
, mouseButtonsMask(0)
, pointerSpeedMultiplier(settings["general"]["relativePointerSpeedMultiplier"].Float())
{
}
InputHandler::~InputHandler() = default;
void InputHandler::handleCurrentEvent(const SDL_Event & current)
{
switch (current.type)
{
case SDL_KEYDOWN:
return keyboardHandler->handleEventKeyDown(current.key);
case SDL_KEYUP:
return keyboardHandler->handleEventKeyUp(current.key);
case SDL_MOUSEMOTION:
return mouseHandler->handleEventMouseMotion(current.motion);
case SDL_MOUSEBUTTONDOWN:
return mouseHandler->handleEventMouseButtonDown(current.button);
case SDL_MOUSEWHEEL:
return mouseHandler->handleEventMouseWheel(current.wheel);
case SDL_TEXTINPUT:
return textHandler->handleEventTextInput(current.text);
case SDL_TEXTEDITING:
return textHandler->handleEventTextEditing(current.edit);
case SDL_MOUSEBUTTONUP:
return mouseHandler->handleEventMouseButtonUp(current.button);
case SDL_FINGERMOTION:
return fingerHandler->handleEventFingerMotion(current.tfinger);
case SDL_FINGERDOWN:
return fingerHandler->handleEventFingerDown(current.tfinger);
case SDL_FINGERUP:
return fingerHandler->handleEventFingerUp(current.tfinger);
}
}
void InputHandler::processEvents()
{
boost::unique_lock<boost::mutex> lock(eventsMutex);
for (auto const & currentEvent : eventsQueue)
{
if (currentEvent.type == SDL_MOUSEMOTION)
{
cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
mouseButtonsMask = currentEvent.motion.state;
}
handleCurrentEvent(currentEvent);
}
eventsQueue.clear();
}
bool InputHandler::ignoreEventsUntilInput()
{
bool inputFound = false;
boost::unique_lock<boost::mutex> lock(eventsMutex);
for (auto const & event : eventsQueue)
{
switch(event.type)
{
case SDL_MOUSEBUTTONDOWN:
case SDL_FINGERDOWN:
case SDL_KEYDOWN:
inputFound = true;
}
}
eventsQueue.clear();
return inputFound;
}
void InputHandler::preprocessEvent(const SDL_Event & ev)
{
if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
{
#ifdef VCMI_ANDROID
handleQuit(false);
#else
handleQuit();
#endif
return;
}
#ifdef VCMI_ANDROID
else if (ev.type == SDL_KEYDOWN && ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK)
{
handleQuit(true);
}
#endif
else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
{
Settings full = settings.write["video"]["fullscreen"];
full->Bool() = !full->Bool();
return;
}
else if(ev.type == SDL_USEREVENT)
{
userHandler->handleUserEvent(ev.user);
return;
}
else if(ev.type == SDL_WINDOWEVENT)
{
switch (ev.window.event) {
case SDL_WINDOWEVENT_RESTORED:
#ifndef VCMI_IOS
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
}
#endif
break;
}
return;
}
else if(ev.type == SDL_SYSWMEVENT)
{
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::handleSdlEvent(ev);
}
}
//preprocessing
if(ev.type == SDL_MOUSEMOTION)
{
if (CCS && CCS->curh)
CCS->curh->cursorMove(ev.motion.x, ev.motion.y);
}
{
boost::unique_lock<boost::mutex> lock(eventsMutex);
if(ev.type == SDL_MOUSEMOTION && !eventsQueue.empty() && eventsQueue.back().type == SDL_MOUSEMOTION)
{
// In a sequence of mouse motion events, skip all but the last one.
// This prevents freezes when every motion event takes longer to handle than interval at which
// the events arrive (like dragging on the minimap in world view, with redraw at every event)
// so that the events would start piling up faster than they can be processed.
eventsQueue.back() = ev;
return;
}
eventsQueue.push_back(ev);
}
}
void InputHandler::fetchEvents()
{
SDL_Event ev;
while(1 == SDL_PollEvent(&ev))
{
preprocessEvent(ev);
}
}
bool InputHandler::isKeyboardCtrlDown() const
{
#ifdef VCMI_MAC
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
#else
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
#endif
}
bool InputHandler::isKeyboardAltDown() const
{
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
}
bool InputHandler::isKeyboardShiftDown() const
{
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
}
void InputHandler::fakeMoveCursor(float dx, float dy)
{
int x, y, w, h;
SDL_Event event;
SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
sme.state = SDL_GetMouseState(&x, &y);
SDL_GetWindowSize(mainWindow, &w, &h);
sme.x = GH.getCursorPosition().x + (int)(pointerSpeedMultiplier * w * dx);
sme.y = GH.getCursorPosition().y + (int)(pointerSpeedMultiplier * h * dy);
vstd::abetween(sme.x, 0, w);
vstd::abetween(sme.y, 0, h);
event.motion = sme;
SDL_PushEvent(&event);
}
void InputHandler::startTextInput(const Rect & where)
{
textHandler->startTextInput(where);
}
void InputHandler::stopTextInput()
{
textHandler->stopTextInput();
}
bool InputHandler::isMouseButtonPressed(MouseButton button) const
{
static_assert(static_cast<uint32_t>(MouseButton::LEFT) == SDL_BUTTON_LEFT, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::RIGHT) == SDL_BUTTON_RIGHT, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::EXTRA1) == SDL_BUTTON_X1, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::EXTRA2) == SDL_BUTTON_X2, "mismatch between VCMI and SDL enum!");
uint32_t index = static_cast<uint32_t>(button);
return mouseButtonsMask & SDL_BUTTON(index);
}
void InputHandler::pushUserEvent(EUserEvent usercode, void * userdata)
{
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = static_cast<int32_t>(usercode);
event.user.data1 = userdata;
SDL_PushEvent(&event);
}
const Point & InputHandler::getCursorPosition() const
{
return cursorPosition;
}

View File

@ -0,0 +1,77 @@
/*
* InputHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/Rect.h"
enum class EUserEvent;
enum class MouseButton;
union SDL_Event;
class InputSourceMouse;
class InputSourceKeyboard;
class InputSourceTouch;
class InputSourceText;
class UserEventHandler;
class InputHandler
{
std::vector<SDL_Event> eventsQueue;
boost::mutex eventsMutex;
Point cursorPosition;
float pointerSpeedMultiplier;
int mouseButtonsMask;
void preprocessEvent(const SDL_Event & event);
void handleCurrentEvent(const SDL_Event & current);
std::unique_ptr<InputSourceMouse> mouseHandler;
std::unique_ptr<InputSourceKeyboard> keyboardHandler;
std::unique_ptr<InputSourceTouch> fingerHandler;
std::unique_ptr<InputSourceText> textHandler;
std::unique_ptr<UserEventHandler> userHandler;
public:
InputHandler();
~InputHandler();
/// Fetches events from SDL input system and prepares them for processing
void fetchEvents();
/// Performs actual processing and dispatching of previously fetched events
void processEvents();
/// drops all incoming events without processing them
/// returns true if input event has been found
bool ignoreEventsUntilInput();
void fakeMoveCursor(float dx, float dy);
/// Initiates text input in selected area, potentially creating IME popup (mobile systems only at the moment)
void startTextInput(const Rect & where);
/// Ends any existing text input state
void stopTextInput();
/// Returns true if selected mouse button is pressed at the moment
bool isMouseButtonPressed(MouseButton button) const;
/// Generates new user event that will be processed on next frame
void pushUserEvent(EUserEvent usercode, void * userdata);
/// Returns current position of cursor, in VCMI logical screen coordinates
const Point & getCursorPosition() const;
/// returns true if chosen keyboard key is currently pressed down
bool isKeyboardAltDown() const;
bool isKeyboardCtrlDown() const;
bool isKeyboardShiftDown() const;
};

View File

@ -0,0 +1,85 @@
/*
* InputSourceKeyboard.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 "InputSourceKeyboard.h"
#include "../../lib/CConfigHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/ShortcutHandler.h"
#include <SDL_events.h>
#include <SDL_hints.h>
InputSourceKeyboard::InputSourceKeyboard()
{
#ifdef VCMI_MAC
// Ctrl+click should be treated as a right click on Mac OS X
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
#endif
}
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
{
if(key.repeat != 0)
return; // ignore periodic event resends
assert(key.state == SDL_PRESSED);
if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
{
//TODO: we need some central place for all interface-independent hotkeys
Settings s = settings.write["session"];
switch(key.keysym.sym)
{
case SDLK_F5:
if(settings["session"]["spectate-locked-pim"].Bool())
CPlayerInterface::pim->unlock();
else
CPlayerInterface::pim->lock();
s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
break;
case SDLK_F6:
s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
break;
case SDLK_F7:
s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
break;
case SDLK_F8:
s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
break;
default:
break;
}
return;
}
auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
GH.events().dispatchShortcutPressed(shortcutsVector);
}
void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
{
if(key.repeat != 0)
return; // ignore periodic event resends
assert(key.state == SDL_RELEASED);
auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
GH.events().dispatchShortcutReleased(shortcutsVector);
}

View File

@ -0,0 +1,23 @@
/*
* InputSourceKeyboard.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
struct SDL_KeyboardEvent;
/// Class that handles keyboard input from SDL events
class InputSourceKeyboard
{
public:
InputSourceKeyboard();
void handleEventKeyDown(const SDL_KeyboardEvent & current);
void handleEventKeyUp(const SDL_KeyboardEvent & current);
};

View File

@ -0,0 +1,72 @@
/*
* InputSourceMouse.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 "InputSourceMouse.h"
#include "../../lib/Point.h"
#include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/MouseButton.h"
#include <SDL_events.h>
void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion)
{
GH.events().dispatchMouseMoved(Point(motion.x, motion.y));
}
void InputSourceMouse::handleEventMouseButtonDown(const SDL_MouseButtonEvent & button)
{
Point position(button.x, button.y);
switch(button.button)
{
case SDL_BUTTON_LEFT:
if(button.clicks > 1)
GH.events().dispatchMouseDoubleClick(position);
else
GH.events().dispatchMouseButtonPressed(MouseButton::LEFT, position);
break;
case SDL_BUTTON_RIGHT:
GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position);
break;
case SDL_BUTTON_MIDDLE:
GH.events().dispatchMouseButtonPressed(MouseButton::MIDDLE, position);
break;
}
}
void InputSourceMouse::handleEventMouseWheel(const SDL_MouseWheelEvent & wheel)
{
// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
int x = 0, y = 0;
SDL_GetMouseState(&x, &y);
GH.events().dispatchMouseScrolled(Point(wheel.x, wheel.y), Point(x, y));
}
void InputSourceMouse::handleEventMouseButtonUp(const SDL_MouseButtonEvent & button)
{
Point position(button.x, button.y);
switch(button.button)
{
case SDL_BUTTON_LEFT:
GH.events().dispatchMouseButtonReleased(MouseButton::LEFT, position);
break;
case SDL_BUTTON_RIGHT:
GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position);
break;
case SDL_BUTTON_MIDDLE:
GH.events().dispatchMouseButtonReleased(MouseButton::MIDDLE, position);
break;
}
}

View File

@ -0,0 +1,25 @@
/*
* InputSourceMouse.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
struct SDL_MouseWheelEvent;
struct SDL_MouseMotionEvent;
struct SDL_MouseButtonEvent;
/// Class that handles mouse input from SDL events
class InputSourceMouse
{
public:
void handleEventMouseMotion(const SDL_MouseMotionEvent & current);
void handleEventMouseButtonDown(const SDL_MouseButtonEvent & current);
void handleEventMouseWheel(const SDL_MouseWheelEvent & current);
void handleEventMouseButtonUp(const SDL_MouseButtonEvent & current);
};

View File

@ -0,0 +1,92 @@
/*
* InputSourceText.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 "InputSourceText.h"
#include "../CMT.h"
#include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h"
#include "../../lib/Rect.h"
#include <SDL_events.h>
#include <SDL_render.h>
#ifdef VCMI_APPLE
# include <dispatch/dispatch.h>
#endif
#ifdef VCMI_IOS
# include "ios/utils.h"
#endif
void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
{
GH.events().dispatchTextInput(text.text);
}
void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
{
GH.events().dispatchTextEditing(text.text);
}
void InputSourceText::startTextInput(const Rect & whereInput)
{
#ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{
#endif
// TODO ios: looks like SDL bug actually, try fixing there
auto renderer = SDL_GetRenderer(mainWindow);
float scaleX, scaleY;
SDL_Rect viewport;
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
SDL_RenderGetViewport(renderer, &viewport);
#ifdef VCMI_IOS
const auto nativeScale = iOS_utils::screenScale();
scaleX /= nativeScale;
scaleY /= nativeScale;
#endif
SDL_Rect rectInScreenCoordinates;
rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
rectInScreenCoordinates.w = whereInput.w * scaleX;
rectInScreenCoordinates.h = whereInput.h * scaleY;
SDL_SetTextInputRect(&rectInScreenCoordinates);
if (SDL_IsTextInputActive() == SDL_FALSE)
{
SDL_StartTextInput();
}
#ifdef VCMI_APPLE
});
#endif
}
void InputSourceText::stopTextInput()
{
#ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{
#endif
if (SDL_IsTextInputActive() == SDL_TRUE)
{
SDL_StopTextInput();
}
#ifdef VCMI_APPLE
});
#endif
}

View File

@ -0,0 +1,29 @@
/*
* InputSourceText.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
VCMI_LIB_NAMESPACE_BEGIN
class Rect;
VCMI_LIB_NAMESPACE_END
struct SDL_TextEditingEvent;
struct SDL_TextInputEvent;
/// Class that handles text input (e.g. IME or direct input from physical keyboard) from SDL events
class InputSourceText
{
public:
void handleEventTextInput(const SDL_TextInputEvent & current);
void handleEventTextEditing(const SDL_TextEditingEvent & current);
void startTextInput(const Rect & where);
void stopTextInput();
};

View File

@ -0,0 +1,135 @@
/*
* InputSourceTouch.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 "InputSourceTouch.h"
#include "InputHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../CMT.h"
#include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/MouseButton.h"
#include <SDL_events.h>
#include <SDL_render.h>
#include <SDL_hints.h>
InputSourceTouch::InputSourceTouch()
: multifinger(false)
, isPointerRelativeMode(settings["general"]["userRelativePointer"].Bool())
{
if(isPointerRelativeMode)
{
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
}
}
void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
{
if(isPointerRelativeMode)
{
GH.input().fakeMoveCursor(tfinger.dx, tfinger.dy);
}
}
void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
{
auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
multifinger = fingerCount > 1;
if(isPointerRelativeMode)
{
if(tfinger.x > 0.5)
{
bool isRightClick = tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(true, isRightClick);
}
}
#ifndef VCMI_IOS
else if(fingerCount == 2)
{
Point position = convertTouchToMouse(tfinger);
GH.events().dispatchMouseMoved(position);
GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position);
}
#endif //VCMI_IOS
}
void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
{
#ifndef VCMI_IOS
auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
#endif //VCMI_IOS
if(isPointerRelativeMode)
{
if(tfinger.x > 0.5)
{
bool isRightClick = tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(false, isRightClick);
}
}
#ifndef VCMI_IOS
else if(multifinger)
{
Point position = convertTouchToMouse(tfinger);
GH.events().dispatchMouseMoved(position);
GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position);
multifinger = fingerCount != 0;
}
#endif //VCMI_IOS
}
Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger)
{
return Point(tfinger.x * GH.screenDimensions().x, tfinger.y * GH.screenDimensions().y);
}
void InputSourceTouch::fakeMouseButtonEventRelativeMode(bool down, bool right)
{
SDL_Event event;
SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(!down)
{
sme.type = SDL_MOUSEBUTTONUP;
}
sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
sme.x = GH.getCursorPosition().x;
sme.y = GH.getCursorPosition().y;
float xScale, yScale;
int w, h, rLogicalWidth, rLogicalHeight;
SDL_GetWindowSize(mainWindow, &w, &h);
SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
moveCursorToPosition(Point((int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2, (int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2)));
SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
event.button = sme;
SDL_PushEvent(&event);
}
void InputSourceTouch::moveCursorToPosition(const Point & position)
{
SDL_WarpMouseInWindow(mainWindow, position.x, position.y);
}

View File

@ -0,0 +1,37 @@
/*
* InputSourceTouch.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
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
struct SDL_TouchFingerEvent;
/// Class that handles touchscreen input from SDL events
class InputSourceTouch
{
bool multifinger;
bool isPointerRelativeMode;
/// moves mouse pointer into specified position inside vcmi window
void moveCursorToPosition(const Point & position);
Point convertTouchToMouse(const SDL_TouchFingerEvent & current);
void fakeMouseButtonEventRelativeMode(bool down, bool right);
public:
InputSourceTouch();
void handleEventFingerMotion(const SDL_TouchFingerEvent & current);
void handleEventFingerDown(const SDL_TouchFingerEvent & current);
void handleEventFingerUp(const SDL_TouchFingerEvent & current);
};

View File

@ -10,11 +10,11 @@
#include "StdInc.h"
#include "NotificationHandler.h"
#include <SDL_video.h>
#include <SDL_events.h>
#if defined(VCMI_WINDOWS)
#include <SDL_syswm.h>
#include <SDL_video.h>
#include <SDL_events.h>
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:

View File

@ -0,0 +1,87 @@
/*
* EventHandlerSDLUser.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 "UserEventHandler.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../mainmenu/CPrologEpilogVideo.h"
#include <SDL_events.h>
void UserEventHandler::handleUserEvent(const SDL_UserEvent & user)
{
switch(static_cast<EUserEvent>(user.code))
{
case EUserEvent::FORCE_QUIT:
{
handleQuit(false);
return;
}
break;
case EUserEvent::RETURN_TO_MAIN_MENU:
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
}
break;
case EUserEvent::RESTART_GAME:
{
CSH->sendRestartGame();
}
break;
case EUserEvent::CAMPAIGN_START_SCENARIO:
{
CSH->campaignServerRestartLock.set(true);
CSH->endGameplay();
auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(user.data1));
auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
auto finisher = [=]()
{
if(!ourCampaign->mapsRemaining.empty())
{
GH.windows().pushWindow(CMM);
GH.windows().pushWindow(CMM->menu);
CMM->openCampaignLobby(ourCampaign);
}
};
if(epilogue.hasPrologEpilog)
{
GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
}
else
{
CSH->campaignServerRestartLock.waitUntil(false);
finisher();
}
}
break;
case EUserEvent::RETURN_TO_MENU_LOAD:
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("load");
break;
case EUserEvent::FULLSCREEN_TOGGLED:
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
break;
}
default:
logGlobal->error("Unknown user event. Code %d", user.code);
break;
}
}

View File

@ -0,0 +1,20 @@
/*
* EventHandlerSDLUser.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
struct SDL_UserEvent;
/// Class for handling events of type SDL_UserEvent
class UserEventHandler
{
public:
void handleUserEvent(const SDL_UserEvent & current);
};

View File

@ -14,10 +14,15 @@
#include "CIntObject.h"
#include "CursorHandler.h"
#include "ShortcutHandler.h"
#include "FramerateManager.h"
#include "WindowHandler.h"
#include "EventDispatcher.h"
#include "../eventsSDL/InputHandler.h"
#include "../CGameInfo.h"
#include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/ScreenHandler.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h"
@ -26,20 +31,8 @@
#include "../../lib/CConfigHandler.h"
#include <SDL_render.h>
#include <SDL_timer.h>
#include <SDL_events.h>
#include <SDL_keycode.h>
#ifdef VCMI_APPLE
#include <dispatch/dispatch.h>
#endif
#ifdef VCMI_IOS
#include "ios/utils.h"
#endif
extern std::queue<SDL_Event> SDLEventsQueue;
extern boost::mutex eventsM;
CGuiHandler GH;
boost::thread_specific_ptr<bool> inGuiThread;
@ -72,590 +65,44 @@ SSetCaptureState::~SSetCaptureState()
GH.defActionsDef = prevActions;
}
static inline void
processList(const ui16 mask, const ui16 flag, std::list<CIntObject*> *lst, std::function<void (std::list<CIntObject*> *)> cb)
{
if (mask & flag)
cb(lst);
}
void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb)
{
processList(CIntObject::LCLICK,activityFlag,&lclickable,cb);
processList(CIntObject::RCLICK,activityFlag,&rclickable,cb);
processList(CIntObject::MCLICK,activityFlag,&mclickable,cb);
processList(CIntObject::HOVER,activityFlag,&hoverable,cb);
processList(CIntObject::MOVE,activityFlag,&motioninterested,cb);
processList(CIntObject::KEYBOARD,activityFlag,&keyinterested,cb);
processList(CIntObject::TIME,activityFlag,&timeinterested,cb);
processList(CIntObject::WHEEL,activityFlag,&wheelInterested,cb);
processList(CIntObject::DOUBLECLICK,activityFlag,&doubleClickInterested,cb);
processList(CIntObject::TEXTINPUT,activityFlag,&textInterested,cb);
}
void CGuiHandler::init()
{
inputHandlerInstance = std::make_unique<InputHandler>();
eventDispatcherInstance = std::make_unique<EventDispatcher>();
windowHandlerInstance = std::make_unique<WindowHandler>();
screenHandlerInstance = std::make_unique<ScreenHandler>();
shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
mainFPSmng = new CFramerateManager();
mainFPSmng->init(settings["video"]["targetfps"].Integer());
isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
}
void CGuiHandler::handleElementActivate(CIntObject * elem, ui16 activityFlag)
{
processLists(activityFlag,[&](std::list<CIntObject*> * lst){
lst->push_front(elem);
});
elem->active_m |= activityFlag;
}
void CGuiHandler::handleElementDeActivate(CIntObject * elem, ui16 activityFlag)
{
processLists(activityFlag,[&](std::list<CIntObject*> * lst){
auto hlp = std::find(lst->begin(),lst->end(),elem);
assert(hlp != lst->end());
lst->erase(hlp);
});
elem->active_m &= ~activityFlag;
}
void CGuiHandler::popInt(std::shared_ptr<IShowActivatable> top)
{
assert(listInt.front() == top);
top->deactivate();
disposed.push_back(top);
listInt.pop_front();
objsToBlit -= top;
if(!listInt.empty())
listInt.front()->activate();
totalRedraw();
}
void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
{
assert(newInt);
assert(!vstd::contains(listInt, newInt)); // do not add same object twice
//a new interface will be present, we'll need to use buffer surface (unless it's advmapint that will alter screenBuf on activate anyway)
screenBuf = screen2;
if(!listInt.empty())
listInt.front()->deactivate();
listInt.push_front(newInt);
CCS->curh->set(Cursor::Map::POINTER);
newInt->activate();
objsToBlit.push_back(newInt);
totalRedraw();
}
void CGuiHandler::popInts(int howMany)
{
if(!howMany) return; //senseless but who knows...
assert(listInt.size() >= howMany);
listInt.front()->deactivate();
for(int i=0; i < howMany; i++)
{
objsToBlit -= listInt.front();
disposed.push_back(listInt.front());
listInt.pop_front();
}
if(!listInt.empty())
{
listInt.front()->activate();
totalRedraw();
}
fakeMouseMove();
}
std::shared_ptr<IShowActivatable> CGuiHandler::topInt()
{
if(listInt.empty())
return std::shared_ptr<IShowActivatable>();
else
return listInt.front();
}
void CGuiHandler::totalRedraw()
{
#ifdef VCMI_ANDROID
SDL_FillRect(screen2, NULL, SDL_MapRGB(screen2->format, 0, 0, 0));
#endif
for(auto & elem : objsToBlit)
elem->showAll(screen2);
CSDL_Ext::blitAt(screen2,0,0,screen);
}
void CGuiHandler::updateTime()
{
int ms = mainFPSmng->getElapsedMilliseconds();
std::list<CIntObject*> hlp = timeinterested;
for (auto & elem : hlp)
{
if(!vstd::contains(timeinterested,elem)) continue;
(elem)->tick(ms);
}
framerateManagerInstance = std::make_unique<FramerateManager>(settings["video"]["targetfps"].Integer());
}
void CGuiHandler::handleEvents()
{
events().dispatchTimer(framerate().getElapsedMilliseconds());
//player interface may want special event handling
if(nullptr != LOCPLINT && LOCPLINT->capturedAllEvents())
return;
boost::unique_lock<boost::mutex> lock(eventsM);
while(!SDLEventsQueue.empty())
{
continueEventHandling = true;
SDL_Event currentEvent = SDLEventsQueue.front();
if (currentEvent.type == SDL_MOUSEMOTION)
{
cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
mouseButtonsMask = currentEvent.motion.state;
}
SDLEventsQueue.pop();
// In a sequence of mouse motion events, skip all but the last one.
// This prevents freezes when every motion event takes longer to handle than interval at which
// the events arrive (like dragging on the minimap in world view, with redraw at every event)
// so that the events would start piling up faster than they can be processed.
if ((currentEvent.type == SDL_MOUSEMOTION) && !SDLEventsQueue.empty() && (SDLEventsQueue.front().type == SDL_MOUSEMOTION))
continue;
handleCurrentEvent(currentEvent);
}
}
void CGuiHandler::convertTouchToMouse(SDL_Event * current)
{
int rLogicalWidth, rLogicalHeight;
SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
int adjustedMouseY = (int)(current->tfinger.y * rLogicalHeight);
int adjustedMouseX = (int)(current->tfinger.x * rLogicalWidth);
current->button.x = adjustedMouseX;
current->motion.x = adjustedMouseX;
current->button.y = adjustedMouseY;
current->motion.y = adjustedMouseY;
}
void CGuiHandler::fakeMoveCursor(float dx, float dy)
{
int x, y, w, h;
SDL_Event event;
SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
sme.state = SDL_GetMouseState(&x, &y);
SDL_GetWindowSize(mainWindow, &w, &h);
sme.x = CCS->curh->position().x + (int)(GH.pointerSpeedMultiplier * w * dx);
sme.y = CCS->curh->position().y + (int)(GH.pointerSpeedMultiplier * h * dy);
vstd::abetween(sme.x, 0, w);
vstd::abetween(sme.y, 0, h);
event.motion = sme;
SDL_PushEvent(&event);
input().processEvents();
}
void CGuiHandler::fakeMouseMove()
{
fakeMoveCursor(0, 0);
input().fakeMoveCursor(0, 0);
}
void CGuiHandler::startTextInput(const Rect & whereInput)
{
#ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{
#endif
// TODO ios: looks like SDL bug actually, try fixing there
auto renderer = SDL_GetRenderer(mainWindow);
float scaleX, scaleY;
SDL_Rect viewport;
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
SDL_RenderGetViewport(renderer, &viewport);
#ifdef VCMI_IOS
const auto nativeScale = iOS_utils::screenScale();
scaleX /= nativeScale;
scaleY /= nativeScale;
#endif
SDL_Rect rectInScreenCoordinates;
rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
rectInScreenCoordinates.w = whereInput.w * scaleX;
rectInScreenCoordinates.h = whereInput.h * scaleY;
SDL_SetTextInputRect(&rectInScreenCoordinates);
if (SDL_IsTextInputActive() == SDL_FALSE)
{
SDL_StartTextInput();
}
#ifdef VCMI_APPLE
});
#endif
input().startTextInput(whereInput);
}
void CGuiHandler::stopTextInput()
{
#ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{
#endif
if (SDL_IsTextInputActive() == SDL_TRUE)
{
SDL_StopTextInput();
}
#ifdef VCMI_APPLE
});
#endif
}
void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
{
SDL_Event event;
SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(!down)
{
sme.type = SDL_MOUSEBUTTONUP;
}
sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
sme.x = CCS->curh->position().x;
sme.y = CCS->curh->position().y;
float xScale, yScale;
int w, h, rLogicalWidth, rLogicalHeight;
SDL_GetWindowSize(mainWindow, &w, &h);
SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
moveCursorToPosition( Point(
(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2)));
SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
event.button = sme;
SDL_PushEvent(&event);
}
void CGuiHandler::handleCurrentEvent( SDL_Event & current )
{
if(current.type == SDL_KEYDOWN || current.type == SDL_KEYUP)
{
SDL_KeyboardEvent key = current.key;
if (key.repeat != 0)
return; // ignore periodic event resends
if(current.type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
{
//TODO: we need some central place for all interface-independent hotkeys
Settings s = settings.write["session"];
switch(key.keysym.sym)
{
case SDLK_F5:
if(settings["session"]["spectate-locked-pim"].Bool())
LOCPLINT->pim->unlock();
else
LOCPLINT->pim->lock();
s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
break;
case SDLK_F6:
s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
break;
case SDLK_F7:
s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
break;
case SDLK_F8:
s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
break;
case SDLK_F9:
//not working yet since CClient::run remain locked after BattleInterface removal
// if(LOCPLINT->battleInt)
// {
// GH.popInts(1);
// vstd::clear_pointer(LOCPLINT->battleInt);
// }
break;
default:
break;
}
return;
}
auto shortcutsVector = shortcutsHandler().translateKeycode(key.keysym.sym);
bool keysCaptured = false;
for(auto i = keyinterested.begin(); i != keyinterested.end() && continueEventHandling; i++)
{
for (EShortcut shortcut : shortcutsVector)
{
if((*i)->captureThisKey(shortcut))
{
keysCaptured = true;
break;
}
}
}
std::list<CIntObject*> miCopy = keyinterested;
for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
{
for (EShortcut shortcut : shortcutsVector)
{
if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(shortcut)))
{
if (key.state == SDL_PRESSED)
(**i).keyPressed(shortcut);
if (key.state == SDL_RELEASED)
(**i).keyReleased(shortcut);
}
}
}
}
else if(current.type == SDL_MOUSEMOTION)
{
handleMouseMotion(current);
}
else if(current.type == SDL_MOUSEBUTTONDOWN)
{
switch(current.button.button)
{
case SDL_BUTTON_LEFT:
{
auto doubleClicked = false;
if(lastClick == getCursorPosition() && (SDL_GetTicks() - lastClickTime) < 300)
{
std::list<CIntObject*> hlp = doubleClickInterested;
for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
{
if(!vstd::contains(doubleClickInterested, *i)) continue;
if((*i)->pos.isInside(current.motion.x, current.motion.y))
{
(*i)->onDoubleClick();
doubleClicked = true;
}
}
}
lastClick = current.motion;
lastClickTime = SDL_GetTicks();
if(!doubleClicked)
handleMouseButtonClick(lclickable, MouseButton::LEFT, true);
break;
}
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, MouseButton::MIDDLE, true);
break;
default:
break;
}
}
else if(current.type == SDL_MOUSEWHEEL)
{
std::list<CIntObject*> hlp = wheelInterested;
for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
{
if(!vstd::contains(wheelInterested,*i)) continue;
// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
int x = 0, y = 0;
SDL_GetMouseState(&x, &y);
(*i)->wheelScrolled(current.wheel.y < 0, (*i)->pos.isInside(x, y));
}
}
else if(current.type == SDL_TEXTINPUT)
{
for(auto it : textInterested)
{
it->textInputed(current.text.text);
}
}
else if(current.type == SDL_TEXTEDITING)
{
for(auto it : textInterested)
{
it->textEdited(current.edit.text);
}
}
else if(current.type == SDL_MOUSEBUTTONUP)
{
if(!multifinger)
{
switch(current.button.button)
{
case SDL_BUTTON_LEFT:
handleMouseButtonClick(lclickable, MouseButton::LEFT, false);
break;
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, MouseButton::MIDDLE, false);
break;
}
}
}
else if(current.type == SDL_FINGERMOTION)
{
if(isPointerRelativeMode)
{
fakeMoveCursor(current.tfinger.dx, current.tfinger.dy);
}
}
else if(current.type == SDL_FINGERDOWN)
{
auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
multifinger = fingerCount > 1;
if(isPointerRelativeMode)
{
if(current.tfinger.x > 0.5)
{
bool isRightClick = current.tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(true, isRightClick);
}
}
#ifndef VCMI_IOS
else if(fingerCount == 2)
{
convertTouchToMouse(&current);
handleMouseMotion(current);
handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
}
#endif //VCMI_IOS
}
else if(current.type == SDL_FINGERUP)
{
#ifndef VCMI_IOS
auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
#endif //VCMI_IOS
if(isPointerRelativeMode)
{
if(current.tfinger.x > 0.5)
{
bool isRightClick = current.tfinger.y < 0.5;
fakeMouseButtonEventRelativeMode(false, isRightClick);
}
}
#ifndef VCMI_IOS
else if(multifinger)
{
convertTouchToMouse(&current);
handleMouseMotion(current);
handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
multifinger = fingerCount != 0;
}
#endif //VCMI_IOS
}
} //event end
void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed)
{
auto hlp = interestedObjs;
for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
{
if(!vstd::contains(interestedObjs, *i)) continue;
auto prev = (*i)->mouseState(btn);
if(!isPressed)
(*i)->updateMouseState(btn, isPressed);
if((*i)->pos.isInside(getCursorPosition()))
{
if(isPressed)
(*i)->updateMouseState(btn, isPressed);
(*i)->click(btn, isPressed, prev);
}
else if(!isPressed)
(*i)->click(btn, boost::logic::indeterminate, prev);
}
}
void CGuiHandler::handleMouseMotion(const SDL_Event & current)
{
//sending active, hovered hoverable objects hover() call
std::vector<CIntObject*> hlp;
auto hoverableCopy = hoverable;
for(auto & elem : hoverableCopy)
{
if(elem->pos.isInside(getCursorPosition()))
{
if (!(elem)->hovered)
hlp.push_back((elem));
}
else if ((elem)->hovered)
{
(elem)->hover(false);
(elem)->hovered = false;
}
}
for(auto & elem : hlp)
{
elem->hover(true);
elem->hovered = true;
}
// do not send motion events for events outside our window
//if (current.motion.windowID == 0)
handleMoveInterested(current.motion);
}
void CGuiHandler::simpleRedraw()
{
//update only top interface and draw background
if(objsToBlit.size() > 1)
CSDL_Ext::blitAt(screen2,0,0,screen); //blit background
if(!objsToBlit.empty())
objsToBlit.back()->show(screen); //blit active interface/window
}
void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
{
//sending active, MotionInterested objects mouseMoved() call
std::list<CIntObject*> miCopy = motioninterested;
for(auto & elem : miCopy)
{
if(elem->strongInterest || Rect::createAround(elem->pos, 1).isInside( motion.x, motion.y)) //checking bounds including border fixes bug #2476
{
(elem)->mouseMoved(Point(motion.x, motion.y));
}
}
input().stopTextInput();
}
void CGuiHandler::renderFrame()
{
// Updating GUI requires locking pim mutex (that protects screen and GUI state).
// During game:
// When ending the game, the pim mutex might be hold by other thread,
@ -686,71 +133,56 @@ void CGuiHandler::renderFrame()
SDL_RenderPresent(mainRenderer);
disposed.clear();
windows().onFrameRendered();
}
mainFPSmng->framerateDelay(); // holds a constant FPS
framerate().framerateDelay(); // holds a constant FPS
}
CGuiHandler::CGuiHandler()
: lastClick(-500, -500)
, lastClickTime(0)
, defActionsDef(0)
: defActionsDef(0)
, captureChildren(false)
, multifinger(false)
, mouseButtonsMask(0)
, continueEventHandling(true)
, curInt(nullptr)
, mainFPSmng(nullptr)
, statusbar(nullptr)
, fakeStatusBar(std::make_shared<EmptyStatusBar>())
, terminate_cond (new CondSh<bool>(false))
{
terminate_cond = new CondSh<bool>(false);
}
CGuiHandler::~CGuiHandler()
{
delete mainFPSmng;
delete terminate_cond;
}
ShortcutHandler & CGuiHandler::shortcutsHandler()
ShortcutHandler & CGuiHandler::shortcuts()
{
assert(shortcutsHandlerInstance);
return *shortcutsHandlerInstance;
}
void CGuiHandler::moveCursorToPosition(const Point & position)
FramerateManager & CGuiHandler::framerate()
{
SDL_WarpMouseInWindow(mainWindow, position.x, position.y);
assert(framerateManagerInstance);
return *framerateManagerInstance;
}
bool CGuiHandler::isKeyboardCtrlDown() const
{
#ifdef VCMI_MAC
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
#else
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
#endif
return inputHandlerInstance->isKeyboardCtrlDown();
}
bool CGuiHandler::isKeyboardAltDown() const
{
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
return inputHandlerInstance->isKeyboardAltDown();
}
bool CGuiHandler::isKeyboardShiftDown() const
{
return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
}
void CGuiHandler::breakEventHandling()
{
continueEventHandling = false;
return inputHandlerInstance->isKeyboardShiftDown();
}
const Point & CGuiHandler::getCursorPosition() const
{
return cursorPosition;
return inputHandlerInstance->getCursorPosition();
}
Point CGuiHandler::screenDimensions() const
@ -758,21 +190,9 @@ Point CGuiHandler::screenDimensions() const
return Point(screen->w, screen->h);
}
bool CGuiHandler::isMouseButtonPressed() const
{
return mouseButtonsMask > 0;
}
bool CGuiHandler::isMouseButtonPressed(MouseButton button) const
{
static_assert(static_cast<uint32_t>(MouseButton::LEFT) == SDL_BUTTON_LEFT, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::RIGHT) == SDL_BUTTON_RIGHT, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::EXTRA1) == SDL_BUTTON_X1, "mismatch between VCMI and SDL enum!");
static_assert(static_cast<uint32_t>(MouseButton::EXTRA2) == SDL_BUTTON_X2, "mismatch between VCMI and SDL enum!");
uint32_t index = static_cast<uint32_t>(button);
return mouseButtonsMask & SDL_BUTTON(index);
return inputHandlerInstance->isMouseButtonPressed(button);
}
void CGuiHandler::drawFPSCounter()
@ -780,7 +200,7 @@ void CGuiHandler::drawFPSCounter()
static SDL_Rect overlay = { 0, 0, 64, 32};
uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10);
SDL_FillRect(screen, &overlay, black);
std::string fps = std::to_string(mainFPSmng->getFramerate());
std::string fps = std::to_string(framerate().getFramerate());
graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, Colors::YELLOW, Point(10, 10));
}
@ -791,63 +211,52 @@ bool CGuiHandler::amIGuiThread()
void CGuiHandler::pushUserEvent(EUserEvent usercode)
{
pushUserEvent(usercode, nullptr);
inputHandlerInstance->pushUserEvent(usercode, nullptr);
}
void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
{
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = static_cast<int32_t>(usercode);
event.user.data1 = userdata;
SDL_PushEvent(&event);
inputHandlerInstance->pushUserEvent(usercode, userdata);
}
CFramerateManager::CFramerateManager()
: rate(0)
, rateticks(0)
, fps(0)
, accumulatedFrames(0)
, accumulatedTime(0)
, lastticks(0)
, timeElapsed(0)
{}
void CFramerateManager::init(int newRate)
IScreenHandler & CGuiHandler::screenHandler()
{
rate = newRate;
rateticks = 1000.0 / rate;
this->lastticks = SDL_GetTicks();
return *screenHandlerInstance;
}
void CFramerateManager::framerateDelay()
EventDispatcher & CGuiHandler::events()
{
ui32 currentTicks = SDL_GetTicks();
timeElapsed = currentTicks - lastticks;
accumulatedFrames++;
// FPS is higher than it should be, then wait some time
if(timeElapsed < rateticks)
{
int timeToSleep = (uint32_t)ceil(this->rateticks) - timeElapsed;
boost::this_thread::sleep(boost::posix_time::milliseconds(timeToSleep));
}
currentTicks = SDL_GetTicks();
// recalculate timeElapsed for external calls via getElapsed()
// limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
timeElapsed = std::min<ui32>(currentTicks - lastticks, 100);
lastticks = SDL_GetTicks();
accumulatedTime += timeElapsed;
if(accumulatedFrames >= 100)
{
//about 2 second should be passed
fps = static_cast<int>(ceil(1000.0 / (accumulatedTime / accumulatedFrames)));
accumulatedTime = 0;
accumulatedFrames = 0;
}
return *eventDispatcherInstance;
}
InputHandler & CGuiHandler::input()
{
return *inputHandlerInstance;
}
WindowHandler & CGuiHandler::windows()
{
assert(windowHandlerInstance);
return *windowHandlerInstance;
}
std::shared_ptr<IStatusBar> CGuiHandler::statusbar()
{
auto locked = currentStatusBar.lock();
if (!locked)
return fakeStatusBar;
return locked;
}
void CGuiHandler::setStatusbar(std::shared_ptr<IStatusBar> newStatusBar)
{
currentStatusBar = newStatusBar;
}
void CGuiHandler::onScreenResize()
{
screenHandler().onScreenResize();
windows().onScreenResize();
}

View File

@ -9,118 +9,66 @@
*/
#pragma once
#include "MouseButton.h"
#include "../../lib/Point.h"
VCMI_LIB_NAMESPACE_BEGIN
template <typename T> struct CondSh;
class Point;
class Rect;
VCMI_LIB_NAMESPACE_END
union SDL_Event;
struct SDL_MouseMotionEvent;
enum class MouseButton;
class ShortcutHandler;
class CFramerateManager;
class FramerateManager;
class IStatusBar;
class CIntObject;
class IUpdateable;
class IShowActivatable;
class IShowable;
class IScreenHandler;
class WindowHandler;
class EventDispatcher;
class InputHandler;
// TODO: event handling need refactoring
// TODO: event handling need refactoring. Perhaps convert into delayed function call?
enum class EUserEvent
{
/*CHANGE_SCREEN_RESOLUTION = 1,*/
RETURN_TO_MAIN_MENU = 2,
//STOP_CLIENT = 3,
RESTART_GAME = 4,
RETURN_TO_MAIN_MENU,
RESTART_GAME,
RETURN_TO_MENU_LOAD,
FULLSCREEN_TOGGLED,
CAMPAIGN_START_SCENARIO,
FORCE_QUIT, //quit client without question
};
// A fps manager which holds game updates at a constant rate
class CFramerateManager
{
private:
double rateticks;
ui32 lastticks;
ui32 timeElapsed;
int rate;
int fps; // the actual fps value
ui32 accumulatedTime;
ui32 accumulatedFrames;
public:
CFramerateManager(); // initializes the manager with a given fps rate
void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
void framerateDelay(); // needs to be called every game update cycle
ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
ui32 getFrameNumber() const { return accumulatedFrames; }
ui32 getFramerate() const { return fps; };
FORCE_QUIT,
};
// Handles GUI logic and drawing
class CGuiHandler
{
public:
CFramerateManager * mainFPSmng; //to keep const framerate
std::list<std::shared_ptr<IShowActivatable>> listInt; //list of interfaces - front=foreground; back = background (includes adventure map, window interfaces, all kind of active dialogs, and so on)
std::shared_ptr<IStatusBar> statusbar;
private:
Point cursorPosition;
uint32_t mouseButtonsMask;
/// Fake no-op version status bar, for use in windows that have no status bar
std::shared_ptr<IStatusBar> fakeStatusBar;
std::vector<std::shared_ptr<IShowActivatable>> disposed;
/// Status bar of current window, if any. Uses weak_ptr to allow potential hanging reference after owned window has been deleted
std::weak_ptr<IStatusBar> currentStatusBar;
std::unique_ptr<ShortcutHandler> shortcutsHandlerInstance;
std::unique_ptr<WindowHandler> windowHandlerInstance;
std::atomic<bool> continueEventHandling;
using CIntObjectList = std::list<CIntObject *>;
//active GUI elements (listening for events
CIntObjectList lclickable;
CIntObjectList rclickable;
CIntObjectList mclickable;
CIntObjectList hoverable;
CIntObjectList keyinterested;
CIntObjectList motioninterested;
CIntObjectList timeinterested;
CIntObjectList wheelInterested;
CIntObjectList doubleClickInterested;
CIntObjectList textInterested;
void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
void handleCurrentEvent(SDL_Event &current);
void handleMouseMotion(const SDL_Event & current);
void handleMoveInterested( const SDL_MouseMotionEvent & motion );
void convertTouchToMouse(SDL_Event * current);
void fakeMoveCursor(float dx, float dy);
void fakeMouseButtonEventRelativeMode(bool down, bool right);
std::unique_ptr<IScreenHandler> screenHandlerInstance;
std::unique_ptr<FramerateManager> framerateManagerInstance;
std::unique_ptr<EventDispatcher> eventDispatcherInstance;
std::unique_ptr<InputHandler> inputHandlerInstance;
public:
void handleElementActivate(CIntObject * elem, ui16 activityFlag);
void handleElementDeActivate(CIntObject * elem, ui16 activityFlag);
public:
//objs to blit
std::vector<std::shared_ptr<IShowActivatable>> objsToBlit;
/// returns current position of mouse cursor, relative to vcmi window
const Point & getCursorPosition() const;
ShortcutHandler & shortcutsHandler();
ShortcutHandler & shortcuts();
FramerateManager & framerate();
EventDispatcher & events();
InputHandler & input();
/// Returns current logical screen dimensions
/// May not match size of window if user has UI scaling different from 100%
Point screenDimensions() const;
/// returns true if at least one mouse button is pressed
bool isMouseButtonPressed() const;
/// returns true if specified mouse button is pressed
bool isMouseButtonPressed(MouseButton button) const;
@ -132,17 +80,18 @@ public:
void startTextInput(const Rect & where);
void stopTextInput();
/// moves mouse pointer into specified position inside vcmi window
void moveCursorToPosition(const Point & position);
IScreenHandler & screenHandler();
WindowHandler & windows();
/// Returns currently active status bar. Guaranteed to be non-null
std::shared_ptr<IStatusBar> statusbar();
/// Set currently active status bar
void setStatusbar(std::shared_ptr<IStatusBar>);
IUpdateable *curInt;
Point lastClick;
unsigned lastClickTime;
bool multifinger;
bool isPointerRelativeMode;
float pointerSpeedMultiplier;
ui8 defActionsDef; //default auto actions
bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list
std::list<CIntObject *> createdObj; //stack of objs being created
@ -153,32 +102,16 @@ public:
void init();
void renderFrame();
void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering
void simpleRedraw(); //update only top interface and draw background from buffer, sets a flag, method gets called at the end of the rendering
/// called whenever user selects different resolution, requiring to center/resize all windows
void onScreenResize();
void pushInt(std::shared_ptr<IShowActivatable> newInt); //deactivate old top interface, activates this one and pushes to the top
template <typename T, typename ... Args>
void pushIntT(Args && ... args)
{
auto newInt = std::make_shared<T>(std::forward<Args>(args)...);
pushInt(newInt);
}
void popInts(int howMany); //pops one or more interfaces - deactivates top, deletes and removes given number of interfaces, activates new front
void popInt(std::shared_ptr<IShowActivatable> top); //removes given interface from the top and activates next
std::shared_ptr<IShowActivatable> topInt(); //returns top interface
void updateTime(); //handles timeInterested
void handleEvents(); //takes events from queue and calls interested objects
void fakeMouseMove();
void breakEventHandling(); //current event won't be propagated anymore
void drawFPSCounter(); // draws the FPS to the upper left corner of the screen
static bool amIGuiThread();
static void pushUserEvent(EUserEvent usercode);
static void pushUserEvent(EUserEvent usercode, void * userdata);
bool amIGuiThread();
void pushUserEvent(EUserEvent usercode);
void pushUserEvent(EUserEvent usercode, void * userdata);
CondSh<bool> * terminate_cond; // confirm termination
};

View File

@ -11,25 +11,18 @@
#include "CIntObject.h"
#include "CGuiHandler.h"
#include "WindowHandler.h"
#include "Shortcut.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../windows/CMessage.h"
#include "../CMT.h"
#include <SDL_pixels.h>
IShowActivatable::IShowActivatable()
{
type = 0;
}
CIntObject::CIntObject(int used_, Point pos_):
parent_m(nullptr),
active_m(0),
parent(parent_m),
active(active_m)
type(0)
{
hovered = captureAllKeys = strongInterest = false;
captureAllKeys = false;
used = used_;
recActions = defActions = GH.defActionsDef;
@ -45,7 +38,7 @@ CIntObject::CIntObject(int used_, Point pos_):
CIntObject::~CIntObject()
{
if(active_m)
if(isActive())
deactivate();
while(!children.empty())
@ -75,25 +68,16 @@ void CIntObject::showAll(SDL_Surface * to)
for(auto & elem : children)
if(elem->recActions & SHOWALL)
elem->showAll(to);
}
}
void CIntObject::activate()
{
if (active_m)
{
if ((used | GENERAL) == active_m)
return;
else
{
logGlobal->warn("Warning: IntObject re-activated with mismatching used and active");
deactivate(); //FIXME: better to avoid such possibility at all
}
}
if (isActive())
return;
active_m |= GENERAL;
activate(used);
activateEvents(used | GENERAL);
assert(isActive());
if(defActions & ACTIVATE)
for(auto & elem : children)
@ -101,20 +85,14 @@ void CIntObject::activate()
elem->activate();
}
void CIntObject::activate(ui16 what)
{
GH.handleElementActivate(this, what);
}
void CIntObject::deactivate()
{
if (!active_m)
if (!isActive())
return;
active_m &= ~ GENERAL;
deactivate(active_m);
deactivateEvents(ALL);
assert(!active_m);
assert(!isActive());
if(defActions & DEACTIVATE)
for(auto & elem : children)
@ -122,75 +100,33 @@ void CIntObject::deactivate()
elem->deactivate();
}
void CIntObject::deactivate(ui16 what)
{
GH.handleElementDeActivate(this, what);
}
void CIntObject::click(MouseButton btn, tribool down, bool previousState)
{
switch(btn)
{
default:
case MouseButton::LEFT:
clickLeft(down, previousState);
break;
case MouseButton::MIDDLE:
clickMiddle(down, previousState);
break;
case MouseButton::RIGHT:
clickRight(down, previousState);
break;
}
}
void CIntObject::printAtLoc(const std::string & text, int x, int y, EFonts font, SDL_Color kolor, SDL_Surface * dst)
{
graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y));
}
void CIntObject::printAtMiddleLoc(const std::string & text, int x, int y, EFonts font, SDL_Color kolor, SDL_Surface * dst)
{
printAtMiddleLoc(text, Point(x,y), font, kolor, dst);
}
void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color kolor, SDL_Surface * dst)
void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, const SDL_Color & kolor, SDL_Surface * dst)
{
graphics->fonts[font]->renderTextCenter(dst, text, kolor, pos.topLeft() + p);
}
void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst )
void CIntObject::printAtMiddleWBLoc( const std::string & text, const Point &p, EFonts font, int charpr, const SDL_Color & kolor, SDL_Surface * dst)
{
CSDL_Ext::blitAt(src, pos.x + x, pos.y + y, dst);
}
void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst)
{
blitAtLoc(src, p.x, p.y, dst);
}
void CIntObject::printAtMiddleWBLoc( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst)
{
graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, Point(pos.x + x, pos.y + y));
graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, pos.topLeft() + p);
}
void CIntObject::addUsedEvents(ui16 newActions)
{
if (active_m)
activate(~used & newActions);
if (isActive())
activateEvents(~used & newActions);
used |= newActions;
}
void CIntObject::removeUsedEvents(ui16 newActions)
{
if (active_m)
deactivate(used & newActions);
if (isActive())
deactivateEvents(used & newActions);
used &= ~newActions;
}
void CIntObject::disable()
{
if(active)
if(isActive())
deactivate();
recActions = DISPOSE;
@ -198,12 +134,23 @@ void CIntObject::disable()
void CIntObject::enable()
{
if(!active_m && (!parent_m || parent_m->active))
if(!isActive() && (!parent_m || parent_m->isActive()))
{
activate();
redraw();
}
recActions = 255;
}
void CIntObject::setEnabled(bool on)
{
if (on)
enable();
else
disable();
}
void CIntObject::fitToScreen(int borderWidth, bool propagate)
{
Point newPos = pos.topLeft();
@ -242,11 +189,11 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
children.push_back(child);
child->parent_m = this;
if(adjustPosition)
child->pos += pos.topLeft();
child->moveBy(pos.topLeft(), adjustPosition);
if (!active && child->active)
if (!isActive() && child->isActive())
child->deactivate();
if (active && !child->active)
if (isActive()&& !child->isActive())
child->activate();
}
@ -271,7 +218,7 @@ void CIntObject::redraw()
{
//currently most of calls come from active objects so this check won't affect them
//it should fix glitches when called by inactive elements located below active window
if (active)
if (isActive())
{
if (parent_m && (type & REDRAW_PARENT))
{
@ -286,6 +233,16 @@ void CIntObject::redraw()
}
}
bool CIntObject::isInside(const Point & position)
{
return pos.isInside(position);
}
void CIntObject::onScreenResize()
{
center(pos, true);
}
const Rect & CIntObject::center( const Rect &r, bool propagate )
{
pos.w = r.w;
@ -313,6 +270,7 @@ bool CIntObject::captureThisKey(EShortcut key)
CKeyShortcut::CKeyShortcut()
: assignedKey(EShortcut::NONE)
, shortcutPressed(false)
{}
CKeyShortcut::CKeyShortcut(EShortcut key)
@ -322,23 +280,19 @@ CKeyShortcut::CKeyShortcut(EShortcut key)
void CKeyShortcut::keyPressed(EShortcut key)
{
if( assignedKey == key && assignedKey != EShortcut::NONE)
if( assignedKey == key && assignedKey != EShortcut::NONE && !shortcutPressed)
{
bool prev = mouseState(MouseButton::LEFT);
updateMouseState(MouseButton::LEFT, true);
clickLeft(true, prev);
shortcutPressed = true;
clickLeft(true, false);
}
}
void CKeyShortcut::keyReleased(EShortcut key)
{
if( assignedKey == key && assignedKey != EShortcut::NONE)
if( assignedKey == key && assignedKey != EShortcut::NONE && shortcutPressed)
{
bool prev = mouseState(MouseButton::LEFT);
updateMouseState(MouseButton::LEFT, false);
clickLeft(false, prev);
shortcutPressed = false;
clickLeft(false, true);
}
}
@ -350,10 +304,7 @@ WindowBase::WindowBase(int used_, Point pos_)
void WindowBase::close()
{
if(GH.topInt().get() != this)
if(!GH.windows().isTopWindow(this))
logGlobal->error("Only top interface must be closed");
GH.popInts(1);
GH.windows().popWindows(1);
}
IStatusBar::~IStatusBar()
{}

View File

@ -9,84 +9,50 @@
*/
#pragma once
#include "MouseButton.h"
#include "../render/Graphics.h"
#include "../../lib/Rect.h"
#include "EventsReceiver.h"
struct SDL_Surface;
class CGuiHandler;
class CPicture;
enum class EShortcut;
using boost::logic::tribool;
// Defines a activate/deactive method
class IActivatable
{
public:
virtual void activate()=0;
virtual void deactivate()=0;
virtual ~IActivatable(){};
};
class IUpdateable
{
public:
virtual void update()=0;
virtual ~IUpdateable(){};
virtual ~IUpdateable() = default;
};
// Defines a show method
class IShowable
class IShowActivatable
{
public:
virtual void activate()=0;
virtual void deactivate()=0;
virtual void redraw()=0;
virtual void show(SDL_Surface * to) = 0;
virtual void showAll(SDL_Surface * to)
{
show(to);
}
virtual ~IShowable(){};
};
virtual void showAll(SDL_Surface * to) = 0;
class IShowActivatable : public IShowable, public IActivatable
{
public:
//redraw parent flag - this int may be semi-transparent and require redraw of parent window
enum {BLOCK_ADV_HOTKEYS = 2, REDRAW_PARENT=8};
int type; //bin flags using etype
IShowActivatable();
virtual ~IShowActivatable(){};
virtual void onScreenResize() = 0;
virtual ~IShowActivatable() = default;
};
// Base UI element
class CIntObject : public IShowActivatable //interface object
class CIntObject : public IShowActivatable, public AEventsReceiver //interface object
{
ui16 used;//change via addUsed() or delUsed
std::map<MouseButton, bool> currentMouseState;
ui16 used;
//non-const versions of fields to allow changing them in CIntObject
CIntObject *parent_m; //parent object
ui16 active_m;
protected:
//activate or deactivate specific action (LCLICK, RCLICK...)
void activate(ui16 what);
void deactivate(ui16 what);
public:
/*
* Functions and fields that supposed to be private but are not for now.
* Don't use them unless you really know what they are for
*/
//redraw parent flag - this int may be semi-transparent and require redraw of parent window
enum {REDRAW_PARENT=8};
int type; //bin flags using etype
std::vector<CIntObject *> children;
/*
* Public interface
*/
/// read-only parent access. May not be a "clean" solution but allows some compatibility
CIntObject * const & parent;
@ -96,43 +62,14 @@ public:
CIntObject(int used=0, Point offset=Point());
virtual ~CIntObject();
void updateMouseState(MouseButton btn, bool state) { currentMouseState[btn] = state; }
bool mouseState(MouseButton btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
virtual void click(MouseButton btn, tribool down, bool previousState);
virtual void clickLeft(tribool down, bool previousState) {}
virtual void clickRight(tribool down, bool previousState) {}
virtual void clickMiddle(tribool down, bool previousState) {}
//hover handling
/*const*/ bool hovered; //for determining if object is hovered
virtual void hover (bool on){}
void hover (bool on) override{}
//keyboard handling
bool captureAllKeys; //if true, only this object should get info about pressed keys
virtual void keyPressed(EShortcut key){}
virtual void keyReleased(EShortcut key){}
virtual bool captureThisKey(EShortcut key); //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
virtual void textInputed(const std::string & enteredText){};
virtual void textEdited(const std::string & enteredText){};
bool captureThisKey(EShortcut key) override; //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
//mouse movement handling
bool strongInterest; //if true - report all mouse movements, if not - only when hovered
virtual void mouseMoved (const Point & cursorPosition){}
//time handling
virtual void tick(uint32_t msPassed){}
//mouse wheel
virtual void wheelScrolled(bool down, bool in){}
//double click
virtual void onDoubleClick(){}
// These are the arguments that can be used to determine what kind of input the CIntObject will receive
enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
const ui16 & active;
void addUsedEvents(ui16 newActions);
void removeUsedEvents(ui16 newActions);
@ -140,8 +77,12 @@ public:
ui8 defActions; //which calls will be tried to be redirected to children
ui8 recActions; //which calls we allow to receive from parent
void disable(); //deactivates if needed, blocks all automatic activity, allows only disposal
void enable(); //activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
/// deactivates if needed, blocks all automatic activity, allows only disposal
void disable();
/// activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
void enable();
/// deactivates or activates UI element based on flag
void setEnabled(bool on);
// activate or deactivate object. Inactive object won't receive any input events (keyboard\mouse)
// usually used automatically by parent
@ -155,6 +96,12 @@ public:
//request complete redraw of this object
void redraw() override;
/// called only for windows whenever screen size changes
/// default behavior is to re-center, can be overriden
void onScreenResize() override;
bool isInside(const Point & position) override;
const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center
const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position
@ -164,30 +111,18 @@ public:
void addChild(CIntObject *child, bool adjustPosition = false);
void removeChild(CIntObject *child, bool adjustPosition = false);
//delChild - not needed, use normal "delete child" instead
//delChildNull - not needed, use "vstd::clear_pointer(child)" instead
/*
* Functions that should be used only by specific GUI elements. Don't use them unless you really know why they are here
*/
//functions for printing text. Use CLabel where possible instead
void printAtLoc(const std::string & text, int x, int y, EFonts font, SDL_Color color, SDL_Surface * dst);
void printAtMiddleLoc(const std::string & text, int x, int y, EFonts font, SDL_Color color, SDL_Surface * dst);
void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color color, SDL_Surface * dst);
void printAtMiddleWBLoc(const std::string & text, int x, int y, EFonts font, int charsPerLine, SDL_Color color, SDL_Surface * dst);
//image blitting. If possible use CPicture or CAnimImage instead
void blitAtLoc(SDL_Surface * src, int x, int y, SDL_Surface * dst);
void blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst);
friend class CGuiHandler;
/// functions for printing text.
/// Deprecated. Use CLabel where possible instead
void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, const SDL_Color & color, SDL_Surface * dst);
void printAtMiddleWBLoc(const std::string & text, const Point &p, EFonts font, int charsPerLine, const SDL_Color & color, SDL_Surface * dst);
};
/// Class for binding keys to left mouse button clicks
/// Classes wanting use it should have it as one of their base classes
class CKeyShortcut : public virtual CIntObject
{
bool shortcutPressed;
public:
EShortcut assignedKey;
CKeyShortcut();
@ -208,7 +143,7 @@ protected:
class IStatusBar
{
public:
virtual ~IStatusBar();
virtual ~IStatusBar() = default;
/// set current text for the status bar
virtual void write(const std::string & text) = 0;
@ -226,3 +161,12 @@ public:
virtual void setEnteredText(const std::string & text) = 0;
};
class EmptyStatusBar : public IStatusBar
{
virtual void write(const std::string & text){};
virtual void clear(){};
virtual void clearIfMatching(const std::string & testedText){};
virtual void setEnteringMode(bool on){};
virtual void setEnteredText(const std::string & text){};
};

View File

@ -12,6 +12,7 @@
#include "CursorHandler.h"
#include "CGuiHandler.h"
#include "FramerateManager.h"
#include "../renderSDL/CursorSoftware.h"
#include "../renderSDL/CursorHardware.h"
#include "../render/CAnimation.h"
@ -250,7 +251,7 @@ void CursorHandler::updateSpellcastCursor()
{
static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
frameTime += GH.framerate().getElapsedMilliseconds() / 1000.f;
size_t newFrame = frame;
while (frameTime >= frameDisplayDuration)

View File

@ -0,0 +1,244 @@
/*
* EventDispatcher.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 "EventDispatcher.h"
#include "EventsReceiver.h"
#include "FramerateManager.h"
#include "CGuiHandler.h"
#include "MouseButton.h"
#include "../../lib/Point.h"
template<typename Functor>
void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
{
auto processList = [&](ui16 mask, EventReceiversList & lst)
{
if(mask & activityFlag)
cb(lst);
};
processList(AEventsReceiver::LCLICK, lclickable);
processList(AEventsReceiver::RCLICK, rclickable);
processList(AEventsReceiver::MCLICK, mclickable);
processList(AEventsReceiver::HOVER, hoverable);
processList(AEventsReceiver::MOVE, motioninterested);
processList(AEventsReceiver::KEYBOARD, keyinterested);
processList(AEventsReceiver::TIME, timeinterested);
processList(AEventsReceiver::WHEEL, wheelInterested);
processList(AEventsReceiver::DOUBLECLICK, doubleClickInterested);
processList(AEventsReceiver::TEXTINPUT, textInterested);
}
void EventDispatcher::activateElement(AEventsReceiver * elem, ui16 activityFlag)
{
processLists(activityFlag,[&](EventReceiversList & lst){
lst.push_front(elem);
});
elem->activeState |= activityFlag;
}
void EventDispatcher::deactivateElement(AEventsReceiver * elem, ui16 activityFlag)
{
processLists(activityFlag,[&](EventReceiversList & lst){
auto hlp = std::find(lst.begin(),lst.end(),elem);
assert(hlp != lst.end());
lst.erase(hlp);
});
elem->activeState &= ~activityFlag;
}
void EventDispatcher::dispatchTimer(uint32_t msPassed)
{
EventReceiversList hlp = timeinterested;
for (auto & elem : hlp)
{
if(!vstd::contains(timeinterested,elem)) continue;
(elem)->tick(msPassed);
}
}
void EventDispatcher::dispatchShortcutPressed(const std::vector<EShortcut> & shortcutsVector)
{
bool keysCaptured = false;
for(auto & i : keyinterested)
for(EShortcut shortcut : shortcutsVector)
if(i->captureThisKey(shortcut))
keysCaptured = true;
EventReceiversList miCopy = keyinterested;
for(auto & i : miCopy)
{
for(EShortcut shortcut : shortcutsVector)
if(vstd::contains(keyinterested, i) && (!keysCaptured || i->captureThisKey(shortcut)))
{
i->keyPressed(shortcut);
if (keysCaptured)
return;
}
}
}
void EventDispatcher::dispatchShortcutReleased(const std::vector<EShortcut> & shortcutsVector)
{
bool keysCaptured = false;
for(auto & i : keyinterested)
for(EShortcut shortcut : shortcutsVector)
if(i->captureThisKey(shortcut))
keysCaptured = true;
EventReceiversList miCopy = keyinterested;
for(auto & i : miCopy)
{
for(EShortcut shortcut : shortcutsVector)
if(vstd::contains(keyinterested, i) && (!keysCaptured || i->captureThisKey(shortcut)))
{
i->keyReleased(shortcut);
if (keysCaptured)
return;
}
}
}
EventDispatcher::EventReceiversList & EventDispatcher::getListForMouseButton(MouseButton button)
{
switch (button)
{
case MouseButton::LEFT:
return lclickable;
case MouseButton::RIGHT:
return rclickable;
case MouseButton::MIDDLE:
return mclickable;
}
throw std::runtime_error("Invalid mouse button in getListForMouseButton");
}
void EventDispatcher::dispatchMouseDoubleClick(const Point & position)
{
bool doubleClicked = false;
auto hlp = doubleClickInterested;
for(auto & i : hlp)
{
if(!vstd::contains(doubleClickInterested, i))
continue;
if(i->isInside(position))
{
i->onDoubleClick();
doubleClicked = true;
}
}
if(!doubleClicked)
dispatchMouseButtonPressed(MouseButton::LEFT, position);
}
void EventDispatcher::dispatchMouseButtonPressed(const MouseButton & button, const Point & position)
{
handleMouseButtonClick(getListForMouseButton(button), button, true);
}
void EventDispatcher::dispatchMouseButtonReleased(const MouseButton & button, const Point & position)
{
handleMouseButtonClick(getListForMouseButton(button), button, false);
}
void EventDispatcher::handleMouseButtonClick(EventReceiversList & interestedObjs, MouseButton btn, bool isPressed)
{
auto hlp = interestedObjs;
for(auto & i : hlp)
{
if(!vstd::contains(interestedObjs, i))
continue;
auto prev = i->isMouseButtonPressed(btn);
if(!isPressed)
i->currentMouseState[btn] = isPressed;
if(i->isInside(GH.getCursorPosition()))
{
if(isPressed)
i->currentMouseState[btn] = isPressed;
i->click(btn, isPressed, prev);
}
else if(!isPressed)
i->click(btn, boost::logic::indeterminate, prev);
}
}
void EventDispatcher::dispatchMouseScrolled(const Point & distance, const Point & position)
{
EventReceiversList hlp = wheelInterested;
for(auto & i : hlp)
{
if(!vstd::contains(wheelInterested,i))
continue;
i->wheelScrolled(distance.y < 0, i->isInside(position));
}
}
void EventDispatcher::dispatchTextInput(const std::string & text)
{
for(auto it : textInterested)
{
it->textInputed(text);
}
}
void EventDispatcher::dispatchTextEditing(const std::string & text)
{
for(auto it : textInterested)
{
it->textEdited(text);
}
}
void EventDispatcher::dispatchMouseMoved(const Point & position)
{
//sending active, hovered hoverable objects hover() call
EventReceiversList hlp;
auto hoverableCopy = hoverable;
for(auto & elem : hoverableCopy)
{
if(elem->isInside(GH.getCursorPosition()))
{
if (!(elem)->isHovered())
hlp.push_back((elem));
}
else if ((elem)->isHovered())
{
(elem)->hover(false);
(elem)->hoveredState = false;
}
}
for(auto & elem : hlp)
{
elem->hover(true);
elem->hoveredState = true;
}
//sending active, MotionInterested objects mouseMoved() call
EventReceiversList miCopy = motioninterested;
for(auto & elem : miCopy)
{
if(elem->strongInterestState || elem->isInside(position)) //checking bounds including border fixes bug #2476
{
(elem)->mouseMoved(position);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* EventDispatcher.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
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
class AEventsReceiver;
enum class MouseButton;
enum class EShortcut;
/// Class that receives events from event producers and dispatches it to UI elements that are interested in this event
class EventDispatcher
{
using EventReceiversList = std::list<AEventsReceiver *>;
/// list of UI elements that are interested in particular event
EventReceiversList lclickable;
EventReceiversList rclickable;
EventReceiversList mclickable;
EventReceiversList hoverable;
EventReceiversList keyinterested;
EventReceiversList motioninterested;
EventReceiversList timeinterested;
EventReceiversList wheelInterested;
EventReceiversList doubleClickInterested;
EventReceiversList textInterested;
EventReceiversList & getListForMouseButton(MouseButton button);
void handleMouseButtonClick(EventReceiversList & interestedObjs, MouseButton btn, bool isPressed);
template<typename Functor>
void processLists(ui16 activityFlag, const Functor & cb);
public:
/// add specified UI element as interested. Uses unnamed enum from AEventsReceiver for activity flags
void activateElement(AEventsReceiver * elem, ui16 activityFlag);
/// removes specified UI element as interested for specified activities
void deactivateElement(AEventsReceiver * elem, ui16 activityFlag);
/// Regular timer event
void dispatchTimer(uint32_t msPassed);
/// Shortcut events (e.g. keyboard keys)
void dispatchShortcutPressed(const std::vector<EShortcut> & shortcuts);
void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts);
/// Mouse events
void dispatchMouseButtonPressed(const MouseButton & button, const Point & position);
void dispatchMouseButtonReleased(const MouseButton & button, const Point & position);
void dispatchMouseScrolled(const Point & distance, const Point & position);
void dispatchMouseDoubleClick(const Point & position);
void dispatchMouseMoved(const Point & position);
/// Text input events
void dispatchTextInput(const std::string & text);
void dispatchTextEditing(const std::string & text);
};

View File

@ -0,0 +1,74 @@
/*
* EventsReceiver.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 "EventsReceiver.h"
#include "MouseButton.h"
#include "CGuiHandler.h"
#include "EventDispatcher.h"
AEventsReceiver::AEventsReceiver()
: activeState(0)
, hoveredState(false)
, strongInterestState(false)
{
}
bool AEventsReceiver::isHovered() const
{
return hoveredState;
}
bool AEventsReceiver::isActive() const
{
return activeState;
}
bool AEventsReceiver::isMouseButtonPressed(MouseButton btn) const
{
return currentMouseState.count(btn) ? currentMouseState.at(btn) : false;
}
void AEventsReceiver::setMoveEventStrongInterest(bool on)
{
strongInterestState = on;
}
void AEventsReceiver::activateEvents(ui16 what)
{
assert((what & GENERAL) || (activeState & GENERAL));
activeState |= GENERAL;
GH.events().activateElement(this, what);
}
void AEventsReceiver::deactivateEvents(ui16 what)
{
if (what & GENERAL)
activeState &= ~GENERAL;
GH.events().deactivateElement(this, what & activeState);
}
void AEventsReceiver::click(MouseButton btn, tribool down, bool previousState)
{
switch(btn)
{
default:
case MouseButton::LEFT:
clickLeft(down, previousState);
break;
case MouseButton::MIDDLE:
clickMiddle(down, previousState);
break;
case MouseButton::RIGHT:
clickRight(down, previousState);
break;
}
}

View File

@ -0,0 +1,77 @@
/*
* EventsReceiver.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
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
class EventDispatcher;
enum class MouseButton;
enum class EShortcut;
using boost::logic::tribool;
/// Class that is capable of subscribing and receiving input events
/// Acts as base class for all UI elements
class AEventsReceiver
{
friend class EventDispatcher;
ui16 activeState;
bool hoveredState;
bool strongInterestState;
std::map<MouseButton, bool> currentMouseState;
void click(MouseButton btn, tribool down, bool previousState);
protected:
/// If set, UI element will receive all mouse movement events, even those outside this element
void setMoveEventStrongInterest(bool on);
/// Activates particular events for this UI element. Uses unnamed enum from this class
void activateEvents(ui16 what);
/// Deactivates particular events for this UI element. Uses unnamed enum from this class
void deactivateEvents(ui16 what);
virtual void clickLeft(tribool down, bool previousState) {}
virtual void clickRight(tribool down, bool previousState) {}
virtual void clickMiddle(tribool down, bool previousState) {}
virtual void textInputed(const std::string & enteredText) {}
virtual void textEdited(const std::string & enteredText) {}
virtual void tick(uint32_t msPassed) {}
virtual void wheelScrolled(bool down, bool in) {}
virtual void mouseMoved(const Point & cursorPosition) {}
virtual void hover(bool on) {}
virtual void onDoubleClick() {}
virtual void keyPressed(EShortcut key) {}
virtual void keyReleased(EShortcut key) {}
virtual bool captureThisKey(EShortcut key) = 0;
virtual bool isInside(const Point & position) = 0;
public:
AEventsReceiver();
virtual ~AEventsReceiver() = default;
/// These are the arguments that can be used to determine what kind of input UI element will receive
enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
/// Returns true if element is currently hovered by mouse
bool isHovered() const;
/// Returns true if element is currently active and may receive events
bool isActive() const;
/// Returns true if particular mouse button was pressed when inside this element
bool isMouseButtonPressed(MouseButton btn) const;
};

View File

@ -0,0 +1,57 @@
/*
* FramerateManager.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
*
*/
#include "StdInc.h"
#include "FramerateManager.h"
FramerateManager::FramerateManager(int targetFrameRate)
: targetFrameTime(Duration(boost::chrono::seconds(1)) / targetFrameRate)
, lastFrameIndex(0)
, lastFrameTimes({})
, lastTimePoint (Clock::now())
{
boost::range::fill(lastFrameTimes, targetFrameTime);
}
void FramerateManager::framerateDelay()
{
Duration timeSpentBusy = Clock::now() - lastTimePoint;
// FPS is higher than it should be, then wait some time
if(timeSpentBusy < targetFrameTime)
boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
// compute actual timeElapsed taking into account actual sleep interval
// limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
TimePoint currentTicks = Clock::now();
Duration timeElapsed = currentTicks - lastTimePoint;
if(timeElapsed > boost::chrono::milliseconds(100))
timeElapsed = boost::chrono::milliseconds(100);
lastTimePoint = currentTicks;
lastFrameIndex = (lastFrameIndex + 1) % lastFrameTimes.size();
lastFrameTimes[lastFrameIndex] = timeElapsed;
}
ui32 FramerateManager::getElapsedMilliseconds() const
{
return lastFrameTimes[lastFrameIndex] / boost::chrono::milliseconds(1);
}
ui32 FramerateManager::getFramerate() const
{
Duration accumulatedTime = std::accumulate(lastFrameTimes.begin(), lastFrameTimes.end(), Duration());
auto actualFrameTime = accumulatedTime / lastFrameTimes.size();
if(actualFrameTime == actualFrameTime.zero())
return 0;
return std::round(boost::chrono::duration<double>(1) / actualFrameTime);
};

View File

@ -0,0 +1,40 @@
/*
* FramerateManager.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
/// Framerate manager controls current game frame rate by constantly trying to reach targeted frame rate
class FramerateManager
{
using Clock = boost::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
/// cyclic buffer of durations of last frames
std::array<Duration, 60> lastFrameTimes;
Duration targetFrameTime;
TimePoint lastTimePoint;
/// index of last measured frome in lastFrameTimes array
ui32 lastFrameIndex;
public:
FramerateManager(int targetFramerate);
/// must be called every frame
/// updates framerate calculations and executes sleep to maintain target frame rate
void framerateDelay();
/// returns duration of last frame in seconds
ui32 getElapsedMilliseconds() const;
/// returns current estimation of frame rate
ui32 getFramerate() const;
};

View File

@ -26,6 +26,7 @@
#include "../windows/InfoWindows.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/filesystem/ResourceID.h"
InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
InterfaceObjectConfigurable(used, offset)
@ -46,6 +47,7 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset)
REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton);
REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout);
}
void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
@ -65,32 +67,75 @@ void InterfaceObjectConfigurable::deleteWidget(const std::string & name)
widgets.erase(iter);
}
void InterfaceObjectConfigurable::loadCustomBuilders(const JsonNode & config)
{
for(auto & item : config.Struct())
{
std::string typeName = item.first;
JsonNode baseConfig = item.second;
auto const & functor = [this, baseConfig](const JsonNode & widgetConfig) -> std::shared_ptr<CIntObject>
{
JsonNode actualConfig = widgetConfig;
JsonUtils::mergeCopy(actualConfig, baseConfig);
return this->buildWidget(actualConfig);
};
registerBuilder(typeName, functor);
}
}
void InterfaceObjectConfigurable::build(const JsonNode &config)
{
OBJ_CONSTRUCTION;
logGlobal->debug("Building configurable interface object");
auto * items = &config;
if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
{
if (!config["library"].isNull())
{
const JsonNode library(ResourceID(config["library"].String()));
loadCustomBuilders(library);
}
loadCustomBuilders(config["customTypes"]);
for(auto & item : config["variables"].Struct())
{
logGlobal->debug("Read variable named %s", item.first);
variables[item.first] = item.second;
}
items = &config["items"];
}
const std::string unnamedObjectPrefix = "__widget_";
for(const auto & item : items->Vector())
{
std::string name = item["name"].isNull()
? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
: item["name"].String();
logGlobal->debug("Building widget with name %s", name);
widgets[name] = buildWidget(item);
}
addWidget(item["name"].String(), buildWidget(item));
}
void InterfaceObjectConfigurable::addConditional(const std::string & name, bool active)
{
conditionals[name] = active;
}
void InterfaceObjectConfigurable::addWidget(const std::string & namePreferred, std::shared_ptr<CIntObject> widget)
{
static const std::string unnamedObjectPrefix = "__widget_";
std::string nameActual;
if (widgets.count(namePreferred) == 0)
nameActual = namePreferred;
else
logGlobal->error("Duplicated widget name: '%s'", namePreferred);
if (nameActual.empty())
nameActual = unnamedObjectPrefix + std::to_string(unnamedObjectId++);
logGlobal->debug("Building widget with name %s", nameActual);
widgets[nameActual] = widget;
}
std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
@ -174,6 +219,8 @@ EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
return EFonts::FONT_SMALL;
if(config.String() == "tiny")
return EFonts::FONT_TINY;
if(config.String() == "calisto")
return EFonts::FONT_CALLI;
}
logGlobal->debug("Uknown font attribute");
return EFonts::FONT_TIMES;
@ -211,7 +258,7 @@ EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
return EShortcut::NONE;
}
EShortcut result = GH.shortcutsHandler().findShortcut(config.String());
EShortcut result = GH.shortcuts().findShortcut(config.String());
if (result == EShortcut::NONE)
logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String());
return result;;
@ -255,7 +302,8 @@ std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(cons
for(const auto & item : config["items"].Vector())
{
itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
auto newToggle = std::dynamic_pointer_cast<CToggleButton>(buildWidget(item));
group->addToggle(itemIdx, newToggle);
}
}
if(!config["selected"].isNull())
@ -287,15 +335,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
assert(imgOrder.size() >= 4);
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
}
if(!config["callback"].isNull())
{
std::string callbackName = config["callback"].String();
if (callbacks.count(callbackName))
button->addCallback(callbacks.at(callbackName));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
loadToggleButtonCallback(button, config["callback"]);
return button;
}
@ -319,32 +359,69 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
assert(imgOrder.size() >= 4);
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
}
if(!config["callback"].isNull())
{
std::string callbackName = config["callback"].String();
if (callbacks.count(callbackName) > 0)
button->addCallback(std::bind(callbacks.at(callbackName), 0));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
if(!config["hotkey"].isNull())
{
if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING)
{
button->assignedKey = readHotkey(config["hotkey"]);
auto target = shortcuts.find(button->assignedKey);
if (target != shortcuts.end())
{
button->addCallback(target->second.callback);
target->second.assignedToButton = true;
}
}
}
loadButtonBorderColor(button, config["borderColor"]);
loadButtonCallback(button, config["callback"]);
loadButtonHotkey(button, config["hotkey"]);
return button;
}
void InterfaceObjectConfigurable::loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if (config.isNull())
return;
auto color = readColor(config);
button->setBorderColor(color);
}
void InterfaceObjectConfigurable::loadToggleButtonCallback(std::shared_ptr<CToggleButton> button, const JsonNode & config) const
{
if(config.isNull())
return;
std::string callbackName = config.String();
if (callbacks.count(callbackName) > 0)
button->addCallback(callbacks.at(callbackName));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
void InterfaceObjectConfigurable::loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if(config.isNull())
return;
std::string callbackName = config.String();
if (callbacks.count(callbackName) > 0)
button->addCallback(std::bind(callbacks.at(callbackName), 0));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
void InterfaceObjectConfigurable::loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if(config.isNull())
return;
if(config.getType() != JsonNode::JsonType::DATA_STRING)
{
logGlobal->error("Invalid shortcut format - string expected!");
return;
}
button->assignedKey = readHotkey(config);
auto target = shortcuts.find(button->assignedKey);
if (target == shortcuts.end())
return;
button->addCallback(target->second.callback);
target->second.assignedToButton = true;
}
std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
{
logGlobal->debug("Building widget CLabelGroup");
@ -374,7 +451,8 @@ std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode
auto itemsTotal = config["itemsTotal"].Integer();
auto value = config["selected"].Integer();
bool horizontal = config["orientation"].String() == "horizontal";
auto const & result = std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
const auto & result =
std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
if (!config["scrollBounds"].isNull())
{
@ -403,6 +481,69 @@ std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const
return std::make_shared<CFilledTexture>(image, rect);
}
/// Small helper class that provides ownership for shared_ptr's of child elements
class InterfaceLayoutWidget : public CIntObject
{
public:
std::vector<std::shared_ptr<CIntObject>> ownedChildren;
};
std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildLayout(const JsonNode & config)
{
logGlobal->debug("Building widget Layout");
bool vertical = config["vertical"].Bool();
bool horizontal = config["horizontal"].Bool();
bool dynamic = config["dynamic"].Bool();
int distance = config["distance"].Integer();
std::string customType = config["customType"].String();
auto position = readPosition(config["position"]);
auto result = std::make_shared<InterfaceLayoutWidget>();
result->moveBy(position);
Point layoutPosition;
for(auto item : config["items"].Vector())
{
if (item["type"].String().empty())
item["type"].String() = customType;
if (!item["created"].isNull())
{
std::string name = item["created"].String();
if (conditionals.count(name) != 0)
{
if (!conditionals.at(name))
continue;
}
else
{
logMod->warn("Unknown condition %s in widget!", name);
}
}
auto widget = buildWidget(item);
addWidget(item["name"].String(), widget);
result->ownedChildren.push_back(widget);
result->addChild(widget.get(), false);
widget->moveBy(position + layoutPosition);
if (dynamic && vertical)
layoutPosition.y += widget->pos.h;
if (dynamic && horizontal)
layoutPosition.x += widget->pos.w;
if (vertical)
layoutPosition.y += distance;
if (horizontal)
layoutPosition.x += distance;
}
return result;
}
std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
{
logGlobal->debug("Building widget CShowableAnim");

View File

@ -45,9 +45,15 @@ protected:
using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
void registerBuilder(const std::string &, BuilderFunction);
void loadCustomBuilders(const JsonNode & config);
//must be called after adding callbacks
void build(const JsonNode & config);
void addConditional(const std::string & name, bool active);
void addWidget(const std::string & name, std::shared_ptr<CIntObject> widget);
void addCallback(const std::string & callbackName, std::function<void(int)> callback);
JsonNode variables;
@ -73,6 +79,11 @@ protected:
std::pair<std::string, std::string> readHintText(const JsonNode &) const;
EShortcut readHotkey(const JsonNode &) const;
void loadToggleButtonCallback(std::shared_ptr<CToggleButton> button, const JsonNode & config) const;
void loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const;
void loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const;
void loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const;
//basic widgets
std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;
@ -84,6 +95,7 @@ protected:
std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
std::shared_ptr<CIntObject> buildLayout(const JsonNode &);
//composite widgets
std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
@ -100,5 +112,6 @@ private:
std::map<std::string, BuilderFunction> builders;
std::map<std::string, std::shared_ptr<CIntObject>> widgets;
std::map<std::string, std::function<void(int)>> callbacks;
std::map<std::string, bool> conditionals;
std::map<EShortcut, ShortcutState> shortcuts;
};

View File

@ -81,7 +81,9 @@ enum class EShortcut
// Adventure map screen
ADVENTURE_GAME_OPTIONS, // 'o', Open CAdventureOptions window
ADVENTURE_TOGGLE_GRID, // F6, Toggles map grid
ADVENTURE_TOGGLE_SLEEP, // z,w, Toggles hero sleep status
ADVENTURE_TOGGLE_SLEEP, // Toggles hero sleep status
ADVENTURE_SET_HERO_ASLEEP, // Moves hero to sleep state
ADVENTURE_SET_HERO_AWAKE, // Move hero to awake state
ADVENTURE_MOVE_HERO, // Moves hero alongside set path
ADVENTURE_VISIT_OBJECT, // Revisits object hero is standing on
ADVENTURE_VIEW_SELECTED,// Open window with currently selected hero/town
@ -94,12 +96,18 @@ enum class EShortcut
ADVENTURE_DIG_GRAIL,
ADVENTURE_VIEW_PUZZLE,
ADVENTURE_VIEW_WORLD,
ADVENTURE_VIEW_WORLD_X1,
ADVENTURE_VIEW_WORLD_X2,
ADVENTURE_VIEW_WORLD_X4,
ADVENTURE_TOGGLE_MAP_LEVEL,
ADVENTURE_KINGDOM_OVERVIEW,
ADVENTURE_QUEST_LOG,
ADVENTURE_CAST_SPELL,
ADVENTURE_END_TURN,
ADVENTURE_THIEVES_GUILD,
ADVENTURE_EXIT_WORLD_VIEW,
ADVENTURE_ZOOM_IN,
ADVENTURE_ZOOM_OUT,
ADVENTURE_ZOOM_RESET,
// Move hero one tile in specified direction. Bound to cursors & numpad buttons
ADVENTURE_MOVE_HERO_SW,
@ -153,4 +161,3 @@ enum class EShortcut
AFTER_LAST
};

View File

@ -64,6 +64,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_RETURN, EShortcut::LOBBY_LOAD_GAME },
{SDLK_KP_ENTER, EShortcut::LOBBY_LOAD_GAME },
{SDLK_s, EShortcut::LOBBY_SAVE_GAME },
{SDLK_RETURN, EShortcut::LOBBY_SAVE_GAME },
{SDLK_KP_ENTER, EShortcut::LOBBY_SAVE_GAME },
{SDLK_r, EShortcut::LOBBY_RANDOM_MAP },
{SDLK_h, EShortcut::LOBBY_HIDE_CHAT },
{SDLK_a, EShortcut::LOBBY_ADDITIONAL_OPTIONS },
@ -79,8 +81,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_TAB, EShortcut::GAME_ACTIVATE_CONSOLE },
{SDLK_o, EShortcut::ADVENTURE_GAME_OPTIONS },
{SDLK_F6, EShortcut::ADVENTURE_TOGGLE_GRID },
{SDLK_z, EShortcut::ADVENTURE_TOGGLE_SLEEP },
{SDLK_w, EShortcut::ADVENTURE_TOGGLE_SLEEP },
{SDLK_z, EShortcut::ADVENTURE_SET_HERO_ASLEEP },
{SDLK_w, EShortcut::ADVENTURE_SET_HERO_AWAKE },
{SDLK_m, EShortcut::ADVENTURE_MOVE_HERO },
{SDLK_SPACE, EShortcut::ADVENTURE_VISIT_OBJECT },
{SDLK_KP_1, EShortcut::ADVENTURE_MOVE_HERO_SW },
@ -106,12 +108,17 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_d, EShortcut::ADVENTURE_DIG_GRAIL },
{SDLK_p, EShortcut::ADVENTURE_VIEW_PUZZLE },
{SDLK_v, EShortcut::ADVENTURE_VIEW_WORLD },
{SDLK_1, EShortcut::ADVENTURE_VIEW_WORLD_X1 },
{SDLK_2, EShortcut::ADVENTURE_VIEW_WORLD_X2 },
{SDLK_4, EShortcut::ADVENTURE_VIEW_WORLD_X4 },
{SDLK_u, EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
{SDLK_k, EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
{SDLK_q, EShortcut::ADVENTURE_QUEST_LOG },
{SDLK_c, EShortcut::ADVENTURE_CAST_SPELL },
{SDLK_e, EShortcut::ADVENTURE_END_TURN },
{SDLK_g, EShortcut::ADVENTURE_THIEVES_GUILD },
{SDLK_KP_PLUS, EShortcut::ADVENTURE_ZOOM_IN },
{SDLK_KP_MINUS, EShortcut::ADVENTURE_ZOOM_OUT },
{SDLK_BACKSPACE, EShortcut::ADVENTURE_ZOOM_RESET },
{SDLK_q, EShortcut::BATTLE_TOGGLE_QUEUE },
{SDLK_c, EShortcut::BATTLE_USE_CREATURE_SPELL },
{SDLK_s, EShortcut::BATTLE_SURRENDER },
@ -218,6 +225,8 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"adventureGameOptions", EShortcut::ADVENTURE_GAME_OPTIONS },
{"adventureToggleGrid", EShortcut::ADVENTURE_TOGGLE_GRID },
{"adventureToggleSleep", EShortcut::ADVENTURE_TOGGLE_SLEEP },
{"adventureSetHeroAsleep", EShortcut::ADVENTURE_SET_HERO_ASLEEP },
{"adventureSetHeroAwake", EShortcut::ADVENTURE_SET_HERO_AWAKE },
{"adventureMoveHero", EShortcut::ADVENTURE_MOVE_HERO },
{"adventureVisitObject", EShortcut::ADVENTURE_VISIT_OBJECT },
{"adventureMoveHeroSW", EShortcut::ADVENTURE_MOVE_HERO_SW },
@ -238,12 +247,18 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"adventureDigGrail", EShortcut::ADVENTURE_DIG_GRAIL },
{"adventureViewPuzzle", EShortcut::ADVENTURE_VIEW_PUZZLE },
{"adventureViewWorld", EShortcut::ADVENTURE_VIEW_WORLD },
{"adventureViewWorld1", EShortcut::ADVENTURE_VIEW_WORLD_X1 },
{"adventureViewWorld2", EShortcut::ADVENTURE_VIEW_WORLD_X2 },
{"adventureViewWorld4", EShortcut::ADVENTURE_VIEW_WORLD_X4 },
{"adventureToggleMapLevel", EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
{"adventureKingdomOverview", EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
{"adventureQuestLog", EShortcut::ADVENTURE_QUEST_LOG },
{"adventureCastSpell", EShortcut::ADVENTURE_CAST_SPELL },
{"adventureEndTurn", EShortcut::ADVENTURE_END_TURN },
{"adventureThievesGuild", EShortcut::ADVENTURE_THIEVES_GUILD },
{"adventureExitWorldView", EShortcut::ADVENTURE_EXIT_WORLD_VIEW },
{"adventureZoomIn", EShortcut::ADVENTURE_ZOOM_IN },
{"adventureZoomOut", EShortcut::ADVENTURE_ZOOM_OUT },
{"adventureZoomReset", EShortcut::ADVENTURE_ZOOM_RESET },
{"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE },
{"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL },
{"battleSurrender", EShortcut::BATTLE_SURRENDER },
@ -278,85 +293,3 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
return shortcutNames.at(identifier);
return EShortcut::NONE;
}

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