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