/*
Copyright © 2011-2012 Clint Bellanger
Copyright © 2012-2013 Henrik Andersson
Copyright © 2012 Stefan Beller
Copyright © 2012-2015 Justin Jacobs

This file is part of FLARE.

FLARE is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.

FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
FLARE.  If not, see http://www.gnu.org/licenses/
*/

/**
 * class NPC
 */

#include "CampaignManager.h"
#include "EntityBehavior.h"
#include "EntityManager.h"
#include "EventManager.h"
#include "FileParser.h"
#include "ItemManager.h"
#include "LootManager.h"
#include "MapRenderer.h"
#include "MessageEngine.h"
#include "ModManager.h"
#include "NPC.h"
#include "RenderDevice.h"
#include "SharedGameResources.h"
#include "SharedResources.h"
#include "SoundManager.h"
#include "UtilsMath.h"
#include "UtilsParsing.h"
#include <vector>

NPC::NPC(const Entity& e)
	: Entity(e)
	, gfx("")
	, vox_intro()
	, vox_quests()
	, name("")
	, direction(0)
	, show_on_minimap(true)
	, npc_portrait(NULL)
	, hero_portrait(NULL)
	, talker(false)
	, vendor(false)
	, reset_buyback(true)
	, stock()
	, vendor_ratio_buy(0)
	, vendor_ratio_sell(0)
	, vendor_ratio_sell_old(0)
	, dialog()
{
	stock.init(VENDOR_MAX_STOCK);

	// have NPCs face south by default
	stats.direction = 7;
}

/**
 * NPCs are stored in simple config files
 *
 * @param npc_id Config file for npc
 */
bool NPC::load(const std::string& npc_id) {

	FileParser infile;
	ItemStack stack;

	portrait_filenames.resize(1);

	// @CLASS NPC|Description of NPCs in npcs/
	if (infile.open(npc_id, FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
		bool clear_random_table = true;

		while (infile.next()) {
			if (infile.section == "stats") {
				// handled by StatBlock::load()
				continue;
			}
			else if (infile.section == "dialog") {
				if (infile.new_section) {
					dialog.push_back(std::vector<EventComponent>());
				}
				EventComponent e;
				e.type = EventComponent::NONE;
				if (infile.key == "id") {
					// @ATTR dialog.id|string|A unique identifer used to reference this dialog.
					e.type = EventComponent::NPC_DIALOG_ID;
					e.s = infile.val;
				}
				else if (infile.key == "him" || infile.key == "her") {
					// @ATTR dialog.him|repeatable(string)|A line of dialog from the NPC.
					// @ATTR dialog.her|repeatable(string)|A line of dialog from the NPC.
					e.type = EventComponent::NPC_DIALOG_THEM;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "you") {
					// @ATTR dialog.you|repeatable(string)|A line of dialog from the player.
					e.type = EventComponent::NPC_DIALOG_YOU;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "voice") {
					// @ATTR dialog.voice|repeatable(string)|Filename of a voice sound file to play.
					e.type = EventComponent::NPC_VOICE;
					e.data[0].Int = loadSound(infile.val, VOX_QUEST);
				}
				else if (infile.key == "topic") {
					// @ATTR dialog.topic|string|The name of this dialog topic. Displayed when picking a dialog tree.
					e.type = EventComponent::NPC_DIALOG_TOPIC;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "group") {
					// @ATTR dialog.group|string|Adds this dialog node to the specified group and skips adding it to the dialog tree. For each group, one dialog node is selected at random and added to the dialog tree.
					e.type = EventComponent::NPC_DIALOG_GROUP;
					e.s = infile.val;
				}
				else if (infile.key == "allow_movement") {
					// @ATTR dialog.allow_movement|bool|Restrict the player's mvoement during dialog.
					e.type = EventComponent::NPC_ALLOW_MOVEMENT;
					e.s = infile.val;
				}
				else if (infile.key == "portrait_him" || infile.key == "portrait_her") {
					// @ATTR dialog.portrait_him|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
					// @ATTR dialog.portrait_her|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
					e.type = EventComponent::NPC_PORTRAIT_THEM;
					e.s = infile.val;
					portrait_filenames.push_back(e.s);
				}
				else if (infile.key == "portrait_you") {
					// @ATTR dialog.portrait_you|repeatable(filename)|Filename of a portrait to display for the player during this dialog.
					e.type = EventComponent::NPC_PORTRAIT_YOU;
					e.s = infile.val;
					portrait_filenames.push_back(e.s);
				}
				else if (infile.key == "take_a_party") {
					// @ATTR dialog.take_a_party|bool|Start/stop taking a party with player.
					e.type = EventComponent::NPC_TAKE_A_PARTY;
					e.data[0].Bool = Parse::toBool(infile.val);
				}
				else if (infile.key == "response") {
					// @ATTR dialog.response|repeatable(string)|A dialog ID to present as a selectable response. This key must precede the dialog text line.
					e.type = EventComponent::NPC_DIALOG_RESPONSE;
					e.s = infile.val;
				}
				else if (infile.key == "response_only") {
					// @ATTR dialog.response_only|bool|If true, this dialog topic will only appear when explicitly referenced with the "response" key.
					e.type = EventComponent::NPC_DIALOG_RESPONSE_ONLY;
					e.data[0].Bool = Parse::toBool(infile.val);
				}
				else {
					Event ev;
					eventm->loadEventComponent(infile, &ev, NULL);

					for (size_t i=0; i<ev.components.size(); ++i) {
						if (ev.components[i].type != EventComponent::NONE) {
							dialog.back().push_back(ev.components[i]);
						}
					}
				}

				if (e.type != EventComponent::NONE) {
					dialog.back().push_back(e);
				}
			}
			else if (infile.section.empty() || infile.section == "npc") {
				filename = npc_id;

				if (infile.new_section) {
					// APPENDed file
					clear_random_table = true;
				}

				if (infile.key == "name") {
					// @ATTR npc.name|string|NPC's name.
					name = msg->get(infile.val);
				}
				else if (infile.key == "animations" || infile.key == "gfx") {
					// TODO "gfx" is deprecated
				}
				else if (infile.key == "direction") {
					// @ATTR npc.direction|direction|The direction to use for this NPC's stance animation.
					direction = Parse::toDirection(infile.val);
				}
				else if (infile.key == "show_on_minimap") {
					// @ATTR npc.show_on_minimap|bool|If true, this NPC will be shown on the minimap. The default is true.
					show_on_minimap = Parse::toBool(infile.val);
				}

				// handle talkers
				else if (infile.key == "talker") {
					// @ATTR npc.talker|bool|Allows this NPC to be talked to.
					talker = Parse::toBool(infile.val);
				}
				else if (infile.key == "portrait") {
					// @ATTR npc.portrait|filename|Filename of the default portrait image.
					portrait_filenames[0] = infile.val;
				}

				// handle vendors
				else if (infile.key == "vendor") {
					// @ATTR npc.vendor|bool|Allows this NPC to buy/sell items.
					vendor = Parse::toBool(infile.val);
				}
				else if (infile.key == "vendor_requires_status") {
					// @ATTR npc.vendor_requires_status|list(string)|The player must have these statuses in order to use this NPC as a vendor.
					while (infile.val != "") {
						vendor_requires_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
					}
				}
				else if (infile.key == "vendor_requires_not_status") {
					// @ATTR npc.vendor_requires_not_status|list(string)|The player must not have these statuses in order to use this NPC as a vendor.
					while (infile.val != "") {
						vendor_requires_not_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
					}
				}
				else if (infile.key == "constant_stock") {
					// @ATTR npc.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.
					while (infile.val != "") {
						stack = Parse::toItemQuantityPair(Parse::popFirstString(infile.val));
						stack.item = items->verifyID(stack.item, &infile, !ItemManager::VERIFY_ALLOW_ZERO, !ItemManager::VERIFY_ALLOCATE);

						std::vector<ItemStack> ex_stacks;
						items->getExtendedStacks(stack.item, stack.quantity, ex_stacks);
						for (size_t i = 0; i < ex_stacks.size(); ++i) {
							stock.add(ex_stacks[i], ItemStorage::NO_SLOT);
						}
					}
				}
				else if (infile.key == "status_stock") {
					// @ATTR npc.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.
					if (camp->checkStatus(camp->registerStatus(Parse::popFirstString(infile.val)))) {
						while (infile.val != "") {
							stack = Parse::toItemQuantityPair(Parse::popFirstString(infile.val));
							stack.item = items->verifyID(stack.item, &infile, !ItemManager::VERIFY_ALLOW_ZERO, !ItemManager::VERIFY_ALLOCATE);

							std::vector<ItemStack> ex_stacks;
							items->getExtendedStacks(stack.item, stack.quantity, ex_stacks);
							for (size_t i = 0; i < ex_stacks.size(); ++i) {
								stock.add(ex_stacks[i], ItemStorage::NO_SLOT);
							}
						}
					}
				}
				else if (infile.key == "random_stock") {
					// @ATTR npc.random_stock|list(loot)|Use a loot table to add random items to the stock; either a filename or an inline definition.
					if (clear_random_table) {
						random_table.clear();
						clear_random_table = false;
					}

					random_table.push_back(EventComponent());
					loot->parseLoot(infile.val, &random_table.back(), &random_table);
				}
				else if (infile.key == "random_stock_count") {
					// @ATTR npc.random_stock_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of random items this npc can have.
					random_table_count.x = Parse::popFirstInt(infile.val);
					random_table_count.y = Parse::popFirstInt(infile.val);
					if (random_table_count.x != 0 || random_table_count.y != 0) {
						random_table_count.x = std::max(random_table_count.x, 1);
						random_table_count.y = std::max(random_table_count.y, random_table_count.x);
					}
				}
				else if (infile.key == "vendor_ratio_buy") {
					// @ATTR npc.vendor_ratio_buy|float|NPC-specific version of vendor_ratio_buy from engine/loot.txt. Uses the global setting when set to 0.
					vendor_ratio_buy = Parse::toFloat(infile.val);
				}
				else if (infile.key == "vendor_ratio_sell") {
					// @ATTR npc.vendor_ratio_sell|float|NPC-specific version of vendor_ratio_sell from engine/loot.txt. Uses the global setting when set to 0.
					vendor_ratio_sell = Parse::toFloat(infile.val);
				}
				else if (infile.key == "vendor_ratio_sell_old") {
					// @ATTR npc.vendor_ratio_sell_old|float|NPC-specific version of vendor_ratio_sell_old from engine/loot.txt. Uses the global setting when set to 0.
					vendor_ratio_sell_old = Parse::toFloat(infile.val);
				}

				// handle vocals
				else if (infile.key == "vox_intro") {
					// @ATTR npc.vox_intro|repeatable(filename)|Filename of a sound file to play when initially interacting with the NPC.
					loadSound(infile.val, VOX_INTRO);
				}

				else {
					infile.error("NPC: '%s' is not a valid key.", infile.key.c_str());
				}
			}
		}
		infile.close();
	}
	else {
		return false;
	}

	loadAnimations();
	loadGraphics(); // TODO rename?

	// fill inventory with items from random stock table
	unsigned rand_count = Math::randBetween(random_table_count.x, random_table_count.y);

	std::vector<ItemStack> rand_itemstacks;
	for (unsigned i=0; i<rand_count; ++i) {
		loot->checkLoot(random_table, NULL, &rand_itemstacks);
	}
	std::sort(rand_itemstacks.begin(), rand_itemstacks.end(), ItemManager::compareItemStack);
	for (size_t i=0; i<rand_itemstacks.size(); ++i) {
		stock.add(rand_itemstacks[i], ItemStorage::NO_SLOT);
	}

	// warn if dialog nodes lack a topic
	std::string full_filename = mods->locate(npc_id);
	for (size_t i=0; i<dialog.size(); ++i) {
		std::string topic = getDialogTopic(static_cast<int>(i));
		if (topic.empty()) {
			Utils::logInfo("[%s] NPC: Dialog node %d does not have a topic.", full_filename.c_str(), i);
		}
	}

	return true;
}

void NPC::loadGraphics() {
	portraits.resize(portrait_filenames.size(), NULL);

	for (size_t i = 0; i < portrait_filenames.size(); ++i) {
		if (!portrait_filenames[i].empty()) {
			Image *graphics;
			graphics = render_device->loadImage(portrait_filenames[i], RenderDevice::ERROR_NORMAL);
			if (graphics) {
				portraits[i] = graphics->createSprite();
				graphics->unref();
			}
		}
	}
}

/**
 * filename assumes the file is in soundfx/npcs/
 * vox_type is a const int enum, see NPC.h
 * returns -1 if not loaded or error.
 * returns index in specific vector where to be found.
 */
int NPC::loadSound(const std::string& fname, int vox_type) {

	SoundID a = snd->load(fname, "NPC voice");

	if (!a)
		return -1;

	if (vox_type == VOX_INTRO) {
		vox_intro.push_back(a);
		return static_cast<int>(vox_intro.size()) - 1;
	}

	if (vox_type == VOX_QUEST) {
		vox_quests.push_back(a);
		return static_cast<int>(vox_quests.size()) - 1;
	}
	return -1;
}

void NPC::logic() {
	mapr->collider.unblock(stats.pos.x, stats.pos.y);

	Entity::logic();
	moveMapEvents();

	if (!stats.hero_ally)
		mapr->collider.block(stats.pos.x, stats.pos.y, true);
}

bool NPC::playSoundIntro() {
	if (vox_intro.empty())
		return false;

	size_t roll = static_cast<size_t>(rand()) % vox_intro.size();
	snd->play(vox_intro[roll], "NPC_VOX", stats.pos, !snd->LOOP);
	return true;
}

bool NPC::playSoundQuest(int id) {
	if (id < 0 || id >= static_cast<int>(vox_quests.size()))
		return false;

	snd->play(vox_quests[id], "NPC_VOX", stats.pos, !snd->LOOP);
	return true;
}

/**
 * get list of available dialogs with NPC
 */
void NPC::getDialogNodes(std::vector<int> &result, bool allow_responses) {
	result.clear();
	if (!talker)
		return;

	std::string group;
	DialogGroups groups;

	for (size_t i = dialog.size(); i > 0; i--) {
		bool is_available = true;
		bool is_grouped = false;
		size_t dindex = i-1;

		for (size_t j=0; j < dialog[dindex].size(); j++) {
			const EventComponent &ec = dialog[dindex][j];

			if (ec.type == EventComponent::NPC_DIALOG_GROUP) {
				is_grouped = true;
				group = ec.s;
			}
			else if (ec.type == EventComponent::NPC_DIALOG_RESPONSE_ONLY) {
				if (ec.data[0].Bool && !allow_responses) {
					is_available = false;
					break;
				}
			}
			else {
				if (camp->checkAllRequirements(ec))
					continue;

				is_available = false;
				break;
			}
		}

		if (is_available) {
			if (!is_grouped) {
				result.push_back(static_cast<int>(dindex));
			}
			else {
				DialogGroups::iterator it;
				it = groups.find(group);
				if (it == groups.end()) {
					groups.insert(DialogGroups::value_type(group, Dialogs(1, static_cast<int>(dindex))));
				}
				else
					it->second.push_back(static_cast<int>(dindex));

			}
		}
	}

	/* Iterate over dialoggroups and roll a dialog to add to result */
	DialogGroups::iterator it;
	it = groups.begin();
	if (it == groups.end())
		return;

	while (it != groups.end() && !it->second.empty()) {
		/* roll a dialog for this group and add to result */
		int di = it->second[rand() % it->second.size()];
		result.insert(result.begin(), di);
		++it;
	}
}

void NPC::getDialogResponses(std::vector<int>& result, size_t node_id, size_t event_cursor) {
	if (node_id >= dialog.size())
		return;

	if (event_cursor >= dialog[node_id].size())
		return;

	std::vector<size_t> response_ids;
	for (size_t i = event_cursor; i > 0; i--) {
		if (isDialogType(dialog[node_id][i-1].type))
			break;

		if (dialog[node_id][i-1].type == EventComponent::NPC_DIALOG_RESPONSE)
			response_ids.push_back(i-1);
	}

	if (response_ids.empty())
		return;

	std::vector<int> nodes;
	getDialogNodes(nodes, GET_RESPONSE_NODES);

	for (size_t i = 0; i < response_ids.size(); i++) {
		for (size_t j = 0; j < nodes.size(); j++) {
			std::string id;

			for (size_t k = 0; k < dialog[nodes[j]].size(); k++) {
				if (dialog[nodes[j]][k].type == EventComponent::NPC_DIALOG_ID) {
					id = dialog[nodes[j]][k].s;
					break;
				}
			}

			if (id.empty())
				continue;

			if (id == dialog[node_id][response_ids[i]].s) {
				result.push_back(nodes[j]);
				break;
			}
		}
	}
}

std::string NPC::getDialogTopic(unsigned int dialog_node) {
	if (!talker)
		return "";

	for (unsigned int j=0; j<dialog[dialog_node].size(); j++) {
		if (dialog[dialog_node][j].type == EventComponent::NPC_DIALOG_TOPIC)
			return dialog[dialog_node][j].s;
	}

	return "";
}

/**
 * Check if the hero can move during this dialog branch
 */
bool NPC::checkMovement(unsigned int dialog_node) {
	if (dialog_node < dialog.size()) {
		for (unsigned int i=0; i<dialog[dialog_node].size(); i++) {
			if (dialog[dialog_node][i].type == EventComponent::NPC_ALLOW_MOVEMENT)
				return Parse::toBool(dialog[dialog_node][i].s);
		}
	}
	return true;
}

void NPC::moveMapEvents() {

	std::vector<Event>::iterator it;

	for (it = mapr->events.end(); it != mapr->events.begin(); ) {
		--it;

		if (it->type == filename) {
			if (stats.hp > 0) {
				// Update event position after NPC has moved
				it->location.x = static_cast<int>(stats.pos.x);
				it->location.y = static_cast<int>(stats.pos.y);

				it->hotspot.x = static_cast<int>(stats.pos.x);
				it->hotspot.y = static_cast<int>(stats.pos.y);

				it->center.x = static_cast<float>(it->hotspot.x) + static_cast<float>(it->hotspot.w)/2;
				it->center.y = static_cast<float>(it->hotspot.y) + static_cast<float>(it->hotspot.h)/2;

				for (size_t ci = 0; ci < it->components.size(); ci++) {
					if (it->components[ci].type == EventComponent::NPC_HOTSPOT) {
						it->components[ci].data[0].Int = static_cast<int>(stats.pos.x);
						it->components[ci].data[1].Int = static_cast<int>(stats.pos.y);
					}
				}
			}
			else {
				// NPC is dead! Remove the map event
				it = mapr->events.erase(it);
			}
		}
	}
}

bool NPC::checkVendor() {
	if (!vendor)
		return false;

	for (size_t i = 0; i < vendor_requires_status.size(); ++i) {
		if (!camp->checkStatus(vendor_requires_status[i]))
			return false;
	}

	for (size_t i = 0; i < vendor_requires_not_status.size(); ++i) {
		if (camp->checkStatus(vendor_requires_not_status[i]))
			return false;
	}

	return true;
}

/**
 * Process the current dialog
 *
 * Return false if the dialog has ended
 */
bool NPC::processDialog(unsigned int dialog_node, unsigned int &event_cursor) {
	if (dialog_node >= dialog.size())
		return false;

	npc_portrait = portraits[0];
	hero_portrait = NULL;

	while (event_cursor < dialog[dialog_node].size()) {

		// we've already determined requirements are met, so skip these
		if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_STATUS) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_STATUS) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_LEVEL) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_LEVEL) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_CURRENCY) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_CURRENCY) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_ITEM) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_ITEM) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_CLASS) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::REQUIRES_NOT_CLASS) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_ID) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_RESPONSE) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_RESPONSE_ONLY) {
			// continue to next event component
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_THEM) {
			return true;
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_DIALOG_YOU) {
			return true;
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_VOICE) {
			playSoundQuest(dialog[dialog_node][event_cursor].data[0].Int);
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_PORTRAIT_THEM) {
			npc_portrait = portraits[0];
			for (size_t i = 0; i < portrait_filenames.size(); ++i) {
				if (dialog[dialog_node][event_cursor].s == portrait_filenames[i]) {
					npc_portrait = portraits[i];
					break;
				}
			}
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_PORTRAIT_YOU) {
			hero_portrait = NULL;
			for (size_t i = 0; i < portrait_filenames.size(); ++i) {
				if (dialog[dialog_node][event_cursor].s == portrait_filenames[i]) {
					hero_portrait = portraits[i];
					break;
				}
			}
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NPC_TAKE_A_PARTY) {
			bool new_hero_ally = dialog[dialog_node][event_cursor].data[0].Bool;
			if (stats.hero_ally != new_hero_ally) {
				stats.hero_ally = new_hero_ally;
				if (stats.hero_ally) {
					entitym->entities.push_back(this);
				}
				else {
					for (size_t i = entitym->entities.size(); i > 0; --i) {
						if (entitym->entities[i-1] == this)
							entitym->entities.erase(entitym->entities.begin() + i - 1);
					}
				}
			}
		}
		else if (dialog[dialog_node][event_cursor].type == EventComponent::NONE) {
			// conversation ends
			return false;
		}

		event_cursor++;
	}
	return false;
}

void NPC::processEvent(unsigned int dialog_node, unsigned int cursor) {
	if (dialog_node >= dialog.size())
		return;

	Event ev;

	if (cursor < dialog[dialog_node].size() && isDialogType(dialog[dialog_node][cursor].type)) {
		cursor++;
	}

	while (cursor < dialog[dialog_node].size() && !isDialogType(dialog[dialog_node][cursor].type)) {
		ev.components.push_back(dialog[dialog_node][cursor]);
		cursor++;
	}

	eventm->executeEvent(ev);
}

bool NPC::isDialogType(const int &event_type) {
	return event_type == EventComponent::NPC_DIALOG_THEM || event_type == EventComponent::NPC_DIALOG_YOU;
}

NPC::~NPC() {
	for (size_t i = 0; i < portraits.size(); ++i) {
		delete portraits[i];
	}

	while (!vox_intro.empty()) {
		snd->unload(vox_intro.back());
		vox_intro.pop_back();
	}
	while (!vox_quests.empty()) {
		snd->unload(vox_quests.back());
		vox_quests.pop_back();
	}
}
