mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-03 00:46:55 +02:00
Alternative actions submod from extras is now deprecated and will have no effect. As long as screen width allows, game will now display additional panel with all possible unit actions. Panel will also display spells that can be cast by unit, allowing small version of unit spellbook (total limit of actions is 12, but some are used for creature actions, so unit spells are limited to 7-9)
865 lines
27 KiB
C++
865 lines
27 KiB
C++
/*
|
|
* BattleWindow.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 "BattleWindow.h"
|
|
|
|
#include "BattleActionsController.h"
|
|
#include "BattleConsole.h"
|
|
#include "BattleFieldController.h"
|
|
#include "BattleInterface.h"
|
|
#include "BattleStacksController.h"
|
|
#include "HeroInfoWindow.h"
|
|
#include "QuickSpellPanel.h"
|
|
#include "StackInfoBasicPanel.h"
|
|
#include "StackQueue.h"
|
|
#include "UnitActionPanel.h"
|
|
|
|
#include "../CPlayerInterface.h"
|
|
#include "../GameEngine.h"
|
|
#include "../GameInstance.h"
|
|
#include "../adventureMap/CInGameConsole.h"
|
|
#include "../adventureMap/TurnTimerWidget.h"
|
|
#include "../gui/CursorHandler.h"
|
|
#include "../gui/Shortcut.h"
|
|
#include "../gui/WindowHandler.h"
|
|
#include "../render/CAnimation.h"
|
|
#include "../render/Canvas.h"
|
|
#include "../render/IRenderHandler.h"
|
|
#include "../widgets/Buttons.h"
|
|
#include "../widgets/Images.h"
|
|
#include "../windows/CCreatureWindow.h"
|
|
#include "../windows/CMessage.h"
|
|
#include "../windows/CSpellWindow.h"
|
|
#include "../windows/settings/SettingsMainWindow.h"
|
|
|
|
#include "../../lib/CConfigHandler.h"
|
|
#include "../../lib/CPlayerState.h"
|
|
#include "../../lib/CStack.h"
|
|
#include "../../lib/GameLibrary.h"
|
|
#include "../../lib/StartInfo.h"
|
|
#include "../../lib/battle/BattleInfo.h"
|
|
#include "../../lib/battle/CPlayerBattleCallback.h"
|
|
#include "../../lib/callback/CCallback.h"
|
|
#include "../../lib/callback/CDynLibHandler.h"
|
|
#include "../../lib/entities/artifact/CArtHandler.h"
|
|
#include "../../lib/filesystem/ResourcePath.h"
|
|
#include "../../lib/gameState/InfoAboutArmy.h"
|
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
|
#include "../../lib/texts/CGeneralTextHandler.h"
|
|
|
|
BattleWindow::BattleWindow(BattleInterface & Owner)
|
|
: owner(Owner)
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
pos.w = 800;
|
|
pos.h = 600;
|
|
pos = center();
|
|
|
|
PlayerColor defenderColor = owner.getBattle()->getBattle()->getSidePlayer(BattleSide::DEFENDER);
|
|
PlayerColor attackerColor = owner.getBattle()->getBattle()->getSidePlayer(BattleSide::ATTACKER);
|
|
bool isDefenderHuman = defenderColor.isValidPlayer() && GAME->interface()->cb->getStartInfo()->playerInfos.at(defenderColor).isControlledByHuman();
|
|
bool isAttackerHuman = attackerColor.isValidPlayer() && GAME->interface()->cb->getStartInfo()->playerInfos.at(attackerColor).isControlledByHuman();
|
|
onlyOnePlayerHuman = isDefenderHuman != isAttackerHuman;
|
|
|
|
REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
|
|
|
|
const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
|
|
|
|
addShortcut(EShortcut::BATTLE_TOGGLE_QUICKSPELL, [this](){ this->toggleStickyQuickSpellVisibility();});
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_0, [this](){ useSpellIfPossible(0); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_1, [this](){ useSpellIfPossible(1); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_2, [this](){ useSpellIfPossible(2); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_3, [this](){ useSpellIfPossible(3); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_4, [this](){ useSpellIfPossible(4); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_5, [this](){ useSpellIfPossible(5); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_6, [this](){ useSpellIfPossible(6); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_7, [this](){ useSpellIfPossible(7); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_8, [this](){ useSpellIfPossible(8); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_9, [this](){ useSpellIfPossible(9); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_10, [this](){ useSpellIfPossible(10); });
|
|
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_11, [this](){ useSpellIfPossible(11); });
|
|
|
|
addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
|
|
addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
|
|
addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
|
|
addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this));
|
|
addShortcut(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, std::bind(&BattleWindow::endWithAutocombat, this));
|
|
addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this));
|
|
addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this));
|
|
addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this));
|
|
addShortcut(EShortcut::BATTLE_CONSOLE_UP, std::bind(&BattleWindow::bConsoleUpf, this));
|
|
addShortcut(EShortcut::BATTLE_CONSOLE_DOWN, std::bind(&BattleWindow::bConsoleDownf, this));
|
|
addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this));
|
|
addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this));
|
|
addShortcut(EShortcut::BATTLE_OPEN_ACTIVE_UNIT, std::bind(&BattleWindow::bOpenActiveUnit, this));
|
|
addShortcut(EShortcut::BATTLE_OPEN_HOVERED_UNIT, std::bind(&BattleWindow::bOpenHoveredUnit, this));
|
|
|
|
addShortcut(EShortcut::BATTLE_TOGGLE_QUEUE, [this](){ this->toggleQueueVisibility();});
|
|
addShortcut(EShortcut::BATTLE_TOGGLE_HEROES_STATS, [this](){ this->toggleStickyHeroWindowsVisibility();});
|
|
addShortcut(EShortcut::BATTLE_USE_CREATURE_SPELL, [this](){ this->owner.actionsController->enterCreatureCastingMode(); });
|
|
addShortcut(EShortcut::GLOBAL_CANCEL, [this](){ this->owner.actionsController->endCastingSpell(); });
|
|
|
|
build(config);
|
|
|
|
console = widget<BattleConsole>("console");
|
|
|
|
owner.console = console;
|
|
|
|
owner.fieldController.reset( new BattleFieldController(owner));
|
|
owner.fieldController->createHeroes();
|
|
|
|
createQueue();
|
|
createQuickSpellWindow();
|
|
createStickyHeroInfoWindows();
|
|
createTimerInfoWindows();
|
|
|
|
if ( owner.tacticsMode )
|
|
tacticPhaseStarted();
|
|
else
|
|
tacticPhaseEnded();
|
|
|
|
addUsedEvents(LCLICK | KEYBOARD);
|
|
}
|
|
|
|
void BattleWindow::createQueue()
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
|
|
//create stack queue and adjust our own position
|
|
bool embedQueue;
|
|
bool showQueue = settings["battle"]["showQueue"].Bool();
|
|
std::string queueSize = settings["battle"]["queueSize"].String();
|
|
|
|
if(queueSize == "auto")
|
|
embedQueue = ENGINE->screenDimensions().y < 700;
|
|
else
|
|
embedQueue = ENGINE->screenDimensions().y < 700 || queueSize == "small";
|
|
|
|
queue = std::make_shared<StackQueue>(embedQueue, owner);
|
|
if(!embedQueue && showQueue)
|
|
{
|
|
//re-center, taking into account stack queue position
|
|
pos.y -= queue->pos.h;
|
|
pos.h += queue->pos.h;
|
|
pos = center();
|
|
}
|
|
|
|
if (!showQueue)
|
|
queue->disable();
|
|
}
|
|
|
|
void BattleWindow::createStickyHeroInfoWindows()
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
|
|
if(owner.defendingHeroInstance)
|
|
{
|
|
InfoAboutHero info;
|
|
info.initFromHero(owner.defendingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
|
|
defenderHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, nullptr);
|
|
}
|
|
if(owner.attackingHeroInstance)
|
|
{
|
|
InfoAboutHero info;
|
|
info.initFromHero(owner.attackingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
|
|
attackerHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, nullptr);
|
|
}
|
|
|
|
bool showInfoWindows = settings["battle"]["stickyHeroInfoWindows"].Bool();
|
|
|
|
if(!showInfoWindows)
|
|
{
|
|
if(attackerHeroWindow)
|
|
attackerHeroWindow->disable();
|
|
|
|
if(defenderHeroWindow)
|
|
defenderHeroWindow->disable();
|
|
}
|
|
|
|
setPositionInfoWindow();
|
|
}
|
|
|
|
void BattleWindow::createQuickSpellWindow()
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
|
|
quickSpellWindow = std::make_shared<QuickSpellPanel>(owner);
|
|
quickSpellWindow->moveTo(Point(pos.x - 67, pos.y));
|
|
|
|
unitActionWindow = std::make_shared<UnitActionPanel>(owner);
|
|
unitActionWindow->moveTo(Point(pos.x + pos.w + 15, pos.y));
|
|
|
|
if(settings["battle"]["enableQuickSpellPanel"].Bool())
|
|
showStickyQuickSpellWindow();
|
|
else
|
|
hideStickyQuickSpellWindow();
|
|
}
|
|
|
|
void BattleWindow::toggleStickyQuickSpellVisibility()
|
|
{
|
|
if(settings["battle"]["enableQuickSpellPanel"].Bool())
|
|
hideStickyQuickSpellWindow();
|
|
else
|
|
showStickyQuickSpellWindow();
|
|
}
|
|
|
|
void BattleWindow::hideStickyQuickSpellWindow()
|
|
{
|
|
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
|
|
showStickyQuickSpellWindow->Bool() = false;
|
|
|
|
quickSpellWindow->disable();
|
|
unitActionWindow->disable();
|
|
|
|
setPositionInfoWindow();
|
|
createTimerInfoWindows();
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::showStickyQuickSpellWindow()
|
|
{
|
|
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
|
|
showStickyQuickSpellWindow->Bool() = true;
|
|
|
|
auto hero = owner.getBattle()->battleGetMyHero();
|
|
|
|
bool quickSpellWindowVisible = ENGINE->screenDimensions().x >= 1050 && hero != nullptr && hero->hasSpellbook();
|
|
bool unitActionWindowVisible = ENGINE->screenDimensions().x >= 1050;
|
|
|
|
quickSpellWindow->setEnabled(quickSpellWindowVisible);
|
|
unitActionWindow->setEnabled(unitActionWindowVisible);
|
|
|
|
setPositionInfoWindow();
|
|
createTimerInfoWindows();
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::createTimerInfoWindows()
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
|
|
int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
|
|
int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
|
|
|
|
if(GAME->interface()->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || GAME->interface()->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
|
|
{
|
|
PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
|
|
PlayerColor defender = owner.getBattle()->sideToPlayer(BattleSide::DEFENDER);
|
|
|
|
if (attacker.isValidPlayer())
|
|
{
|
|
if (ENGINE->screenDimensions().x >= 1000)
|
|
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92 + xOffsetAttacker, 1), attacker);
|
|
else
|
|
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
|
|
}
|
|
|
|
if (defender.isValidPlayer())
|
|
{
|
|
if (ENGINE->screenDimensions().x >= 1000)
|
|
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16 + xOffsetDefender, 1), defender);
|
|
else
|
|
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w - 78, 135), defender);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
|
|
{
|
|
auto rect = readRect(config["rect"]);
|
|
auto offset = readPosition(config["imagePosition"]);
|
|
auto background = widget<CPicture>("menuBattle");
|
|
return std::make_shared<BattleConsole>(owner, background, rect.topLeft(), offset, rect.dimensions() );
|
|
}
|
|
|
|
void BattleWindow::useSpellIfPossible(int slot)
|
|
{
|
|
SpellID id;
|
|
bool fromSettings;
|
|
std::tie(id, fromSettings) = quickSpellWindow->getSpells()[slot];
|
|
|
|
if(id == SpellID::NONE)
|
|
return;
|
|
|
|
if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero()))
|
|
{
|
|
owner.castThisSpell(id);
|
|
}
|
|
};
|
|
|
|
void BattleWindow::toggleQueueVisibility()
|
|
{
|
|
if(settings["battle"]["showQueue"].Bool())
|
|
hideQueue();
|
|
else
|
|
showQueue();
|
|
}
|
|
|
|
void BattleWindow::hideQueue()
|
|
{
|
|
if(settings["battle"]["showQueue"].Bool() == false)
|
|
return;
|
|
|
|
Settings showQueue = settings.write["battle"]["showQueue"];
|
|
showQueue->Bool() = false;
|
|
|
|
queue->disable();
|
|
|
|
if (!queue->embedded)
|
|
{
|
|
//re-center, taking into account stack queue position
|
|
pos.y += queue->pos.h;
|
|
pos.h -= queue->pos.h;
|
|
pos = center();
|
|
}
|
|
setPositionInfoWindow();
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::showQueue()
|
|
{
|
|
if(settings["battle"]["showQueue"].Bool() == true)
|
|
return;
|
|
|
|
Settings showQueue = settings.write["battle"]["showQueue"];
|
|
showQueue->Bool() = true;
|
|
|
|
createQueue();
|
|
updateQueue();
|
|
setPositionInfoWindow();
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::toggleStickyHeroWindowsVisibility()
|
|
{
|
|
if(settings["battle"]["stickyHeroInfoWindows"].Bool())
|
|
hideStickyHeroWindows();
|
|
else
|
|
showStickyHeroWindows();
|
|
}
|
|
|
|
void BattleWindow::hideStickyHeroWindows()
|
|
{
|
|
if(settings["battle"]["stickyHeroInfoWindows"].Bool() == false)
|
|
return;
|
|
|
|
Settings showStickyHeroInfoWindows = settings.write["battle"]["stickyHeroInfoWindows"];
|
|
showStickyHeroInfoWindows->Bool() = false;
|
|
|
|
if(attackerHeroWindow)
|
|
attackerHeroWindow->disable();
|
|
|
|
if(defenderHeroWindow)
|
|
defenderHeroWindow->disable();
|
|
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::showStickyHeroWindows()
|
|
{
|
|
if(settings["battle"]["stickyHeroInfoWindows"].Bool() == true)
|
|
return;
|
|
|
|
Settings showStickyHeroInfoWindows = settings.write["battle"]["stickyHeroInfoWindows"];
|
|
showStickyHeroInfoWindows->Bool() = true;
|
|
|
|
|
|
createStickyHeroInfoWindows();
|
|
ENGINE->windows().totalRedraw();
|
|
}
|
|
|
|
void BattleWindow::updateQueue()
|
|
{
|
|
queue->update();
|
|
createQuickSpellWindow();
|
|
}
|
|
|
|
void BattleWindow::setPositionInfoWindow()
|
|
{
|
|
int xOffsetAttacker = quickSpellWindow->isDisabled() ? 0 : -53;
|
|
int xOffsetDefender = unitActionWindow->isDisabled() ? 0 : 53;
|
|
|
|
if(defenderHeroWindow)
|
|
{
|
|
Point position = (ENGINE->screenDimensions().x >= 1000)
|
|
? Point(pos.x + pos.w + 15 + xOffsetDefender, pos.y + 60)
|
|
: Point(pos.x + pos.w -79, pos.y + 195);
|
|
defenderHeroWindow->moveTo(position);
|
|
}
|
|
if(attackerHeroWindow)
|
|
{
|
|
Point position = (ENGINE->screenDimensions().x >= 1000)
|
|
? Point(pos.x - 93 + xOffsetAttacker, pos.y + 60)
|
|
: Point(pos.x + 1, pos.y + 195);
|
|
attackerHeroWindow->moveTo(position);
|
|
}
|
|
if(defenderStackWindow)
|
|
{
|
|
Point position = (ENGINE->screenDimensions().x >= 1000)
|
|
? Point(pos.x + pos.w + 15 + xOffsetDefender, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y + 60)
|
|
: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 195);
|
|
defenderStackWindow->moveTo(position);
|
|
}
|
|
if(attackerStackWindow)
|
|
{
|
|
Point position = (ENGINE->screenDimensions().x >= 1000)
|
|
? Point(pos.x - 93 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
|
|
: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195);
|
|
attackerStackWindow->moveTo(position);
|
|
}
|
|
}
|
|
|
|
void BattleWindow::updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero)
|
|
{
|
|
std::shared_ptr<HeroInfoBasicPanel> panelToUpdate = side == 0 ? attackerHeroWindow : defenderHeroWindow;
|
|
panelToUpdate->update(hero);
|
|
}
|
|
|
|
void BattleWindow::updateStackInfoWindow(const CStack * stack)
|
|
{
|
|
OBJECT_CONSTRUCTION;
|
|
|
|
bool showInfoWindows = settings["battle"]["stickyHeroInfoWindows"].Bool();
|
|
|
|
if(stack && stack->unitSide() == BattleSide::DEFENDER)
|
|
{
|
|
defenderStackWindow = std::make_shared<StackInfoBasicPanel>(stack, true);
|
|
defenderStackWindow->setEnabled(showInfoWindows);
|
|
}
|
|
else
|
|
defenderStackWindow = nullptr;
|
|
|
|
if(stack && stack->unitSide() == BattleSide::ATTACKER)
|
|
{
|
|
attackerStackWindow = std::make_shared<StackInfoBasicPanel>(stack, true);
|
|
attackerStackWindow->setEnabled(showInfoWindows);
|
|
}
|
|
else
|
|
attackerStackWindow = nullptr;
|
|
|
|
setPositionInfoWindow();
|
|
createTimerInfoWindows();
|
|
}
|
|
|
|
void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
|
|
{
|
|
if(hero == owner.attackingHeroInstance || hero == owner.defendingHeroInstance)
|
|
{
|
|
InfoAboutHero heroInfo = InfoAboutHero();
|
|
heroInfo.initFromHero(hero, InfoAboutHero::INBATTLE);
|
|
|
|
updateHeroInfoWindow(hero == owner.attackingHeroInstance ? 0 : 1, heroInfo);
|
|
}
|
|
else
|
|
{
|
|
logGlobal->error("BattleWindow::heroManaPointsChanged: 'Mana points changed' called for hero not belonging to current battle window");
|
|
}
|
|
}
|
|
|
|
void BattleWindow::activate()
|
|
{
|
|
ENGINE->setStatusbar(console);
|
|
CIntObject::activate();
|
|
GAME->interface()->cingconsole->activate();
|
|
}
|
|
|
|
void BattleWindow::deactivate()
|
|
{
|
|
ENGINE->setStatusbar(nullptr);
|
|
CIntObject::deactivate();
|
|
GAME->interface()->cingconsole->deactivate();
|
|
}
|
|
|
|
bool BattleWindow::captureThisKey(EShortcut key)
|
|
{
|
|
return owner.openingPlaying();
|
|
}
|
|
|
|
void BattleWindow::keyPressed(EShortcut key)
|
|
{
|
|
if (owner.openingPlaying())
|
|
{
|
|
owner.openingEnd();
|
|
return;
|
|
}
|
|
InterfaceObjectConfigurable::keyPressed(key);
|
|
}
|
|
|
|
void BattleWindow::clickPressed(const Point & cursorPosition)
|
|
{
|
|
if (owner.openingPlaying())
|
|
{
|
|
owner.openingEnd();
|
|
return;
|
|
}
|
|
InterfaceObjectConfigurable::clickPressed(cursorPosition);
|
|
}
|
|
|
|
void BattleWindow::tacticPhaseStarted()
|
|
{
|
|
auto menuBattle = widget<CIntObject>("menuBattle");
|
|
auto console = widget<CIntObject>("console");
|
|
auto menuTactics = widget<CIntObject>("menuTactics");
|
|
auto tacticNext = widget<CIntObject>("tacticNext");
|
|
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
|
|
|
menuBattle->disable();
|
|
console->disable();
|
|
|
|
menuTactics->enable();
|
|
tacticNext->enable();
|
|
tacticEnd->enable();
|
|
|
|
redraw();
|
|
}
|
|
|
|
void BattleWindow::tacticPhaseEnded()
|
|
{
|
|
auto menuBattle = widget<CIntObject>("menuBattle");
|
|
auto console = widget<CIntObject>("console");
|
|
auto menuTactics = widget<CIntObject>("menuTactics");
|
|
auto tacticNext = widget<CIntObject>("tacticNext");
|
|
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
|
|
|
menuBattle->enable();
|
|
console->enable();
|
|
|
|
menuTactics->disable();
|
|
tacticNext->disable();
|
|
tacticEnd->disable();
|
|
|
|
redraw();
|
|
}
|
|
|
|
void BattleWindow::bOptionsf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
ENGINE->cursor().set(Cursor::Map::POINTER);
|
|
|
|
ENGINE->windows().createAndPushWindow<SettingsMainWindow>(&owner);
|
|
}
|
|
|
|
void BattleWindow::bSurrenderf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
int cost = owner.getBattle()->battleGetSurrenderCost();
|
|
if(cost >= 0)
|
|
{
|
|
std::string enemyHeroName = owner.getBattle()->battleGetEnemyHero().name;
|
|
if(enemyHeroName.empty())
|
|
{
|
|
logGlobal->warn("Surrender performed without enemy hero, should not happen!");
|
|
enemyHeroName = "#ENEMY#";
|
|
}
|
|
|
|
std::string surrenderMessage = boost::str(boost::format(LIBRARY->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
|
|
owner.curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
|
|
}
|
|
}
|
|
|
|
void BattleWindow::bFleef()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
if ( owner.getBattle()->battleCanFlee() )
|
|
{
|
|
auto ony = std::bind(&BattleWindow::reallyFlee,this);
|
|
owner.curInt->showYesNoDialog(LIBRARY->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
|
|
}
|
|
else
|
|
{
|
|
std::vector<std::shared_ptr<CComponent>> comps;
|
|
std::string heroName;
|
|
//calculating fleeing hero's name
|
|
if (owner.attackingHeroInstance)
|
|
if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getPlayerID())
|
|
heroName = owner.attackingHeroInstance->getNameTranslated();
|
|
if (owner.defendingHeroInstance)
|
|
if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getPlayerID())
|
|
heroName = owner.defendingHeroInstance->getNameTranslated();
|
|
//calculating text
|
|
auto txt = boost::format(LIBRARY->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present. %s can not retreat!
|
|
|
|
//printing message
|
|
owner.curInt->showInfoDialog(boost::str(txt), comps);
|
|
}
|
|
}
|
|
|
|
void BattleWindow::reallyFlee()
|
|
{
|
|
owner.giveCommand(EActionType::RETREAT);
|
|
ENGINE->cursor().set(Cursor::Map::POINTER);
|
|
}
|
|
|
|
void BattleWindow::reallySurrender()
|
|
{
|
|
if (owner.curInt->cb->getResourceAmount(EGameResID::GOLD) < owner.getBattle()->battleGetSurrenderCost())
|
|
{
|
|
owner.curInt->showInfoDialog(LIBRARY->generaltexth->allTexts[29]); //You don't have enough gold!
|
|
}
|
|
else
|
|
{
|
|
owner.giveCommand(EActionType::SURRENDER);
|
|
ENGINE->cursor().set(Cursor::Map::POINTER);
|
|
}
|
|
}
|
|
|
|
void BattleWindow::setPossibleActions(const std::vector<PossiblePlayerBattleAction> & actions)
|
|
{
|
|
unitActionWindow->setPossibleActions(actions);
|
|
}
|
|
|
|
void BattleWindow::bAutofightf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
if(settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman)
|
|
{
|
|
endWithAutocombat();
|
|
return;
|
|
}
|
|
|
|
//Stop auto-fight mode
|
|
if(owner.curInt->isAutoFightOn)
|
|
{
|
|
assert(owner.curInt->autofightingAI);
|
|
owner.curInt->isAutoFightOn = false;
|
|
logGlobal->trace("Stopping the autofight...");
|
|
}
|
|
else if(!owner.curInt->autofightingAI)
|
|
{
|
|
owner.curInt->isAutoFightOn = true;
|
|
blockUI(true);
|
|
|
|
auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
|
|
|
|
AutocombatPreferences autocombatPreferences = AutocombatPreferences();
|
|
autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool();
|
|
|
|
ai->initBattleInterface(owner.curInt->env, owner.curInt->cb, autocombatPreferences);
|
|
ai->battleStart(owner.getBattleID(), owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.getBattle()->battleGetMySide(), false);
|
|
owner.curInt->registerBattleInterface(ai);
|
|
|
|
owner.requestAutofightingAIToTakeAction();
|
|
}
|
|
}
|
|
|
|
void BattleWindow::bSpellf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
if (!owner.makingTurn())
|
|
return;
|
|
|
|
auto myHero = owner.currentHero();
|
|
if(!myHero)
|
|
return;
|
|
|
|
ENGINE->cursor().set(Cursor::Map::POINTER);
|
|
|
|
ESpellCastProblem spellCastProblem = owner.getBattle()->battleCanCastSpell(myHero, spells::Mode::HERO);
|
|
|
|
if(spellCastProblem == ESpellCastProblem::OK)
|
|
{
|
|
ENGINE->windows().createAndPushWindow<CSpellWindow>(myHero, owner.curInt.get());
|
|
}
|
|
else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
|
|
{
|
|
//TODO: move to spell mechanics, add more information to spell cast problem
|
|
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
|
|
auto blockingBonus = owner.currentHero()->getFirstBonus(Selector::type()(BonusType::BLOCK_ALL_MAGIC));
|
|
if (!blockingBonus)
|
|
return;
|
|
|
|
if (blockingBonus->source == BonusSource::ARTIFACT)
|
|
{
|
|
const auto artID = blockingBonus->sid.as<ArtifactID>();
|
|
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
|
|
//TODO check who *really* is source of bonus
|
|
std::string heroName = myHero->hasArt(artID, true) ? myHero->getNameTranslated() : owner.enemyHero().name;
|
|
|
|
//%s wields the %s, an ancient artifact which creates a p dead to all magic.
|
|
GAME->interface()->showInfoDialog(boost::str(boost::format(LIBRARY->generaltexth->allTexts[683])
|
|
% heroName % LIBRARY->artifacts()->getByIndex(artID)->getNameTranslated()));
|
|
}
|
|
else if(blockingBonus->source == BonusSource::OBJECT_TYPE)
|
|
{
|
|
if(blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON || blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON2)
|
|
GAME->interface()->showInfoDialog(LIBRARY->generaltexth->allTexts[684]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logGlobal->warn("Unexpected problem with readiness to cast spell");
|
|
}
|
|
}
|
|
|
|
void BattleWindow::bWaitf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
if (owner.stacksController->getActiveStack() != nullptr)
|
|
owner.giveCommand(EActionType::WAIT);
|
|
}
|
|
|
|
void BattleWindow::bDefencef()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
if (owner.stacksController->getActiveStack() != nullptr)
|
|
owner.giveCommand(EActionType::DEFEND);
|
|
}
|
|
|
|
void BattleWindow::bConsoleUpf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
console->scrollUp();
|
|
}
|
|
|
|
void BattleWindow::bConsoleDownf()
|
|
{
|
|
if (owner.actionsController->heroSpellcastingModeActive())
|
|
return;
|
|
|
|
console->scrollDown();
|
|
}
|
|
|
|
void BattleWindow::bTacticNextStack()
|
|
{
|
|
owner.tacticNextStack(nullptr);
|
|
}
|
|
|
|
void BattleWindow::bTacticPhaseEnd()
|
|
{
|
|
owner.tacticPhaseEnd();
|
|
}
|
|
|
|
void BattleWindow::blockUI(bool on)
|
|
{
|
|
bool canCastSpells = false;
|
|
auto hero = owner.getBattle()->battleGetMyHero();
|
|
|
|
if(hero)
|
|
{
|
|
ESpellCastProblem spellcastingProblem = owner.getBattle()->battleCanCastSpell(hero, spells::Mode::HERO);
|
|
|
|
//if magic is blocked, we leave button active, so the message can be displayed after button click
|
|
canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
|
|
}
|
|
|
|
bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
|
|
|
|
setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on);
|
|
setShortcutBlocked(EShortcut::BATTLE_OPEN_ACTIVE_UNIT, on);
|
|
setShortcutBlocked(EShortcut::BATTLE_OPEN_HOVERED_UNIT, on);
|
|
setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.getBattle()->battleCanFlee());
|
|
setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.getBattle()->battleGetSurrenderCost() < 0);
|
|
setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
|
|
setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
|
|
setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);
|
|
setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, (settings["battle"]["endWithAutocombat"].Bool() && onlyOnePlayerHuman) ? on || owner.tacticsMode || owner.actionsController->heroSpellcastingModeActive() : owner.actionsController->heroSpellcastingModeActive());
|
|
setShortcutBlocked(EShortcut::BATTLE_END_WITH_AUTOCOMBAT, on || !onlyOnePlayerHuman || owner.actionsController->heroSpellcastingModeActive());
|
|
setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on || !owner.tacticsMode);
|
|
setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode);
|
|
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
|
|
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
|
|
|
|
quickSpellWindow->setInputEnabled(!on);
|
|
unitActionWindow->setInputEnabled(!on);
|
|
}
|
|
|
|
void BattleWindow::bOpenActiveUnit()
|
|
{
|
|
const auto * unit = owner.stacksController->getActiveStack();
|
|
|
|
if (unit)
|
|
ENGINE->windows().createAndPushWindow<CStackWindow>(unit, false);
|
|
}
|
|
|
|
void BattleWindow::bOpenHoveredUnit()
|
|
{
|
|
const auto units = owner.stacksController->getHoveredStacksUnitIds();
|
|
|
|
if (!units.empty())
|
|
{
|
|
const auto * unit = owner.getBattle()->battleGetStackByID(units[0]);
|
|
if (unit)
|
|
ENGINE->windows().createAndPushWindow<CStackWindow>(unit, false);
|
|
}
|
|
}
|
|
|
|
std::optional<uint32_t> BattleWindow::getQueueHoveredUnitId()
|
|
{
|
|
return queue->getHoveredUnitIdIfAny();
|
|
}
|
|
|
|
void BattleWindow::endWithAutocombat()
|
|
{
|
|
if(!owner.makingTurn())
|
|
return;
|
|
|
|
GAME->interface()->showYesNoDialog(
|
|
LIBRARY->generaltexth->translate("vcmi.battleWindow.endWithAutocombat"),
|
|
[this]()
|
|
{
|
|
owner.curInt->isAutoFightEndBattle = true;
|
|
|
|
auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
|
|
|
|
AutocombatPreferences autocombatPreferences = AutocombatPreferences();
|
|
autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool();
|
|
|
|
ai->initBattleInterface(owner.curInt->env, owner.curInt->cb, autocombatPreferences);
|
|
ai->battleStart(owner.getBattleID(), owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.getBattle()->battleGetMySide(), false);
|
|
|
|
owner.curInt->isAutoFightOn = true;
|
|
owner.curInt->registerBattleInterface(ai);
|
|
owner.requestAutofightingAIToTakeAction();
|
|
|
|
close();
|
|
|
|
owner.curInt->battleInt.reset();
|
|
},
|
|
nullptr
|
|
);
|
|
}
|
|
|
|
void BattleWindow::showAll(Canvas & to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
|
|
if (ENGINE->screenDimensions().x != 800 || ENGINE->screenDimensions().y !=600)
|
|
CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
|
|
}
|
|
|
|
void BattleWindow::show(Canvas & to)
|
|
{
|
|
CIntObject::show(to);
|
|
GAME->interface()->cingconsole->show(to);
|
|
}
|
|
|
|
void BattleWindow::close()
|
|
{
|
|
if(!ENGINE->windows().isTopWindow(this))
|
|
logGlobal->error("Only top interface must be closed");
|
|
ENGINE->windows().popWindows(1);
|
|
}
|