1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012 Igor Paliychuk
4 Copyright © 2012-2014 Henrik Andersson
5 Copyright © 2012 Stefan Beller
6 Copyright © 2013 Kurt Rinnert
7 Copyright © 2012-2016 Justin Jacobs
8 
9 This file is part of FLARE.
10 
11 FLARE is free software: you can redistribute it and/or modify it under the terms
12 of the GNU General Public License as published by the Free Software Foundation,
13 either version 3 of the License, or (at your option) any later version.
14 
15 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
16 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
17 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License along with
20 FLARE.  If not, see http://www.gnu.org/licenses/
21 */
22 
23 /**
24  * class GameStatePlay
25  *
26  * Handles logic and rendering of the main action game play
27  * Also handles message passing between child objects, often to avoid circular dependencies.
28  */
29 
30 #include "Avatar.h"
31 #include "CampaignManager.h"
32 #include "CombatText.h"
33 #include "CursorManager.h"
34 #include "EnemyGroupManager.h"
35 #include "Entity.h"
36 #include "EntityManager.h"
37 #include "EngineSettings.h"
38 #include "FileParser.h"
39 #include "GameState.h"
40 #include "GameStateCutscene.h"
41 #include "GameStatePlay.h"
42 #include "GameStateTitle.h"
43 #include "Hazard.h"
44 #include "HazardManager.h"
45 #include "InputState.h"
46 #include "LootManager.h"
47 #include "MapRenderer.h"
48 #include "Menu.h"
49 #include "MenuActionBar.h"
50 #include "MenuBook.h"
51 #include "MenuCharacter.h"
52 #include "MenuDevConsole.h"
53 #include "MenuEnemy.h"
54 #include "MenuExit.h"
55 #include "MenuHUDLog.h"
56 #include "MenuInventory.h"
57 #include "MenuLog.h"
58 #include "MenuManager.h"
59 #include "MenuMiniMap.h"
60 #include "MenuPowers.h"
61 #include "MenuStash.h"
62 #include "MenuTalker.h"
63 #include "MenuVendor.h"
64 #include "ModManager.h"
65 #include "NPC.h"
66 #include "NPCManager.h"
67 #include "PowerManager.h"
68 #include "QuestLog.h"
69 #include "RenderDevice.h"
70 #include "SaveLoad.h"
71 #include "Settings.h"
72 #include "SharedGameResources.h"
73 #include "SharedResources.h"
74 #include "SoundManager.h"
75 #include "UtilsFileSystem.h"
76 #include "UtilsParsing.h"
77 #include "WidgetLabel.h"
78 
79 #include <cassert>
80 
GameStatePlay()81 GameStatePlay::GameStatePlay()
82 	: GameState()
83 	, enemy(NULL)
84 	, npc_id(-1)
85 	, is_first_map_load(true)
86 {
87 	second_timer.setDuration(settings->max_frames_per_sec);
88 
89 	hasMusic = true;
90 	has_background = false;
91 	// GameEngine scope variables
92 
93 	if (items == NULL)
94 		items = new ItemManager();
95 
96 	camp = new CampaignManager();
97 
98 	loot = new LootManager();
99 	powers = new PowerManager();
100 	mapr = new MapRenderer();
101 	pc = new Avatar();
102 	entitym = new EntityManager();
103 	enemyg = new EnemyGroupManager();
104 	hazards = new HazardManager();
105 	menu = new MenuManager();
106 	npcs = new NPCManager();
107 	quests = new QuestLog(menu->questlog);
108 
109 	// load the config file for character titles
110 	loadTitles();
111 
112 	refreshWidgets();
113 }
114 
refreshWidgets()115 void GameStatePlay::refreshWidgets() {
116 	menu->alignAll();
117 }
118 
119 /**
120  * Reset all game states to a new game.
121  */
resetGame()122 void GameStatePlay::resetGame() {
123 	camp->resetAllStatuses();
124 	pc->init();
125 	pc->stats.currency = 0;
126 	menu->act->clear(!MenuActionBar::CLEAR_SKIP_ITEMS);
127 	menu->inv->inventory[0].clear();
128 	menu->inv->inventory[1].clear();
129 	menu->inv->changed_equipment = true;
130 	menu->inv->currency = 0;
131 	menu->questlog->clearAll();
132 	quests->createQuestList();
133 	menu->hudlog->clear();
134 
135 	// Finalize new character settings
136 	menu->talker->setHero(pc->stats);
137 	pc->loadSounds();
138 
139 	mapr->teleportation = true;
140 	mapr->teleport_mapname = "maps/spawn.txt";
141 }
142 
143 /**
144  * Check mouseover for enemies.
145  * class variable "enemy" contains a live enemy on mouseover.
146  * This function also sets enemy mouseover for Menu Enemy.
147  */
checkEnemyFocus()148 void GameStatePlay::checkEnemyFocus() {
149 	pc->stats.target_corpse = NULL;
150 	pc->stats.target_nearest = NULL;
151 	pc->stats.target_nearest_corpse = NULL;
152 	pc->stats.target_nearest_dist = 0;
153 	pc->stats.target_nearest_corpse_dist = 0;
154 
155 	FPoint src_pos = pc->stats.pos;
156 
157 	// check the last hit enemy first
158 	// if there's none, then either get the nearest enemy or one under the mouse (depending on mouse mode)
159 	if (!inpt->usingMouse()) {
160 		if (hazards->last_enemy) {
161 			if (enemy == hazards->last_enemy) {
162 				if (!menu->enemy->timeout.isEnd() && hazards->last_enemy->stats.hp > 0)
163 					return;
164 				else
165 					hazards->last_enemy = NULL;
166 			}
167 			enemy = hazards->last_enemy;
168 		}
169 		else {
170 			enemy = entitym->getNearestEntity(pc->stats.pos, !EntityManager::GET_CORPSE, NULL, eset->misc.interact_range);
171 		}
172 	}
173 	else {
174 		if (hazards->last_enemy) {
175 			enemy = hazards->last_enemy;
176 			hazards->last_enemy = NULL;
177 		}
178 		else {
179 			enemy = entitym->entityFocus(inpt->mouse, mapr->cam.pos, EntityManager::IS_ALIVE);
180 			if (enemy) {
181 				curs->setCursor(CursorManager::CURSOR_ATTACK);
182 			}
183 			src_pos = Utils::screenToMap(inpt->mouse.x, inpt->mouse.y, mapr->cam.pos.x, mapr->cam.pos.y);
184 
185 		}
186 	}
187 
188 	if (enemy) {
189 		// set the actual menu with the enemy selected above
190 		if (!enemy->stats.suppress_hp) {
191 			menu->enemy->enemy = enemy;
192 			menu->enemy->timeout.reset(Timer::BEGIN);
193 		}
194 	}
195 	else if (inpt->usingMouse()) {
196 		// if we're using a mouse and we didn't select an enemy, try selecting a dead one instead
197 		Entity *temp_enemy = entitym->entityFocus(inpt->mouse, mapr->cam.pos, !EntityManager::IS_ALIVE);
198 		if (temp_enemy) {
199 			pc->stats.target_corpse = &(temp_enemy->stats);
200 			menu->enemy->enemy = temp_enemy;
201 			menu->enemy->timeout.reset(Timer::BEGIN);
202 		}
203 	}
204 
205 	// save the highlighted enemy position for auto-targeting purposes
206 	if (enemy) {
207 		pc->cursor_enemy = enemy;
208 	}
209 	else {
210 		pc->cursor_enemy = NULL;
211 	}
212 
213 	// save the positions of the nearest enemies for powers that use "target_nearest"
214 	Entity *nearest = entitym->getNearestEntity(src_pos, !EntityManager::GET_CORPSE, &(pc->stats.target_nearest_dist), eset->misc.interact_range);
215 	if (nearest)
216 		pc->stats.target_nearest = &(nearest->stats);
217 	Entity *nearest_corpse = entitym->getNearestEntity(src_pos, EntityManager::GET_CORPSE, &(pc->stats.target_nearest_corpse_dist), eset->misc.interact_range);
218 	if (nearest_corpse)
219 		pc->stats.target_nearest_corpse = &(nearest_corpse->stats);
220 }
221 
222 /**
223  * Similar to the above checkEnemyFocus(), but handles NPCManager instead
224  */
checkNPCFocus()225 void GameStatePlay::checkNPCFocus() {
226 	Entity *focus_npc;
227 
228 	if (!inpt->usingMouse() && (!menu->enemy->enemy || menu->enemy->enemy->stats.hero_ally)) {
229 		// TODO bug? If mixed monster allies and npc allies, npc allies will always be highlighted, regardless of distance to player
230 		focus_npc = npcs->getNearestNPC(pc->stats.pos);
231 	}
232 	else {
233 		focus_npc = npcs->npcFocus(inpt->mouse, mapr->cam.pos, true);
234 	}
235 
236 	if (focus_npc) {
237 		// set the actual menu with the npc selected above
238 		if (!focus_npc->stats.suppress_hp) {
239 			menu->enemy->enemy = focus_npc;
240 			menu->enemy->timeout.reset(Timer::BEGIN);
241 		}
242 	}
243 	else if (inpt->usingMouse()) {
244 		// if we're using a mouse and we didn't select an npc, try selecting a dead one instead
245 		Entity *temp_npc = npcs->npcFocus(inpt->mouse, mapr->cam.pos, false);
246 		if (temp_npc) {
247 			menu->enemy->enemy = temp_npc;
248 			menu->enemy->timeout.reset(Timer::BEGIN);
249 		}
250 	}
251 }
252 
253 /**
254  * Check to see if the player is picking up loot on the ground
255  */
checkLoot()256 void GameStatePlay::checkLoot() {
257 
258 	if (!pc->stats.alive)
259 		return;
260 
261 	if (menu->isDragging())
262 		return;
263 
264 	ItemStack pickup;
265 
266 	// Autopickup
267 	if (eset->loot.autopickup_currency) {
268 		pickup = loot->checkAutoPickup(pc->stats.pos);
269 		if (!pickup.empty()) {
270 			menu->inv->add(pickup, MenuInventory::CARRIED, ItemStorage::NO_SLOT, MenuInventory::ADD_PLAY_SOUND, MenuInventory::ADD_AUTO_EQUIP);
271 			pickup.clear();
272 		}
273 	}
274 
275 	// Normal pickups
276 	if (!pc->using_main1) {
277 		pickup = loot->checkPickup(inpt->mouse, mapr->cam.pos, pc->stats.pos);
278 	}
279 
280 	if (!pickup.empty()) {
281 		menu->inv->add(pickup, MenuInventory::CARRIED, ItemStorage::NO_SLOT, MenuInventory::ADD_PLAY_SOUND, MenuInventory::ADD_AUTO_EQUIP);
282 		StatusID pickup_status = camp->registerStatus(items->items[pickup.item].pickup_status);
283 		camp->setStatus(pickup_status);
284 		pickup.clear();
285 	}
286 
287 }
288 
checkTeleport()289 void GameStatePlay::checkTeleport() {
290 	bool on_load_teleport = false;
291 
292 	// both map events and player powers can cause teleportation
293 	if (mapr->teleportation || pc->stats.teleportation) {
294 
295 		mapr->collider.unblock(pc->stats.pos.x, pc->stats.pos.y);
296 
297 		if (mapr->teleportation) {
298 			// camera gets interpolated movement during intramap teleport
299 			// during intermap teleport, we set the camera to the player position
300 			pc->stats.pos.x = mapr->teleport_destination.x;
301 			pc->stats.pos.y = mapr->teleport_destination.y;
302 			pc->teleport_camera_lock = true;
303 		}
304 		else {
305 			pc->stats.pos.x = pc->stats.teleport_destination.x;
306 			pc->stats.pos.y = pc->stats.teleport_destination.y;
307 		}
308 
309 		// if we're not changing map, move allies to a the player's new position
310 		// when changing maps, entitym->handleNewMap() does something similar to this
311 		if (mapr->teleport_mapname.empty()) {
312 			FPoint spawn_pos = mapr->collider.getRandomNeighbor(Point(pc->stats.pos), 1, !MapCollision::IGNORE_BLOCKED);
313 			for (unsigned int i=0; i < entitym->entities.size(); i++) {
314 				if(entitym->entities[i]->stats.hero_ally && entitym->entities[i]->stats.alive) {
315 					mapr->collider.unblock(entitym->entities[i]->stats.pos.x, entitym->entities[i]->stats.pos.y);
316 					entitym->entities[i]->stats.pos = spawn_pos;
317 					mapr->collider.block(entitym->entities[i]->stats.pos.x, entitym->entities[i]->stats.pos.y, MapCollision::IS_ALLY);
318 				}
319 			}
320 		}
321 
322 		// process intermap teleport
323 		if (mapr->teleportation && !mapr->teleport_mapname.empty()) {
324 			mapr->cam.warpTo(pc->stats.pos);
325 			std::string teleport_mapname = mapr->teleport_mapname;
326 			mapr->teleport_mapname = "";
327 			inpt->lock_all = (teleport_mapname == "maps/spawn.txt");
328 			mapr->executeOnMapExitEvents();
329 			showLoading();
330 			mapr->load(teleport_mapname);
331 			setLoadingFrame();
332 
333 			// use the default hero spawn position for this map
334 			if (mapr->teleport_destination.x == -1 && mapr->teleport_destination.y == -1) {
335 				pc->stats.pos.x = mapr->hero_pos.x;
336 				pc->stats.pos.y = mapr->hero_pos.y;
337 				mapr->cam.warpTo(pc->stats.pos);
338 			}
339 
340 			// store this as the new respawn point (provided the tile is open)
341 			if (mapr->collider.isValidPosition(pc->stats.pos.x, pc->stats.pos.y, MapCollision::MOVE_NORMAL, MapCollision::COLLIDE_HERO)) {
342 				mapr->respawn_map = teleport_mapname;
343 				mapr->respawn_point = pc->stats.pos;
344 			}
345 			else {
346 				Utils::logError("GameStatePlay: Spawn position (%d, %d) is blocked.", static_cast<int>(pc->stats.pos.x), static_cast<int>(pc->stats.pos.y));
347 			}
348 
349 			pc->handleNewMap();
350 			hazards->handleNewMap();
351 			loot->handleNewMap();
352 			powers->handleNewMap(&mapr->collider);
353 			menu->enemy->handleNewMap();
354 			menu->stash->visible = false;
355 
356 			// switch off teleport flag so we can check if an on_load event has teleportation
357 			mapr->teleportation = false;
358 
359 			mapr->executeOnLoadEvents();
360 			if (mapr->teleportation)
361 				on_load_teleport = true;
362 
363 			// enemies and npcs should be initialized AFTER on_load events execute
364 			entitym->handleNewMap();
365 			npcs->handleNewMap();
366 			resetNPC();
367 
368 			menu->mini->prerender(&mapr->collider, mapr->w, mapr->h);
369 
370 			// return to title (permadeath) OR auto-save
371 			if (pc->stats.permadeath && pc->stats.cur_state == StatBlock::ENTITY_DEAD) {
372 				snd->stopMusic();
373 				showLoading();
374 				setRequestedGameState(new GameStateTitle());
375 			}
376 			else if (eset->misc.save_onload) {
377 				if (!is_first_map_load)
378 					save_load->saveGame();
379 				else
380 					is_first_map_load = false;
381 			}
382 		}
383 
384 		if (mapr->collider.isOutsideMap(pc->stats.pos.x, pc->stats.pos.y)) {
385 			Utils::logError("GameStatePlay: Teleport position is outside of map bounds.");
386 			pc->stats.pos.x = 0.5f;
387 			pc->stats.pos.y = 0.5f;
388 		}
389 
390 		mapr->collider.block(pc->stats.pos.x, pc->stats.pos.y, !MapCollision::IS_ALLY);
391 
392 		pc->stats.teleportation = false;
393 	}
394 
395 	if (!on_load_teleport && mapr->teleport_mapname.empty())
396 		mapr->teleportation = false;
397 }
398 
399 /**
400  * Check for cancel key to exit menus or exit the game.
401  * Also check closing the game window entirely.
402  */
checkCancel()403 void GameStatePlay::checkCancel() {
404 	bool save_on_exit = eset->misc.save_onexit && !(pc->stats.permadeath && pc->stats.cur_state == StatBlock::ENTITY_DEAD);
405 
406 	if (save_on_exit && eset->misc.save_pos_onexit) {
407 		mapr->respawn_point = pc->stats.pos;
408 	}
409 
410 	// if user has clicked exit game from exit menu
411 	if (menu->requestingExit()) {
412 		menu->closeAll();
413 
414 		if (save_on_exit)
415 			save_load->saveGame();
416 
417 		// audio levels can be changed in the pause menu, so update our settings file
418 		settings->saveSettings();
419 		inpt->saveKeyBindings();
420 
421 		snd->stopMusic();
422 		showLoading();
423 		setRequestedGameState(new GameStateTitle());
424 
425 		save_load->setGameSlot(0);
426 	}
427 
428 	// if user closes the window
429 	if (inpt->done) {
430 		menu->closeAll();
431 
432 		if (save_on_exit)
433 			save_load->saveGame();
434 
435 		settings->saveSettings();
436 		inpt->saveKeyBindings();
437 
438 		snd->stopMusic();
439 		exitRequested = true;
440 	}
441 }
442 
443 /**
444  * Check for log messages from various child objects
445  */
checkLog()446 void GameStatePlay::checkLog() {
447 
448 	// If the player has just respawned, we want to clear the HUD log
449 	if (pc->respawn) {
450 		menu->hudlog->clear();
451 	}
452 
453 	while (!pc->log_msg.empty()) {
454 		const std::string& str = pc->log_msg.front().first;
455 		const int msg_type = pc->log_msg.front().second;
456 
457 		menu->questlog->add(str, MenuLog::TYPE_MESSAGES, msg_type);
458 		menu->hudlog->add(str, msg_type);
459 
460 		pc->log_msg.pop();
461 	}
462 }
463 
464 /**
465  * Check if we need to open book
466  */
checkBook()467 void GameStatePlay::checkBook() {
468 	// Map events can open books
469 	if (mapr->show_book != "") {
470 		menu->book->book_name = mapr->show_book;
471 		mapr->show_book = "";
472 	}
473 
474 	// items can be readable books
475 	if (menu->inv->show_book != "") {
476 		menu->book->book_name = menu->inv->show_book;
477 		menu->inv->show_book = "";
478 	}
479 }
480 
loadTitles()481 void GameStatePlay::loadTitles() {
482 	FileParser infile;
483 	// @CLASS GameStatePlay: Titles|Description of engine/titles.txt
484 	if (infile.open("engine/titles.txt", FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
485 		while (infile.next()) {
486 			if (infile.new_section && infile.section == "title") {
487 				Title t;
488 				titles.push_back(t);
489 			}
490 
491 			if (titles.empty()) continue;
492 
493 			if (infile.key == "title") {
494 				// @ATTR title.title|string|The displayed title.
495 				titles.back().title = infile.val;
496 			}
497 			else if (infile.key == "level") {
498 				// @ATTR title.level|int|Requires level.
499 				titles.back().level = Parse::toInt(infile.val);
500 			}
501 			else if (infile.key == "power") {
502 				// @ATTR title.power|power_id|Requires power.
503 				titles.back().power = Parse::toPowerID(infile.val);
504 			}
505 			else if (infile.key == "requires_status") {
506 				// @ATTR title.requires_status|list(string)|Requires status.
507 				std::string repeat_val = Parse::popFirstString(infile.val);
508 				while (repeat_val != "") {
509 					titles.back().requires_status.push_back(camp->registerStatus(repeat_val));
510 					repeat_val = Parse::popFirstString(infile.val);
511 				}
512 			}
513 			else if (infile.key == "requires_not_status") {
514 				// @ATTR title.requires_not_status|list(string)|Requires not status.
515 				std::string repeat_val = Parse::popFirstString(infile.val);
516 				while (repeat_val != "") {
517 					titles.back().requires_not_status.push_back(camp->registerStatus(repeat_val));
518 					repeat_val = Parse::popFirstString(infile.val);
519 				}
520 			}
521 			else if (infile.key == "primary_stat") {
522 				// @ATTR title.primary_stat|predefined_string, predefined_string : Primary stat, Lesser primary stat|Required primary stat(s). The lesser stat is optional.
523 				titles.back().primary_stat_1 = Parse::popFirstString(infile.val);
524 				titles.back().primary_stat_2 = Parse::popFirstString(infile.val);
525 			}
526 			else infile.error("GameStatePlay: '%s' is not a valid key.", infile.key.c_str());
527 		}
528 		infile.close();
529 	}
530 }
531 
checkTitle()532 void GameStatePlay::checkTitle() {
533 	if (!pc->stats.check_title || titles.empty())
534 		return;
535 
536 	int title_id = -1;
537 
538 	for (unsigned i=0; i<titles.size(); i++) {
539 		if (titles[i].title.empty())
540 			continue;
541 
542 		if (titles[i].level > 0 && pc->stats.level < titles[i].level)
543 			continue;
544 		if (titles[i].power > 0 && std::find(pc->stats.powers_list.begin(), pc->stats.powers_list.end(), titles[i].power) == pc->stats.powers_list.end())
545 			continue;
546 		if (!titles[i].primary_stat_1.empty() && !checkPrimaryStat(titles[i].primary_stat_1, titles[i].primary_stat_2))
547 			continue;
548 
549 		bool status_failed = false;
550 		for (size_t j = 0; j < titles[i].requires_status.size(); ++j) {
551 			if (!camp->checkStatus(titles[i].requires_status[j])) {
552 				status_failed = true;
553 				break;
554 			}
555 		}
556 		for (size_t j = 0; j < titles[i].requires_not_status.size(); ++j) {
557 			if (camp->checkStatus(titles[i].requires_not_status[j])) {
558 				status_failed = true;
559 				break;
560 			}
561 		}
562 
563 		if (status_failed)
564 			continue;
565 
566 		// Title meets the requirements
567 		title_id = i;
568 		break;
569 	}
570 
571 	if (title_id != -1) pc->stats.character_subclass = titles[title_id].title;
572 	pc->stats.check_title = false;
573 	pc->stats.refresh_stats = true;
574 }
575 
checkEquipmentChange()576 void GameStatePlay::checkEquipmentChange() {
577 	if (menu->inv->changed_equipment) {
578 		// force the actionbar to update when we change gear
579 		menu->act->updated = true;
580 
581 		int feet_index = -1;
582 		std::vector<Avatar::Layer_gfx> img_gfx;
583 		// load only displayable layers
584 		for (unsigned int j=0; j<pc->layer_reference_order.size(); j++) {
585 			Avatar::Layer_gfx gfx;
586 			gfx.gfx = "";
587 			gfx.type = pc->layer_reference_order[j];
588 			for (int i=0; i<menu->inv->inventory[MenuInventory::EQUIPMENT].getSlotNumber(); i++) {
589 				if (pc->layer_reference_order[j] == menu->inv->inventory[MenuInventory::EQUIPMENT].slot_type[i]) {
590 					gfx.gfx = items->items[menu->inv->inventory[MenuInventory::EQUIPMENT][i].item].gfx;
591 					gfx.type = menu->inv->inventory[MenuInventory::EQUIPMENT].slot_type[i];
592 				}
593 				if (menu->inv->inventory[MenuInventory::EQUIPMENT].slot_type[i] == "feet") {
594 					feet_index = i;
595 				}
596 			}
597 			// special case: if we don't have a head, use the portrait's head
598 			if (gfx.gfx == "" && pc->layer_reference_order[j] == "head") {
599 				gfx.gfx = pc->stats.gfx_head;
600 				gfx.type = "head";
601 			}
602 			// fall back to default if it exists
603 			if (gfx.gfx == "") {
604 				bool exists = Filesystem::fileExists(mods->locate("animations/avatar/" + pc->stats.gfx_base + "/default_" + gfx.type + ".txt"));
605 				if (exists) gfx.gfx = "default_" + gfx.type;
606 			}
607 			img_gfx.push_back(gfx);
608 		}
609 		assert(pc->layer_reference_order.size()==img_gfx.size());
610 		pc->loadGraphics(img_gfx);
611 
612 		if (feet_index != -1)
613 			pc->loadStepFX(items->items[menu->inv->inventory[MenuInventory::EQUIPMENT][feet_index].item].stepfx);
614 	}
615 
616 	menu->inv->changed_equipment = false;
617 }
618 
checkLootDrop()619 void GameStatePlay::checkLootDrop() {
620 
621 	// if the player has dropped an item from the inventory
622 	while (!menu->drop_stack.empty()) {
623 		if (!menu->drop_stack.front().empty()) {
624 			loot->addLoot(menu->drop_stack.front(), pc->stats.pos, LootManager::DROPPED_BY_HERO);
625 		}
626 		menu->drop_stack.pop();
627 	}
628 
629 	// if the player has dropped a quest reward because inventory full
630 	while (!camp->drop_stack.empty()) {
631 		if (!camp->drop_stack.front().empty()) {
632 			loot->addLoot(camp->drop_stack.front(), pc->stats.pos, LootManager::DROPPED_BY_HERO);
633 		}
634 		camp->drop_stack.pop();
635 	}
636 
637 	// if the player been directly given items, but their inventory is full
638 	// this happens when adding currency from older save files
639 	while (!menu->inv->drop_stack.empty()) {
640 		if (!menu->inv->drop_stack.front().empty()) {
641 			loot->addLoot(menu->inv->drop_stack.front(), pc->stats.pos, LootManager::DROPPED_BY_HERO);
642 		}
643 		menu->inv->drop_stack.pop();
644 	}
645 }
646 
647 /**
648  * Removes items as required by certain powers
649  */
checkUsedItems()650 void GameStatePlay::checkUsedItems() {
651 	for (unsigned i=0; i<powers->used_items.size(); i++) {
652 		menu->inv->remove(powers->used_items[i], 1);
653 	}
654 	for (unsigned i=0; i<powers->used_equipped_items.size(); i++) {
655 		menu->inv->inventory[MenuInventory::EQUIPMENT].remove(powers->used_equipped_items[i], 1);
656 		menu->inv->applyEquipment();
657 	}
658 	powers->used_items.clear();
659 	powers->used_equipped_items.clear();
660 }
661 
662 /**
663  * Marks the menu if it needs attention.
664  */
checkNotifications()665 void GameStatePlay::checkNotifications() {
666 	if (pc->newLevelNotification || menu->chr->getUnspent() > 0) {
667 		pc->newLevelNotification = false;
668 		menu->act->requires_attention[MenuActionBar::MENU_CHARACTER] = !menu->chr->visible;
669 	}
670 	if (menu->pow->newPowerNotification) {
671 		menu->pow->newPowerNotification = false;
672 		menu->act->requires_attention[MenuActionBar::MENU_POWERS] = !menu->pow->visible;
673 	}
674 	if (quests->newQuestNotification) {
675 		quests->newQuestNotification = false;
676 		menu->act->requires_attention[MenuActionBar::MENU_LOG] = !menu->questlog->visible && !pc->questlog_dismissed;
677 		pc->questlog_dismissed = false;
678 	}
679 
680 	// if the player is transformed into a creature, don't notifications for the powers menu
681 	if (pc->stats.transformed) {
682 		menu->act->requires_attention[MenuActionBar::MENU_POWERS] = false;
683 	}
684 }
685 
686 /**
687  * If the player has clicked on an NPC, the game mode might be changed.
688  * If a player walks away from an NPC, end the interaction with that NPC
689  * If an NPC is giving a reward, process it
690  */
checkNPCInteraction()691 void GameStatePlay::checkNPCInteraction() {
692 	if (pc->using_main1 || !pc->stats.humanoid)
693 		return;
694 
695 	// reset movement restrictions when we're not in dialog
696 	if (!menu->talker->visible) {
697 		pc->allow_movement = true;
698 	}
699 
700 	if (npc_id != -1 && !menu->isNPCMenuVisible()) {
701 		// if we have an NPC, but no NPC windows are open, clear the NPC
702 		resetNPC();
703 	}
704 
705 	// get NPC by ID
706 	// event NPCs take precedence over map NPCs
707 	if (mapr->event_npc != "") {
708 		// if the player is already interacting with an NPC when triggering an event NPC, clear the current NPC
709 		if (npc_id != -1) {
710 			resetNPC();
711 		}
712 		npc_id = mapr->npc_id = npcs->getID(mapr->event_npc);
713 		menu->talker->npc_from_map = false;
714 	}
715 	else if (mapr->npc_id != -1) {
716 		npc_id = mapr->npc_id;
717 		menu->talker->npc_from_map = true;
718 	}
719 	mapr->event_npc = "";
720 	mapr->npc_id = -1;
721 
722 	if (npc_id == -1)
723 		return;
724 
725 	if (npc_id != -1) {
726 		bool interact_with_npc = false;
727 		if (menu->talker->npc_from_map) {
728 			float interact_distance = Utils::calcDist(pc->stats.pos, npcs->npcs[npc_id]->stats.pos);
729 
730 			if (interact_distance < eset->misc.interact_range) {
731 				interact_with_npc = true;
732 			}
733 			else {
734 				resetNPC();
735 			}
736 		}
737 		else {
738 			// npc is from event
739 			interact_with_npc = true;
740 
741 			// since its impossible for the player to walk away from event NPCs, we disable their movement here
742 			pc->allow_movement = false;
743 		}
744 
745 		if (interact_with_npc) {
746 			if (!menu->isNPCMenuVisible()) {
747 				if (inpt->pressing[Input::MAIN1] && inpt->usingMouse()) inpt->lock[Input::MAIN1] = true;
748 				if (inpt->pressing[Input::ACCEPT]) inpt->lock[Input::ACCEPT] = true;
749 
750 				menu->closeAll();
751 				menu->talker->setNPC(npcs->npcs[npc_id]);
752 				menu->talker->chooseDialogNode(-1);
753 			}
754 		}
755 	}
756 }
757 
checkStash()758 void GameStatePlay::checkStash() {
759 	if (mapr->stash) {
760 		// If triggered, open the stash and inventory menus
761 		menu->closeAll();
762 		menu->inv->visible = true;
763 		menu->stash->visible = true;
764 		mapr->stash = false;
765 		menu->stash->validate(menu->drop_stack);
766 	}
767 	else if (menu->stash->visible) {
768 		// Close stash if inventory is closed
769 		if (!menu->inv->visible) {
770 			menu->resetDrag();
771 			menu->stash->visible = false;
772 		}
773 
774 		// If the player walks away from the stash, close its menu
775 		float interact_distance = Utils::calcDist(pc->stats.pos, mapr->stash_pos);
776 		if (interact_distance > eset->misc.interact_range || !pc->stats.alive) {
777 			menu->resetDrag();
778 			menu->stash->visible = false;
779 		}
780 
781 	}
782 
783 	// If the stash has been updated, save the game
784 	if (menu->stash->checkUpdates()) {
785 		save_load->saveGame();
786 	}
787 }
788 
checkCutscene()789 void GameStatePlay::checkCutscene() {
790 	if (!mapr->cutscene)
791 		return;
792 
793 	showLoading();
794 	GameStateCutscene *cutscene = new GameStateCutscene(NULL);
795 
796 	if (!cutscene->load(mapr->cutscene_file)) {
797 		delete cutscene;
798 		mapr->cutscene = false;
799 		return;
800 	}
801 
802 	// handle respawn point and set game play game_slot
803 	cutscene->game_slot = save_load->getGameSlot();
804 
805 	if (mapr->teleportation) {
806 
807 		if (mapr->teleport_mapname != "")
808 			mapr->respawn_map = mapr->teleport_mapname;
809 
810 		mapr->respawn_point = mapr->teleport_destination;
811 
812 	}
813 	else {
814 		mapr->respawn_point = pc->stats.pos;
815 	}
816 
817 	if (eset->misc.save_oncutscene)
818 		save_load->saveGame();
819 
820 	menu->closeAll();
821 
822 	setRequestedGameState(cutscene);
823 }
824 
checkSaveEvent()825 void GameStatePlay::checkSaveEvent() {
826 	if (mapr->save_game) {
827 		mapr->respawn_point = pc->stats.pos;
828 		save_load->saveGame();
829 		mapr->save_game = false;
830 	}
831 }
832 
833 /**
834  * Recursively update the action bar powers based on equipment
835  */
updateActionBar(unsigned index)836 void GameStatePlay::updateActionBar(unsigned index) {
837 	if (menu->act->slots_count == 0 || index > menu->act->slots_count - 1) return;
838 
839 	if (items->items.empty()) return;
840 
841 	for (unsigned i = index; i < menu->act->slots_count; i++) {
842 		if (menu->act->hotkeys[i] == 0) continue;
843 
844 		PowerID id = menu->inv->getPowerMod(menu->act->hotkeys_mod[i]);
845 		if (id > 0) {
846 			menu->act->hotkeys_mod[i] = id;
847 			return updateActionBar(i);
848 		}
849 	}
850 }
851 
852 /**
853  * Process all actions for a single frame
854  * This includes some message passing between child object
855  */
logic()856 void GameStatePlay::logic() {
857 	if (inpt->window_resized)
858 		refreshWidgets();
859 
860 	curs->setLowHP(pc->isLowHpCursorEnabled() && pc->isLowHp());
861 
862 	checkCutscene();
863 
864 	// check menus first (top layer gets mouse click priority)
865 	menu->logic();
866 
867 	if (!isPaused()) {
868 		if (!second_timer.isEnd())
869 			second_timer.tick();
870 		else {
871 			pc->time_played++;
872 			second_timer.reset(Timer::BEGIN);
873 		}
874 
875 		// these actions only occur when the game isn't paused
876 		if (pc->stats.alive) checkLoot();
877 		checkEnemyFocus();
878 		checkNPCFocus();
879 		if (pc->stats.alive) {
880 			mapr->checkHotspots();
881 			mapr->checkNearestEvent();
882 			checkNPCInteraction();
883 		}
884 		checkTitle();
885 
886 		menu->act->checkAction(pc->action_queue);
887 		pc->logic();
888 
889 		// Transform powers change the actionbar layout,
890 		// so we need to prevent accidental clicks if a new power is placed under the slot we clicked on.
891 		// It's a bit hacky, but it works
892 		if (pc->isTransforming()) {
893 			menu->act->resetSlots();
894 		}
895 
896 		// transfer hero data to enemies, for AI use
897 		if (pc->stats.get(Stats::STEALTH) > 100) entitym->hero_stealth = 100;
898 		else entitym->hero_stealth = pc->stats.get(Stats::STEALTH);
899 
900 		entitym->logic();
901 		hazards->logic();
902 		loot->logic();
903 		npcs->logic();
904 
905 		snd->logic(pc->stats.pos);
906 
907 		comb->logic(mapr->cam.pos);
908 	}
909 
910 	// close menus when the player dies, but still allow them to be reopened
911 	if (pc->close_menus) {
912 		pc->close_menus = false;
913 		menu->closeAll();
914 	}
915 
916 	// these actions occur whether the game is paused or not.
917 	// TODO Why? Some of these probably don't need to be executed when paused
918 	checkTeleport();
919 	checkLootDrop();
920 	checkLog();
921 	checkBook();
922 	checkEquipmentChange();
923 	checkUsedItems();
924 	checkStash();
925 	checkSaveEvent();
926 	checkNotifications();
927 	checkCancel();
928 
929 	mapr->logic(isPaused());
930 	mapr->enemies_cleared = entitym->isCleared();
931 	quests->logic();
932 
933 	pc->checkTransform();
934 
935 	// change hero powers on transformation
936 	if (pc->setPowers) {
937 		pc->setPowers = false;
938 		if (!pc->stats.humanoid && menu->pow->visible) menu->closeRight();
939 		// save ActionBar state and lock slots from removing/replacing power
940 		for (int i = 0; i < MenuActionBar::SLOT_MAX ; i++) {
941 			menu->act->hotkeys_temp[i] = menu->act->hotkeys[i];
942 			menu->act->hotkeys[i] = 0;
943 		}
944 		int count = MenuActionBar::SLOT_MAIN1;
945 		// put creature powers on action bar
946 		for (size_t i=0; i<pc->charmed_stats->powers_ai.size(); i++) {
947 			if (pc->charmed_stats->powers_ai[i].id != 0 && powers->powers[pc->charmed_stats->powers_ai[i].id].beacon != true) {
948 				menu->act->hotkeys[count] = pc->charmed_stats->powers_ai[i].id;
949 				menu->act->locked[count] = true;
950 				count++;
951 				if (count == MenuActionBar::SLOT_MAX)
952 					count = 0;
953 				else if (count == MenuActionBar::SLOT_MAIN1)
954 					// we've filled the actionbar, stop adding powers to it
955 					break;
956 			}
957 		}
958 		if (pc->stats.manual_untransform && pc->untransform_power > 0) {
959 			menu->act->hotkeys[count] = pc->untransform_power;
960 			menu->act->locked[count] = true;
961 		}
962 		else if (pc->stats.manual_untransform && pc->untransform_power == 0)
963 			Utils::logError("GameStatePlay: Untransform power not found, you can't untransform manually");
964 
965 		menu->act->updated = true;
966 
967 		// reapply equipment if the transformation allows it
968 		if (pc->stats.transform_with_equipment)
969 			menu->inv->applyEquipment();
970 	}
971 	// revert hero powers
972 	if (pc->revertPowers) {
973 		pc->revertPowers = false;
974 
975 		// restore ActionBar state
976 		for (int i = 0; i < MenuActionBar::SLOT_MAX; i++) {
977 			menu->act->hotkeys[i] = menu->act->hotkeys_temp[i];
978 			menu->act->locked[i] = false;
979 		}
980 
981 		menu->act->updated = true;
982 
983 		// also reapply equipment here, to account items that give bonuses to base stats
984 		menu->inv->applyEquipment();
985 	}
986 
987 	// when the hero (re)spawns, reapply equipment & passive effects
988 	if (pc->respawn) {
989 		pc->stats.alive = true;
990 		pc->stats.corpse = false;
991 		pc->stats.cur_state = StatBlock::ENTITY_STANCE;
992 		menu->inv->applyEquipment();
993 		menu->inv->changed_equipment = true;
994 		checkEquipmentChange();
995 		pc->stats.hp = pc->stats.get(Stats::HP_MAX);
996 		pc->stats.logic();
997 		pc->stats.recalc();
998 		powers->activatePassives(&pc->stats);
999 		pc->respawn = false;
1000 	}
1001 
1002 	// use a normal mouse cursor is menus are open
1003 	if (menu->menus_open) {
1004 		curs->setCursor(CursorManager::CURSOR_NORMAL);
1005 	}
1006 
1007 	// update the action bar as it may have been changed by items
1008 	if (menu->act->updated) {
1009 		menu->act->updated = false;
1010 
1011 		// set all hotkeys to their base powers
1012 		for (unsigned i = 0; i < menu->act->slots_count; i++) {
1013 			menu->act->hotkeys_mod[i] = menu->act->hotkeys[i];
1014 		}
1015 
1016 		updateActionBar(UPDATE_ACTIONBAR_ALL);
1017 	}
1018 
1019 	// reload music if changed in the pause menu
1020 	if (menu->exit->reload_music) {
1021 		mapr->loadMusic();
1022 		menu->exit->reload_music = false;
1023 	}
1024 }
1025 
1026 
1027 /**
1028  * Render all graphics for a single frame
1029  */
render()1030 void GameStatePlay::render() {
1031 	if (mapr->is_spawn_map)
1032 		return;
1033 
1034 	// Create a list of Renderables from all objects not already on the map.
1035 	// split the list into the beings alive (may move) and dead beings (must not move)
1036 	std::vector<Renderable> rens;
1037 	std::vector<Renderable> rens_dead;
1038 
1039 	pc->addRenders(rens);
1040 
1041 	entitym->addRenders(rens, rens_dead);
1042 
1043 	npcs->addRenders(rens); // npcs cannot be dead
1044 
1045 	loot->addRenders(rens, rens_dead);
1046 
1047 	hazards->addRenders(rens, rens_dead);
1048 
1049 
1050 	// render the static map layers plus the renderables
1051 	mapr->render(rens, rens_dead);
1052 
1053 	// mouseover tooltips
1054 	loot->renderTooltips(mapr->cam.pos);
1055 
1056 	if (mapr->map_change) {
1057 		menu->mini->prerender(&mapr->collider, mapr->w, mapr->h);
1058 		mapr->map_change = false;
1059 	}
1060 	menu->mini->setMapTitle(mapr->title);
1061 	menu->mini->render(pc->stats.pos);
1062 	menu->render();
1063 
1064 	// render combat text last - this should make it obvious you're being
1065 	// attacked, even if you have menus open
1066 	if (!isPaused())
1067 		comb->render();
1068 }
1069 
isPaused()1070 bool GameStatePlay::isPaused() {
1071 	return menu->pause;
1072 }
1073 
resetNPC()1074 void GameStatePlay::resetNPC() {
1075 	npc_id = -1;
1076 	menu->talker->npc_from_map = true;
1077 	menu->vendor->setNPC(NULL);
1078 	menu->talker->setNPC(NULL);
1079 }
1080 
checkPrimaryStat(const std::string & first,const std::string & second)1081 bool GameStatePlay::checkPrimaryStat(const std::string& first, const std::string& second) {
1082 	int high = 0;
1083 	size_t high_index = eset->primary_stats.list.size();
1084 	size_t low_index = eset->primary_stats.list.size();
1085 
1086 	for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
1087 		int stat = pc->stats.get_primary(i);
1088 		if (stat > high) {
1089 			if (high_index != eset->primary_stats.list.size()) {
1090 				low_index = high_index;
1091 			}
1092 			high = stat;
1093 			high_index = i;
1094 		}
1095 		else if (stat == high && low_index == eset->primary_stats.list.size()) {
1096 			low_index = i;
1097 		}
1098 		else if (low_index == eset->primary_stats.list.size() || (low_index < eset->primary_stats.list.size() && stat > pc->stats.get_primary(low_index))) {
1099 			low_index = i;
1100 		}
1101 	}
1102 
1103 	// if the first primary stat doesn't match, we don't care about the second one
1104 	if (high_index != eset->primary_stats.list.size() && first != eset->primary_stats.list[high_index].id)
1105 		return false;
1106 
1107 	if (!second.empty()) {
1108 		if (low_index != eset->primary_stats.list.size() && second != eset->primary_stats.list[low_index].id)
1109 			return false;
1110 	}
1111 	else if (pc->stats.get_primary(high_index) == pc->stats.get_primary(low_index)) {
1112 		// titles that require a single stat are ignored if two stats are equal
1113 		return false;
1114 	}
1115 
1116 	return true;
1117 }
1118 
~GameStatePlay()1119 GameStatePlay::~GameStatePlay() {
1120 	curs->setLowHP(false);
1121 
1122 	delete quests;
1123 	delete npcs;
1124 	delete hazards;
1125 	delete entitym;
1126 	delete pc;
1127 	delete mapr;
1128 	delete menu;
1129 	delete loot;
1130 	delete camp;
1131 	delete items;
1132 	delete powers;
1133 
1134 	delete enemyg;
1135 
1136 	// NULL-ify shared game resources
1137 	pc = NULL;
1138 	menu = NULL;
1139 	camp = NULL;
1140 	enemyg = NULL;
1141 	entitym = NULL;
1142 	items = NULL;
1143 	loot = NULL;
1144 	mapr = NULL;
1145 	menu_act = NULL;
1146 	menu_powers = NULL;
1147 	powers = NULL;
1148 }
1149 
1150