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