Merge branch 'develop' into rmg-split-enum-monster-strength
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|
||||
|
||||
if(blockerObject)
|
||||
{
|
||||
blockers.push_back(blockerObject);
|
||||
blockers.insert(blockers.begin(), blockerObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 747 B |
@ -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": "强化师",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "Заклинатель (массовое)",
|
||||
|
@ -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",
|
||||
|
@ -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" : "Чарівник",
|
||||
|
@ -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;
|
||||
|
||||
|
676
client/CMT.cpp
@ -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
|
||||
{
|
||||
|
@ -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>"
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
827
client/adventureMap/AdventureMapInterface.cpp
Normal 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();
|
||||
}
|
@ -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;
|
455
client/adventureMap/AdventureMapShortcuts.cpp
Normal 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;
|
||||
}
|
88
client/adventureMap/AdventureMapShortcuts.h
Normal 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);
|
||||
};
|
455
client/adventureMap/AdventureMapWidget.cpp
Normal 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);
|
||||
}
|
109
client/adventureMap/AdventureMapWidget.h
Normal 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);
|
||||
};
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
20
client/adventureMap/AdventureState.h
Normal 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
|
||||
};
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()> &);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
269
client/eventsSDL/InputHandler.cpp
Normal 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;
|
||||
}
|
77
client/eventsSDL/InputHandler.h
Normal 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;
|
||||
};
|
85
client/eventsSDL/InputSourceKeyboard.cpp
Normal 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);
|
||||
}
|
23
client/eventsSDL/InputSourceKeyboard.h
Normal 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);
|
||||
};
|
72
client/eventsSDL/InputSourceMouse.cpp
Normal 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;
|
||||
}
|
||||
}
|
25
client/eventsSDL/InputSourceMouse.h
Normal 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);
|
||||
};
|
92
client/eventsSDL/InputSourceText.cpp
Normal 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
|
||||
}
|
29
client/eventsSDL/InputSourceText.h
Normal 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();
|
||||
};
|
135
client/eventsSDL/InputSourceTouch.cpp
Normal 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);
|
||||
}
|
37
client/eventsSDL/InputSourceTouch.h
Normal 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);
|
||||
};
|
@ -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:
|
87
client/eventsSDL/UserEventHandler.cpp
Normal 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;
|
||||
}
|
||||
}
|
20
client/eventsSDL/UserEventHandler.h
Normal 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);
|
||||
};
|
@ -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(¤t);
|
||||
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(¤t);
|
||||
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();
|
||||
}
|
||||
|
@ -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 ¤t);
|
||||
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
|
||||
};
|
||||
|
@ -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()
|
||||
{}
|
||||
|
@ -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){};
|
||||
};
|
||||
|
@ -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)
|
||||
|
244
client/gui/EventDispatcher.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
68
client/gui/EventDispatcher.h
Normal 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);
|
||||
};
|
74
client/gui/EventsReceiver.cpp
Normal 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;
|
||||
}
|
||||
}
|
77
client/gui/EventsReceiver.h
Normal 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;
|
||||
};
|
57
client/gui/FramerateManager.cpp
Normal 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);
|
||||
};
|
40
client/gui/FramerateManager.h
Normal 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;
|
||||
};
|
@ -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");
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|