Downloads containing npcBunniesDialogue.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: NpcBunnies Cranky Mutator N/A Download file

File preview

namespace npcBunniesDialogue
{
string NpcQuestEventName(int eventID)
{
	if(eventID == OBJECT::XMASNORMTURTLE) return "xmasTurtles";
	if(eventID == OBJECT::NORMTURTLE) return "turtles";
	if(eventID == OBJECT::XMASLIZARD) return "xmasLizards";
	if(eventID == OBJECT::LIZARD) return "lizards";
	if(eventID == OBJECT::BAT) return "bats";
	if(eventID == OBJECT::BEE) return "bees";
	if(eventID == OBJECT::BEEBOY) return "bee boys";
	if(eventID == OBJECT::BEES) return "bees";
	if(eventID == OBJECT::BUTTERFLY) return "butterflies";
	if(eventID == OBJECT::CRAB) return "crabs";
	if(eventID == OBJECT::DEMON) return "demons";
	if(eventID == OBJECT::DOGGYDOGG) return "dogs";
	if(eventID == OBJECT::DRAGON) return "dragons";
	if(eventID == OBJECT::DRAGONFLY) return "dragonflies";
	if(eventID == OBJECT::FATCHICK) return "fat chicks";
	if(eventID == OBJECT::FENCER) return "fencers";
	if(eventID == OBJECT::FISH) return "fish";
	if(eventID == OBJECT::HATTER) return "hatters";
	if(eventID == OBJECT::HELMUT) return "helmuts";
	if(eventID == OBJECT::LABRAT) return "lab rats";
	if(eventID == OBJECT::MONKEY) return "monkeys";
	if(eventID == OBJECT::STANDMONKEY) return "stand monkeys";
	if(eventID == OBJECT::TUBETURTLE) return "tube turtles";
	if(eventID == OBJECT::TUFTURT) return "tuf turtles";
	if(eventID == OBJECT::RAPIER) return "rapiers";
	if(eventID == OBJECT::RAVEN) return "ravens";
	if(eventID == OBJECT::SKELETON) return "skeletons";
	if(eventID == OBJECT::SPARK) return "sparks";
	if(eventID == OBJECT::SUCKER) return "suckers";
	if(eventID == OBJECT::FLOATSUCKER) return "float suckers";
	if(eventID == OBJECT::CAT) return "cats";
	if(eventID == OBJECT::PACMANGHOST) return "pacman ghosts";
	if(eventID == OBJECT::MOTH) return "moths";
	if(eventID == OBJECT::WITCH) return "witches";
	return "object " + eventID;
}

string NpcQuestLineText(npcBunniesQuests::NpcQuest@ quest, jjPLAYER@ play, int npcId, int setIndex)
{
	if(quest is null) return "";
	int progress = 0;
	string name = "quest";
	if(quest.questType == npcBunniesQuests::QuestType::Coins) { progress = play.coins; name = "coins"; }
	else if(quest.questType == npcBunniesQuests::QuestType::Gems) { progress = play.gems[quest.gemType]; name = "gems"; }
	else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) { progress = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, npcId, setIndex); name = NpcQuestEventName(quest.objectType); }
	if(progress > quest.amount) progress = quest.amount;
	return name + ": " + progress + "/" + quest.amount;
}

void CollectNpcQuestGroup(jjPLAYER@ play, int wantedGroup, string &out npcName, array<string> &out names, array<int> &out progress, array<int> &out amounts)
{
	npcName = "";
	names.resize(0);
	progress.resize(0);
	amounts.resize(0);
	if(play is null) return;
	string playerName = play.nameUnformatted;
	int group = 0;
	for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
	{
		npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
		array<string> keys;
		array<string> groupNames;
		array<int> groupProgress;
		array<int> groupAmounts;
		for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
		{
			if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
			DialogueSet@ set = npc.dialogueSets[setIndex];
			if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest) continue;
			for(uint q = 0; q < set.quests.length; q++)
			{
				npcBunniesQuests::NpcQuest@ quest = set.quests[q];
				if(quest is null) continue;
				string name = "quest";
				int current = 0;
				if(quest.questType == npcBunniesQuests::QuestType::Coins) { name = "coins"; current = play.coins; }
				else if(quest.questType == npcBunniesQuests::QuestType::Gems) { name = "gems"; current = play.gems[quest.gemType]; }
				else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) { name = NpcQuestEventName(quest.objectType); current = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, int(npcId), int(setIndex)); }
				string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
				int found = keys.find(key);
				if(found >= 0) groupAmounts[found] += quest.amount;
				else
				{
					keys.insertLast(key);
					groupNames.insertLast(name);
					groupProgress.insertLast(current);
					groupAmounts.insertLast(quest.amount);
				}
			}
		}
		if(keys.length > 0)
		{
			if(group == wantedGroup)
			{
				npcName = npc.name;
				names = groupNames;
				progress = groupProgress;
				amounts = groupAmounts;
				npcBunniesCore::npcBunnies[npcId] = npc;
				return;
			}
			group++;
		}
		npcBunniesCore::npcBunnies[npcId] = npc;
	}
}

void CollectNpcQuestGroupDetails(jjPLAYER@ play, int wantedGroup, string &out npcName, array<npcBunniesQuests::NpcQuest@> &out quests, array<int> &out progress)
{
	npcName = "";
	quests.resize(0);
	progress.resize(0);
	if(play is null) return;
	string playerName = play.nameUnformatted;
	int group = 0;
	for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
	{
		npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
		array<npcBunniesQuests::NpcQuest@> groupQuests;
		array<int> groupProgress;
		for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
		{
			if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
			DialogueSet@ set = npc.dialogueSets[setIndex];
			if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest) continue;
			for(uint q = 0; q < set.quests.length; q++)
			{
				npcBunniesQuests::NpcQuest@ quest = set.quests[q];
				if(quest is null) continue;
				int current = 0;
				if(quest.questType == npcBunniesQuests::QuestType::Coins) current = play.coins;
				else if(quest.questType == npcBunniesQuests::QuestType::Gems) current = play.gems[quest.gemType];
				else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) current = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, int(npcId), int(setIndex));
				if(current > quest.amount) current = quest.amount;
				groupQuests.insertLast(quest);
				groupProgress.insertLast(current);
			}
		}
		if(groupQuests.length > 0)
		{
			if(group == wantedGroup)
			{
				npcName = npc.name;
				quests = groupQuests;
				progress = groupProgress;
				npcBunniesCore::npcBunnies[npcId] = npc;
				return;
			}
			group++;
		}
		npcBunniesCore::npcBunnies[npcId] = npc;
	}
}

string NpcQuestSetTrackerText(DialogueSet@ set)
{
	if(set is null) return "";
	if(set.questInProgressLine != "") return set.questInProgressLine;
	if(set.dialogue.length > 0 && set.dialogue[0] !is null) return set.dialogue[0].text;
	return "";
}

void CollectNpcQuestGroupText(jjPLAYER@ play, int wantedGroup, string &out npcName, array<string> &out lines)
{
	npcName = "";
	lines.resize(0);
	if(play is null) return;
	string playerName = play.nameUnformatted;
	int group = 0;
	for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
	{
		npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
		array<string> groupLines;
		for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
		{
			if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
			DialogueSet@ set = npc.dialogueSets[setIndex];
			if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest || set.quests.length == 0) continue;
			string line = NpcQuestSetTrackerText(set);
			if(line != "" && groupLines.find(line) < 0) groupLines.insertLast(line);
		}
		if(groupLines.length > 0)
		{
			if(group == wantedGroup)
			{
				npcName = npc.name;
				lines = groupLines;
				npcBunniesCore::npcBunnies[npcId] = npc;
				return;
			}
			group++;
		}
		else
		{
			bool hasQuest = false;
			for(uint setIndex = 0; setIndex < npc.dialogueSets.length && !hasQuest; setIndex++)
				hasQuest = npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex)) && npc.dialogueSets[setIndex].choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && npc.dialogueSets[setIndex].quests.length > 0;
			if(hasQuest) group++;
		}
		npcBunniesCore::npcBunnies[npcId] = npc;
	}
}

int CountNpcQuestGroups(jjPLAYER@ play)
{
	if(play is null) return 0;
	string playerName = play.nameUnformatted;
	int groups = 0;
	for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
	{
		npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
		bool hasGroup = false;
		for(uint setIndex = 0; setIndex < npc.dialogueSets.length && !hasGroup; setIndex++)
		{
			if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
			DialogueSet@ set = npc.dialogueSets[setIndex];
			if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && set.quests.length > 0) hasGroup = true;
		}
		if(hasGroup) groups++;
		npcBunniesCore::npcBunnies[npcId] = npc;
	}
	return groups;
}

string NpcQuestDialogText(jjPLAYER@ play, int page)
{
	string textNpcName;
	array<string> textLines;
	CollectNpcQuestGroupText(play, page, textNpcName, textLines);
	if(textNpcName != "" && textLines.length > 0)
	{
		string text = textNpcName + ":";
		for(uint i = 0; i < textLines.length; i++)
			text += "\n" + textLines[i];
		return text;
	}
	string npcName;
	array<string> names;
	array<int> progress;
	array<int> amounts;
	CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
	if(npcName == "") return "No active quests.";
	string text = npcName + ":";
	for(uint i = 0; i < names.length; i++)
	{
		int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
		text += "\n" + names[i] + ": " + shownProgress + "/" + amounts[i];
	}
	return text;
}

string NpcQuestBasicDialogText(jjPLAYER@ play, int page)
{
	string npcName;
	array<string> names;
	array<int> progress;
	array<int> amounts;
	CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
	if(npcName == "") return "No active quests.";
	string text = npcName + ":";
	for(uint i = 0; i < names.length; i++)
	{
		int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
		text += "\n" + names[i] + ": " + shownProgress + "/" + amounts[i];
	}
	return text;
}

int CountNpcQuestDialogLines(jjPLAYER@ play, int page)
{
	return DbWrappedSmallLineCount(NpcQuestDialogText(play, page), 160);
}

int CountNpcQuestBasicDialogLines(jjPLAYER@ play, int page)
{
	string npcName;
	array<string> names;
	array<int> progress;
	array<int> amounts;
	CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
	if(npcName == "") return 1;
	return int(names.length) + 1;
}

const int NPC_QUEST_DIALOG_CACHE_TICKS = 15;
int cachedNpcQuestDialogTick = -999999;
int cachedNpcQuestDialogPlayerId = -1;
int cachedNpcQuestDialogPage = -1;
bool cachedNpcQuestDialogShowInfo = false;
int cachedNpcQuestDialogTextW = -1;
int cachedNpcQuestDialogVisibleHeight = -1;
int cachedNpcQuestDialogGroups = 0;
int cachedNpcQuestDialogBasicLines = 1;
int cachedNpcQuestDialogMaxScroll = 0;
string cachedNpcQuestDialogText = "No active quests.";
array<string> cachedNpcQuestDialogWrappedLines;
array<string> cachedNpcQuestDialogBasicDrawLines;
bool cachedNpcQuestLevelQuestDefinitionsKnown = false;
bool cachedNpcQuestLevelHasQuestDefinitions = false;

void InvalidateNpcQuestDialogCache()
{
	cachedNpcQuestDialogTick = -999999;
	cachedNpcQuestLevelQuestDefinitionsKnown = false;
}

bool NpcLevelHasQuestDefinitions()
{
	if(cachedNpcQuestLevelQuestDefinitionsKnown) return cachedNpcQuestLevelHasQuestDefinitions;
	cachedNpcQuestLevelQuestDefinitionsKnown = true;
	cachedNpcQuestLevelHasQuestDefinitions = false;
	for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
	{
		for(uint setIndex = 0; setIndex < npcBunniesCore::npcBunnies[npcId].dialogueSets.length; setIndex++)
		{
			DialogueSet@ set = npcBunniesCore::npcBunnies[npcId].dialogueSets[setIndex];
			if(set !is null && set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && set.quests.length > 0)
			{
				cachedNpcQuestLevelHasQuestDefinitions = true;
				return true;
			}
		}
	}
	return false;
}

void EnsureNpcQuestDialogCache(jjPLAYER@ play, int page, bool showInfo, int textW, int visibleHeight, bool force = false)
{
	int playerId = play is null ? -1 : play.playerID;
	if(textW < 20) textW = 20;
	if(visibleHeight < 14) visibleHeight = 14;
	if(!force
	&& cachedNpcQuestDialogPlayerId == playerId
	&& cachedNpcQuestDialogPage == page
	&& cachedNpcQuestDialogShowInfo == showInfo
	&& cachedNpcQuestDialogTextW == textW
	&& cachedNpcQuestDialogVisibleHeight == visibleHeight
	&& jjGameTicks < cachedNpcQuestDialogTick + NPC_QUEST_DIALOG_CACHE_TICKS)
		return;

	cachedNpcQuestDialogTick = jjGameTicks;
	cachedNpcQuestDialogPlayerId = playerId;
	cachedNpcQuestDialogPage = page;
	cachedNpcQuestDialogShowInfo = showInfo;
	cachedNpcQuestDialogTextW = textW;
	cachedNpcQuestDialogVisibleHeight = visibleHeight;
	cachedNpcQuestDialogGroups = NpcLevelHasQuestDefinitions() ? CountNpcQuestGroups(play) : 0;
	if(cachedNpcQuestDialogGroups <= 0)
	{
		cachedNpcQuestDialogBasicLines = 1;
		cachedNpcQuestDialogText = "No active quests.";
		cachedNpcQuestDialogBasicDrawLines.resize(0);
		cachedNpcQuestDialogBasicDrawLines.insertLast(cachedNpcQuestDialogText);
	}
	else
	{
		string basicText = NpcQuestBasicDialogText(play, page);
		cachedNpcQuestDialogBasicDrawLines = basicText.split("\n");
		if(cachedNpcQuestDialogBasicDrawLines.length == 0) cachedNpcQuestDialogBasicDrawLines.insertLast("");
		cachedNpcQuestDialogBasicLines = int(cachedNpcQuestDialogBasicDrawLines.length);
		cachedNpcQuestDialogText = showInfo ? NpcQuestDialogText(play, page) : basicText;
	}
	if(showInfo)
	{
		cachedNpcQuestDialogWrappedLines = DbWrappedSmallLines(cachedNpcQuestDialogText, textW);
		cachedNpcQuestDialogMaxScroll = int(cachedNpcQuestDialogWrappedLines.length) - DbVisibleSmallLines(visibleHeight);
		if(cachedNpcQuestDialogMaxScroll < 0) cachedNpcQuestDialogMaxScroll = 0;
	}
	else
	{
		cachedNpcQuestDialogWrappedLines.resize(0);
		cachedNpcQuestDialogMaxScroll = 0;
	}
}

int DrawCachedNpcQuestDialogWrappedText(jjCANVAS@ canvas, int x, int y, int maxY, int scrollLine, int lineHeight = 14)
{
	for(uint i = 0; i < cachedNpcQuestDialogWrappedLines.length; i++)
	{
		if(int(i) < scrollLine) continue;
		if(y + lineHeight > maxY) break;
		canvas.drawString(x, y, cachedNpcQuestDialogWrappedLines[i], STRING::SMALL, STRING::NORMAL);
		y += lineHeight;
	}
	return y;
}

int DrawCachedNpcQuestBasicLines(jjCANVAS@ canvas, int x, int y)
{
	for(uint i = 0; i < cachedNpcQuestDialogBasicDrawLines.length; i++)
		canvas.drawString(x + (i == 0 ? 0 : 12), y + int(i) * 18, cachedNpcQuestDialogBasicDrawLines[i], STRING::SMALL, STRING::NORMAL);
	return int(cachedNpcQuestDialogBasicDrawLines.length);
}

int DrawNpcQuestLines(jjCANVAS@ canvas, jjPLAYER@ play, int x, int y, int page)
{
	string npcName;
	array<string> names;
	array<int> progress;
	array<int> amounts;
	CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
	if(npcName == "")
	{
		canvas.drawString(x, y, "No active quests.", STRING::SMALL, STRING::NORMAL);
		return 1;
	}
	canvas.drawString(x, y, npcName + ":", STRING::SMALL, STRING::NORMAL);
	int lines = 1;
	for(uint i = 0; i < names.length; i++)
	{
		int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
		canvas.drawString(x + 12, y + lines * 18, names[i] + ": " + shownProgress + "/" + amounts[i], STRING::SMALL, STRING::NORMAL);
		lines++;
	}
	return lines;
}

void SlowNpcUiAnimation(npcBunniesAnimations::NpcUiAnimation &inout icon, int ticksPerFrame = 16)
{
	if(icon.frameCount <= 1) return;
	icon.firstFrame = jjGameTicks / ticksPerFrame;
	icon.ticksPerFrame = ticksPerFrame;
}

void GetNpcQuestTrackerUiAnimation(npcBunniesQuests::NpcQuest@ quest, npcBunniesAnimations::NpcUiAnimation &inout icon)
{
	if(quest is null) { npcBunniesAnimations::SetNpcUiAnimation(icon, 0, 0, 0, 1, 6); icon.hidden = true; return; }
	if(quest.iconHidden) { npcBunniesAnimations::CopyNpcUiAnimation(quest.icon, icon); icon.hidden = true; return; }
	npcBunniesAnimations::GetQuestUiAnimation(quest, icon);
	SlowNpcUiAnimation(icon, 16);
}

void GetNpcRewardTrackerUiAnimation(npcBunniesQuests::NpcReward@ reward, npcBunniesAnimations::NpcUiAnimation &inout icon)
{
	if(reward is null) { npcBunniesAnimations::SetNpcUiAnimation(icon, 0, 0, 0, 1, 6); icon.hidden = true; return; }
	if(reward.iconHidden) { npcBunniesAnimations::CopyNpcUiAnimation(reward.icon, icon); icon.hidden = true; return; }
	npcBunniesAnimations::GetRewardUiAnimation(reward, icon);
	SlowNpcUiAnimation(icon, 16);
}

void DrawNpcQuestObjectives(jjCANVAS@ canvas, jjPLAYER@ play, int x, int y, int w, int h, int page)
{
	string npcName;
	array<npcBunniesQuests::NpcQuest@> quests;
	array<int> progress;
	CollectNpcQuestGroupDetails(play, page, npcName, quests, progress);
	if(quests.length == 0) return;
	array<string> keys;
	array<npcBunniesQuests::NpcQuest@> iconQuests;
	array<int> amounts;
	for(uint i = 0; i < quests.length; i++)
	{
		npcBunniesQuests::NpcQuest@ quest = quests[i];
		if(quest is null || quest.iconHidden) continue;
		string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
		int found = keys.find(key);
		if(found >= 0) amounts[found] += quest.amount;
		else
		{
			keys.insertLast(key);
			iconQuests.insertLast(quest);
			amounts.insertLast(quest.amount);
		}
	}
	int rowX = x;
	int rowY = y;
	for(uint i = 0; i < iconQuests.length; i++)
	{
		npcBunniesQuests::NpcQuest@ quest = iconQuests[i];
		int amount = amounts[i];
		int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
		if(rowX > x && rowX + itemW > x + w)
		{
			rowX = x;
			rowY += 24;
		}
		if(rowY + 20 > y + h) break;
		npcBunniesAnimations::NpcUiAnimation icon;
		GetNpcQuestTrackerUiAnimation(quest, icon);
		npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, rowY, amount, icon);
		rowX += itemW + 16;
	}
}

void DrawNpcQuestSetObjectives(jjCANVAS@ canvas, DialogueSet@ set, int x, int y, int w, int h, int ticksPerFrame = 20)
{
	if(set is null || (set.quests.length == 0 && set.rewards.length == 0)) return;
	array<string> keys;
	array<npcBunniesQuests::NpcQuest@> iconQuests;
	array<int> amounts;
	for(uint i = 0; i < set.quests.length; i++)
	{
		npcBunniesQuests::NpcQuest@ quest = set.quests[i];
		if(quest is null || quest.iconHidden) continue;
		string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
		int found = keys.find(key);
		if(found >= 0) amounts[found] += quest.amount;
		else
		{
			keys.insertLast(key);
			iconQuests.insertLast(quest);
			amounts.insertLast(quest.amount);
		}
	}
	array<string> rewardKeys;
	array<npcBunniesQuests::NpcReward@> iconRewards;
	array<int> rewardAmounts;
	for(uint i = 0; i < set.rewards.length; i++)
	{
		npcBunniesQuests::NpcReward@ reward = set.rewards[i];
		if(reward is null || reward.iconHidden) continue;
		string key = reward.rewardType + "|" + reward.gemType + "|" + reward.weaponType + "|" + reward.guidePathIndex + "|" + reward.triggerId + "|" + int(reward.x) + "|" + int(reward.y);
		int found = rewardKeys.find(key);
		int amount = npcBunniesAnimations::GetRewardDisplayAmount(reward);
		if(found >= 0) rewardAmounts[found] += amount;
		else
		{
			rewardKeys.insertLast(key);
			iconRewards.insertLast(reward);
			rewardAmounts.insertLast(amount);
		}
	}
	int questRowY = y;
	int rewardRowY = y + 24;
	int labelW = 18;
	canvas.drawString(x, questRowY, "Q:", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(x, rewardRowY, "R:", STRING::SMALL, STRING::NORMAL);
	int rowX = x + labelW;
	for(uint i = 0; i < iconQuests.length; i++)
	{
		npcBunniesQuests::NpcQuest@ quest = iconQuests[i];
		int amount = amounts[i];
		int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
		if(rowX > x + labelW && rowX + itemW > x + w) break;
		if(questRowY + 20 > y + h) break;
		npcBunniesAnimations::NpcUiAnimation icon;
		GetNpcQuestTrackerUiAnimation(quest, icon);
		SlowNpcUiAnimation(icon, ticksPerFrame);
		npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, questRowY, amount, icon);
		rowX += itemW + 16;
	}
	rowX = x + labelW;
	for(uint i = 0; i < iconRewards.length; i++)
	{
		npcBunniesQuests::NpcReward@ reward = iconRewards[i];
		int amount = rewardAmounts[i];
		int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
		if(rowX > x + labelW && rowX + itemW > x + w) break;
		if(rewardRowY + 20 > y + h) break;
		npcBunniesAnimations::NpcUiAnimation icon;
		GetNpcRewardTrackerUiAnimation(reward, icon);
		SlowNpcUiAnimation(icon, ticksPerFrame);
		npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, rewardRowY, amount, icon);
		rowX += itemW + 16;
	}
}

int cachedNpcQuestHudKey = -1;
string cachedNpcQuestHudText = "";
int cachedNpcQuestHudWidth = 0;

void EnsureNpcQuestHudCache()
{
	if(cachedNpcQuestHudKey == npcBunniesCore::NPC_QUEST_DIALOG_KEY) return;
	cachedNpcQuestHudKey = npcBunniesCore::NPC_QUEST_DIALOG_KEY;
	cachedNpcQuestHudText = "Quests (" + npcBunniesCore::NpcQuestKeyName(npcBunniesCore::NPC_QUEST_DIALOG_KEY) + ")";
	cachedNpcQuestHudWidth = jjGetStringWidth(cachedNpcQuestHudText, STRING::SMALL, STRING::NORMAL);
}

string NpcQuestHudText()
{
	EnsureNpcQuestHudCache();
	return cachedNpcQuestHudText;
}

int NpcQuestHudWidth()
{
	EnsureNpcQuestHudCache();
	return cachedNpcQuestHudWidth;
}

bool NpcQuestHotkeyEnabled()
{
	return jjGameMode == GAME::SP;
}

void DrawNpcQuestHud(jjCANVAS@ canvas)
{
	if(!NpcQuestHotkeyEnabled()) return;
	int x = npcBunniesCore::HUD_COUNTS_X;
	int y = npcBunniesCore::HUD_COUNTS_Y + 32;
	canvas.drawString(x, y, NpcQuestHudText(), STRING::SMALL, STRING::NORMAL);
}

jjPLAYER@ NpcQuestLocalPlayer()
{
	for(int i = 0; i < jjLocalPlayerCount; i++)
	{
		jjPLAYER@ play = jjLocalPlayers[i];
		if(play.isInGame) return play;
	}
	return jjLocalPlayers[0];
}

void EnsureNpcQuestDialogPosition(int w = 315, int h = 180)
{
	if(npcBunniesCore::npcQuestDialogX < 0) npcBunniesCore::npcQuestDialogX = npcBunniesCore::HUD_COUNTS_X;
	if(npcBunniesCore::npcQuestDialogY < 0) npcBunniesCore::npcQuestDialogY = npcBunniesCore::HUD_COUNTS_Y + 52;
	if(npcBunniesCore::npcQuestDialogX + w > jjResolutionWidth) npcBunniesCore::npcQuestDialogX = jjResolutionWidth - w;
	if(npcBunniesCore::npcQuestDialogY + h > jjResolutionHeight) npcBunniesCore::npcQuestDialogY = jjResolutionHeight - h;
	if(npcBunniesCore::npcQuestDialogX < 0) npcBunniesCore::npcQuestDialogX = 0;
	if(npcBunniesCore::npcQuestDialogY < 0) npcBunniesCore::npcQuestDialogY = 0;
}

void ClampNpcQuestDialogPage(jjPLAYER@ play)
{
	int groups = CountNpcQuestGroups(play);
	if(groups <= 0) { npcBunniesCore::npcQuestDialogPage = 0; return; }
	if(npcBunniesCore::npcQuestDialogPage < 0) npcBunniesCore::npcQuestDialogPage = 0;
	if(npcBunniesCore::npcQuestDialogPage >= groups) npcBunniesCore::npcQuestDialogPage = groups - 1;
}
void DrawNpcQuestDialog(jjCANVAS@ canvas, jjPLAYER@ play)
{
	if(!npcBunniesCore::npcQuestDialogOpen) return;
	int w = 315;
	int textW = npcBunniesCore::npcQuestDialogShowInfo ? w - 20 - 62 : w - 20;
	if(textW < 20) textW = w - 20;
	int visibleHeight = npcBunniesCore::npcQuestDialogShowInfo ? 69 : 14;
	EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight);
	if(cachedNpcQuestDialogGroups <= 0) npcBunniesCore::npcQuestDialogPage = 0;
	else if(npcBunniesCore::npcQuestDialogPage >= cachedNpcQuestDialogGroups)
	{
		npcBunniesCore::npcQuestDialogPage = cachedNpcQuestDialogGroups - 1;
		EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight, true);
	}
	else if(npcBunniesCore::npcQuestDialogPage < 0)
	{
		npcBunniesCore::npcQuestDialogPage = 0;
		EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight, true);
	}
	int h = npcBunniesCore::npcQuestDialogShowInfo ? 180 : 58 + cachedNpcQuestDialogBasicLines * 18;
	if(h < 110) h = 110;
	EnsureNpcQuestDialogPosition(w, h);
	npcBunniesCore::npcQuestDialogWindow.preferencesFile = "npcBunniesDialogueBox.asdat";
	DbBegin(@npcBunniesCore::npcQuestDialogWindow, "npcQuestTracker", "Active quests", npcBunniesCore::npcQuestDialogX, npcBunniesCore::npcQuestDialogY, w, h);
	npcBunniesCore::npcQuestDialogWindow.closeButton = true;
	npcBunniesCore::npcQuestDialogWindow.draggable = true;
	npcBunniesCore::npcQuestDialogWindow.titleBar = true;
	npcBunniesCore::npcQuestDialogWindow.pad = 10;
	DbDrawFrame(canvas, @npcBunniesCore::npcQuestDialogWindow);
	int x = npcBunniesCore::npcQuestDialogWindow.x;
	int y = npcBunniesCore::npcQuestDialogWindow.y;
	npcBunniesCore::npcQuestDialogX = x;
	npcBunniesCore::npcQuestDialogY = y;
	int textX = x + 10;
	int textY = y + 33;
	int controlsY = y + h - 22;
	if(npcBunniesCore::npcQuestDialogShowInfo)
	{
		int objectivesY = controlsY - 50;
		int textMaxY = objectivesY - 6;
		if(npcBunniesCore::npcQuestDialogScroll < 0) npcBunniesCore::npcQuestDialogScroll = 0;
		if(npcBunniesCore::npcQuestDialogScroll > cachedNpcQuestDialogMaxScroll) npcBunniesCore::npcQuestDialogScroll = cachedNpcQuestDialogMaxScroll;
		if(cachedNpcQuestDialogMaxScroll > 0)
		{
			if(npcBunniesCore::npcQuestDialogScroll > 0) DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, textX + w - 20 - 46, textY, 42, 14, npcBunniesCore::DB_NPC_QUEST_SCROLL_UP, 0, -1, "UP");
			if(npcBunniesCore::npcQuestDialogScroll < cachedNpcQuestDialogMaxScroll) DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, textX + w - 20 - 58, textMaxY - 12, 54, 14, npcBunniesCore::DB_NPC_QUEST_SCROLL_DOWN, 0, 1, "DOWN");
		}
		DrawCachedNpcQuestDialogWrappedText(canvas, textX, textY, textMaxY, npcBunniesCore::npcQuestDialogScroll);
		if(cachedNpcQuestDialogGroups > 0) DrawNpcQuestObjectives(canvas, play, textX, objectivesY, w - 20, 44, npcBunniesCore::npcQuestDialogPage);
	}
	else
	{
		npcBunniesCore::npcQuestDialogScroll = 0;
		DrawCachedNpcQuestBasicLines(canvas, textX, textY);
	}
	if(NpcQuestHotkeyEnabled())
	{
		string keyText = npcBunniesCore::npcQuestDialogCaptureKey ? "Press a key" : "Key: " + npcBunniesCore::NpcQuestKeyName(npcBunniesCore::NPC_QUEST_DIALOG_KEY);
		DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 10, controlsY, 90, 14, npcBunniesCore::DB_NPC_QUEST_KEY, 0, 0, keyText);
	}
	DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 112, controlsY - 1, 16, 14, npcBunniesCore::DB_NPC_QUEST_PREV, 0, -1, "<");
	DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 136, controlsY - 1, 16, 14, npcBunniesCore::DB_NPC_QUEST_NEXT, 0, 1, ">");
	DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 164, controlsY, 130, 14, npcBunniesCore::DB_NPC_QUEST_INFO, 0, 0, npcBunniesCore::npcQuestDialogShowInfo ? "Show info: ON" : "Show info: OFF");
	DbDrawFocus(canvas, @npcBunniesCore::npcQuestDialogWindow);
}

bool loadKeyFrame = false;
int loadKey = 0;
int highlightChoice = 0;
int highlightChoiceTicks = 0;
string answerText = "";
int lastKey = -1;

array<string> npcMenuOptions = {"Exit", "Talk", "Follow", "Shop", "Guide", "Fighting", "Return"};
uint npcMenuOptionsLength = 4;
int chosenMenuOption = 0;
int chosenMenuTicksUp = 0;
int chosenMenuTicksDown = 0;

int GetNpcMenuOptionCount(npcBunniesCore::npcBunny npc)
{
	int count = npcBunniesAnimations::IsNpcStaticType(npc.type) ? 2 : 3; // Talk, Follow, Shop; Exit is drawn separately
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && (npc.guideEnabled || npc.guidePaths.length > 0)) count++;
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && npc.fightingAvailable) count++;
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) count++;
	return count;
}

int GetNpcMenuActionAt(npcBunniesCore::npcBunny npc, int index)
{
	if(index == 0) return npcBunniesCore::NpcAction::Talk;
	if(npcBunniesAnimations::IsNpcStaticType(npc.type))
	{
		if(index == 1) return npcBunniesCore::NpcAction::Shop;
		return npcBunniesCore::NpcAction::Menu;
	}
	if(index == 1) return npcBunniesCore::NpcAction::Follow;
	if(index == 2) return npcBunniesCore::NpcAction::Shop;
	int current = 3;
	if(npc.guideEnabled || npc.guidePaths.length > 0)
	{
		if(index == current) return npcBunniesCore::NpcAction::Guide;
		current++;
	}
	if(npc.fightingAvailable)
	{
		if(index == current) return npcBunniesCore::NpcAction::Fighting;
		current++;
	}
	if(index == current) return npcBunniesCore::NpcAction::Return;
	return npcBunniesCore::NpcAction::Menu;
}

void ReturnNpcToOrigin(int npcId)
{
	if(npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
	if(npcBunniesCore::npcBunnies[npcId].followingTrainIndex >= 0 && npcBunniesCore::npcBunnies[npcId].playerFollowing >= 0)
		npcBunniesMovement::recalculateFollowingTrainIndexes(npcBunniesCore::npcBunnies[npcId].followingTrainIndex, npcBunniesCore::npcBunnies[npcId].playerFollowing);
	npcBunniesCore::npcBunnies[npcId].playerFollowing = -1;
	npcBunniesCore::npcBunnies[npcId].followingTrainIndex = -1;
	npcBunniesCore::npcBunnies[npcId].guideActive = false;
	npcBunniesCore::npcBunnies[npcId].guideActivePathIndex = -1;
	npcBunniesCore::npcBunnies[npcId].guidePendingAction = 0;
	npcBunniesCore::npcBunnies[npcId].guidePendingPathIndex = -1;
	npcBunniesCore::npcBunnies[npcId].fightingTargetObjectID = -1;
	npcBunniesCore::npcBunnies[npcId].tpTargetX = npcBunniesCore::npcBunnies[npcId].xTile * 32;
	npcBunniesCore::npcBunnies[npcId].tpTargetY = npcBunniesCore::npcBunnies[npcId].yTile * 32;
	npcBunniesCore::npcBunnies[npcId].tpAnim = 0;
	npcBunniesHooks::sendNpcSync(npcId, -1, -1, 0);
}

void RequestNpcReturnToOrigin(int npcId)
{
	if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
	{
		ReturnNpcToOrigin(npcId);
		return;
	}
	jjSTREAM npcPacket;
	npcPacket.push("npcReturn " + npcId);
	jjSendPacket(npcPacket);
}

string GetNpcMenuLabel(npcBunniesCore::npcBunny npc, jjPLAYER@ play, int index)
{
	int action = GetNpcMenuActionAt(npc, index);
	if(action == npcBunniesCore::NpcAction::Follow && play !is null && npc.playerFollowing == play.playerID) return "Stop Following";
	if(action == npcBunniesCore::NpcAction::Fighting) return npc.fightingEnabled ? "Fighting: ON" : "Fighting: OFF";
	if(action >= 0 && action < int(npcMenuOptions.length)) return npcMenuOptions[action];
	return npcMenuOptions[0];
}

void getMenuOptionControl(jjPLAYER@ play)
{
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn];
	npcMenuOptionsLength = GetNpcMenuOptionCount(npc);
	if(chosenMenuOption > int(npcMenuOptionsLength)) chosenMenuOption = int(npcMenuOptionsLength);
	if(chosenMenuOption < 0) chosenMenuOption = 0;

	if(play.keyUp && chosenMenuTicksUp + 30 < jjGameTicks)
	{
		chosenMenuOption--;
		if(chosenMenuOption < 0) chosenMenuOption = int(npcMenuOptionsLength);
		chosenMenuTicksUp = jjGameTicks;
	}
	else if(play.keyDown && chosenMenuTicksDown + 30 < jjGameTicks)
	{
		chosenMenuOption++;
		if(chosenMenuOption > int(npcMenuOptionsLength)) chosenMenuOption = 0;
		chosenMenuTicksDown = jjGameTicks;
	}
	else if(jjKey[0x0D] && npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].action == npcBunniesCore::NpcAction::Menu) //ENTER
	{
		int selectedAction = chosenMenuOption == int(npcMenuOptionsLength) ? npcBunniesCore::NpcAction::Menu : GetNpcMenuActionAt(npc, chosenMenuOption);
		npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].action = selectedAction;
		npcBunniesMovement::lastKeyFire = jjGameTicks;
		switch(selectedAction)
		{
			case npcBunniesCore::NpcAction::Menu:
				npcBunniesHooks::exitConv();
				break;
			case npcBunniesCore::NpcAction::Talk:
				if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueSets.length > 0)
				{
					npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].convIndex = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].getDialogueSetNumber();
					npcBunniesCore::RouteActiveQuestBeforeDialogue(npcBunniesMovement::convOn, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerTalking);
				}
				else
				{
					npcBunniesHooks::exitConv();
				}
				break;
			case npcBunniesCore::NpcAction::Follow:
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideActive = false;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideActivePathIndex = -1;
				if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing == -1) //Follow
				{
					int nextFollowingTrainIndex = npcBunniesMovement::getFollowingTrainIndex(play.playerID);
					if(npcBunniesCore::NPC_FOLLOW_LIMIT == -1 || nextFollowingTrainIndex <= npcBunniesCore::NPC_FOLLOW_LIMIT)
					{
						if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
						{
							npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex = nextFollowingTrainIndex;
							npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing = play.playerID;
							npcBunniesHooks::sendNpcSync(npcBunniesMovement::convOn, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, 0);
						}
						else
						{
							npcBunniesHooks::sendNpcSync(npcBunniesMovement::convOn, play.playerID, nextFollowingTrainIndex, 0);
						}
					}
				}
				else if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing == play.playerID) //Disconnect
				{
					if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
					{
						npcBunniesMovement::recalculateFollowingTrainIndexes(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing);
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing = -1;
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex = -1;
						npcBunniesHooks::sendNpcSync(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].id, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, 0);
					}
					else
					{
						npcBunniesHooks::sendNpcSync(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].id, -1, -1, 0);
					}
				}
				npcBunniesHooks::exitConv();
				break;
			case npcBunniesCore::NpcAction::Shop:
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopSelectedIndex = 0;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopConfirmOpen = false;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopConfirmYes = true;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopMessage = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopItems.length == 0 ? "This NPC sells no items." : "";
				npcBunniesShop::shopBuyTicks = jjGameTicks;
				npcBunniesShop::shopInputReleased = false;
				npcBunniesMovement::keyTicks[13] = jjGameTicks;
				break;
			case npcBunniesCore::NpcAction::Fighting:
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingEnabled = !npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingEnabled;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingTargetObjectID = -1;
				npcBunniesFighting::MarkNpcFightingStateDirty();
				npcBunniesFighting::SendNpcFightingSync(npcBunniesMovement::convOn, 0);
				npcBunniesHooks::exitConv();
				break;
			case npcBunniesCore::NpcAction::Return:
				RequestNpcReturnToOrigin(npcBunniesMovement::convOn);
				npcBunniesHooks::exitConv();
				break;
			case npcBunniesCore::NpcAction::Guide:
			{
				if(!npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEnabled || npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths.length == 0)
				{
					npcBunniesHooks::exitConv();
					break;
				}
				string guidePlayerName = jjPlayers[play.playerID].nameUnformatted;
				int availableGuidePaths = npcBunniesQuests::GetAvailableGuidePathCountForPlayer(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn], guidePlayerName);
				if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideNextPathIndex >= availableGuidePaths)
				{
					if(availableGuidePaths >= int(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths.length) && !npcBunniesCore::HasPlayerSeenGuideEndLine(guidePlayerName, npcBunniesMovement::convOn) && npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEndLine != "")
					{
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEndLine;
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 2;
					}
					else
					{
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideAlreadyAtDestinationLine;
						npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 3;
					}
					npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex = -1;
					npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePlayerId = play.playerID;
					npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueIndex = 0;
					break;
				}
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideNextPathIndex;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePlayerId = play.playerID;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 1;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths[npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex].line;
				npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueIndex = 0;
				break;
			}
		}
	}
}

class DialogueSet
{
	array<DialogueLine@> dialogue;
	array<Choice@> choices;
	array<npcBunniesQuests::NpcQuest@> quests;
	array<npcBunniesQuests::NpcReward@> rewards;
	int choicesAnswerMethod = 0;
	int goToSet = -1;
	int autoContinueSet = -2;
	int questInProgressSet = -1;
	int questCompletedSet = -1;
	string questInProgressLine = "";
	string questCompletedLine = "";
	bool respawnMonsters = false;
	bool conditionsExist = false;
	bool rewardsGranted = false;
	int goToSetAfterChoice = -1;
	int bigFrame = -1;
	int speakerNpcIndex = -1;

	DialogueSet(array<DialogueLine@> dialogue, array<Choice@> choices = null, int choicesAnswerMethod = 0)
	{
		this.dialogue = dialogue;
		this.choices = choices;
		this.choicesAnswerMethod = choicesAnswerMethod;
	}
}

class DialogueLine
{
	int personId = 0;
	int isNpc = 0; //0 = NPC, 1 = Player
	string text;

	DialogueLine(int personId = 0, int isNpc = 0, string text = "")
	{
		this.personId = personId;
		this.isNpc = isNpc;
		this.text = text;
	}
}

class Choice
{
	string choiceText;
	int goToSet = -1;

	Choice(string choiceText, int goToSet = -1)
	{
		this.choiceText = choiceText;
		this.goToSet = goToSet;
	}
}

}