1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012-2013 Henrik Andersson
4 Copyright © 2012 Stefan Beller
5 Copyright © 2012-2015 Justin Jacobs
6 
7 This file is part of FLARE.
8 
9 FLARE is free software: you can redistribute it and/or modify it under the terms
10 of the GNU General Public License as published by the Free Software Foundation,
11 either version 3 of the License, or (at your option) any later version.
12 
13 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License along with
18 FLARE.  If not, see http://www.gnu.org/licenses/
19 */
20 
21 /**
22  * class NPC
23  */
24 
25 #include "Animation.h"
26 #include "AnimationManager.h"
27 #include "AnimationSet.h"
28 #include "CampaignManager.h"
29 #include "EntityBehavior.h"
30 #include "EventManager.h"
31 #include "FileParser.h"
32 #include "ItemManager.h"
33 #include "LootManager.h"
34 #include "MapRenderer.h"
35 #include "MessageEngine.h"
36 #include "ModManager.h"
37 #include "NPC.h"
38 #include "RenderDevice.h"
39 #include "SharedGameResources.h"
40 #include "SharedResources.h"
41 #include "SoundManager.h"
42 #include "UtilsMath.h"
43 #include "UtilsParsing.h"
44 
NPC(const Entity & e)45 NPC::NPC(const Entity& e)
46 	: Entity(e)
47 	, gfx("")
48 	, vox_intro()
49 	, vox_quests()
50 	, name("")
51 	, direction(0)
52 	, show_on_minimap(true)
53 	, npc_portrait(NULL)
54 	, hero_portrait(NULL)
55 	, talker(false)
56 	, vendor(false)
57 	, reset_buyback(true)
58 	, stock()
59 	, dialog()
60 {
61 	stock.init(VENDOR_MAX_STOCK);
62 }
63 
64 /**
65  * NPCs are stored in simple config files
66  *
67  * @param npc_id Config file for npc
68  */
load(const std::string & npc_id)69 void NPC::load(const std::string& npc_id) {
70 
71 	FileParser infile;
72 	ItemStack stack;
73 
74 	portrait_filenames.resize(1);
75 
76 	// @CLASS NPC|Description of NPCs in npcs/
77 	if (infile.open(npc_id, FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
78 		bool clear_random_table = true;
79 
80 		while (infile.next()) {
81 			if (infile.section == "stats") {
82 				// handled by StatBlock::load()
83 				continue;
84 			}
85 			else if (infile.section == "dialog") {
86 				if (infile.new_section) {
87 					dialog.push_back(std::vector<EventComponent>());
88 				}
89 				EventComponent e;
90 				e.type = EventComponent::NONE;
91 				if (infile.key == "id") {
92 					// @ATTR dialog.id|string|A unique identifer used to reference this dialog.
93 					e.type = EventComponent::NPC_DIALOG_ID;
94 					e.s = infile.val;
95 				}
96 				else if (infile.key == "him" || infile.key == "her") {
97 					// @ATTR dialog.him|repeatable(string)|A line of dialog from the NPC.
98 					// @ATTR dialog.her|repeatable(string)|A line of dialog from the NPC.
99 					e.type = EventComponent::NPC_DIALOG_THEM;
100 					e.s = msg->get(infile.val);
101 				}
102 				else if (infile.key == "you") {
103 					// @ATTR dialog.you|repeatable(string)|A line of dialog from the player.
104 					e.type = EventComponent::NPC_DIALOG_YOU;
105 					e.s = msg->get(infile.val);
106 				}
107 				else if (infile.key == "voice") {
108 					// @ATTR dialog.voice|repeatable(string)|Filename of a voice sound file to play.
109 					e.type = EventComponent::NPC_VOICE;
110 					e.x = loadSound(infile.val, VOX_QUEST);
111 				}
112 				else if (infile.key == "topic") {
113 					// @ATTR dialog.topic|string|The name of this dialog topic. Displayed when picking a dialog tree.
114 					e.type = EventComponent::NPC_DIALOG_TOPIC;
115 					e.s = msg->get(infile.val);
116 				}
117 				else if (infile.key == "group") {
118 					// @ATTR dialog.group|string|Dialog group.
119 					e.type = EventComponent::NPC_DIALOG_GROUP;
120 					e.s = infile.val;
121 				}
122 				else if (infile.key == "allow_movement") {
123 					// @ATTR dialog.allow_movement|bool|Restrict the player's mvoement during dialog.
124 					e.type = EventComponent::NPC_ALLOW_MOVEMENT;
125 					e.s = infile.val;
126 				}
127 				else if (infile.key == "portrait_him" || infile.key == "portrait_her") {
128 					// @ATTR dialog.portrait_him|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
129 					// @ATTR dialog.portrait_her|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
130 					e.type = EventComponent::NPC_PORTRAIT_THEM;
131 					e.s = infile.val;
132 					portrait_filenames.push_back(e.s);
133 				}
134 				else if (infile.key == "portrait_you") {
135 					// @ATTR dialog.portrait_you|repeatable(filename)|Filename of a portrait to display for the player during this dialog.
136 					e.type = EventComponent::NPC_PORTRAIT_YOU;
137 					e.s = infile.val;
138 					portrait_filenames.push_back(e.s);
139 				}
140 				else if (infile.key == "take_a_party") {
141 					// @ATTR dialog.take_a_party|bool|Start/stop taking a party with player.
142 					e.type = EventComponent::NPC_TAKE_A_PARTY;
143 					e.x = Parse::toBool(infile.val);
144 				}
145 				else if (infile.key == "response") {
146 					// @ATTR dialog.response|repeatable(string)|A dialog ID to present as a selectable response. This key must precede the dialog text line.
147 					e.type = EventComponent::NPC_DIALOG_RESPONSE;
148 					e.s = infile.val;
149 				}
150 				else if (infile.key == "response_only") {
151 					// @ATTR dialog.response_only|bool|If true, this dialog topic will only appear when explicitly referenced with the "response" key.
152 					e.type = EventComponent::NPC_DIALOG_RESPONSE_ONLY;
153 					e.x = Parse::toBool(infile.val);
154 				}
155 				else {
156 					Event ev;
157 					EventManager::loadEventComponent(infile, &ev, NULL);
158 
159 					for (size_t i=0; i<ev.components.size(); ++i) {
160 						if (ev.components[i].type != EventComponent::NONE) {
161 							dialog.back().push_back(ev.components[i]);
162 						}
163 					}
164 				}
165 
166 				if (e.type != EventComponent::NONE) {
167 					dialog.back().push_back(e);
168 				}
169 			}
170 			else if (infile.section.empty() || infile.section == "npc") {
171 				filename = npc_id;
172 
173 				if (infile.new_section) {
174 					// APPENDed file
175 					clear_random_table = true;
176 				}
177 
178 				if (infile.key == "name") {
179 					// @ATTR name|string|NPC's name.
180 					name = msg->get(infile.val);
181 				}
182 				else if (infile.key == "animations" || infile.key == "gfx") {
183 					// TODO "gfx" is deprecated
184 				}
185 				else if (infile.key == "direction") {
186 					// @ATTR direction|direction|The direction to use for this NPC's stance animation.
187 					direction = Parse::toDirection(infile.val);
188 				}
189 				else if (infile.key == "show_on_minimap") {
190 					// @ATTR show_on_minimap|bool|If true, this NPC will be shown on the minimap. The default is true.
191 					show_on_minimap = Parse::toBool(infile.val);
192 				}
193 
194 				// handle talkers
195 				else if (infile.key == "talker") {
196 					// @ATTR talker|bool|Allows this NPC to be talked to.
197 					talker = Parse::toBool(infile.val);
198 				}
199 				else if (infile.key == "portrait") {
200 					// @ATTR portrait|filename|Filename of the default portrait image.
201 					portrait_filenames[0] = infile.val;
202 				}
203 
204 				// handle vendors
205 				else if (infile.key == "vendor") {
206 					// @ATTR vendor|bool|Allows this NPC to buy/sell items.
207 					vendor = Parse::toBool(infile.val);
208 				}
209 				else if (infile.key == "vendor_requires_status") {
210 					// @ATTR vendor_requires_status|list(string)|The player must have these statuses in order to use this NPC as a vendor.
211 					while (infile.val != "") {
212 						vendor_requires_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
213 					}
214 				}
215 				else if (infile.key == "vendor_requires_not_status") {
216 					// @ATTR vendor_requires_not_status|list(string)|The player must not have these statuses in order to use this NPC as a vendor.
217 					while (infile.val != "") {
218 						vendor_requires_not_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
219 					}
220 				}
221 				else if (infile.key == "constant_stock") {
222 					// @ATTR constant_stock|repeatable(list(item_id))|A list of items this vendor has for sale. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.
223 					while (infile.val != "") {
224 						stack = Parse::toItemQuantityPair(Parse::popFirstString(infile.val));
225 						stock.add(stack, ItemStorage::NO_SLOT);
226 					}
227 				}
228 				else if (infile.key == "status_stock") {
229 					// @ATTR status_stock|repeatable(string, list(item_id)) : Required status, Item(s)|A list of items this vendor will have for sale if the required status is met. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.
230 					if (camp->checkStatus(camp->registerStatus(Parse::popFirstString(infile.val)))) {
231 						while (infile.val != "") {
232 							stack = Parse::toItemQuantityPair(Parse::popFirstString(infile.val));
233 							stock.add(stack, ItemStorage::NO_SLOT);
234 						}
235 					}
236 				}
237 				else if (infile.key == "random_stock") {
238 					// @ATTR random_stock|list(loot)|Use a loot table to add random items to the stock; either a filename or an inline definition.
239 					if (clear_random_table) {
240 						random_table.clear();
241 						clear_random_table = false;
242 					}
243 
244 					random_table.push_back(EventComponent());
245 					loot->parseLoot(infile.val, &random_table.back(), &random_table);
246 				}
247 				else if (infile.key == "random_stock_count") {
248 					// @ATTR random_stock_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of random items this npc can have.
249 					random_table_count.x = Parse::popFirstInt(infile.val);
250 					random_table_count.y = Parse::popFirstInt(infile.val);
251 					if (random_table_count.x != 0 || random_table_count.y != 0) {
252 						random_table_count.x = std::max(random_table_count.x, 1);
253 						random_table_count.y = std::max(random_table_count.y, random_table_count.x);
254 					}
255 				}
256 
257 				// handle vocals
258 				else if (infile.key == "vox_intro") {
259 					// @ATTR vox_intro|repeatable(filename)|Filename of a sound file to play when initially interacting with the NPC.
260 					loadSound(infile.val, VOX_INTRO);
261 				}
262 
263 				else {
264 					infile.error("NPC: '%s' is not a valid key.", infile.key.c_str());
265 				}
266 			}
267 		}
268 		infile.close();
269 	}
270 
271 	loadGraphics();
272 
273 	// fill inventory with items from random stock table
274 	unsigned rand_count = Math::randBetween(random_table_count.x, random_table_count.y);
275 
276 	std::vector<ItemStack> rand_itemstacks;
277 	for (unsigned i=0; i<rand_count; ++i) {
278 		loot->checkLoot(random_table, NULL, &rand_itemstacks);
279 	}
280 	std::sort(rand_itemstacks.begin(), rand_itemstacks.end(), compareItemStack);
281 	for (size_t i=0; i<rand_itemstacks.size(); ++i) {
282 		stock.add(rand_itemstacks[i], ItemStorage::NO_SLOT);
283 	}
284 
285 	// warn if dialog nodes lack a topic
286 	std::string full_filename = mods->locate(npc_id);
287 	for (size_t i=0; i<dialog.size(); ++i) {
288 		std::string topic = getDialogTopic(static_cast<int>(i));
289 		if (topic.empty()) {
290 			Utils::logInfo("[%s] NPC: Dialog node %d does not have a topic.", full_filename.c_str(), i);
291 		}
292 	}
293 }
294 
loadGraphics()295 void NPC::loadGraphics() {
296 
297 	if (stats.animations != "") {
298 		anim->increaseCount(stats.animations);
299 		animationSet = anim->getAnimationSet(stats.animations);
300 		activeAnimation = animationSet->getAnimation("");
301 	}
302 
303 	portraits.resize(portrait_filenames.size(), NULL);
304 
305 	for (size_t i = 0; i < portrait_filenames.size(); ++i) {
306 		if (!portrait_filenames[i].empty()) {
307 			Image *graphics;
308 			graphics = render_device->loadImage(portrait_filenames[i], RenderDevice::ERROR_NORMAL);
309 			if (graphics) {
310 				portraits[i] = graphics->createSprite();
311 				graphics->unref();
312 			}
313 		}
314 	}
315 }
316 
317 /**
318  * filename assumes the file is in soundfx/npcs/
319  * vox_type is a const int enum, see NPC.h
320  * returns -1 if not loaded or error.
321  * returns index in specific vector where to be found.
322  */
loadSound(const std::string & fname,int vox_type)323 int NPC::loadSound(const std::string& fname, int vox_type) {
324 
325 	SoundID a = snd->load(fname, "NPC voice");
326 
327 	if (!a)
328 		return -1;
329 
330 	if (vox_type == VOX_INTRO) {
331 		vox_intro.push_back(a);
332 		return static_cast<int>(vox_intro.size()) - 1;
333 	}
334 
335 	if (vox_type == VOX_QUEST) {
336 		vox_quests.push_back(a);
337 		return static_cast<int>(vox_quests.size()) - 1;
338 	}
339 	return -1;
340 }
341 
logic()342 void NPC::logic() {
343 	mapr->collider.unblock(stats.pos.x, stats.pos.y);
344 
345 	Entity::logic();
346 	moveMapEvents();
347 
348 	if (!stats.hero_ally)
349 		mapr->collider.block(stats.pos.x, stats.pos.y, true);
350 }
351 
playSoundIntro()352 bool NPC::playSoundIntro() {
353 	if (vox_intro.empty())
354 		return false;
355 
356 	size_t roll = static_cast<size_t>(rand()) % vox_intro.size();
357 	snd->play(vox_intro[roll], "NPC_VOX", snd->NO_POS, !snd->LOOP);
358 	return true;
359 }
360 
playSoundQuest(int id)361 bool NPC::playSoundQuest(int id) {
362 	if (id < 0 || id >= static_cast<int>(vox_quests.size()))
363 		return false;
364 
365 	snd->play(vox_quests[id], "NPC_VOX", snd->NO_POS, !snd->LOOP);
366 	return true;
367 }
368 
369 /**
370  * get list of available dialogs with NPC
371  */
getDialogNodes(std::vector<int> & result,bool allow_responses)372 void NPC::getDialogNodes(std::vector<int> &result, bool allow_responses) {
373 	result.clear();
374 	if (!talker)
375 		return;
376 
377 	std::string group;
378 	typedef std::vector<int> Dialogs;
379 	typedef std::map<std::string, Dialogs > DialogGroups;
380 	DialogGroups groups;
381 
382 	for (size_t i=dialog.size(); i>0; i--) {
383 		bool is_available = true;
384 		bool is_grouped = false;
385 		for (size_t j=0; j<dialog[i-1].size(); j++) {
386 			if (dialog[i-1][j].type == EventComponent::NPC_DIALOG_GROUP) {
387 				is_grouped = true;
388 				group = dialog[i-1][j].s;
389 			}
390 			else if (dialog[i-1][j].type == EventComponent::NPC_DIALOG_RESPONSE_ONLY) {
391 				if (dialog[i-1][j].x && !allow_responses) {
392 					is_available = false;
393 					break;
394 				}
395 			}
396 			else {
397 				if (camp->checkAllRequirements(dialog[i-1][j]))
398 					continue;
399 
400 				is_available = false;
401 				break;
402 			}
403 		}
404 
405 		if (is_available) {
406 			if (!is_grouped) {
407 				result.push_back(static_cast<int>(i-1));
408 			}
409 			else {
410 				DialogGroups::iterator it;
411 				it = groups.find(group);
412 				if (it == groups.end()) {
413 					groups.insert(DialogGroups::value_type(group, Dialogs()));
414 				}
415 				else
416 					it->second.push_back(static_cast<int>(i-1));
417 
418 			}
419 		}
420 	}
421 
422 	/* Iterate over dialoggroups and roll a dialog to add to result */
423 	DialogGroups::iterator it;
424 	it = groups.begin();
425 	if (it == groups.end())
426 		return;
427 
428 	while (it != groups.end()) {
429 		/* roll a dialog for this group and add to result */
430 		int di = it->second[rand() % it->second.size()];
431 		result.push_back(di);
432 		++it;
433 	}
434 }
435 
getDialogResponses(std::vector<int> & result,size_t node_id,size_t event_cursor)436 void NPC::getDialogResponses(std::vector<int>& result, size_t node_id, size_t event_cursor) {
437 	if (node_id >= dialog.size())
438 		return;
439 
440 	if (event_cursor >= dialog[node_id].size())
441 		return;
442 
443 	std::vector<size_t> response_ids;
444 	for (size_t i = event_cursor; i > 0; i--) {
445 		if (isDialogType(dialog[node_id][i-1].type))
446 			break;
447 
448 		if (dialog[node_id][i-1].type == EventComponent::NPC_DIALOG_RESPONSE)
449 			response_ids.push_back(i-1);
450 	}
451 
452 	if (response_ids.empty())
453 		return;
454 
455 	std::vector<int> nodes;
456 	getDialogNodes(nodes, GET_RESPONSE_NODES);
457 
458 	for (size_t i = 0; i < response_ids.size(); i++) {
459 		for (size_t j = 0; j < nodes.size(); j++) {
460 			std::string id;
461 
462 			for (size_t k = 0; k < dialog[nodes[j]].size(); k++) {
463 				if (dialog[nodes[j]][k].type == EventComponent::NPC_DIALOG_ID) {
464 					id = dialog[nodes[j]][k].s;
465 					break;
466 				}
467 			}
468 
469 			if (id.empty())
470 				continue;
471 
472 			if (id == dialog[node_id][response_ids[i]].s) {
473 				result.push_back(nodes[j]);
474 				break;
475 			}
476 		}
477 	}
478 }
479 
getDialogTopic(unsigned int dialog_node)480 std::string NPC::getDialogTopic(unsigned int dialog_node) {
481 	if (!talker)
482 		return "";
483 
484 	for (unsigned int j=0; j<dialog[dialog_node].size(); j++) {
485 		if (dialog[dialog_node][j].type == EventComponent::NPC_DIALOG_TOPIC)
486 			return dialog[dialog_node][j].s;
487 	}
488 
489 	return "";
490 }
491 
492 /**
493  * Check if the hero can move during this dialog branch
494  */
checkMovement(unsigned int dialog_node)495 bool NPC::checkMovement(unsigned int dialog_node) {
496 	if (dialog_node < dialog.size()) {
497 		for (unsigned int i=0; i<dialog[dialog_node].size(); i++) {
498 			if (dialog[dialog_node][i].type == EventComponent::NPC_ALLOW_MOVEMENT)
499 				return Parse::toBool(dialog[dialog_node][i].s);
500 		}
501 	}
502 	return true;
503 }
504 
moveMapEvents()505 void NPC::moveMapEvents() {
506 
507 	// Update event position after NPC has moved
508 	for (size_t i = 0; i < mapr->events.size(); i++)
509 	{
510 		if (mapr->events[i].type == filename)
511 		{
512 			mapr->events[i].location.x = static_cast<int>(stats.pos.x);
513 			mapr->events[i].location.y = static_cast<int>(stats.pos.y);
514 
515 			mapr->events[i].hotspot.x = static_cast<int>(stats.pos.x);
516 			mapr->events[i].hotspot.y = static_cast<int>(stats.pos.y);
517 
518 			mapr->events[i].center.x =
519 				static_cast<float>(stats.pos.x) + static_cast<float>(mapr->events[i].hotspot.w)/2;
520 			mapr->events[i].center.y =
521 				static_cast<float>(stats.pos.y) + static_cast<float>(mapr->events[i].hotspot.h)/2;
522 
523 			for (size_t ci = 0; ci < mapr->events[i].components.size(); ci++)
524 			{
525 				if (mapr->events[i].components[ci].type == EventComponent::NPC_HOTSPOT)
526 				{
527 					mapr->events[i].components[ci].x = static_cast<int>(stats.pos.x);
528 					mapr->events[i].components[ci].y = static_cast<int>(stats.pos.y);
529 				}
530 			}
531 		}
532 	}
533 }
534 
checkVendor()535 bool NPC::checkVendor() {
536 	if (!vendor)
537 		return false;
538 
539 	for (size_t i = 0; i < vendor_requires_status.size(); ++i) {
540 		if (!camp->checkStatus(vendor_requires_status[i]))
541 			return false;
542 	}
543 
544 	for (size_t i = 0; i < vendor_requires_not_status.size(); ++i) {
545 		if (camp->checkStatus(vendor_requires_not_status[i]))
546 			return false;
547 	}
548 
549 	return true;
550 }
551 
552 /**
553  * Process the current dialog
554  *
555  * Return false if the dialog has ended
556  */
processDialog(unsigned int dialog_node,unsigned int & event_cursor)557 bool NPC::processDialog(unsigned int dialog_node, unsigned int &event_cursor) {
558 	if (dialog_node >= dialog.size())
559 		return false;
560 
561 	npc_portrait = portraits[0];
562 	hero_portrait = NULL;
563 
564 	while (event_cursor < dialog[dialog_node].size()) {
565 
566 		// we've already determined requirements are met, so skip these
567 		if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_STATUS) {
568 			// continue to next event component
569 		}
570 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_STATUS) {
571 			// continue to next event component
572 		}
573 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_LEVEL) {
574 			// continue to next event component
575 		}
576 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_LEVEL) {
577 			// continue to next event component
578 		}
579 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_CURRENCY) {
580 			// continue to next event component
581 		}
582 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_CURRENCY) {
583 			// continue to next event component
584 		}
585 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_ITEM) {
586 			// continue to next event component
587 		}
588 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_ITEM) {
589 			// continue to next event component
590 		}
591 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_CLASS) {
592 			// continue to next event component
593 		}
594 		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_CLASS) {
595 			// continue to next event component
596 		}
597 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_ID) {
598 			// continue to next event component
599 		}
600 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_RESPONSE) {
601 			// continue to next event component
602 		}
603 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_RESPONSE_ONLY) {
604 			// continue to next event component
605 		}
606 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_THEM) {
607 			return true;
608 		}
609 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_YOU) {
610 			return true;
611 		}
612 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_VOICE) {
613 			playSoundQuest(dialog[dialog_node][event_cursor].x);
614 		}
615 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_PORTRAIT_THEM) {
616 			npc_portrait = portraits[0];
617 			for (size_t i = 0; i < portrait_filenames.size(); ++i) {
618 				if (dialog[dialog_node][event_cursor].s == portrait_filenames[i]) {
619 					npc_portrait = portraits[i];
620 					break;
621 				}
622 			}
623 		}
624 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_PORTRAIT_YOU) {
625 			hero_portrait = NULL;
626 			for (size_t i = 0; i < portrait_filenames.size(); ++i) {
627 				if (dialog[dialog_node][event_cursor].s == portrait_filenames[i]) {
628 					hero_portrait = portraits[i];
629 					break;
630 				}
631 			}
632 		}
633 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_TAKE_A_PARTY) {
634 			stats.hero_ally = dialog[dialog_node][event_cursor].x == 0 ? false : true;
635 		}
636 		else if (dialog[dialog_node][event_cursor].type == EventComponent::NONE) {
637 			// conversation ends
638 			return false;
639 		}
640 
641 		event_cursor++;
642 	}
643 	return false;
644 }
645 
processEvent(unsigned int dialog_node,unsigned int cursor)646 void NPC::processEvent(unsigned int dialog_node, unsigned int cursor) {
647 	if (dialog_node >= dialog.size())
648 		return;
649 
650 	Event ev;
651 
652 	if (cursor < dialog[dialog_node].size() && isDialogType(dialog[dialog_node][cursor].type)) {
653 		cursor++;
654 	}
655 
656 	while (cursor < dialog[dialog_node].size() && !isDialogType(dialog[dialog_node][cursor].type)) {
657 		ev.components.push_back(dialog[dialog_node][cursor]);
658 		cursor++;
659 	}
660 
661 	EventManager::executeEvent(ev);
662 }
663 
isDialogType(const int & event_type)664 bool NPC::isDialogType(const int &event_type) {
665 	return event_type == EventComponent::NPC_DIALOG_THEM || event_type == EventComponent::NPC_DIALOG_YOU;
666 }
667 
~NPC()668 NPC::~NPC() {
669 
670 	for (size_t i = 0; i < portraits.size(); ++i) {
671 		delete portraits[i];
672 	}
673 
674 	if (stats.animations != "") {
675 		anim->decreaseCount(stats.animations);
676 	}
677 
678 	while (!vox_intro.empty()) {
679 		snd->unload(vox_intro.back());
680 		vox_intro.pop_back();
681 	}
682 	while (!vox_quests.empty()) {
683 		snd->unload(vox_quests.back());
684 		vox_quests.pop_back();
685 	}
686 }
687