1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012 Igor Paliychuk
4 Copyright © 2012 Stefan Beller
5 Copyright © 2013-2014 Henrik Andersson
6 Copyright © 2012-2016 Justin Jacobs
7
8 This file is part of FLARE.
9
10 FLARE is free software: you can redistribute it and/or modify it under the terms
11 of the GNU General Public License as published by the Free Software Foundation,
12 either version 3 of the License, or (at your option) any later version.
13
14 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE. See the GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License along with
19 FLARE. If not, see http://www.gnu.org/licenses/
20 */
21
22 /**
23 * class MenuPowers
24 */
25
26 #include "Avatar.h"
27 #include "EffectManager.h"
28 #include "EngineSettings.h"
29 #include "CampaignManager.h"
30 #include "CommonIncludes.h"
31 #include "EngineSettings.h"
32 #include "FileParser.h"
33 #include "FontEngine.h"
34 #include "Menu.h"
35 #include "MenuActionBar.h"
36 #include "MenuInventory.h"
37 #include "MenuManager.h"
38 #include "MenuPowers.h"
39 #include "MessageEngine.h"
40 #include "PowerManager.h"
41 #include "RenderDevice.h"
42 #include "Settings.h"
43 #include "SharedGameResources.h"
44 #include "SharedResources.h"
45 #include "SoundManager.h"
46 #include "StatBlock.h"
47 #include "TooltipManager.h"
48 #include "UtilsParsing.h"
49 #include "WidgetButton.h"
50 #include "WidgetLabel.h"
51 #include "WidgetSlot.h"
52 #include "WidgetTabControl.h"
53
54 #include <climits>
55
MenuPowersCell()56 MenuPowersCell::MenuPowersCell()
57 : id(-1)
58 , requires_point(false)
59 , requires_level(0)
60 , requires_primary(eset->primary_stats.list.size(), 0)
61 , requires_power()
62 , requires_status()
63 , requires_not_status()
64 , visible(true)
65 , visible_check_locked(false)
66 , visible_check_status(false)
67 , upgrade_level(0)
68 , passive_on(false)
69 , is_unlocked(false)
70 , group(0)
71 , next(NULL) {
72 }
73
MenuPowersCellGroup()74 MenuPowersCellGroup::MenuPowersCellGroup()
75 : tab(0)
76 , pos()
77 , current_cell(0)
78 , cells()
79 , upgrade_button(NULL) {
80 }
81
getCurrent()82 MenuPowersCell* MenuPowersCellGroup::getCurrent() {
83 return &cells[current_cell];
84 }
85
getBonusCurrent(MenuPowersCell * pcell)86 MenuPowersCell* MenuPowersCellGroup::getBonusCurrent(MenuPowersCell* pcell) {
87 if (bonus_levels.empty())
88 return pcell;
89
90 size_t current = current_cell;
91
92 for (size_t i = 0; i < cells.size(); ++i) {
93 if (pcell == &cells[i]) {
94 current = i;
95 break;
96 }
97 }
98
99 int current_bonus_levels = getBonusLevels();
100 size_t bonus_cell = current + static_cast<size_t>(current_bonus_levels);
101
102 if (bonus_cell >= cells.size())
103 return &cells[cells.size() - 1];
104
105 return &cells[bonus_cell];
106 }
107
getBonusLevels()108 int MenuPowersCellGroup::getBonusLevels() {
109 int blevel = 0;
110 for (size_t i = 0; i < bonus_levels.size(); ++i) {
111 if (current_cell >= bonus_levels[i].first)
112 blevel += bonus_levels[i].second;
113 }
114 return blevel;
115 }
116
MenuPowers()117 MenuPowers::MenuPowers()
118 : skip_section(false)
119 , powers_unlock(NULL)
120 , overlay_disabled(NULL)
121 , points_left(0)
122 , default_background("")
123 , label_powers(new WidgetLabel)
124 , label_unspent(new WidgetLabel)
125 , tab_control(NULL)
126 , tree_loaded(false)
127 , default_power_tab(-1)
128 , newPowerNotification(false)
129 {
130
131 closeButton = new WidgetButton("images/menus/buttons/button_x.png");
132
133 // Read powers data from config file
134 FileParser infile;
135 // @CLASS MenuPowers: Menu layout|Description of menus/powers.txt
136 if (infile.open("menus/powers.txt", FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
137 while (infile.next()) {
138 if (parseMenuKey(infile.key, infile.val))
139 continue;
140
141 // @ATTR label_title|label|Position of the "Powers" text.
142 if (infile.key == "label_title") {
143 label_powers->setFromLabelInfo(Parse::popLabelInfo(infile.val));
144 }
145 // @ATTR unspent_points|label|Position of the text that displays the amount of unused power points.
146 else if (infile.key == "unspent_points") {
147 label_unspent->setFromLabelInfo(Parse::popLabelInfo(infile.val));
148 }
149 // @ATTR close|point|Position of the close button.
150 else if (infile.key == "close") close_pos = Parse::toPoint(infile.val);
151 // @ATTR tab_area|rectangle|Position and dimensions of the tree pages.
152 else if (infile.key == "tab_area") tab_area = Parse::toRect(infile.val);
153
154 else infile.error("MenuPowers: '%s' is not a valid key.", infile.key.c_str());
155 }
156 infile.close();
157 }
158
159 label_powers->setText(msg->get("Powers"));
160 label_powers->setColor(font->getColor(FontEngine::COLOR_MENU_NORMAL));
161
162 label_unspent->setColor(font->getColor(FontEngine::COLOR_MENU_BONUS));
163
164 loadGraphics();
165
166 menu_powers = this;
167
168 align();
169 }
170
~MenuPowers()171 MenuPowers::~MenuPowers() {
172 if (powers_unlock) delete powers_unlock;
173 if (overlay_disabled) delete overlay_disabled;
174
175 for (size_t i=0; i<tree_surf.size(); i++) {
176 if (tree_surf[i]) delete tree_surf[i];
177 }
178 for (size_t i=0; i<slots.size(); i++) {
179 delete slots.at(i);
180 }
181 slots.clear();
182
183 for (size_t i=0; i<power_cell.size(); i++) {
184 delete power_cell[i].upgrade_button;
185 }
186
187 delete closeButton;
188 if (tab_control) delete tab_control;
189 menu_powers = NULL;
190
191 delete label_powers;
192 delete label_unspent;
193 }
194
align()195 void MenuPowers::align() {
196 Menu::align();
197
198 label_powers->setPos(window_area.x, window_area.y);
199 label_unspent->setPos(window_area.x, window_area.y);
200
201 closeButton->pos.x = window_area.x+close_pos.x;
202 closeButton->pos.y = window_area.y+close_pos.y;
203
204 if (tab_control) {
205 tab_control->setMainArea(window_area.x + tab_area.x, window_area.y + tab_area.y);
206 }
207
208 for (size_t i = 0; i < slots.size(); i++) {
209 if (!slots[i]) continue;
210
211 slots[i]->setPos(window_area.x, window_area.y);
212
213 if (power_cell[i].upgrade_button != NULL) {
214 power_cell[i].upgrade_button->setPos(window_area.x, window_area.y);
215 }
216 }
217
218 }
219
loadGraphics()220 void MenuPowers::loadGraphics() {
221
222 Image *graphics;
223
224 setBackground("images/menus/powers.png");
225
226 graphics = render_device->loadImage("images/menus/powers_unlock.png", RenderDevice::ERROR_NORMAL);
227 if (graphics) {
228 powers_unlock = graphics->createSprite();
229 graphics->unref();
230 }
231
232 graphics = render_device->loadImage("images/menus/disabled.png", RenderDevice::ERROR_NORMAL);
233 if (graphics) {
234 overlay_disabled = graphics->createSprite();
235 graphics->unref();
236 }
237 }
238
239 /**
240 * Loads a given power tree and sets up the menu accordingly
241 *
242 * @param filename Path to the file that will be loaded
243 */
loadPowerTree(const std::string & filename)244 void MenuPowers::loadPowerTree(const std::string &filename) {
245 // only load the power tree once per instance
246 if (tree_loaded) return;
247
248 // First, parse the power tree file
249 std::vector<MenuPowersCell> power_cell_upgrade;
250
251 FileParser infile;
252 // @CLASS MenuPowers: Power tree layout|Description of powers/trees/
253 if (infile.open(filename, FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
254 while (infile.next()) {
255 if (infile.new_section) {
256 // for sections that are stored in collections, add a new object here
257 if (infile.section == "power") {
258 slots.push_back(NULL);
259 power_cell.push_back(MenuPowersCellGroup());
260 power_cell.back().cells.push_back(MenuPowersCell());
261 power_cell.back().cells.back().group = power_cell.size() - 1;
262 }
263 else if (infile.section == "upgrade")
264 power_cell_upgrade.push_back(MenuPowersCell());
265 else if (infile.section == "tab")
266 tabs.push_back(MenuPowersTab());
267 }
268
269 if (infile.section == "") {
270 // @ATTR background|filename|Filename of the default background image
271 if (infile.key == "background") default_background = infile.val;
272 }
273 else if (infile.section == "tab")
274 loadTab(infile);
275 else if (infile.section == "power")
276 loadPower(infile);
277 else if (infile.section == "upgrade")
278 loadUpgrade(infile, power_cell_upgrade);
279 }
280 infile.close();
281 }
282
283 // fill cell groups with upgrades
284 for (size_t i = 0; i < power_cell.size(); ++i) {
285 for (size_t j = 1; j < power_cell[i].cells.size(); ++j) {
286 for (size_t k = 0; k < power_cell_upgrade.size(); ++k) {
287 if (power_cell_upgrade[k].id == power_cell[i].cells[j].id) {
288 power_cell[i].cells[j] = power_cell_upgrade[k];
289 power_cell[i].cells[j].upgrade_level = static_cast<int>(j) + 1;
290 power_cell[i].cells[j].group = i;
291 power_cell[i].cells[j-1].next = &power_cell[i].cells[j];
292 }
293 }
294 }
295 }
296
297 // load any specified graphics into the tree_surf vector
298 Image *graphics;
299 if (tabs.empty() && default_background != "") {
300 graphics = render_device->loadImage(default_background, RenderDevice::ERROR_NORMAL);
301 if (graphics) {
302 tree_surf.push_back(graphics->createSprite());
303 graphics->unref();
304 }
305 }
306 else {
307 for (size_t i=0; i<tabs.size(); ++i) {
308 if (tabs[i].background == "")
309 tabs[i].background = default_background;
310
311 if (tabs[i].background == "") {
312 tree_surf.push_back(NULL);
313 continue;
314 }
315
316 graphics = render_device->loadImage(tabs[i].background, RenderDevice::ERROR_NORMAL);
317 if (graphics) {
318 tree_surf.push_back(graphics->createSprite());
319 graphics->unref();
320 }
321 else {
322 tree_surf.push_back(NULL);
323 }
324 }
325 }
326
327 // If we have more than one tab, create tab_control
328 if (!tabs.empty()) {
329 tab_control = new WidgetTabControl();
330
331 if (tab_control) {
332 // Define the header.
333 for (size_t i=0; i<tabs.size(); i++)
334 tab_control->setTabTitle(static_cast<unsigned>(i), msg->get(tabs[i].title));
335
336 // Initialize the tab control.
337 tab_control->setMainArea(window_area.x + tab_area.x, window_area.y + tab_area.y);
338
339 tablist.add(tab_control);
340 }
341
342 tablist_pow.resize(tabs.size());
343 }
344
345 // create power slots
346 for (size_t i=0; i<slots.size(); i++) {
347 if (!power_cell[i].cells.empty() && !powers->powers[power_cell[i].cells[0].id].is_empty) {
348 slots[i] = new WidgetSlot(powers->powers[power_cell[i].cells[0].id].icon, Input::ACCEPT);
349 slots[i]->setBasePos(power_cell[i].pos.x, power_cell[i].pos.y, Utils::ALIGN_TOPLEFT);
350
351 if (!tablist_pow.empty()) {
352 tablist_pow[power_cell[i].tab].add(slots[i]);
353 tablist_pow[power_cell[i].tab].setPrevTabList(&tablist);
354 tablist_pow[power_cell[i].tab].lock();
355 }
356 else {
357 tablist.add(slots[i]);
358 }
359
360 if (power_cell[i].upgrade_button != NULL) {
361 power_cell[i].upgrade_button->setBasePos(power_cell[i].pos.x + eset->resolutions.icon_size, power_cell[i].pos.y, Utils::ALIGN_TOPLEFT);
362 }
363 }
364 }
365
366 setUnlockedPowers();
367
368 // set the default tab from character class setting
369 EngineSettings::HeroClasses::HeroClass* pc_class;
370 pc_class = eset->hero_classes.getByName(pc->stats.character_class);
371 if (pc_class) {
372 default_power_tab = pc_class->default_power_tab;
373 }
374
375 tree_loaded = true;
376
377 align();
378 }
379
loadTab(FileParser & infile)380 void MenuPowers::loadTab(FileParser &infile) {
381 // @ATTR tab.title|string|The name of this power tree tab
382 if (infile.key == "title") tabs.back().title = infile.val;
383 // @ATTR tab.background|filename|Filename of the background image for this tab's power tree
384 else if (infile.key == "background") tabs.back().background = infile.val;
385 }
386
loadPower(FileParser & infile)387 void MenuPowers::loadPower(FileParser &infile) {
388 // base power cell storage hasn't been set up!
389 if (power_cell.back().cells.empty())
390 return;
391
392 // @ATTR power.id|int|A power id from powers/powers.txt for this slot.
393 if (infile.key == "id") {
394 int id = Parse::popFirstInt(infile.val);
395 if (id > 0) {
396 skip_section = false;
397 power_cell.back().cells[0].id = id;
398 }
399 else {
400 infile.error("MenuPowers: Power index out of bounds 1-%d, skipping power.", INT_MAX);
401 }
402 return;
403 }
404
405 if (power_cell.back().cells[0].id == 0) {
406 skip_section = true;
407 power_cell.pop_back();
408 slots.pop_back();
409 Utils::logError("MenuPowers: There is a power without a valid id as the first attribute. IDs must be the first attribute in the power menu definition.");
410 }
411
412 if (skip_section)
413 return;
414
415 // @ATTR power.tab|int|Tab index to place this power on, starting from 0.
416 if (infile.key == "tab") power_cell.back().tab = Parse::toInt(infile.val);
417 // @ATTR power.position|point|Position of this power icon; relative to MenuPowers "pos".
418 else if (infile.key == "position") power_cell.back().pos = Parse::toPoint(infile.val);
419
420 // @ATTR power.requires_point|bool|Power requires a power point to unlock.
421 else if (infile.key == "requires_point") power_cell.back().cells[0].requires_point = Parse::toBool(infile.val);
422
423 // @ATTR power.requires_primary|predefined_string, int : Primary stat name, Required value|Power requires this primary stat to be at least the specificed value.
424 else if (infile.key == "requires_primary") {
425 std::string prim_stat = Parse::popFirstString(infile.val);
426 size_t prim_stat_index = eset->primary_stats.getIndexByID(prim_stat);
427
428 if (prim_stat_index != eset->primary_stats.list.size()) {
429 power_cell.back().cells[0].requires_primary[prim_stat_index] = Parse::toInt(infile.val);
430 }
431 else {
432 infile.error("MenuPowers: '%s' is not a valid primary stat.", prim_stat.c_str());
433 }
434 }
435 // @ATTR power.requires_level|int|Power requires at least this level for the hero.
436 else if (infile.key == "requires_level") power_cell.back().cells[0].requires_level = Parse::toInt(infile.val);
437 // @ATTR power.requires_power|power_id|Power requires another power id.
438 else if (infile.key == "requires_power") power_cell.back().cells[0].requires_power.push_back(Parse::toPowerID(infile.val));
439 // @ATTR power.requires_status|repeatable(string)|Power requires this campaign status.
440 else if (infile.key == "requires_status") power_cell.back().cells[0].requires_status.push_back(camp->registerStatus(infile.val));
441 // @ATTR power.requires_not_status|repeatable(string)|Power requires not having this campaign status.
442 else if (infile.key == "requires_not_status") power_cell.back().cells[0].requires_not_status.push_back(camp->registerStatus(infile.val));
443
444 // @ATTR power.visible_requires_status|repeatable(string)|(Deprecated as of v1.11.75) Hide the power if we don't have this campaign status.
445 else if (infile.key == "visible_requires_status") {
446 infile.error("MenuPowers: visible_requires_status is deprecated. Use requires_status and visible_check_status=true instead.");
447 power_cell.back().cells[0].requires_status.push_back(camp->registerStatus(infile.val));
448 power_cell.back().cells[0].visible_check_status = true;
449 }
450 // @ATTR power.visible_requires_not_status|repeatable(string)|(Deprecated as of v1.11.75) Hide the power if we have this campaign status.
451 else if (infile.key == "visible_requires_not_status") {
452 infile.error("MenuPowers: visible_requires_not_status is deprecated. Use requires_not_status and visible_check_status=true instead.");
453 power_cell.back().cells[0].requires_not_status.push_back(camp->registerStatus(infile.val));
454 power_cell.back().cells[0].visible_check_status = true;
455 }
456
457 // @ATTR power.upgrades|list(power_id)|A list of upgrade power ids that this power slot can upgrade to. Each of these powers should have a matching upgrade section.
458 else if (infile.key == "upgrades") {
459 std::string repeat_val = Parse::popFirstString(infile.val);
460 while (repeat_val != "") {
461 PowerID test_id = Parse::toPowerID(repeat_val);
462 if (test_id == power_cell.back().cells[0].id) {
463 infile.error("MenuPowers: Upgrade ID '%d' is the same as the base ID. Ignoring.", test_id);
464 }
465 else {
466 power_cell.back().cells.push_back(MenuPowersCell());
467 power_cell.back().cells.back().id = test_id;
468 }
469 repeat_val = Parse::popFirstString(infile.val);
470 }
471
472 if (power_cell.back().cells.size() > 1) {
473 power_cell.back().cells[0].upgrade_level = 1;
474 if (!power_cell.back().upgrade_button)
475 power_cell.back().upgrade_button = new WidgetButton("images/menus/buttons/button_plus.png");
476 }
477 }
478
479 // @ATTR power.visible|bool|Controls whether or not a power is visible or hidden regardless of unlocked state. Defaults to true.
480 else if (infile.key == "visible") power_cell.back().cells[0].visible = Parse::toBool(infile.val);
481 // @ATTR power.visible_check_locked|bool|When set to true, the power will be hidden if it is locked. Defaults to false.
482 else if (infile.key == "visible_check_locked") power_cell.back().cells[0].visible_check_locked = Parse::toBool(infile.val);
483 // @ATTR power.visible_check_status|bool|When set to true, the power will be hidden if its status requirements are not met. Defaults to false.
484 else if (infile.key == "visible_check_status") power_cell.back().cells[0].visible_check_status = Parse::toBool(infile.val);
485
486 else infile.error("MenuPowers: '%s' is not a valid key.", infile.key.c_str());
487 }
488
loadUpgrade(FileParser & infile,std::vector<MenuPowersCell> & power_cell_upgrade)489 void MenuPowers::loadUpgrade(FileParser &infile, std::vector<MenuPowersCell>& power_cell_upgrade) {
490 // @ATTR upgrade.id|int|A power id from powers/powers.txt for this upgrade.
491 if (infile.key == "id") {
492 int id = Parse::popFirstInt(infile.val);
493 if (id > 0) {
494 skip_section = false;
495 power_cell_upgrade.back().id = (id);
496 }
497 else {
498 skip_section = true;
499 power_cell_upgrade.pop_back();
500 infile.error("MenuPowers: Power index out of bounds 1-%d, skipping power.", INT_MAX);
501 }
502 return;
503 }
504
505 if (skip_section)
506 return;
507
508 // @ATTR upgrade.requires_primary|predefined_string, int : Primary stat name, Required value|Upgrade requires this primary stat to be at least the specificed value.
509 if (infile.key == "requires_primary") {
510 std::string prim_stat = Parse::popFirstString(infile.val);
511 size_t prim_stat_index = eset->primary_stats.getIndexByID(prim_stat);
512
513 if (prim_stat_index != eset->primary_stats.list.size()) {
514 power_cell_upgrade.back().requires_primary[prim_stat_index] = Parse::toInt(infile.val);
515 }
516 else {
517 infile.error("MenuPowers: '%s' is not a valid primary stat.", prim_stat.c_str());
518 }
519 }
520 // @ATTR upgrade.requires_point|bool|Upgrade requires a power point to unlock.
521 else if (infile.key == "requires_point") power_cell_upgrade.back().requires_point = Parse::toBool(infile.val);
522 // @ATTR upgrade.requires_level|int|Upgrade requires at least this level for the hero.
523 else if (infile.key == "requires_level") power_cell_upgrade.back().requires_level = Parse::toInt(infile.val);
524 // @ATTR upgrade.requires_power|int|Upgrade requires another power id.
525 else if (infile.key == "requires_power") power_cell_upgrade.back().requires_power.push_back(Parse::toPowerID(infile.val));
526 // @ATTR upgrade.requires_status|repeatable(string)|Upgrade requires this campaign status.
527 else if (infile.key == "requires_status") power_cell_upgrade.back().requires_status.push_back(camp->registerStatus(infile.val));
528 // @ATTR upgrade.requires_not_status|repeatable(string)|Upgrade requires not having this campaign status.
529 else if (infile.key == "requires_not_status") power_cell_upgrade.back().requires_not_status.push_back(camp->registerStatus(infile.val));
530
531 // @ATTR upgrade.visible_requires_status|repeatable(string)|(Deprecated as of v1.11.75) Hide the upgrade if we don't have this campaign status.
532 else if (infile.key == "visible_requires_status") {
533 infile.error("MenuPowers: visible_requires_status is deprecated. Use requires_status and visible_check_status=true instead.");
534 power_cell_upgrade.back().requires_status.push_back(camp->registerStatus(infile.val));
535 power_cell_upgrade.back().visible_check_status = true;
536 }
537 // @ATTR upgrade.visible_requires_not_status|repeatable(string)|(Deprecated as of v1.11.75) Hide the upgrade if we have this campaign status.
538 else if (infile.key == "visible_requires_not_status") {
539 infile.error("MenuPowers: visible_requires_not_status is deprecated. Use requires_not_status and visible_check_status=true instead.");
540 power_cell_upgrade.back().requires_not_status.push_back(camp->registerStatus(infile.val));
541 power_cell_upgrade.back().visible_check_status = true;
542 }
543
544 // @ATTR upgrade.visible|bool|Controls whether or not a power is visible or hidden regardless of unlocked state. Defaults to true.
545 else if (infile.key == "visible") power_cell_upgrade.back().visible = Parse::toBool(infile.val);
546 // @ATTR upgrade.visible_check_locked|bool|When set to true, the power will be hidden if it is locked. Defaults to false.
547 else if (infile.key == "visible_check_locked") power_cell_upgrade.back().visible_check_locked = Parse::toBool(infile.val);
548 // @ATTR upgrade.visible_check_status|bool|When set to true, the power will be hidden if its status requirements are not met. Defaults to false.
549 else if (infile.key == "visible_check_status") power_cell_upgrade.back().visible_check_status = Parse::toBool(infile.val);
550
551 else infile.error("MenuPowers: '%s' is not a valid key.", infile.key.c_str());
552 }
553
checkRequirements(MenuPowersCell * pcell)554 bool MenuPowers::checkRequirements(MenuPowersCell* pcell) {
555 if (!pcell)
556 return false;
557
558 if (pc->stats.level < pcell->requires_level)
559 return false;
560
561 for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
562 if (pc->stats.get_primary(i) < pcell->requires_primary[i])
563 return false;
564 }
565
566 for (size_t i = 0; i < pcell->requires_status.size(); ++i)
567 if (!camp->checkStatus(pcell->requires_status[i]))
568 return false;
569
570 for (size_t i = 0; i < pcell->requires_not_status.size(); ++i)
571 if (camp->checkStatus(pcell->requires_not_status[i]))
572 return false;
573
574 for (size_t i = 0; i < pcell->requires_power.size(); ++i) {
575 if (!checkUnlocked(getCellByPowerIndex(pcell->requires_power[i])))
576 return false;
577 }
578
579 // NOTE if the player is dies, canUsePower() fails and causes passive powers to be locked
580 // so we can guard against this be checking player HP > 0
581 if (powers->powers[pcell->id].passive && pc->stats.hp > 0) {
582 if (!pc->stats.canUsePower(pcell->id, StatBlock::CAN_USE_PASSIVE))
583 return false;
584 }
585
586 return true;
587 }
588
checkRequirementStatus(MenuPowersCell * pcell)589 bool MenuPowers::checkRequirementStatus(MenuPowersCell* pcell) {
590 if (!pcell)
591 return false;
592
593 for (size_t i = 0; i < pcell->requires_status.size(); ++i)
594 if (!camp->checkStatus(pcell->requires_status[i]))
595 return false;
596
597 for (size_t i = 0; i < pcell->requires_not_status.size(); ++i)
598 if (camp->checkStatus(pcell->requires_not_status[i]))
599 return false;
600
601 return true;
602 }
603
checkUnlocked(MenuPowersCell * pcell)604 bool MenuPowers::checkUnlocked(MenuPowersCell* pcell) {
605 // If this power is not in the menu, than it has no requirements
606 if (!pcell)
607 return true;
608
609 // If power_id is saved into vector, it's unlocked anyway
610 // check if the unlocked flag is set and check the player's power list
611 if (pcell->is_unlocked)
612 return true;
613 if (std::find(pc->stats.powers_list.begin(), pc->stats.powers_list.end(), pcell->id) != pc->stats.powers_list.end())
614 return true;
615
616 // Check the rest of the requirements
617 // only check base level; upgrades are checked in logic()
618 if (!pcell->requires_point && pcell->upgrade_level <= 1 && checkRequirements(pcell))
619 return true;
620
621 return false;
622 }
623
624 /**
625 * Check if we can unlock power.
626 */
checkUnlock(MenuPowersCell * pcell)627 bool MenuPowers::checkUnlock(MenuPowersCell* pcell) {
628 // If this power is not in the menu, than it has no requirements
629 if (!pcell)
630 return true;
631
632 // If we already have a power, don't try to unlock it
633 if (checkUnlocked(pcell))
634 return false;
635
636 // Check base requirements
637 if (checkRequirements(pcell))
638 return true;
639
640 return false;
641 }
642
checkUpgrade(MenuPowersCell * pcell)643 bool MenuPowers::checkUpgrade(MenuPowersCell* pcell) {
644 if (!checkUnlocked(pcell))
645 return false;
646
647 if (!pcell->next || (pcell->next->requires_point && points_left < 1))
648 return false;
649
650 if (!checkUnlock(pcell->next))
651 return false;
652
653 return true;
654 }
655
lockCell(MenuPowersCell * pcell)656 void MenuPowers::lockCell(MenuPowersCell* pcell) {
657 pcell->is_unlocked = false;
658
659 // remove passive effects
660 if (powers->powers[pcell->id].passive && pcell->passive_on) {
661 std::vector<PowerID>::iterator passive_it = std::find(pc->stats.powers_passive.begin(), pc->stats.powers_passive.end(), pcell->id);
662 if (passive_it != pc->stats.powers_passive.end())
663 pc->stats.powers_passive.erase(passive_it);
664
665 pc->stats.effects.removeEffectPassive(pcell->id);
666 pcell->passive_on = false;
667 pc->stats.refresh_stats = true;
668 }
669
670 // remove from player's power list
671 std::vector<PowerID>::iterator it = std::find(pc->stats.powers_list.begin(), pc->stats.powers_list.end(), pcell->id);
672 if (it != pc->stats.powers_list.end())
673 pc->stats.powers_list.erase(it);
674
675 // remove from action bar
676 menu->act->addPower(0, pcell->id);
677
678 // lock higher levels as well (careful: recursion)
679 if (pcell->next) {
680 lockCell(pcell->next);
681 }
682 }
683
isBonusCell(MenuPowersCell * pcell)684 bool MenuPowers::isBonusCell(MenuPowersCell* pcell) {
685 if (!pcell)
686 return false;
687
688 if (power_cell[pcell->group].getBonusLevels() <= 0)
689 return false;
690
691 return pcell == power_cell[pcell->group].getBonusCurrent(power_cell[pcell->group].getCurrent());
692 }
693
isCellVisible(MenuPowersCell * pcell)694 bool MenuPowers::isCellVisible(MenuPowersCell* pcell) {
695 if (!pcell)
696 return false;
697
698 if (!pcell->visible)
699 return false;
700
701 if (pcell->visible_check_status && !checkRequirementStatus(pcell))
702 return false;
703 else if (pcell->visible_check_locked && !checkUnlocked(pcell))
704 return false;
705
706 return true;
707 }
708
getCellByPowerIndex(PowerID power_index)709 MenuPowersCell* MenuPowers::getCellByPowerIndex(PowerID power_index) {
710 // Powers can not have an id of 0
711 if (power_index == 0)
712 return NULL;
713
714 // Find cell with our power
715 for (size_t i = 0; i < power_cell.size(); ++i) {
716 for (size_t j = 0; j < power_cell[i].cells.size(); ++j) {
717 if (power_cell[i].cells[j].id == power_index)
718 return &power_cell[i].cells[j];
719 }
720 }
721
722 return NULL;
723 }
724
725 /**
726 * Upgrade power cell "pci" to the next level
727 */
upgradePower(MenuPowersCell * pcell,bool ignore_tab)728 void MenuPowers::upgradePower(MenuPowersCell* pcell, bool ignore_tab) {
729 if (!pcell || !pcell->next)
730 return;
731
732 if (!tab_control || ignore_tab || (tab_control && tab_control->getActiveTab() == power_cell[pcell->group].tab)) {
733 pcell->next->is_unlocked = true;
734 pc->stats.powers_list.push_back(pcell->next->id);
735 pc->stats.check_title = true;
736 }
737 setUnlockedPowers();
738 }
739
setUnlockedPowers()740 void MenuPowers::setUnlockedPowers() {
741 bool did_cell_lock = false;
742
743 // restore bonus-modified action bar powers before performing upgrades
744 clearActionBarBonusLevels();
745
746 for (size_t i = 0; i<power_cell.size(); ++i) {
747 for (size_t j = 0; j < power_cell[i].cells.size(); ++j) {
748 if (std::find(pc->stats.powers_list.begin(), pc->stats.powers_list.end(), power_cell[i].cells[j].id) != pc->stats.powers_list.end()) {
749 power_cell[i].cells[j].is_unlocked = true;
750 }
751 else {
752 if (checkUnlocked(&power_cell[i].cells[j])) {
753 // power is unlocked, but not in the player's powers_list
754 pc->stats.powers_list.push_back(power_cell[i].cells[j].id);
755 power_cell[i].cells[j].is_unlocked = true;
756 }
757 }
758
759 if (power_cell[i].cells[j].is_unlocked) {
760 if (!checkRequirements(&power_cell[i].cells[j])) {
761 lockCell(&power_cell[i].cells[j]);
762 did_cell_lock = true;
763 }
764 else {
765 // if power was present in ActionBar, update it there
766 if (power_cell[i].current_cell != j)
767 menu->act->addPower(power_cell[i].cells[j].id, power_cell[i].getCurrent()->id);
768
769 power_cell[i].current_cell = j;
770 if (slots[i])
771 slots[i]->setIcon(powers->powers[power_cell[i].cells[j].id].icon, WidgetSlot::NO_OVERLAY);
772 }
773 }
774 }
775 }
776
777 // if we locked a cell, we need to re-run this function to make sure the proper current_cell is set
778 if (did_cell_lock) {
779 setUnlockedPowers();
780 return;
781 }
782
783 for (size_t i = 0; i < power_cell.size(); ++i) {
784 // handle passive powers
785 MenuPowersCell* current_pcell = power_cell[i].getCurrent();
786 if (!current_pcell->is_unlocked)
787 continue;
788
789 MenuPowersCell* bonus_pcell = power_cell[i].getBonusCurrent(current_pcell);
790
791 for (size_t j = 0; j < power_cell[i].cells.size(); ++j) {
792 MenuPowersCell* pcell = &power_cell[i].cells[j];
793
794 if (pcell != bonus_pcell || (pcell->passive_on && powers->powers[pcell->id].passive && (!checkRequirements(current_pcell) || (!pcell->is_unlocked && !isBonusCell(pcell))))) {
795 // passive power is activated, but does not meet requirements, so remove it
796 std::vector<PowerID>::iterator passive_it = std::find(pc->stats.powers_passive.begin(), pc->stats.powers_passive.end(), pcell->id);
797 if (passive_it != pc->stats.powers_passive.end()) {
798 pc->stats.powers_passive.erase(passive_it);
799
800 pc->stats.effects.removeEffectPassive(pcell->id);
801 pcell->passive_on = false;
802 pc->stats.refresh_stats = true;
803
804 // passive powers can lock equipment slots, so update equipment here
805 menu->inv->applyEquipment();
806 }
807 }
808 else if (pcell == bonus_pcell && !pcell->passive_on && powers->powers[pcell->id].passive && checkRequirements(current_pcell)) {
809 // passive power has not been activated, so activate it here
810 std::vector<PowerID>::iterator passive_it = std::find(pc->stats.powers_passive.begin(), pc->stats.powers_passive.end(), pcell->id);
811 if (passive_it == pc->stats.powers_passive.end()) {
812 pc->stats.powers_passive.push_back(pcell->id);
813
814 pcell->passive_on = true;
815 // for passives without special triggers, we need to trigger them here
816 if (pc->stats.effects.triggered_others)
817 powers->activateSinglePassive(&pc->stats, pcell->id);
818
819 // passive powers can lock equipment slots, so update equipment here
820 menu->inv->applyEquipment();
821 }
822 }
823 }
824
825 // update the action bar for powers upgraded via item bonuses
826 if (current_pcell != bonus_pcell) {
827 menu->act->addPower(bonus_pcell->id, current_pcell->id);
828 }
829 }
830 }
831
getPointsUsed()832 int MenuPowers::getPointsUsed() {
833 int used = 0;
834
835 for (size_t i = 0; i < pc->stats.powers_list.size(); ++i) {
836 MenuPowersCell* pcell = getCellByPowerIndex(pc->stats.powers_list[i]);
837 if (pcell && pcell->requires_point)
838 used++;
839 }
840
841 return used;
842 }
843
createTooltipFromActionBar(TooltipData * tip_data,unsigned slot,int tooltip_length)844 void MenuPowers::createTooltipFromActionBar(TooltipData* tip_data, unsigned slot, int tooltip_length) {
845 if (slot >= menu->act->hotkeys.size() || slot >= menu->act->hotkeys_mod.size())
846 return;
847
848 PowerID power_index = menu->act->hotkeys[slot];
849 PowerID mod_power_index = menu->act->hotkeys_mod[slot];
850
851 PowerID pindex = mod_power_index;
852 MenuPowersCell* pcell = getCellByPowerIndex(pindex);
853
854 // action bar slot is modded and not found in the menu
855 if (power_index != mod_power_index && !pcell) {
856 PowerID test_pindex = power_index;
857 MenuPowersCell* test_pcell = getCellByPowerIndex(test_pindex);
858
859 // non-modded power found in the menu; use it instead
860 if (test_pcell) {
861 pindex = test_pindex;
862 pcell = test_pcell;
863 }
864 // else, neither is found in the menu, so default to the modded power
865 }
866
867 createTooltip(tip_data, pcell, pindex, false, tooltip_length);
868 }
869
createTooltip(TooltipData * tip_data,MenuPowersCell * pcell,PowerID power_index,bool show_unlock_prompt,int tooltip_length)870 void MenuPowers::createTooltip(TooltipData* tip_data, MenuPowersCell* pcell, PowerID power_index, bool show_unlock_prompt, int tooltip_length) {
871
872 MenuPowersCell* pcell_bonus = NULL;
873 if (pcell) {
874 pcell_bonus = power_cell[pcell->group].getBonusCurrent(pcell);
875 }
876 const Power &pwr = pcell_bonus ? powers->powers[pcell_bonus->id] : powers->powers[power_index];
877
878 {
879 std::stringstream ss;
880 ss << pwr.name;
881 if (pcell && pcell->upgrade_level > 0) {
882 ss << " (" << msg->get("Level %d", pcell->upgrade_level);
883 int bonus_levels = power_cell[pcell->group].getBonusLevels();
884 if (bonus_levels > 0)
885 ss << ", +" << bonus_levels;
886 ss << ")";
887 }
888 tip_data->addText(ss.str());
889 }
890
891 if (tooltip_length == MenuPowers::TOOLTIP_SHORT || (!pcell && tooltip_length != MenuPowers::TOOLTIP_LONG_ALL))
892 return;
893
894 if (pwr.passive) tip_data->addText(msg->get("Passive"));
895 if (pwr.description != "") {
896 tip_data->addColoredText(Utils::substituteVarsInString(pwr.description, pc), font->getColor(FontEngine::COLOR_ITEM_FLAVOR));
897 }
898
899 // add mana cost
900 if (pwr.requires_mp > 0) {
901 tip_data->addText(msg->get("Costs %d MP", pwr.requires_mp));
902 }
903 // add health cost
904 if (pwr.requires_hp > 0) {
905 tip_data->addText(msg->get("Costs %d HP", pwr.requires_hp));
906 }
907 // add cooldown time
908 if (pwr.cooldown > 0) {
909 tip_data->addText(msg->get("Cooldown:") + " " + Utils::getDurationString(pwr.cooldown, 2));
910 }
911
912 for (size_t i=0; i<pwr.post_effects.size(); ++i) {
913 std::stringstream ss;
914 EffectDef* effect_ptr = powers->getEffectDef(pwr.post_effects[i].id);
915
916 int effect_type = Effect::NONE;
917 if (effect_ptr) {
918 effect_type = effect_ptr->type;
919 }
920 else {
921 effect_type = Effect::getTypeFromString(pwr.post_effects[i].id);
922 }
923
924 if (Effect::typeIsStat(effect_type) ||
925 Effect::typeIsDmgMin(effect_type) ||
926 Effect::typeIsDmgMax(effect_type) ||
927 Effect::typeIsResist(effect_type) ||
928 Effect::typeIsPrimary(effect_type))
929 {
930 if (pwr.post_effects[i].magnitude > 0) {
931 ss << "+";
932 }
933 ss << pwr.post_effects[i].magnitude;
934 }
935
936 if (Effect::typeIsStat(effect_type)) {
937 int index = Effect::getStatFromType(effect_type);
938 if (Stats::PERCENT[index]) {
939 ss << "%";
940 }
941 ss << " " << Stats::NAME[index];
942 }
943 else if (Effect::typeIsDmgMin(effect_type)) {
944 size_t index = Effect::getDmgFromType(effect_type);
945 ss << " " << eset->damage_types.list[index].name_min;
946 }
947 else if (Effect::typeIsDmgMax(effect_type)) {
948 size_t index = Effect::getDmgFromType(effect_type);
949 ss << " " << eset->damage_types.list[index].name_max;
950 }
951 else if (Effect::typeIsResist(effect_type)) {
952 size_t index = Effect::getResistFromType(effect_type);
953 ss << "% " << msg->get("Resistance (%s)", eset->elements.list[index].name);
954 }
955 else if (Effect::typeIsPrimary(effect_type)) {
956 size_t index = Effect::getPrimaryFromType(effect_type);
957 ss << " " << eset->primary_stats.list[index].name;
958 }
959 else if (effect_type == Effect::DAMAGE) {
960 ss << pwr.post_effects[i].magnitude << " " << msg->get("Damage per second");
961 }
962 else if (effect_type == Effect::DAMAGE_PERCENT) {
963 ss << pwr.post_effects[i].magnitude << "% " << msg->get("Damage per second");
964 }
965 else if (effect_type == Effect::HPOT) {
966 ss << pwr.post_effects[i].magnitude << " " << msg->get("HP per second");
967 }
968 else if (effect_type == Effect::HPOT_PERCENT) {
969 ss << pwr.post_effects[i].magnitude << "% " << msg->get("HP per second");
970 }
971 else if (effect_type == Effect::MPOT) {
972 ss << pwr.post_effects[i].magnitude << " " << msg->get("MP per second");
973 }
974 else if (effect_type == Effect::MPOT_PERCENT) {
975 ss << pwr.post_effects[i].magnitude << "% " << msg->get("MP per second");
976 }
977 else if (effect_type == Effect::SPEED) {
978 if (pwr.post_effects[i].magnitude == 0)
979 ss << msg->get("Immobilize");
980 else
981 ss << msg->get("%d%% Speed", pwr.post_effects[i].magnitude);
982 }
983 else if (effect_type == Effect::ATTACK_SPEED) {
984 ss << msg->get("%d%% Attack Speed", pwr.post_effects[i].magnitude);
985 }
986 else if (effect_type == Effect::IMMUNITY) {
987 ss << msg->get("Immunity");
988 }
989 else if (effect_type == Effect::IMMUNITY_DAMAGE) {
990 ss << msg->get("Immunity to damage over time");
991 }
992 else if (effect_type == Effect::IMMUNITY_SLOW) {
993 ss << msg->get("Immunity to slow");
994 }
995 else if (effect_type == Effect::IMMUNITY_STUN) {
996 ss << msg->get("Immunity to stun");
997 }
998 else if (effect_type == Effect::IMMUNITY_HP_STEAL) {
999 ss << msg->get("Immunity to HP steal");
1000 }
1001 else if (effect_type == Effect::IMMUNITY_MP_STEAL) {
1002 ss << msg->get("Immunity to MP steal");
1003 }
1004 else if (effect_type == Effect::IMMUNITY_KNOCKBACK) {
1005 ss << msg->get("Immunity to knockback");
1006 }
1007 else if (effect_type == Effect::IMMUNITY_DAMAGE_REFLECT) {
1008 ss << msg->get("Immunity to damage reflection");
1009 }
1010
1011 // TODO Effect::IMMUNITY_STAT_DEBUFF?
1012
1013 else if (effect_type == Effect::STUN) {
1014 ss << msg->get("Stun");
1015 }
1016 else if (effect_type == Effect::REVIVE) {
1017 ss << msg->get("Automatic revive on death");
1018 }
1019 else if (effect_type == Effect::CONVERT) {
1020 ss << msg->get("Convert");
1021 }
1022 else if (effect_type == Effect::FEAR) {
1023 ss << msg->get("Fear");
1024 }
1025 else if (effect_type == Effect::DEATH_SENTENCE) {
1026 ss << msg->get("Lifespan");
1027 }
1028 else if (effect_type == Effect::SHIELD) {
1029 if (pwr.base_damage == eset->damage_types.list.size())
1030 continue;
1031
1032 if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_MULTIPLY) {
1033 int magnitude = pc->stats.getDamageMax(pwr.base_damage) * pwr.mod_damage_value_min / 100;
1034 ss << magnitude;
1035 }
1036 else if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_ADD) {
1037 int magnitude = pc->stats.getDamageMax(pwr.base_damage) + pwr.mod_damage_value_min;
1038 ss << magnitude;
1039 }
1040 else if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_ABSOLUTE) {
1041 if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max)
1042 ss << pwr.mod_damage_value_min;
1043 else
1044 ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max;
1045 }
1046 else {
1047 ss << pc->stats.getDamageMax(pwr.base_damage);
1048 }
1049
1050 ss << " " << msg->get("Magical Shield");
1051 }
1052 else if (effect_type == Effect::HEAL) {
1053 if (pwr.base_damage == eset->damage_types.list.size())
1054 continue;
1055
1056 int mag_min = pc->stats.getDamageMin(pwr.base_damage);
1057 int mag_max = pc->stats.getDamageMax(pwr.base_damage);
1058
1059 if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_MULTIPLY) {
1060 mag_min = mag_min * pwr.mod_damage_value_min / 100;
1061 mag_max = mag_max * pwr.mod_damage_value_min / 100;
1062 ss << mag_min << "-" << mag_max;
1063 }
1064 else if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_ADD) {
1065 mag_min = mag_min + pwr.mod_damage_value_min;
1066 mag_max = mag_max + pwr.mod_damage_value_min;
1067 ss << mag_min << "-" << mag_max;
1068 }
1069 else if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_ABSOLUTE) {
1070 if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max)
1071 ss << pwr.mod_damage_value_min;
1072 else
1073 ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max;
1074 }
1075 else {
1076 ss << mag_min << "-" << mag_max;
1077 }
1078
1079 ss << " " << msg->get("Healing");
1080 }
1081 else if (effect_type == Effect::KNOCKBACK) {
1082 ss << pwr.post_effects[i].magnitude << " " << msg->get("Knockback");
1083 }
1084 else if (effect_ptr && !effect_ptr->name.empty() && pwr.post_effects[i].magnitude > 0) {
1085 if (effect_ptr->can_stack)
1086 ss << "+";
1087 ss << pwr.post_effects[i].magnitude << " " << msg->get(effect_ptr->name);
1088 }
1089 else if (pwr.post_effects[i].magnitude == 0) {
1090 // nothing
1091 }
1092
1093 if (!ss.str().empty()) {
1094 if (pwr.post_effects[i].duration > 0) {
1095 if (effect_type == Effect::DEATH_SENTENCE) {
1096 ss << ": " << Utils::getDurationString(pwr.post_effects[i].duration, 2);
1097 }
1098 else {
1099 ss << " (" << Utils::getDurationString(pwr.post_effects[i].duration, 2) << ")";
1100 }
1101
1102 if (pwr.post_effects[i].chance != 100)
1103 ss << " ";
1104 }
1105 if (pwr.post_effects[i].chance != 100) {
1106 ss << "(" << msg->get("%d%% chance", pwr.post_effects[i].chance) << ")";
1107 }
1108
1109 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1110 }
1111 }
1112
1113 if (pwr.use_hazard || pwr.type == Power::TYPE_REPEATER) {
1114 std::stringstream ss;
1115
1116 // modifier_damage
1117 if (pwr.mod_damage_mode > -1) {
1118 if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_ADD && pwr.mod_damage_value_min > 0)
1119 ss << "+";
1120
1121 if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max) {
1122 ss << pwr.mod_damage_value_min;
1123 }
1124 else {
1125 ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max;
1126 }
1127
1128 if (pwr.mod_damage_mode == Power::STAT_MODIFIER_MODE_MULTIPLY) {
1129 ss << "%";
1130 }
1131 ss << " ";
1132
1133 if (pwr.base_damage != eset->damage_types.list.size()) {
1134 ss << eset->damage_types.list[pwr.base_damage].name;
1135 }
1136
1137 if (pwr.count > 1 && pwr.type != Power::TYPE_REPEATER)
1138 ss << " (x" << pwr.count << ")";
1139
1140 if (!ss.str().empty())
1141 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1142 }
1143
1144 // modifier_accuracy
1145 if (pwr.mod_accuracy_mode > -1) {
1146 ss.str("");
1147
1148 if (pwr.mod_accuracy_mode == Power::STAT_MODIFIER_MODE_ADD && pwr.mod_accuracy_value > 0)
1149 ss << "+";
1150
1151 ss << pwr.mod_accuracy_value;
1152
1153 if (pwr.mod_accuracy_mode == Power::STAT_MODIFIER_MODE_MULTIPLY) {
1154 ss << "%";
1155 }
1156 ss << " ";
1157
1158 ss << msg->get("Base Accuracy");
1159
1160 if (!ss.str().empty())
1161 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1162 }
1163
1164 // modifier_critical
1165 if (pwr.mod_crit_mode > -1) {
1166 ss.str("");
1167
1168 if (pwr.mod_crit_mode == Power::STAT_MODIFIER_MODE_ADD && pwr.mod_crit_value > 0)
1169 ss << "+";
1170
1171 ss << pwr.mod_crit_value;
1172
1173 if (pwr.mod_crit_mode == Power::STAT_MODIFIER_MODE_MULTIPLY) {
1174 ss << "%";
1175 }
1176 ss << " ";
1177
1178 ss << msg->get("Base Critical Chance");
1179
1180 if (!ss.str().empty())
1181 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1182 }
1183
1184 if (pwr.trait_armor_penetration) {
1185 ss.str("");
1186 ss << msg->get("Ignores Absorbtion");
1187 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1188 }
1189 if (pwr.trait_avoidance_ignore) {
1190 ss.str("");
1191 ss << msg->get("Ignores Avoidance");
1192 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1193 }
1194 if (pwr.trait_crits_impaired > 0) {
1195 ss.str("");
1196 ss << msg->get("%d%% Chance to crit slowed targets", pwr.trait_crits_impaired);
1197 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1198 }
1199 if (pwr.trait_elemental > -1) {
1200 ss.str("");
1201 ss << msg->get("Elemental Damage (%s)", eset->elements.list[pwr.trait_elemental].name);
1202 tip_data->addColoredText(ss.str(), font->getColor(FontEngine::COLOR_MENU_BONUS));
1203 }
1204 }
1205
1206 std::set<std::string>::iterator it;
1207 for (it = pwr.requires_flags.begin(); it != pwr.requires_flags.end(); ++it) {
1208 for (size_t i = 0; i < eset->equip_flags.list.size(); ++i) {
1209 if ((*it) == eset->equip_flags.list[i].id) {
1210 tip_data->addText(msg->get("Requires a %s", msg->get(eset->equip_flags.list[i].name)));
1211 }
1212 }
1213 }
1214
1215 if (pcell) {
1216 // add requirement
1217 for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
1218 if (pcell->requires_primary[i] > 0) {
1219 if (pc->stats.get_primary(i) < pcell->requires_primary[i])
1220 tip_data->addColoredText(msg->get("Requires %s %d", eset->primary_stats.list[i].name, pcell->requires_primary[i]), font->getColor(FontEngine::COLOR_MENU_PENALTY));
1221 else
1222 tip_data->addText(msg->get("Requires %s %d", eset->primary_stats.list[i].name, pcell->requires_primary[i]));
1223 }
1224 }
1225
1226 // Draw required Level Tooltip
1227 if ((pcell->requires_level > 0) && pc->stats.level < pcell->requires_level) {
1228 tip_data->addColoredText(msg->get("Requires Level %d", pcell->requires_level), font->getColor(FontEngine::COLOR_MENU_PENALTY));
1229 }
1230 else if ((pcell->requires_level > 0) && pc->stats.level >= pcell->requires_level) {
1231 tip_data->addText(msg->get("Requires Level %d", pcell->requires_level));
1232 }
1233
1234 for (size_t j=0; j < pcell->requires_power.size(); ++j) {
1235 MenuPowersCell* req_cell = getCellByPowerIndex(pcell->requires_power[j]);
1236 if (!req_cell)
1237 continue;
1238
1239 std::string req_power_name;
1240 if (req_cell->upgrade_level > 0)
1241 req_power_name = powers->powers[req_cell->id].name + " (" + msg->get("Level %d", req_cell->upgrade_level) + ")";
1242 else
1243 req_power_name = powers->powers[req_cell->id].name;
1244
1245
1246 // Required Power Tooltip
1247 if (!checkUnlocked(req_cell)) {
1248 tip_data->addColoredText(msg->get("Requires Power: %s", req_power_name), font->getColor(FontEngine::COLOR_MENU_PENALTY));
1249 }
1250 else {
1251 tip_data->addText(msg->get("Requires Power: %s", req_power_name));
1252 }
1253
1254 }
1255
1256 // Draw unlock power Tooltip
1257 if (pcell->requires_point && !(std::find(pc->stats.powers_list.begin(), pc->stats.powers_list.end(), pcell->id) != pc->stats.powers_list.end())) {
1258 MenuPowersCell* unlock_cell = getCellByPowerIndex(pcell->id);
1259 if (show_unlock_prompt && points_left > 0 && checkUnlock(unlock_cell)) {
1260 tip_data->addColoredText(msg->get("Click to Unlock (uses 1 Skill Point)"), font->getColor(FontEngine::COLOR_MENU_BONUS));
1261 }
1262 else {
1263 if (pcell->requires_point && points_left < 1)
1264 tip_data->addColoredText(msg->get("Requires 1 Skill Point"), font->getColor(FontEngine::COLOR_MENU_PENALTY));
1265 else
1266 tip_data->addText(msg->get("Requires 1 Skill Point"));
1267 }
1268 }
1269 }
1270 }
1271
renderPowers(int tab_num)1272 void MenuPowers::renderPowers(int tab_num) {
1273
1274 Rect disabled_src;
1275 disabled_src.x = disabled_src.y = 0;
1276 disabled_src.w = disabled_src.h = eset->resolutions.icon_size;
1277
1278 for (size_t i=0; i<power_cell.size(); i++) {
1279 // Continue if slot is not filled with data
1280 if (power_cell[i].tab != tab_num) continue;
1281
1282 MenuPowersCell* slot_cell = power_cell[i].getCurrent();
1283 if (!slot_cell || !isCellVisible(slot_cell))
1284 continue;
1285
1286 if (slots[i])
1287 slots[i]->render();
1288
1289 // upgrade buttons
1290 if (power_cell[i].upgrade_button)
1291 power_cell[i].upgrade_button->render();
1292
1293 // highlighting
1294 if (checkUnlocked(slot_cell)) {
1295 Rect src_unlock;
1296
1297 src_unlock.x = 0;
1298 src_unlock.y = 0;
1299 src_unlock.w = eset->resolutions.icon_size;
1300 src_unlock.h = eset->resolutions.icon_size;
1301
1302 int selected_slot = -1;
1303 if (isTabListSelected()) {
1304 selected_slot = getSelectedCellIndex();
1305 }
1306
1307 if (selected_slot == static_cast<int>(i))
1308 continue;
1309
1310 if (powers_unlock && slots[i]) {
1311 powers_unlock->setClipFromRect(src_unlock);
1312 powers_unlock->setDestFromRect(slots[i]->pos);
1313 render_device->render(powers_unlock);
1314 }
1315 }
1316 else {
1317 if (overlay_disabled && slots[i]) {
1318 overlay_disabled->setClipFromRect(disabled_src);
1319 overlay_disabled->setDestFromRect(slots[i]->pos);
1320 render_device->render(overlay_disabled);
1321 }
1322 }
1323
1324 if (slots[i])
1325 slots[i]->renderSelection();
1326 }
1327 }
1328
logic()1329 void MenuPowers::logic() {
1330 if (!visible && tab_control && default_power_tab > -1) {
1331 tab_control->setActiveTab(static_cast<unsigned>(default_power_tab));
1332 }
1333
1334 setUnlockedPowers();
1335
1336 points_left = (pc->stats.level * pc->stats.power_points_per_level) - getPointsUsed();
1337 if (points_left > 0) {
1338 newPowerNotification = true;
1339 }
1340
1341 for (size_t i=0; i<power_cell.size(); i++) {
1342 // make sure invisible cells are skipped in the tablist
1343 if (visible && slots[i])
1344 slots[i]->enable_tablist_nav = isCellVisible(power_cell[i].getCurrent());
1345
1346 // disable upgrade buttons by default
1347 if (power_cell[i].upgrade_button != NULL) {
1348 power_cell[i].upgrade_button->enabled = false;
1349 }
1350
1351 // try to automatically upgrade powers is no power point is required
1352 MenuPowersCell* pcell = power_cell[i].getCurrent();
1353 while (checkUpgrade(pcell)) {
1354 if (pcell->next && !pcell->next->requires_point) {
1355 // automatic upgrade possible; do upgrade and re-check upgrade possibility
1356 upgradePower(pcell, UPGRADE_POWER_ALL_TABS);
1357 pcell = power_cell[i].getCurrent();
1358 if (power_cell[i].upgrade_button != NULL)
1359 power_cell[i].upgrade_button->enabled = (pc->stats.hp > 0 && isCellVisible(pcell) && checkUpgrade(pcell));
1360 }
1361 else {
1362 // power point required or no upgrade available; stop trying to upgrade
1363 if (power_cell[i].upgrade_button != NULL)
1364 power_cell[i].upgrade_button->enabled = (pc->stats.hp > 0 && isCellVisible(pcell));
1365 break;
1366 }
1367 }
1368
1369 // handle clicking of upgrade button
1370 if (visible && pc->stats.hp > 0 && power_cell[i].upgrade_button != NULL) {
1371 if ((!tab_control || power_cell[i].tab == tab_control->getActiveTab()) && power_cell[i].upgrade_button->checkClick()) {
1372 upgradePower(power_cell[i].getCurrent(), !UPGRADE_POWER_ALL_TABS);
1373 }
1374 }
1375 }
1376
1377 if (!visible) return;
1378
1379 tablist.logic();
1380 if (!tabs.empty()) {
1381 for (size_t i=0; i<tabs.size(); i++) {
1382 if (tab_control && tab_control->getActiveTab() == static_cast<int>(i)) {
1383 tablist.setNextTabList(&tablist_pow[i]);
1384 }
1385 tablist_pow[i].logic();
1386 }
1387 }
1388
1389 if (closeButton->checkClick()) {
1390 visible = false;
1391 snd->play(sfx_close, snd->DEFAULT_CHANNEL, snd->NO_POS, !snd->LOOP);
1392 }
1393
1394 if (tab_control) {
1395 // make shure keyboard navigation leads us to correct tab
1396 for (size_t i=0; i<slots.size(); i++) {
1397 if (power_cell[i].tab == tab_control->getActiveTab())
1398 continue;
1399
1400 if (slots[i] && slots[i]->in_focus)
1401 slots[i]->defocus();
1402 }
1403
1404 tab_control->logic();
1405 }
1406 }
1407
render()1408 void MenuPowers::render() {
1409 if (!visible) return;
1410
1411 Rect src;
1412 Rect dest;
1413
1414 // background
1415 dest = window_area;
1416 src.x = 0;
1417 src.y = 0;
1418 src.w = window_area.w;
1419 src.h = window_area.h;
1420
1421 setBackgroundClip(src);
1422 setBackgroundDest(dest);
1423 Menu::render();
1424
1425
1426 if (tab_control) {
1427 tab_control->render();
1428 int active_tab = tab_control->getActiveTab();
1429 for (size_t i=0; i<tabs.size(); i++) {
1430 if (active_tab == static_cast<int>(i)) {
1431 // power tree
1432 Sprite *r = tree_surf[i];
1433 if (r) {
1434 r->setClipFromRect(src);
1435 r->setDestFromRect(dest);
1436 render_device->render(r);
1437 }
1438
1439 // power icons
1440 renderPowers(active_tab);
1441 }
1442 }
1443 }
1444 else if (!tree_surf.empty()) {
1445 Sprite *r = tree_surf[0];
1446 if (r) {
1447 r->setClipFromRect(src);
1448 r->setDestFromRect(dest);
1449 render_device->render(r);
1450 }
1451 renderPowers(0);
1452 }
1453 else {
1454 renderPowers(0);
1455 }
1456
1457 // close button
1458 closeButton->render();
1459
1460 // text overlay
1461 label_powers->render();
1462
1463 // stats
1464 if (!label_unspent->isHidden()) {
1465 if (points_left >= 1) {
1466 label_unspent->setText(msg->get("Available skill points: %d", points_left));
1467 }
1468 else {
1469 label_unspent->setText("");
1470 }
1471 label_unspent->render();
1472 }
1473 }
1474
1475 /**
1476 * Show mouseover descriptions of disciplines and powers
1477 */
renderTooltips(const Point & position)1478 void MenuPowers::renderTooltips(const Point& position) {
1479 if (!visible || !Utils::isWithinRect(window_area, position))
1480 return;
1481
1482 TooltipData tip_data;
1483
1484 for (size_t i=0; i<power_cell.size(); i++) {
1485
1486 if (tab_control && (tab_control->getActiveTab() != power_cell[i].tab))
1487 continue;
1488
1489 MenuPowersCell* tip_cell = power_cell[i].getCurrent();
1490 if (!isCellVisible(tip_cell))
1491 continue;
1492
1493 if (slots[i] && Utils::isWithinRect(slots[i]->pos, position)) {
1494 bool base_unlocked = checkUnlocked(tip_cell);
1495
1496 createTooltip(&tip_data, tip_cell, tip_cell->id, !base_unlocked, MenuPowers::TOOLTIP_LONG_MENU);
1497 if (tip_cell->next) {
1498 tip_data.addText("\n" + msg->get("Next Level:"));
1499 createTooltip(&tip_data, tip_cell->next, tip_cell->next->id, base_unlocked, MenuPowers::TOOLTIP_LONG_MENU);
1500 }
1501
1502 tooltipm->push(tip_data, position, TooltipData::STYLE_FLOAT);
1503 break;
1504 }
1505 }
1506 }
1507
1508 /**
1509 * Click-to-drag a power (to the action bar)
1510 */
click(const Point & mouse)1511 PowerID MenuPowers::click(const Point& mouse) {
1512 int active_tab = (tab_control) ? tab_control->getActiveTab() : 0;
1513
1514 for (size_t i=0; i<power_cell.size(); i++) {
1515 if (slots[i] && Utils::isWithinRect(slots[i]->pos, mouse) && (power_cell[i].tab == active_tab)) {
1516 if (settings->touchscreen) {
1517 if (!slots[i]->in_focus) {
1518 slots[i]->in_focus = true;
1519 if (!tabs.empty()) {
1520 tablist_pow[active_tab].setCurrent(slots[i]);
1521 }
1522 else {
1523 tablist.setCurrent(slots[i]);
1524 }
1525 return 0;
1526 }
1527 }
1528
1529 MenuPowersCell* pcell = power_cell[i].getCurrent();
1530 if (!pcell || !isCellVisible(pcell))
1531 return 0;
1532
1533 if (checkUnlock(pcell) && points_left > 0 && pcell->requires_point) {
1534 // unlock power
1535 pc->stats.powers_list.push_back(pcell->id);
1536 pc->stats.check_title = true;
1537 setUnlockedPowers();
1538 menu->act->addPower(pcell->id, 0);
1539 return 0;
1540 }
1541 else if (checkUnlocked(pcell) && !powers->powers[pcell->id].passive) {
1542 // pick up and drag power
1543 if (inpt->usingMouse()) {
1544 slots[i]->defocus();
1545 if (!tabs.empty()) {
1546 tablist_pow[active_tab].setCurrent(NULL);
1547 }
1548 else {
1549 tablist.setCurrent(NULL);
1550 }
1551 }
1552 return power_cell[i].getBonusCurrent(pcell)->id;
1553 }
1554 else
1555 return 0;
1556 }
1557 }
1558
1559 // nothing selected, defocus everything
1560 defocusTabLists();
1561
1562 return 0;
1563 }
1564
upgradeBySlotIndex(int slot_index)1565 void MenuPowers::upgradeBySlotIndex(int slot_index) {
1566 MenuPowersCell* pcell = power_cell[slot_index].getCurrent();
1567 if (checkUpgrade(pcell))
1568 upgradePower(pcell, !UPGRADE_POWER_ALL_TABS);
1569 }
1570
resetToBasePowers()1571 void MenuPowers::resetToBasePowers() {
1572 for (size_t i = 0; i < power_cell.size(); ++i) {
1573 power_cell[i].current_cell = 0;
1574 for (size_t j = 0; j < power_cell[i].cells.size(); ++j) {
1575 power_cell[i].cells[j].is_unlocked = false;
1576 power_cell[i].cells[j].passive_on = false;
1577 }
1578 }
1579
1580 setUnlockedPowers();
1581 }
1582
1583 /**
1584 * Return true if required stats for power usage are met. Else return false.
1585 */
meetsUsageStats(PowerID power_index)1586 bool MenuPowers::meetsUsageStats(PowerID power_index) {
1587 // Find cell with our power
1588 MenuPowersCell* pcell = getCellByPowerIndex(power_index);
1589
1590 // If we didn't find power in power_menu, than it has no stats requirements
1591 if (!pcell)
1592 return true;
1593
1594 // ignore bonuses to power level
1595 MenuPowersCell* base_pcell = power_cell[pcell->group].getCurrent();
1596
1597 if (pc->stats.level < base_pcell->requires_level)
1598 return false;
1599
1600 for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
1601 if (pc->stats.get_primary(i) < base_pcell->requires_primary[i])
1602 return false;
1603 }
1604
1605 return true;
1606 }
1607
clearActionBarBonusLevels()1608 void MenuPowers::clearActionBarBonusLevels() {
1609 for (size_t i = 0; i < power_cell.size(); ++i) {
1610 if (power_cell[i].getBonusLevels() > 0) {
1611 MenuPowersCell* pcell = power_cell[i].getCurrent();
1612 menu->act->addPower(pcell->id, power_cell[i].getBonusCurrent(pcell)->id);
1613 }
1614 }
1615 }
1616
clearBonusLevels()1617 void MenuPowers::clearBonusLevels() {
1618 clearActionBarBonusLevels();
1619
1620 for (size_t i = 0; i < power_cell.size(); ++i) {
1621 power_cell[i].bonus_levels.clear();
1622 }
1623 }
1624
addBonusLevels(PowerID power_index,int bonus_levels)1625 void MenuPowers::addBonusLevels(PowerID power_index, int bonus_levels) {
1626 MenuPowersCell* pcell = getCellByPowerIndex(power_index);
1627
1628 if (!pcell)
1629 return;
1630
1631 MenuPowersCellGroup* pgroup = &power_cell[pcell->group];
1632
1633 size_t min_level = pgroup->cells.size() - 1;
1634 for (size_t i = 0; i < pgroup->cells.size(); ++i) {
1635 if (pcell == &pgroup->cells[i]) {
1636 min_level = i;
1637 break;
1638 }
1639 }
1640
1641 std::pair<size_t, int> bonus(min_level, bonus_levels);
1642 pgroup->bonus_levels.push_back(bonus);
1643 }
1644
getItemBonusPowerReqString(PowerID power_index)1645 std::string MenuPowers::getItemBonusPowerReqString(PowerID power_index) {
1646 MenuPowersCell* pcell = getCellByPowerIndex(power_index);
1647
1648 if (!pcell)
1649 return "";
1650
1651 std::string output = powers->powers[power_index].name;
1652 if (pcell->upgrade_level > 0) {
1653 output += " (" + msg->get("Level %d", pcell->upgrade_level) + ")";
1654 }
1655
1656 return output;
1657 }
1658
isTabListSelected()1659 bool MenuPowers::isTabListSelected() {
1660 return (getCurrentTabList() && (tabs.empty() || (tabs.size() > 0 && getCurrentTabList() != (&tablist))));
1661 }
1662
getSelectedCellIndex()1663 int MenuPowers::getSelectedCellIndex() {
1664 TabList* cur_tablist = getCurrentTabList();
1665 int current = cur_tablist->getCurrent();
1666
1667 if (tabs.empty()) {
1668 return current;
1669 }
1670 else {
1671 WidgetSlot *cur_slot = static_cast<WidgetSlot*>(cur_tablist->getWidgetByIndex(current));
1672
1673 for (size_t i = 0; i < slots.size(); ++i) {
1674 if (slots[i] == cur_slot)
1675 return static_cast<int>(i);
1676 }
1677
1678 // we should never hit this return statement
1679 return 0;
1680 }
1681 }
1682
setNextTabList(TabList * tl)1683 void MenuPowers::setNextTabList(TabList *tl) {
1684 if (!tabs.empty()) {
1685 for (size_t i=0; i<tabs.size(); ++i) {
1686 tablist_pow[i].setNextTabList(tl);
1687 }
1688 }
1689 }
1690
getCurrentTabList()1691 TabList* MenuPowers::getCurrentTabList() {
1692 if (tablist.getCurrent() != -1) {
1693 return (&tablist);
1694 }
1695 else if (!tabs.empty()) {
1696 for (size_t i=0; i<tabs.size(); ++i) {
1697 if (tablist_pow[i].getCurrent() != -1)
1698 return (&tablist_pow[i]);
1699 }
1700 }
1701
1702 return NULL;
1703 }
1704
defocusTabLists()1705 void MenuPowers::defocusTabLists() {
1706 tablist.defocus();
1707
1708 if (!tabs.empty()) {
1709 for (size_t i=0; i<tabs.size(); ++i) {
1710 tablist_pow[i].defocus();
1711 }
1712 }
1713 }
1714
1715