Downloads containing npcBunniesEditorViews.asc

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

File preview

namespace npcBunniesEditorViews
{
void NpcDesigner_Draw(jjPLAYER@ play, jjCANVAS@ canvas)
{
	if(!npcBunniesEditorCore::NpcDesignerOpen || (!npcBunniesEditorCore::NpcDesignerHelpOnly && !npcBunniesCore::LocalPlayerHasNpcBunniesAdminAccess())) return;
	npcBunniesEditorCore::NdeClearButtons();
	int x = npcBunniesEditorCore::NdePanelX(), y = npcBunniesEditorCore::NdePanelY(), w = npcBunniesEditorCore::NdePanelWidth(), h = npcBunniesEditorCore::NdePanelHeight();
	canvas.drawRectangle(x, y, w, h, 0, SPRITE::SHADOW);
	canvas.drawString(x + npcBunniesEditorCore::NdePad(), y + npcBunniesEditorCore::NdePad(), npcBunniesEditorCore::NpcDesignerHelpOnly ? "NPC Help" : "NPC Designer", STRING::MEDIUM, STRING::NORMAL);
	if(!npcBunniesEditorCore::NpcDesignerHelpOnly)
	{
		canvas.drawRectangle(x + 14 + npcBunniesEditorCore::NDE_TOGGLE_UNDERLAY_OFFSET_X, y + 30 + npcBunniesEditorCore::NDE_TOGGLE_UNDERLAY_OFFSET_Y, w - 28 + 6, 16, npcBunniesEditorCore::NpcDesignerAllowPlayerMovement ? 80 : 24, SPRITE::TRANSLUCENT);
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_PLAYER_MOVEMENT, 0, 0, npcBunniesEditorCore::NpcDesignerAllowPlayerMovement ? "Enable player movement: ON" : "Enable player movement: OFF");
	}
	npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 24, y + npcBunniesEditorCore::NdePad(), 18, 15, npcBunniesEditorCore::NDE_ACT_EXIT, 0, 0, "X");
	int contentY = y + npcBunniesEditorCore::NDE_CONTENT_OFFSET_Y;
	int contentH = h - npcBunniesEditorCore::NDE_CONTENT_OFFSET_Y;
	if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_MAIN) NdeDrawMain(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_CREATE) NdeDrawCreate(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_EDIT_PICK) NdeDrawEditPick(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_EDIT) NdeDrawEdit(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_DIALOGUE) NdeDrawDialogue(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_SHOP) NdeDrawShop(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_GUIDE) NdeDrawGuide(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_PATH_RECORD) NdeDrawPathRecord(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_QUEST) NdeDrawQuestReward(canvas, x, contentY, w, contentH, true);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_REWARD) NdeDrawQuestReward(canvas, x, contentY, w, contentH, false);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_HELP) NdeDrawHelp(canvas, x, contentY, w, contentH);
	else if(npcBunniesEditorCore::NpcDesignerView == npcBunniesEditorCore::NDE_LOAD_ANIMS) npcBunniesEditorPreview::NdeDrawLoadAdditionalAnimations(canvas, x, contentY, w, contentH);
	npcBunniesEditorActions::NdeNormalizeFocus();
	npcBunniesEditorActions::NdeProcessClick(play);
	npcBunniesEditorActions::NdeDrawRecordedPath(canvas);
}

/// <summary>
/// Draws the main portion of the NPC Designer UI.
/// </summary>
void NdeDrawMain(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	int rowY = y + 48;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_MAIN_CREATE, 0, 0, "Create NPC"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_MAIN_EDIT, 0, 0, "Edit NPC"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_MAIN_DELETE, 0, 0, "Delete NPC"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_MAIN_UNDO_DELETE, 0, 0, npcBunniesEditorCore::NpcDesignerHasDeletedNpc ? "Undo delete" : "||Undo delete"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_BIG_ANIMS, 0, 0, npcBunniesCore::LOAD_BIG_NPC_ANIMATIONS ? "Big dialogue animations: ON" : "Big dialogue animations: OFF"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_ALL_MONSTER_ANIMS, 0, 0, npcBunniesCore::LOAD_ALL_MONSTER_ANIMATIONS ? "All monster/boss animations: ON" : "All monster/boss animations: OFF"); rowY += 22;
	if(!npcBunniesCore::LOAD_ALL_MONSTER_ANIMATIONS) { npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIMS, 0, 0, "Load additional monsters"); rowY += 22; }
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 52, w - 28, 16, npcBunniesEditorCore::NDE_ACT_HELP, 0, 0, "Help");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EXIT, 0, 0, "Exit");
}

/// <summary>
/// Draws the create portion of the NPC Designer UI.
/// </summary>
void NdeDrawCreate(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	int top = y + 42, bottom = y + h - 42;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, bottom + 12, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 0, 0, "Main Menu");
	int visible = (bottom - top) / 34;
	int maxScroll = int(npcBunniesEditorCore::NpcDesignerNpcNames.length) - visible;
	if(maxScroll < 0) maxScroll = 0;
	npcBunniesEditorCore::NpcDesignerScroll = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerScroll, 0, maxScroll);
	if(npcBunniesEditorCore::NpcDesignerScroll > 0) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 52, top - 18, 42, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_UP, 0, 0, "UP");
	if(npcBunniesEditorCore::NpcDesignerScroll < maxScroll) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 60, bottom - 2, 52, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_DOWN, 0, 0, "DOWN");
	for(int row = 0; row < visible; row++)
	{
		int index = npcBunniesEditorCore::NpcDesignerScroll + row;
		if(index >= int(npcBunniesEditorCore::NpcDesignerNpcNames.length)) break;
		int rowY = top + row * 34;
		npcBunniesEditorPreview::NdeDrawNpcPreview(canvas, x + npcBunniesEditorCore::NDE_CREATE_PREVIEW_OFFSET_X, rowY + npcBunniesEditorCore::NDE_CREATE_PREVIEW_OFFSET_Y, index);
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 58, rowY + 8, w - 72, 16, npcBunniesEditorCore::NDE_ACT_CREATE_TYPE, index, 0, npcBunniesEditorCore::NpcDesignerNpcNames[index], npcBunniesEditorCore::NpcDesignerSelectedType == index);
	}
}

/// <summary>
/// Draws the edit pick portion of the NPC Designer UI.
/// </summary>
void NdeDrawEditPick(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	int top = y + 44, bottom = y + h - 44;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, bottom + 12, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 0, 0, "Main Menu");
	int visible = (bottom - top) / 22;
	int maxScroll = int(npcBunniesCore::npcBunnies.length) - visible;
	if(maxScroll < 0) maxScroll = 0;
	npcBunniesEditorCore::NpcDesignerScroll = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerScroll, 0, maxScroll);
	if(npcBunniesEditorCore::NpcDesignerScroll > 0) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 52, top - 18, 42, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_UP, 0, 0, "UP");
	if(npcBunniesEditorCore::NpcDesignerScroll < maxScroll) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 60, bottom - 2, 52, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_DOWN, 0, 0, "DOWN");
	for(int row = 0; row < visible; row++)
	{
		int index = npcBunniesEditorCore::NpcDesignerScroll + row;
		if(index >= int(npcBunniesCore::npcBunnies.length)) break;
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, top + row * 22, w - 28, 16, npcBunniesEditorCore::NDE_ACT_PICK_EDIT_NPC, index, 0, "" + (index + 1) + ". " + npcBunniesCore::npcBunnies[index].name, npcBunniesEditorCore::NpcDesignerSelectedNpc == index);
	}
}

/// <summary>
/// Draws the edit portion of the NPC Designer UI.
/// </summary>
void NdeDrawEdit(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	if(!npcBunniesEditorActions::NdeValidNpc()) { npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_EDIT_PICK; return; }
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	int labelX = x + 14, valueX = x + 112, rowY = y + 44, valueW = w - 126;
	canvas.drawString(labelX, rowY, "Name:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, valueW, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 0, 0, npc.name, npcBunniesEditorCore::NpcDesignerEditingField == 0); rowY += 22;
	canvas.drawString(labelX, rowY, "Char:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_CHAR_PREV, 0, -1, "<");
	npcBunniesEditorPreview::NdeDrawNpcTypePreview(canvas, valueX + npcBunniesEditorCore::NDE_CHAR_EDIT_PREVIEW_OFFSET_X, rowY + npcBunniesEditorCore::NDE_CHAR_EDIT_PREVIEW_OFFSET_Y, npc.type, npc.fur);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + 56, rowY, valueW - 78, 16, npcBunniesEditorCore::NDE_ACT_CHAR_CATEGORY, 0, 1, npcBunniesEditorPreview::NdeNpcTypeName(npc));
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + valueW - 16, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_CHAR_NEXT, 0, 1, ">"); rowY += 24;
	canvas.drawString(labelX, rowY, "Fur:", STRING::SMALL, STRING::NORMAL);
	int fw = (valueW - 18) / 4;
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, fw, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 1, 0, "" + npc.fur.c1, npcBunniesEditorCore::NpcDesignerEditingField == 1);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + fw + 6, rowY, fw, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 2, 0, "" + npc.fur.c2, npcBunniesEditorCore::NpcDesignerEditingField == 2);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + (fw + 6) * 2, rowY, fw, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 3, 0, "" + npc.fur.c3, npcBunniesEditorCore::NpcDesignerEditingField == 3);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + (fw + 6) * 3, rowY, fw, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 4, 0, "" + npc.fur.c4, npcBunniesEditorCore::NpcDesignerEditingField == 4); rowY += 24;
	canvas.drawString(labelX, rowY, "Size:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_SIZE, 0, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + 22, rowY, valueW - 44, 16, npcBunniesEditorCore::NDE_ACT_NPC_SIZE, 0, 1, npcBunniesEditorActions::NdeNpcSizeName(npc.sizeMode));
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + valueW - 16, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_SIZE, 0, 1, ">"); rowY += 24;
	canvas.drawString(labelX, rowY, "Speed:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_SPEED, 0, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + 22, rowY, valueW - 44, 16, npcBunniesEditorCore::NDE_ACT_NPC_SPEED, 0, 1, npcBunniesEditorActions::NdeNpcSpeedName(npc.moveSpeed));
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + valueW - 16, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_SPEED, 0, 1, ">"); rowY += 24;
	if(npcBunniesAccessories::NpcAccessories::CanNpcUseHat(npc.type)) { NdeDrawNpcAccessoryRow(canvas, labelX, valueX, rowY, valueW, npc, npcBunniesAccessories::NpcAccessories::CATEGORY_HAT, npcBunniesEditorCore::NDE_FIELD_NPC_HAT, "Hat"); rowY += 22; }
	NdeDrawNpcAccessoryRow(canvas, labelX, valueX, rowY, valueW, npc, npcBunniesAccessories::NpcAccessories::CATEGORY_SCARF, npcBunniesEditorCore::NDE_FIELD_NPC_SCARF, "Scarf"); rowY += 22;
	NdeDrawNpcAccessoryRow(canvas, labelX, valueX, rowY, valueW, npc, npcBunniesAccessories::NpcAccessories::CATEGORY_GLASSES, npcBunniesEditorCore::NDE_FIELD_NPC_GLASSES, "Glasses"); rowY += 24;
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_POSITION, 0, 0, npcBunniesEditorCore::NpcDesignerMovingNpc ? "Edit position: ON" : "Edit position", npcBunniesEditorCore::NpcDesignerMovingNpc); rowY += 22;
	canvas.drawString(labelX, rowY, "X:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX + 24, rowY, 66, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 5, 0, npcBunniesEditorActions::NdeGetFieldValue(npc, 5), npcBunniesEditorCore::NpcDesignerEditingField == 5);
	canvas.drawString(labelX + 104, rowY, "Y:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX + 128, rowY, 66, 16, npcBunniesEditorCore::NDE_ACT_FIELD, 6, 0, npcBunniesEditorActions::NdeGetFieldValue(npc, 6), npcBunniesEditorCore::NpcDesignerEditingField == 6); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_DIRECTION, 0, 0, "Change direction: " + (npc.direction < 0 ? "left" : "right")); rowY += 22;
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_FOLLOW, 0, 0, "Can follow: " + (npc.moveable ? "ON" : "OFF")); rowY += 22; }
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_PUSHABLE, 0, 0, "PUSHABLE: < " + (npc.pushable ? "ON" : "OFF") + " >"); rowY += 22; }
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_FIGHTING, 0, 0, "Fighting: " + (npc.fightingAvailable ? "ON" : "OFF")); rowY += 22; }
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && npc.fightingAvailable) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_FIGHTING_STYLE, 0, 0, "Fighting style: " + npcBunniesFighting::NpcFightingStyleName(npc.fightingStyle)); rowY += 22; }
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_DIALOGUE, 0, 0, "Edit dialogue"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_SHOP, 0, 0, "Edit shop"); rowY += 22;
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TOGGLE_GUIDE, 0, 0, "Has guide: " + (npc.guideEnabled ? "ON" : "OFF")); rowY += 22; }
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && npc.guideEnabled) { npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_GUIDE, 0, 0, "Edit guide"); rowY += 22; }
	if(!npcBunniesAnimations::IsNpcStaticType(npc.type))
	{
		npcBunniesEditorCore::NdeDrawButton(canvas, labelX, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_NPC_TRACE_MODE, 0, 0, "Trace: " + npcBunniesCore::NpcTraceModeName(npc.traceMode)); rowY += 22;
		canvas.drawString(labelX, rowY + 1, "Trace color:", STRING::SMALL, STRING::NORMAL);
		int traceColorX = x + 154;
		int traceColorW = 40;
		int traceSquareX = traceColorX + traceColorW + 8;
		npcBunniesEditorCore::NdeDrawButton(canvas, traceColorX, rowY, traceColorW, 16, npcBunniesEditorCore::NDE_ACT_FIELD, npcBunniesEditorCore::NDE_FIELD_NPC_TRACE_COLOR, 0, "" + npc.traceColor, npcBunniesEditorCore::NpcDesignerEditingField == npcBunniesEditorCore::NDE_FIELD_NPC_TRACE_COLOR);
		npcBunniesEditorCore::NdeAddButton(traceSquareX, rowY - 10, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_TRACE_COLOR, 0, 0, "");
		canvas.drawRectangle(traceSquareX + 3, rowY - 7, 10, 10, npc.traceColor, SPRITE::NORMAL);
		rowY += 22;
	}
	npcBunniesEditorCore::NdeDrawButton(canvas, labelX, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 0, 0, "Main Menu");
}

void NdeDrawNpcAccessoryRow(jjCANVAS@ canvas, int labelX, int valueX, int rowY, int valueW, npcBunniesCore::npcBunny &in npc, int category, int field, const string &in label)
{
	canvas.drawString(labelX, rowY, label + ":", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_ACCESSORY, category, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + 22, rowY, valueW - 44, 16, npcBunniesEditorCore::NDE_ACT_FIELD, field, 0, npcBunniesEditorActions::NdeNpcAccessoryDisplay(npc, category), npcBunniesEditorCore::NpcDesignerEditingField == field);
	npcBunniesEditorCore::NdeDrawButton(canvas, valueX + valueW - 16, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_NPC_ACCESSORY, category, 1, ">");
}

/// <summary>
/// Draws the dialogue portion of the NPC Designer UI.
/// </summary>
void NdeDrawDialogue(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	if(!npcBunniesEditorActions::NdeValidNpc()) { npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_EDIT_PICK; return; }
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	if(npc.dialogueSets.length == 0) npc.dialogueSets.insertLast(@npcBunniesDialogue::DialogueSet(array<npcBunniesDialogue::DialogueLine@>(), array<npcBunniesDialogue::Choice@>(), npcBunniesCore::DialogueAction::Normal));
	npcBunniesEditorCore::NpcDesignerDialogueSet = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerDialogueSet, 0, int(npc.dialogueSets.length) - 1);
	npcBunniesDialogue::DialogueSet@ set = npc.dialogueSets[npcBunniesEditorCore::NpcDesignerDialogueSet];
	int rowY = y + 42, right = x + w - 14;
	canvas.drawString(x + 14, rowY, "Dialogue set:", STRING::SMALL, STRING::NORMAL);
	string setText = "" + (npcBunniesEditorCore::NpcDesignerDialogueSet + 1) + " out of " + npc.dialogueSets.length;
	int setWidth = jjGetStringWidth(setText, STRING::SMALL, STRING::NORMAL);
	int setRight = right - 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, setRight - setWidth - 26, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_PREV, 0, 0, "<");
	canvas.drawString(setRight - setWidth, rowY, setText, STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, right - 16, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_NEXT, 0, 0, ">"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_ADD, 0, 0, "Create New dialogue set"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_DELETE, 0, 0, npc.dialogueSets.length > 1 ? "Delete dialogue set" : "||Delete dialogue set"); rowY += 22;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_TYPE, 0, 0, "\"Type\": \"" + npcBunniesEditorPreview::NdeDialogueTypeName(set.choicesAnswerMethod) + "\""); rowY += 22;
	canvas.drawString(x + 14, rowY, "Speaker:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 122, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_SPEAKER, 0, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 144, rowY, 100, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_SPEAKER, 0, 1, npcBunniesEditorActions::NdeDialogueSpeakerName(npc, set));
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 250, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_SPEAKER, 0, 1, ">"); rowY += 22;
	canvas.drawString(x + 14, rowY, "Go to set:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 166, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_GOTO, 0, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 188, rowY, 56, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_GOTO, 0, 1, npcBunniesEditorActions::NdeDialogueGoToName(npc, set));
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 250, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_GOTO, 0, 1, ">"); rowY += 22;
	if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Normal)
	{
		canvas.drawString(x + 14, rowY, "Auto-Continue:", STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 166, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_AUTO_CONTINUE, 0, -1, "<");
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 188, rowY, 56, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_AUTO_CONTINUE, 0, 1, npcBunniesEditorActions::NdeDialogueAutoContinueName(npc, set));
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 250, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_AUTO_CONTINUE, 0, 1, ">"); rowY += 22;
	}
	npcBunniesCore::npcBunny speakerNpc = npcBunniesEditorActions::NdeDialogueSpeakerNpc(npc, set);
	if(npcBunniesEditorPreview::NdeHasBigDialoguePreview(speakerNpc))
	{
		npcBunniesEditorPreview::NdeDrawBigDialogueFramePicker(canvas, x + 14, rowY, w - 28, speakerNpc, set);
		rowY += 58;
	}
	string lineText = set.dialogue.length > 0 ? set.dialogue[0].text : "";
	npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 62, 10, lineText, "NPC line"); rowY += 72;
	if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Answer)
	{
		string answer = set.choices.length > 0 ? set.choices[0].choiceText : "";
		canvas.drawString(x + 14, rowY, "Right answer:", STRING::SMALL, STRING::NORMAL); rowY += 16;
		npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 42, 20, answer, "Answer"); rowY += 50;
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_CHOICE_GOTO, 0, 0, "Right answer goes to set: " + npcBunniesEditorPreview::NdeChoiceGoTo(set, 0)); rowY += 22;
	}
	else if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Choice)
	{
		for(int i = 0; i < 4; i++)
		{
			string choiceText = i < int(set.choices.length) ? set.choices[i].choiceText : "";
			canvas.drawString(x + 14, rowY, "Choice " + (i + 1) + ":", STRING::SMALL, STRING::NORMAL); rowY += 14;
			npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 82, 32, 30 + i, choiceText, "Choice text");
			npcBunniesEditorCore::NdeDrawButton(canvas, right - 54, rowY, 40, 16, npcBunniesEditorCore::NDE_ACT_CHOICE_GOTO, i, 0, ">" + npcBunniesEditorPreview::NdeChoiceGoTo(set, i));
			rowY += 40;
		}
	}
	else if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest)
	{
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_QUEST, 0, 0, "Edit quest"); rowY += 22;
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_REWARD, 0, 0, "Edit reward"); rowY += 24;
		canvas.drawString(x + 14, rowY, "Quest in progress line:", STRING::SMALL, STRING::NORMAL); rowY += 16;
		npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 42, npcBunniesEditorCore::NDE_FIELD_QUEST_IN_PROGRESS_LINE, set.questInProgressLine, "Quest in progress line"); rowY += 50;
		canvas.drawString(x + 14, rowY, "Quest finished line:", STRING::SMALL, STRING::NORMAL); rowY += 16;
		npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 42, npcBunniesEditorCore::NDE_FIELD_QUEST_FINISHED_LINE, set.questCompletedLine, "Quest finished line"); rowY += 48;
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_QUEST_RESPAWN_MONSTERS, 0, 0, "Respawn monsters: " + (set.respawnMonsters ? "ON" : "OFF")); rowY += 22;
	}
	npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc] = npc;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 1, 0, "Back");
}

/// <summary>
/// Draws the shop portion of the NPC Designer UI.
/// </summary>
void NdeDrawShop(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	if(!npcBunniesEditorActions::NdeValidNpc()) { npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_EDIT_PICK; return; }
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	int rowY = y + 42, bottom = y + h - 34;
	int iconX = x + 24;
	int itemLeftX = x + 56;
	int itemNameX = x + 96;
	int amountX = x + w - 430;
	int coinsX = x + w - 340;
	int gemsX = x + w - 250;
	int stockX = x + w - 180;
	int deleteX = x + w - 88;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_SHOP_ADD, 0, 0, "Add item"); rowY += 24;
	canvas.drawString(iconX - 8, rowY, "Item", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(itemNameX, rowY, "Item Name", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(amountX, rowY, "Amount", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(coinsX, rowY, "Coins", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(gemsX, rowY, "Gems", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(stockX, rowY, "Stock", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(deleteX, rowY, "Delete", STRING::SMALL, STRING::NORMAL);
	rowY += 18;
	for(int i = 0; i < int(npc.shopItems.length) && rowY + 22 < bottom; i++)
	{
		npcBunniesShop::NpcShopItem@ item = npc.shopItems[i];
		npcBunniesAnimations::NpcUiAnimation icon; npcBunniesAnimations::GetRewardUiAnimation(item.item, icon);
		npcBunniesAnimations::DrawNpcUiIcon(canvas, iconX, rowY + 10, icon, 0.55);
		npcBunniesEditorCore::NdeDrawButton(canvas, itemLeftX, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_SHOP_REWARD_PREV, i, -1, "<");
		DbDrawWrappedSmallInBox(canvas, itemNameX, rowY, npcBunniesEditorActions::NdeShopRewardName(item.item), amountX - itemNameX - 10, 16);
		npcBunniesEditorCore::NdeDrawButton(canvas, amountX - 20, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_SHOP_REWARD_NEXT, i, 1, ">");
		npcBunniesEditorCore::NdeDrawButton(canvas, amountX, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_AMOUNT, i, -1, "<");
		canvas.drawString(amountX + 16, rowY, "" + item.item.amount, STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, amountX + 54, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_AMOUNT, i, 1, ">");
		npcBunniesEditorCore::NdeDrawButton(canvas, coinsX, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_COINS, i, -1, "<");
		canvas.drawString(coinsX + 16, rowY, "" + item.priceCoins + "c", STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, coinsX + 54, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_COINS, i, 1, ">");
		npcBunniesEditorCore::NdeDrawButton(canvas, gemsX, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_GEMS, i, -1, "<");
		canvas.drawString(gemsX + 16, rowY, "" + item.priceGems + "g", STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, gemsX + 54, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_GEMS, i, 1, ">");
		npcBunniesEditorCore::NdeDrawButton(canvas, stockX, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_STOCK, i, -1, "<");
		canvas.drawString(stockX + 16, rowY, item.stock < 0 ? "inf" : "" + item.stock, STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, stockX + 54, rowY, 12, 16, npcBunniesEditorCore::NDE_ACT_SHOP_STOCK, i, 1, ">");
		npcBunniesEditorCore::NdeDrawButton(canvas, deleteX, rowY, 76, 16, npcBunniesEditorCore::NDE_ACT_SHOP_DELETE, i, 0, "Delete");
		rowY += 22;
	}
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 1, 0, "Back");
}

/// <summary>
/// Draws the guide portion of the NPC Designer UI.
/// </summary>
void NdeDrawGuide(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	if(!npcBunniesEditorActions::NdeValidNpc()) { npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_EDIT_PICK; return; }
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	int rowY = y + 42, listBottom = y + h - 158;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, 88, 16, npcBunniesEditorCore::NDE_ACT_GUIDE_ADD, 0, 0, "Add path");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 122, rowY, 108, 16, npcBunniesEditorCore::NDE_ACT_GUIDE_ADD_TELEPORT, 0, 0, "Add Tp"); rowY += 24;
	int visible = (listBottom - rowY) / 22;
	if(visible < 1) visible = 1;
	int maxScroll = int(npc.guidePaths.length) - visible;
	if(maxScroll < 0) maxScroll = 0;
	npcBunniesEditorCore::NpcDesignerScroll = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerScroll, 0, maxScroll);
	if(npcBunniesEditorCore::NpcDesignerScroll > 0) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 52, rowY - 18, 42, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_UP, 0, 0, "UP");
	if(npcBunniesEditorCore::NpcDesignerScroll < maxScroll) npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 60, listBottom + 2, 52, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_DOWN, 0, 0, "DOWN");
	for(int row = 0; row < visible; row++)
	{
		int i = npcBunniesEditorCore::NpcDesignerScroll + row;
		if(i >= int(npc.guidePaths.length)) break;
		canvas.drawString(x + 14, rowY, npcBunniesEditorActions::NdeGuideEntryDisplayName(npc, i), STRING::SMALL, STRING::NORMAL);
		npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 140, rowY, 48, 16, npcBunniesEditorCore::NDE_ACT_GUIDE_EDIT, i, 0, "Edit");
		npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 84, rowY, 72, 16, npcBunniesEditorCore::NDE_ACT_GUIDE_DELETE, i, 0, "Delete");
		rowY += 22;
	}
	rowY = listBottom + 24;
	canvas.drawString(x + 14, rowY, "EndLine:", STRING::SMALL, STRING::NORMAL); rowY += 16;
	npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 42, npcBunniesEditorCore::NDE_FIELD_GUIDE_END_LINE, npc.guideEndLine, "Dialogue after final path"); rowY += 50;
	canvas.drawString(x + 14, rowY, "AlreadyAtTheDestination:", STRING::SMALL, STRING::NORMAL); rowY += 16;
	npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 42, npcBunniesEditorCore::NDE_FIELD_GUIDE_ALREADY_LINE, npc.guideAlreadyAtDestinationLine, "Dialogue when already there");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 1, 0, "Back");
}

/// <summary>
/// Draws the path record portion of the NPC Designer UI.
/// </summary>
void NdeDrawPathRecord(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	if(!npcBunniesEditorActions::NdeValidNpc() || npcBunniesEditorCore::NpcDesignerPathIndex < 0 || npcBunniesEditorCore::NpcDesignerPathIndex >= int(npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc].guidePaths.length)) { npcBunniesEditorActions::NdeCancelGuideEntryEdit(); npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_GUIDE; return; }
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	int maxY = y + h - 44;
	int rowY = y + 44;
	bool teleport = npc.guidePaths[npcBunniesEditorCore::NpcDesignerPathIndex].isTeleport;
	string editText = teleport ? "Teleport edit mode owns the arrow keys. Move the green square with arrows or click a tile. Press Enter or Fire to save. Press Esc to cancel." : "Path edit mode owns the arrow keys. Path 1 starts at the NPC; each later path starts where the previous path or teleport ended. Press arrows to create the path. Returning to an earlier square deletes the loop. Press Enter or Esc when done.";
	rowY = NdeDrawWrapped(canvas, x + 14, rowY, editText, w - 28, maxY);
	rowY += 8;
	canvas.drawRectangle(x + 14, rowY + npcBunniesEditorCore::NDE_FOCUS_OFFSET_Y, 92, 16, 80, SPRITE::TRANSLUCENT);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, 92, 16, npcBunniesEditorCore::NDE_ACT_PATH_EDIT_START, 0, 0, teleport ? "Edit Teleport" : "Edit Path", npcBunniesEditorCore::NpcDesignerRecordingPath || npcBunniesEditorCore::NpcDesignerEditingTeleport);
	canvas.drawRectangle(x + 126, rowY + npcBunniesEditorCore::NDE_FOCUS_OFFSET_Y, 104, 16, 24, SPRITE::TRANSLUCENT);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 126, rowY, 104, 16, npcBunniesEditorCore::NDE_ACT_PATH_EDIT_STOP, 0, 0, "Stop Editing", !(npcBunniesEditorCore::NpcDesignerRecordingPath || npcBunniesEditorCore::NpcDesignerEditingTeleport)); rowY += 28;
	canvas.drawString(x + 14, rowY, "Guide dialogue:", STRING::SMALL, STRING::NORMAL); rowY += 18;
	string guideLine = npc.guidePaths[npcBunniesEditorCore::NpcDesignerPathIndex].line;
	npcBunniesEditorCore::NdeDrawTextBox(canvas, x + 14, rowY, w - 28, 48, npcBunniesEditorCore::NDE_FIELD_GUIDE_PATH_BASE + npcBunniesEditorCore::NpcDesignerPathIndex, guideLine, teleport ? "Line before this teleport" : "Line before this path");
	if(teleport)
	{
		rowY += 56;
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, npcBunniesEditorCore::NDE_ACT_TELEPORT_DIRECTION, 0, 0, "Direction: " + (npc.guidePaths[npcBunniesEditorCore::NpcDesignerPathIndex].teleportDirection < 0 ? "left" : "right"));
	}
	int rewardSetIndex, rewardIndex;
	if(npcBunniesQuests::FindGuidePathReward(npc, npcBunniesEditorCore::NpcDesignerPathIndex, rewardSetIndex, rewardIndex))
	{
		string warning = "This " + (teleport ? "teleport" : "path") + " is a reward for quest of dialogue set number " + (rewardSetIndex + 1);
		npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 52, w - 28, 16, npcBunniesEditorCore::NDE_ACT_EDIT_PATH_REWARD, rewardSetIndex, rewardIndex, warning);
	}
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 2, 0, "Back");
}

/// <summary>
/// Draws the quest reward portion of the NPC Designer UI.
/// </summary>
void NdeDrawQuestReward(jjCANVAS@ canvas, int x, int y, int w, int h, bool questMode)
{
	if(!npcBunniesEditorActions::NdeValidNpc() || npcBunniesEditorCore::NpcDesignerDialogueSet < 0 || npcBunniesEditorCore::NpcDesignerDialogueSet >= int(npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc].dialogueSets.length)) { npcBunniesEditorCore::NpcDesignerView = npcBunniesEditorCore::NDE_DIALOGUE; return; }
	npcBunniesDialogue::DialogueSet@ set = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc].dialogueSets[npcBunniesEditorCore::NpcDesignerDialogueSet];
	int rowY = y + 42, bottom = y + h - 34;
	int iconX = x + 24;
	int itemLeftX = x + 56;
	int itemNameX = x + 96;
	int amountX = x + w - (questMode ? 200 : 250);
	int deleteX = x + w - 88;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, w - 28, 16, questMode ? npcBunniesEditorCore::NDE_ACT_QUEST_ADD : npcBunniesEditorCore::NDE_ACT_REWARD_ADD, 0, 0, questMode ? "Add quest item" : "Add reward item"); rowY += 24;
	canvas.drawString(iconX - 8, rowY, questMode ? "Item" : "Reward", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(itemNameX, rowY, questMode ? "Item Name" : "Reward Name", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(amountX, rowY, "Amount", STRING::SMALL, STRING::NORMAL);
	canvas.drawString(deleteX, rowY, "Delete", STRING::SMALL, STRING::NORMAL);
	rowY += 18;
	int count = questMode ? int(set.quests.length) : int(set.rewards.length);
	for(int i = 0; i < count && rowY + 22 < bottom; i++)
	{
		string typeText;
		npcBunniesAnimations::NpcUiAnimation icon;
		if(questMode)
		{
			npcBunniesQuests::NpcQuest@ quest = set.quests[i];
			typeText = npcBunniesEditorPreview::NdeQuestName(quest);
			npcBunniesAnimations::GetQuestUiAnimation(quest, icon);
		}
		else
		{
			npcBunniesQuests::NpcReward@ reward = set.rewards[i];
			typeText = npcBunniesEditorPreview::NdeRewardName(reward);
			npcBunniesAnimations::GetRewardUiAnimation(reward, icon);
		}
		npcBunniesAnimations::DrawNpcUiIcon(canvas, iconX, rowY + 10, icon, 0.55);
		npcBunniesEditorCore::NdeDrawButton(canvas, itemLeftX, rowY, 16, 16, questMode ? npcBunniesEditorCore::NDE_ACT_QUEST_TYPE : npcBunniesEditorCore::NDE_ACT_REWARD_TYPE, i, -1, "<");
		DbDrawWrappedSmallInBox(canvas, itemNameX, rowY, typeText, amountX - itemNameX - 24, 16);
		npcBunniesEditorCore::NdeDrawButton(canvas, amountX - 20, rowY, 16, 16, questMode ? npcBunniesEditorCore::NDE_ACT_QUEST_TYPE : npcBunniesEditorCore::NDE_ACT_REWARD_TYPE, i, 1, ">");
		if(questMode)
		{
			npcBunniesQuests::NpcQuest@ quest = set.quests[i];
			NdeDrawNumberStepper(canvas, amountX, rowY, quest.amount, questMode ? npcBunniesEditorCore::NDE_ACT_QUEST_AMOUNT : npcBunniesEditorCore::NDE_ACT_REWARD_AMOUNT, i, 0);
		}
		else
		{
			npcBunniesQuests::NpcReward@ reward = set.rewards[i];
			NdeDrawRewardAmountEditor(canvas, amountX, rowY, reward, i);
		}
		npcBunniesEditorCore::NdeDrawButton(canvas, deleteX, rowY, 76, 16, questMode ? npcBunniesEditorCore::NDE_ACT_QUEST_DELETE : npcBunniesEditorCore::NDE_ACT_REWARD_DELETE, i, 0, "Delete");
		rowY += 22;
	}
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 3, 0, "Back");
}

/// <summary>
/// Draws the number stepper portion of the NPC Designer UI.
/// </summary>
void NdeDrawNumberStepper(jjCANVAS@ canvas, int x, int y, int value, int action, int index, int part)
{
	npcBunniesEditorCore::NdeDrawButton(canvas, x, y, 12, 16, action, index, -1 - part * 10, "<");
	canvas.drawString(x + 16, y, "" + value, STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 54, y, 12, 16, action, index, 1 + part * 10, ">");
}

/// <summary>
/// Draws the reward amount editor portion of the NPC Designer UI.
/// </summary>
void NdeDrawRewardAmountEditor(jjCANVAS@ canvas, int x, int y, npcBunniesQuests::NpcReward@ reward, int index)
{
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTeleport)
	{
		NdeDrawNumberStepper(canvas, x, y, int(reward.x), npcBunniesEditorCore::NDE_ACT_REWARD_AMOUNT, index, 1);
		NdeDrawNumberStepper(canvas, x + 84, y, int(reward.y), npcBunniesEditorCore::NDE_ACT_REWARD_AMOUNT, index, 2);
		return;
	}
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTrigger)
	{
		NdeDrawNumberStepper(canvas, x, y, reward.triggerId < 0 ? 0 : reward.triggerId, npcBunniesEditorCore::NDE_ACT_REWARD_AMOUNT, index, 3);
		return;
	}
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardGuidePath)
	{
		canvas.drawString(x, y, npcBunniesEditorActions::NdeGuideEntryRewardName(reward.guidePathIndex), STRING::SMALL, STRING::NORMAL);
		return;
	}
	NdeDrawNumberStepper(canvas, x, y, reward.amount, npcBunniesEditorCore::NDE_ACT_REWARD_AMOUNT, index, 0);
}

/// <summary>
/// Global editor state for draw wrapped.
/// </summary>
int NdeDrawWrapped(jjCANVAS@ canvas, int x, int y, const string &in text, int maxWidth, int maxY)
{
	return DbDrawWrappedSmall(canvas, x, y, text, maxWidth, maxY);
}

int NdeVisibleTextBoxLines(int h)
{
	return DbVisibleSmallLines(h);
}

int NdeWrappedLineCount(const string &in text, int maxWidth)
{
	return DbWrappedSmallLineCount(text, maxWidth);
}

void NdeClampActiveTextScroll(const string &in text, int maxWidth, int innerHeight)
{
	if(npcBunniesEditorCore::NpcDesignerTextScrollField != npcBunniesEditorCore::NpcDesignerEditingField)
	{
		npcBunniesEditorCore::NpcDesignerTextScrollField = npcBunniesEditorCore::NpcDesignerEditingField;
		npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
	}
	int maxScroll = NdeWrappedLineCount(text, maxWidth) - NdeVisibleTextBoxLines(innerHeight);
	if(maxScroll < 0) maxScroll = 0;
	npcBunniesEditorCore::NpcDesignerTextScrollLine = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerTextScrollLine, 0, maxScroll);
}

void NdeScrollActiveTextField(int delta)
{
	if(npcBunniesEditorCore::NpcDesignerEditingField < 0) return;
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc];
	string text = npcBunniesEditorActions::NdeGetFieldValue(npc, npcBunniesEditorCore::NpcDesignerEditingField);
	int maxScroll = NdeWrappedLineCount(text, NdeActiveTextFieldInnerWidth(npcBunniesEditorCore::NpcDesignerEditingField)) - NdeVisibleTextBoxLines(NdeActiveTextFieldInnerHeight(npcBunniesEditorCore::NpcDesignerEditingField));
	if(maxScroll < 0) maxScroll = 0;
	if(npcBunniesEditorCore::NpcDesignerTextScrollField != npcBunniesEditorCore::NpcDesignerEditingField)
	{
		npcBunniesEditorCore::NpcDesignerTextScrollField = npcBunniesEditorCore::NpcDesignerEditingField;
		npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
		npcBunniesEditorCore::NpcDesignerTextScrollManual = false;
	}
	npcBunniesEditorCore::NpcDesignerTextScrollLine += delta;
	if(npcBunniesEditorCore::NpcDesignerTextScrollLine < 0) npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
	if(npcBunniesEditorCore::NpcDesignerTextScrollLine > maxScroll) npcBunniesEditorCore::NpcDesignerTextScrollLine = maxScroll;
	npcBunniesEditorCore::NpcDesignerTextScrollManual = npcBunniesEditorCore::NpcDesignerTextScrollLine < maxScroll;
}

void NdeScrollActiveTextFieldToEnd(const string &in text, int maxWidth, int innerHeight)
{
	if(npcBunniesEditorCore::NpcDesignerEditingField < 0) return;
	npcBunniesEditorCore::NpcDesignerTextScrollField = npcBunniesEditorCore::NpcDesignerEditingField;
	npcBunniesEditorCore::NpcDesignerTextScrollLine = NdeWrappedLineCount(text, maxWidth) - NdeVisibleTextBoxLines(innerHeight);
	if(npcBunniesEditorCore::NpcDesignerTextScrollLine < 0) npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
}

void NdeKeepActiveTextFieldTypingViewport(const string &in oldText, const string &in newText, int maxWidth, int innerHeight)
{
	if(npcBunniesEditorCore::NpcDesignerEditingField < 0) return;
	if(npcBunniesEditorCore::NpcDesignerTextScrollField != npcBunniesEditorCore::NpcDesignerEditingField)
	{
		npcBunniesEditorCore::NpcDesignerTextScrollField = npcBunniesEditorCore::NpcDesignerEditingField;
		npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
		npcBunniesEditorCore::NpcDesignerTextScrollManual = false;
	}
	int visibleLines = NdeVisibleTextBoxLines(innerHeight);
	int oldMaxScroll = NdeWrappedLineCount(oldText, maxWidth) - visibleLines;
	if(oldMaxScroll < 0) oldMaxScroll = 0;
	int newMaxScroll = NdeWrappedLineCount(newText, maxWidth) - visibleLines;
	if(newMaxScroll < 0) newMaxScroll = 0;
	bool wasAtEnd = !npcBunniesEditorCore::NpcDesignerTextScrollManual || npcBunniesEditorCore::NpcDesignerTextScrollLine >= oldMaxScroll;
	if(wasAtEnd) npcBunniesEditorCore::NpcDesignerTextScrollLine = newMaxScroll;
	else npcBunniesEditorCore::NpcDesignerTextScrollLine = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerTextScrollLine, 0, newMaxScroll);
}

void NdeKeepActiveTextFieldCursorViewport(const string &in text, int maxWidth, int innerHeight)
{
	if(npcBunniesEditorCore::NpcDesignerEditingField < 0) return;
	if(npcBunniesEditorCore::NpcDesignerTextScrollField != npcBunniesEditorCore::NpcDesignerEditingField)
	{
		npcBunniesEditorCore::NpcDesignerTextScrollField = npcBunniesEditorCore::NpcDesignerEditingField;
		npcBunniesEditorCore::NpcDesignerTextScrollLine = 0;
		npcBunniesEditorCore::NpcDesignerTextScrollManual = false;
	}
	npcBunniesEditorCore::NpcDesignerTextScrollLine = DbClampEditableScrollToCursor(text, maxWidth, innerHeight, npcBunniesEditorCore::NpcDesignerTextScrollLine, npcBunniesEditorCore::NpcDesignerEditingCursor);
}

int NdeActiveTextFieldInnerWidth(int field)
{
	if(field >= 30 && field < 40) return npcBunniesEditorCore::NdePanelWidth() - 94;
	return npcBunniesEditorCore::NdePanelWidth() - 40;
}

int NdeActiveTextFieldInnerHeight(int field)
{
	if(field == 10) return 50;
	if(field >= 30 && field < 40) return 20;
	if(field == 20 || field == npcBunniesEditorCore::NDE_FIELD_QUEST_IN_PROGRESS_LINE || field == npcBunniesEditorCore::NDE_FIELD_QUEST_FINISHED_LINE || field == npcBunniesEditorCore::NDE_FIELD_GUIDE_END_LINE || field == npcBunniesEditorCore::NDE_FIELD_GUIDE_ALREADY_LINE) return 30;
	if(field >= npcBunniesEditorCore::NDE_FIELD_GUIDE_PATH_BASE && field < npcBunniesEditorCore::NDE_FIELD_GUIDE_END_LINE) return 36;
	return 20;
}

int NdeDrawWrappedScrolled(jjCANVAS@ canvas, int x, int y, const string &in text, int maxWidth, int maxY, int scrollLine)
{
	return DbDrawWrappedSmallScrolled(canvas, x, y, text, maxWidth, maxY, scrollLine);
}

/// <summary>
/// Processes a debounced mouse click against registered editor buttons or level placement.
/// </summary>

string NdeHelpText()
{
	return npcHelp::NPC_HELP_TEXT;
}

void NdeDrawHelp(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	int pad = 18;
	int panelX = x + 10;
	int panelY = y + 36;
	int panelW = w - 20;
	int panelH = h - 74;
	if(panelW < 120) panelW = w - 28;
	if(panelH < 60) panelH = h - 52;
	canvas.drawRectangle(panelX, panelY, panelW, panelH, 0, SPRITE::TRANSLUCENT);
	canvas.drawString(panelX + pad, panelY + 10, "Help", STRING::MEDIUM, STRING::NORMAL);
	int textY = panelY + 34;
	int maxY = panelY + panelH - 14;
	int textW = panelW - pad * 2 - 62;
	int visibleLines = (maxY - textY) / 14;
	if(visibleLines < 1) visibleLines = 1;
	int maxScroll = NdeWrappedLineCount(NdeHelpText(), textW) - visibleLines;
	if(maxScroll < 0) maxScroll = 0;
	npcBunniesEditorCore::NpcDesignerScroll = npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerScroll, 0, maxScroll);
	if(npcBunniesEditorCore::NpcDesignerScroll > 0) npcBunniesEditorCore::NdeDrawButton(canvas, panelX + panelW - 60, textY, 42, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_UP, 0, 0, "UP");
	if(npcBunniesEditorCore::NpcDesignerScroll < maxScroll) npcBunniesEditorCore::NdeDrawButton(canvas, panelX + panelW - 72, maxY - 12, 54, 14, npcBunniesEditorCore::NDE_ACT_SCROLL_DOWN, 0, 0, "DOWN");
	NdeDrawWrappedScrolled(canvas, panelX + pad, textY, NdeHelpText(), textW, maxY, npcBunniesEditorCore::NpcDesignerScroll);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 0, 0, npcBunniesEditorCore::NpcDesignerHelpOnly ? "Close" : "Main Menu");
}





}