BattleWindow.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. /*
  2. * BattleWindow.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "BattleWindow.h"
  12. #include "BattleInterface.h"
  13. #include "BattleInterfaceClasses.h"
  14. #include "BattleFieldController.h"
  15. #include "BattleStacksController.h"
  16. #include "BattleActionsController.h"
  17. #include "../CGameInfo.h"
  18. #include "../CMessage.h"
  19. #include "../CPlayerInterface.h"
  20. #include "../CMusicHandler.h"
  21. #include "../gui/Canvas.h"
  22. #include "../gui/CCursorHandler.h"
  23. #include "../gui/CGuiHandler.h"
  24. #include "../gui/CAnimation.h"
  25. #include "../windows/CSpellWindow.h"
  26. #include "../widgets/AdventureMapClasses.h"
  27. #include "../widgets/Buttons.h"
  28. #include "../widgets/Images.h"
  29. #include "../../CCallback.h"
  30. #include "../../lib/CGeneralTextHandler.h"
  31. #include "../../lib/mapObjects/CGHeroInstance.h"
  32. #include "../../lib/CStack.h"
  33. #include "../../lib/CConfigHandler.h"
  34. #include "../../lib/filesystem/ResourceID.h"
  35. BattleWindow::BattleWindow(BattleInterface & owner):
  36. owner(owner)
  37. {
  38. OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
  39. pos.w = 800;
  40. pos.h = 600;
  41. pos = center();
  42. REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
  43. const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
  44. addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
  45. addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
  46. addCallback("flee", std::bind(&BattleWindow::bFleef, this));
  47. addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
  48. addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
  49. addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
  50. addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
  51. addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
  52. addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
  53. addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
  54. addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
  55. addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
  56. build(config);
  57. console = widget<BattleConsole>("console");
  58. GH.statusbar = console;
  59. owner.console = console;
  60. owner.fieldController.reset( new BattleFieldController(owner));
  61. owner.fieldController->createHeroes();
  62. //create stack queue and adjust our own position
  63. bool embedQueue;
  64. std::string queueSize = settings["battle"]["queueSize"].String();
  65. if(queueSize == "auto")
  66. embedQueue = screen->h < 700;
  67. else
  68. embedQueue = screen->h < 700 || queueSize == "small";
  69. queue = std::make_shared<StackQueue>(embedQueue, owner);
  70. if(!embedQueue && settings["battle"]["showQueue"].Bool())
  71. {
  72. //re-center, taking into account stack queue position
  73. pos.y -= queue->pos.h;
  74. pos.h += queue->pos.h;
  75. pos = center();
  76. }
  77. if ( owner.tacticsMode )
  78. tacticPhaseStarted();
  79. else
  80. tacticPhaseEnded();
  81. addUsedEvents(RCLICK | KEYBOARD);
  82. }
  83. BattleWindow::~BattleWindow()
  84. {
  85. CPlayerInterface::battleInt = nullptr;
  86. }
  87. std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
  88. {
  89. auto rect = readRect(config["rect"]);
  90. auto offset = readPosition(config["imagePosition"]);
  91. auto background = widget<CPicture>("menuBattle");
  92. return std::make_shared<BattleConsole>(background, rect.topLeft(), offset, rect.dimensions() );
  93. }
  94. void BattleWindow::hideQueue()
  95. {
  96. Settings showQueue = settings.write["battle"]["showQueue"];
  97. showQueue->Bool() = false;
  98. queue->disable();
  99. if (!queue->embedded)
  100. {
  101. //re-center, taking into account stack queue position
  102. pos.y += queue->pos.h;
  103. pos.h -= queue->pos.h;
  104. pos = center();
  105. GH.totalRedraw();
  106. }
  107. }
  108. void BattleWindow::showQueue()
  109. {
  110. Settings showQueue = settings.write["battle"]["showQueue"];
  111. showQueue->Bool() = true;
  112. queue->enable();
  113. if (!queue->embedded)
  114. {
  115. //re-center, taking into account stack queue position
  116. pos.y -= queue->pos.h;
  117. pos.h += queue->pos.h;
  118. pos = center();
  119. GH.totalRedraw();
  120. }
  121. }
  122. void BattleWindow::updateQueue()
  123. {
  124. queue->update();
  125. }
  126. void BattleWindow::activate()
  127. {
  128. GH.statusbar = console;
  129. CIntObject::activate();
  130. LOCPLINT->cingconsole->activate();
  131. }
  132. void BattleWindow::deactivate()
  133. {
  134. CIntObject::deactivate();
  135. LOCPLINT->cingconsole->deactivate();
  136. }
  137. void BattleWindow::keyPressed(const SDL_KeyboardEvent & key)
  138. {
  139. if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
  140. {
  141. if(settings["battle"]["showQueue"].Bool()) //hide queue
  142. hideQueue();
  143. else
  144. showQueue();
  145. }
  146. else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
  147. {
  148. owner.actionsController->enterCreatureCastingMode();
  149. }
  150. else if(key.keysym.sym == SDLK_ESCAPE)
  151. {
  152. if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true)
  153. CCS->soundh->stopSound(owner.battleIntroSoundChannel);
  154. else
  155. owner.actionsController->endCastingSpell();
  156. }
  157. }
  158. void BattleWindow::clickRight(tribool down, bool previousState)
  159. {
  160. if (!down)
  161. owner.actionsController->endCastingSpell();
  162. }
  163. void BattleWindow::tacticPhaseStarted()
  164. {
  165. auto menuBattle = widget<CIntObject>("menuBattle");
  166. auto console = widget<CIntObject>("console");
  167. auto menuTactics = widget<CIntObject>("menuTactics");
  168. auto tacticNext = widget<CIntObject>("tacticNext");
  169. auto tacticEnd = widget<CIntObject>("tacticEnd");
  170. menuBattle->disable();
  171. console->disable();
  172. menuTactics->enable();
  173. tacticNext->enable();
  174. tacticEnd->enable();
  175. redraw();
  176. }
  177. void BattleWindow::tacticPhaseEnded()
  178. {
  179. auto menuBattle = widget<CIntObject>("menuBattle");
  180. auto console = widget<CIntObject>("console");
  181. auto menuTactics = widget<CIntObject>("menuTactics");
  182. auto tacticNext = widget<CIntObject>("tacticNext");
  183. auto tacticEnd = widget<CIntObject>("tacticEnd");
  184. menuBattle->enable();
  185. console->enable();
  186. menuTactics->disable();
  187. tacticNext->disable();
  188. tacticEnd->disable();
  189. redraw();
  190. }
  191. void BattleWindow::bOptionsf()
  192. {
  193. if (owner.actionsController->spellcastingModeActive())
  194. return;
  195. CCS->curh->set(Cursor::Map::POINTER);
  196. GH.pushIntT<BattleOptionsWindow>(owner);
  197. }
  198. void BattleWindow::bSurrenderf()
  199. {
  200. if (owner.actionsController->spellcastingModeActive())
  201. return;
  202. int cost = owner.curInt->cb->battleGetSurrenderCost();
  203. if(cost >= 0)
  204. {
  205. std::string enemyHeroName = owner.curInt->cb->battleGetEnemyHero().name;
  206. if(enemyHeroName.empty())
  207. {
  208. logGlobal->warn("Surrender performed without enemy hero, should not happen!");
  209. enemyHeroName = "#ENEMY#";
  210. }
  211. std::string surrenderMessage = boost::str(boost::format(CGI->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."
  212. owner.curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
  213. }
  214. }
  215. void BattleWindow::bFleef()
  216. {
  217. if (owner.actionsController->spellcastingModeActive())
  218. return;
  219. if ( owner.curInt->cb->battleCanFlee() )
  220. {
  221. CFunctionList<void()> ony = std::bind(&BattleWindow::reallyFlee,this);
  222. owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
  223. }
  224. else
  225. {
  226. std::vector<std::shared_ptr<CComponent>> comps;
  227. std::string heroName;
  228. //calculating fleeing hero's name
  229. if (owner.attackingHeroInstance)
  230. if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
  231. heroName = owner.attackingHeroInstance->name;
  232. if (owner.defendingHeroInstance)
  233. if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
  234. heroName = owner.defendingHeroInstance->name;
  235. //calculating text
  236. auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present. %s can not retreat!
  237. //printing message
  238. owner.curInt->showInfoDialog(boost::to_string(txt), comps);
  239. }
  240. }
  241. void BattleWindow::reallyFlee()
  242. {
  243. owner.giveCommand(EActionType::RETREAT);
  244. CCS->curh->set(Cursor::Map::POINTER);
  245. }
  246. void BattleWindow::reallySurrender()
  247. {
  248. if (owner.curInt->cb->getResourceAmount(Res::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
  249. {
  250. owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
  251. }
  252. else
  253. {
  254. owner.giveCommand(EActionType::SURRENDER);
  255. CCS->curh->set(Cursor::Map::POINTER);
  256. }
  257. }
  258. void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
  259. {
  260. auto w = widget<CButton>("alternativeAction");
  261. if(!w)
  262. return;
  263. std::string iconName = variables["actionIconDefault"].String();
  264. switch(action)
  265. {
  266. case PossiblePlayerBattleAction::ATTACK:
  267. iconName = variables["actionIconAttack"].String();
  268. break;
  269. case PossiblePlayerBattleAction::SHOOT:
  270. iconName = variables["actionIconShoot"].String();
  271. break;
  272. case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
  273. iconName = variables["actionIconSpell"].String();
  274. break;
  275. //TODO: figure out purpose of this icon
  276. //case PossiblePlayerBattleAction::???:
  277. //iconName = variables["actionIconWalk"].String();
  278. //break;
  279. case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
  280. iconName = variables["actionIconReturn"].String();
  281. break;
  282. case PossiblePlayerBattleAction::WALK_AND_ATTACK:
  283. iconName = variables["actionIconNoReturn"].String();
  284. break;
  285. }
  286. auto anim = std::make_shared<CAnimation>(iconName);
  287. w->setImage(anim, false);
  288. }
  289. void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
  290. {
  291. alternativeActions = actions;
  292. defaultAction = PossiblePlayerBattleAction::INVALID;
  293. if(alternativeActions.size() > 1)
  294. defaultAction = alternativeActions.back();
  295. if(!alternativeActions.empty())
  296. showAlternativeActionIcon(alternativeActions.front());
  297. else
  298. showAlternativeActionIcon(defaultAction);
  299. }
  300. void BattleWindow::bAutofightf()
  301. {
  302. if (owner.actionsController->spellcastingModeActive())
  303. return;
  304. //Stop auto-fight mode
  305. if(owner.curInt->isAutoFightOn)
  306. {
  307. assert(owner.curInt->autofightingAI);
  308. owner.curInt->isAutoFightOn = false;
  309. logGlobal->trace("Stopping the autofight...");
  310. }
  311. else if(!owner.curInt->autofightingAI)
  312. {
  313. owner.curInt->isAutoFightOn = true;
  314. blockUI(true);
  315. auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
  316. ai->initBattleInterface(owner.curInt->env, owner.curInt->cb);
  317. ai->battleStart(owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.curInt->cb->battleGetMySide());
  318. owner.curInt->autofightingAI = ai;
  319. owner.curInt->cb->registerBattleInterface(ai);
  320. owner.requestAutofightingAIToTakeAction();
  321. }
  322. }
  323. void BattleWindow::bSpellf()
  324. {
  325. if (owner.actionsController->spellcastingModeActive())
  326. return;
  327. if (!owner.myTurn)
  328. return;
  329. auto myHero = owner.currentHero();
  330. if(!myHero)
  331. return;
  332. CCS->curh->set(Cursor::Map::POINTER);
  333. ESpellCastProblem::ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
  334. if(spellCastProblem == ESpellCastProblem::OK)
  335. {
  336. GH.pushIntT<CSpellWindow>(myHero, owner.curInt.get());
  337. }
  338. else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
  339. {
  340. //TODO: move to spell mechanics, add more information to spell cast problem
  341. //Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
  342. auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
  343. if (!blockingBonus)
  344. return;
  345. if (blockingBonus->source == Bonus::ARTIFACT)
  346. {
  347. const auto artID = ArtifactID(blockingBonus->sid);
  348. //If we have artifact, put name of our hero. Otherwise assume it's the enemy.
  349. //TODO check who *really* is source of bonus
  350. std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name;
  351. //%s wields the %s, an ancient artifact which creates a p dead to all magic.
  352. LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
  353. % heroName % CGI->artifacts()->getByIndex(artID)->getName()));
  354. }
  355. }
  356. }
  357. void BattleWindow::bSwitchActionf()
  358. {
  359. if(alternativeActions.empty())
  360. return;
  361. if(alternativeActions.front() == defaultAction)
  362. {
  363. alternativeActions.push_back(alternativeActions.front());
  364. alternativeActions.pop_front();
  365. }
  366. auto actions = owner.actionsController->getPossibleActions();
  367. if(!actions.empty() && actions.front() == alternativeActions.front())
  368. {
  369. owner.actionsController->removePossibleAction(alternativeActions.front());
  370. showAlternativeActionIcon(defaultAction);
  371. }
  372. else
  373. {
  374. owner.actionsController->pushFrontPossibleAction(alternativeActions.front());
  375. showAlternativeActionIcon(alternativeActions.front());
  376. }
  377. alternativeActions.push_back(alternativeActions.front());
  378. alternativeActions.pop_front();
  379. }
  380. void BattleWindow::bWaitf()
  381. {
  382. if (owner.actionsController->spellcastingModeActive())
  383. return;
  384. if (owner.stacksController->getActiveStack() != nullptr)
  385. owner.giveCommand(EActionType::WAIT);
  386. }
  387. void BattleWindow::bDefencef()
  388. {
  389. if (owner.actionsController->spellcastingModeActive())
  390. return;
  391. if (owner.stacksController->getActiveStack() != nullptr)
  392. owner.giveCommand(EActionType::DEFEND);
  393. }
  394. void BattleWindow::bConsoleUpf()
  395. {
  396. if (owner.actionsController->spellcastingModeActive())
  397. return;
  398. console->scrollUp();
  399. }
  400. void BattleWindow::bConsoleDownf()
  401. {
  402. if (owner.actionsController->spellcastingModeActive())
  403. return;
  404. console->scrollDown();
  405. }
  406. void BattleWindow::bTacticNextStack()
  407. {
  408. owner.tacticNextStack(nullptr);
  409. }
  410. void BattleWindow::bTacticPhaseEnd()
  411. {
  412. owner.tacticPhaseEnd();
  413. }
  414. void BattleWindow::blockUI(bool on)
  415. {
  416. bool canCastSpells = false;
  417. auto hero = owner.curInt->cb->battleGetMyHero();
  418. if(hero)
  419. {
  420. ESpellCastProblem::ESpellCastProblem spellcastingProblem = owner.curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
  421. //if magic is blocked, we leave button active, so the message can be displayed after button click
  422. canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
  423. }
  424. bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
  425. if(auto w = widget<CButton>("options"))
  426. w->block(on);
  427. if(auto w = widget<CButton>("flee"))
  428. w->block(on || !owner.curInt->cb->battleCanFlee());
  429. if(auto w = widget<CButton>("surrender"))
  430. w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
  431. if(auto w = widget<CButton>("cast"))
  432. w->block(on || owner.tacticsMode || !canCastSpells);
  433. if(auto w = widget<CButton>("wait"))
  434. w->block(on || owner.tacticsMode || !canWait);
  435. if(auto w = widget<CButton>("defence"))
  436. w->block(on || owner.tacticsMode);
  437. if(auto w = widget<CButton>("alternativeAction"))
  438. w->block(on || owner.tacticsMode);
  439. // block only if during enemy turn and auto-fight is off
  440. // otherwise - crash on accessing non-exisiting active stack
  441. if(auto w = widget<CButton>("options"))
  442. w->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack());
  443. auto btactEnd = widget<CButton>("tacticEnd");
  444. auto btactNext = widget<CButton>("tacticNext");
  445. if(owner.tacticsMode && btactEnd && btactNext)
  446. {
  447. btactNext->block(on);
  448. btactEnd->block(on);
  449. }
  450. else
  451. {
  452. auto bConsoleUp = widget<CButton>("consoleUp");
  453. auto bConsoleDown = widget<CButton>("consoleDown");
  454. if(bConsoleUp && bConsoleDown)
  455. {
  456. bConsoleUp->block(on);
  457. bConsoleDown->block(on);
  458. }
  459. }
  460. }
  461. void BattleWindow::showAll(SDL_Surface *to)
  462. {
  463. CIntObject::showAll(to);
  464. if (screen->w != 800 || screen->h !=600)
  465. CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
  466. }
  467. void BattleWindow::show(SDL_Surface *to)
  468. {
  469. CIntObject::show(to);
  470. LOCPLINT->cingconsole->show(to);
  471. }
  472. void BattleWindow::close()
  473. {
  474. if(GH.topInt().get() != this)
  475. logGlobal->error("Only top interface must be closed");
  476. GH.popInts(1);
  477. }