Downloads containing npcBunniesEditorPreview.asc

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

File preview

namespace npcBunniesEditorPreview
{
void NdeDrawNpcPreview(jjCANVAS@ canvas, int x, int y, int index)
{
	npcBunniesCore::furClass fur;
	fur.c1 = 16; fur.c2 = 24; fur.c3 = 32; fur.c4 = 40;
	NdeDrawNpcPreview(canvas, x, y, index, fur);
}

void NdeDrawNpcPreview(jjCANVAS@ canvas, int x, int y, int index, npcBunniesCore::furClass fur)
{
	int charIndex = index >= 0 && index < int(npcBunniesCore::npcList.length) ? npcBunniesCore::npcList[index] : npcBunniesCore::NpcAnim::JAZZ;
	if(charIndex == npcBunniesCore::NpcAnim::FROG)
	{
		npcBunniesAnimations::DrawParams draw;
		draw.add(x, y, ANIM::FROG, 2, jjGameTicks / 5, 0.75, 0.75, SPRITE::PALSHIFT, fur.c1 == 16 ? 0 : fur.c1 - 80, 0);
		npcBunniesAnimations::DrawCharacterOnCanvas(canvas, draw);
		draw.animSet = ANIM::CUSTOM[npcBunniesAnimations::emptyCharSet[npcBunniesCore::NpcAnim::FROG]];
		draw.anim = 1;
		draw.spriteMode = SPRITE::NORMAL;
		draw.param = 0;
		npcBunniesAnimations::DrawCharacterOnCanvas(canvas, draw);
		return;
	}
	if(charIndex == npcBunniesCore::NpcAnim::BIRD)
	{
		npcBunniesAnimations::DrawParams draw;
		draw.add(x, y, ANIM::BIRD, 9, jjGameTicks / 10, 0.75, 0.75, SPRITE::PALSHIFT, fur.c1 == 16 ? 0 : fur.c1 - 80, 0);
		npcBunniesAnimations::DrawCharacterOnCanvas(canvas, draw);
		draw.animSet = ANIM::CUSTOM[npcBunniesAnimations::emptyCharSet[npcBunniesCore::NpcAnim::BIRD]]; draw.anim = 0; draw.spriteMode = SPRITE::PALSHIFT;
		array<int> birdParams = { fur.c1 - 80, fur.c2 - 24, fur.c3 - 32, 0 };
		for(int i = 1; i <= 3; i++) { draw.param = birdParams[i]; npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 4, i); }
		return;
	}
	if(charIndex == npcBunniesCore::NpcAnim::DEVAN)
	{
		canvas.drawResizedSprite(x, y - 8, ANIM::DEVILDEVAN, 8, jjGameTicks / 10, 0.75, 0.75, SPRITE::NORMAL, 0);
		return;
	}
	if(charIndex == npcBunniesCore::NpcAnim::EVA)
	{
		canvas.drawResizedSprite(x, y - 3, ANIM::EVA, 0, jjGameTicks / 20, -0.75, 0.75, SPRITE::NORMAL, 0);
		return;
	}
	npcBunniesAnimations::DrawParams draw;
	draw.add(x, y, ANIM::CUSTOM[npcBunniesAnimations::emptyCharSet[charIndex]], 0, jjGameTicks / 10, 0.75, 0.75, SPRITE::PALSHIFT, 0, 0);
	if(charIndex == npcBunniesCore::NpcAnim::LORI)
	{
		array<int> params = { npcBunniesCore::GetPrimaryFurShift(charIndex, fur.c1), fur.c4 - 40, 0, fur.c4 - 40 };
		for(int i = 0; i < 4; i++)
		{
			if(i == 2) continue;
			if(i == 3 && fur.c4 == 40) continue;
			draw.param = params[i];
			npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 4, i);
		}
		draw.param = params[2];
		npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 4, 2);
		return;
	}
	array<int> params = { npcBunniesCore::GetPrimaryFurShift(charIndex, fur.c1), fur.c2 - 24, fur.c3 - 32, fur.c4 - 40, 0 };
	for(int i = 0; i < 5; i++)
	{
		draw.param = params[i];
		npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 5, i);
	}
}

void NdeDrawNpcTypePreview(jjCANVAS@ canvas, int x, int y, int type, npcBunniesCore::furClass fur)
{
	if(npcBunniesAnimations::IsNpcStaticType(type)) return;
	if(npcBunniesAnimations::IsNpcSimpleAnimType(type))
	{
		NdeLoadSimpleAnimSetForDesigner(type);
		npcBunniesAnimations::NpcUiAnimation icon;
		npcBunniesAnimations::GetNpcSimpleAnimation(type, icon);
		if(!npcBunniesAnimations::CanDrawNpcSimpleAnimation(icon)) return;
		canvas.drawResizedSprite(x, y, icon.animSet, icon.anim, npcBunniesAnimations::NpcSimpleAnimationFrame(icon), 0.75, 0.75, icon.spriteMode, icon.param);
		return;
	}
	int index = 0;
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerNpcNames.length; i++)
	{
		if(type == npcBunniesAnimations::getTypeFromTypeName(npcBunniesEditorCore::NpcDesignerNpcNames[i])) { index = int(i); break; }
	}
	NdeDrawNpcPreview(canvas, x, y, index, fur);
}
/// <summary>
/// Returns the JSON/display name for a dialogue action type.
/// </summary>
string NdeDialogueTypeName(int type)
{
	if(type == npcBunniesCore::DialogueAction::Answer) return "TextAnswer";
	if(type == npcBunniesCore::DialogueAction::Choice) return "Choices";
	if(type == npcBunniesCore::DialogueAction::Quest) return "Quest";
	return "Dialogue";
}

/// <summary>
/// Returns the display target for a choice transition.
/// </summary>
bool NdeHasBigDialoguePreview(npcBunniesCore::npcBunny &in npc)
{
	int animIndex = int(npcBunniesAnimations::animTypes.find(npc.type));
	return npcBunniesCore::LOAD_BIG_NPC_ANIMATIONS && animIndex >= npcBunniesCore::NpcAnim::JAZZ && animIndex <= npcBunniesCore::NpcAnim::LORI;
}

int NdeBigDialogueAnimIndex(npcBunniesCore::npcBunny &in npc)
{
	return int(npcBunniesAnimations::animTypes.find(npc.type));
}

int NdeBigDialogueFrameCount(npcBunniesCore::npcBunny &in npc)
{
	int animIndex = NdeBigDialogueAnimIndex(npc);
	if(animIndex < npcBunniesCore::NpcAnim::JAZZ || animIndex > npcBunniesCore::NpcAnim::LORI) return 1;
	int animSet = ANIM::CUSTOM[npcBunniesAnimations::customMenu[animIndex]];
	if(jjAnimSets[animSet].firstAnim == 0) return 1;
	int count = jjAnimations[jjAnimSets[animSet].firstAnim].frameCount / 5;
	return count > 0 ? count : 1;
}

void NdeClampDialogueBigFrame(npcBunniesCore::npcBunny &in npc, npcBunniesDialogue::DialogueSet@ set)
{
	if(set is null) return;
	int count = NdeBigDialogueFrameCount(npc);
	if(set.bigFrame < -1) set.bigFrame = -1;
	if(set.bigFrame >= count) set.bigFrame = -1;
}

void NdeCycleDialogueBigFrame(npcBunniesCore::npcBunny &in npc, npcBunniesDialogue::DialogueSet@ set, int delta)
{
	if(set is null) return;
	int count = NdeBigDialogueFrameCount(npc);
	set.bigFrame += delta;
	if(set.bigFrame < -1) set.bigFrame = count - 1;
	if(set.bigFrame >= count) set.bigFrame = -1;
}

void NdeDrawBigDialoguePreview(jjCANVAS@ canvas, int x, int y, npcBunniesCore::npcBunny &in npc, npcBunniesDialogue::DialogueSet@ set)
{
	if(set is null || !NdeHasBigDialoguePreview(npc)) return;
	NdeClampDialogueBigFrame(npc, set);
	int animIndex = NdeBigDialogueAnimIndex(npc);
	npcBunniesAnimations::DrawParams draw;
	draw.add(xPos: x, yPos: y, animSet: ANIM::CUSTOM[npcBunniesAnimations::customMenu[animIndex]], anim: 0, frame: set.bigFrame < 0 ? (jjGameTicks / 8) : set.bigFrame, sizeX: animIndex == npcBunniesCore::NpcAnim::JAZZ ? 0.5 : -0.5, sizeY: 0.5, spriteMode: SPRITE::PALSHIFT, param: 0, grav: 0);
	array<int> params = { npc.fur.c2 - 24, npc.fur.c3 - 32, npc.fur.c4 - 40, 0, npcBunniesCore::GetPrimaryFurShift(animIndex, npc.fur.c1) };
	draw.param = params[4];
	npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 5, 4);
	for(int i = 0; i <= 3; i++)
	{
		draw.anim = 0;
		draw.param = params[i];
		draw.spriteMode = i == 3 ? SPRITE::NORMAL : SPRITE::PALSHIFT;
		npcBunniesAnimations::DrawMergedCharacterLayerOnCanvas(canvas, draw, 5, i);
	}
}

void NdeDrawBigDialogueFramePicker(jjCANVAS@ canvas, int x, int y, int w, npcBunniesCore::npcBunny &in npc, npcBunniesDialogue::DialogueSet@ set)
{
	NdeClampDialogueBigFrame(npc, set);
	string frameText = set.bigFrame < 0 ? "Animated" : "Frame " + set.bigFrame;
	canvas.drawString(x, y, "Big frame:", STRING::SMALL, STRING::NORMAL);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 122, y, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_FRAME_PREV, 0, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 144, y, 100, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_FRAME_NEXT, 0, 1, frameText);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 250, y, 16, 16, npcBunniesEditorCore::NDE_ACT_DIALOGUE_FRAME_NEXT, 0, 1, ">");
	NdeDrawBigDialoguePreview(canvas, x + w - 44, y + 44, npc, set);
}
string NdeChoiceGoTo(npcBunniesDialogue::DialogueSet@ set, int index)
{
	if(index < 0 || index >= int(set.choices.length)) return "-";
	return set.choices[index].goToSet < 0 ? "-" : "" + (set.choices[index].goToSet + 1);
}

/// <summary>
/// Returns the display name for a quest item.
/// </summary>
string NdeQuestName(npcBunniesQuests::NpcQuest@ quest)
{
	if(quest.questType == npcBunniesQuests::QuestType::Gems) return NdeGemName(quest.gemType) + " gems";
	if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster) return NdeDefeatMonsterName(quest.objectType);
	if(quest.questType == npcBunniesQuests::QuestType::DefeatBoss) return NdeDefeatBossName(quest.objectType);
	return "Coins";
}

/// <summary>
/// Returns the display name for a reward item.
/// </summary>
string NdeRewardName(npcBunniesQuests::NpcReward@ reward)
{
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardGems) return NdeGemName(reward.gemType) + " gems";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardAmmo) return npcBunniesShop::GetWeaponDisplayName(reward.weaponType) + " ammo";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardPowerup) return npcBunniesShop::GetWeaponDisplayName(reward.weaponType) + " powerup";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTeleport) return "Teleport";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardMoveable) return "Moveable";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTrigger) return "Trigger";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardFastFire) return "FastFire";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardGuidePath) return npcBunniesEditorActions::NdeGuideEntryRewardName(reward.guidePathIndex);
	return "Coins";
}

/// <summary>
/// Returns the display/JSON name for a gem color.
/// </summary>
string NdeGemName(int gemType)
{
	if(gemType == GEM::GREEN) return "Green";
	if(gemType == GEM::BLUE) return "Blue";
	if(gemType == GEM::PURPLE) return "Purple";
	return "Red";
}

/// <summary>
/// Converts an integer gem color value to a GEM::Color.
/// </summary>
GEM::Color NdeGemFromInt(int gemType)
{
	if(gemType == GEM::GREEN) return GEM::GREEN;
	if(gemType == GEM::BLUE) return GEM::BLUE;
	if(gemType == GEM::PURPLE) return GEM::PURPLE;
	return GEM::RED;
}

/// <summary>
/// Returns the display/JSON name for a monster event id.
/// </summary>
string NdeDefeatMonsterName(int eventID)
{
	if(eventID == OBJECT::BAT) return "Bat";
	if(eventID == OBJECT::BEEBOY) return "BeeBoy";
	if(eventID == OBJECT::BEES) return "Bees";
	if(eventID == OBJECT::BEE) return "Bee";
	if(eventID == OBJECT::BUTTERFLY) return "Butterfly";
	if(eventID == OBJECT::CRAB) return "Crab";
	if(eventID == OBJECT::DEMON) return "Demon";
	if(eventID == OBJECT::DOGGYDOGG) return "DoggyDogg";
	if(eventID == OBJECT::DRAGONFLY) return "Dragonfly";
	if(eventID == OBJECT::DRAGON) return "Dragon";
	if(eventID == OBJECT::FATCHICK) return "FatChick";
	if(eventID == OBJECT::FENCER) return "Fencer";
	if(eventID == OBJECT::FISH) return "Fish";
	if(eventID == OBJECT::HATTER) return "Hatter";
	if(eventID == OBJECT::HELMUT) return "Helmut";
	if(eventID == OBJECT::LABRAT) return "LabRat";
	if(eventID == OBJECT::FLOATLIZARD) return "FloatLizard";
	if(eventID == OBJECT::XMASLIZARD) return "XmasLizard";
	if(eventID == OBJECT::XMASFLOATLIZARD) return "XmasFloatLizard";
	if(eventID == OBJECT::LIZARD) return "Lizard";
	if(eventID == OBJECT::STANDMONKEY) return "StandMonkey";
	if(eventID == OBJECT::MONKEY) return "Monkey";
	if(eventID == OBJECT::XMASNORMTURTLE) return "XmasNormalTurtle";
	if(eventID == OBJECT::TUBETURTLE) return "TubeTurtle";
	if(eventID == OBJECT::TUFTURT) return "TufTurtle";
	if(eventID == OBJECT::NORMTURTLE) return "NormalTurtle";
	if(eventID == OBJECT::RAPIER) return "Rapier";
	if(eventID == OBJECT::RAVEN) return "Raven";
	if(eventID == OBJECT::SKELETON) return "Skeleton";
	if(eventID == OBJECT::SPARK) return "Spark";
	if(eventID == OBJECT::FLOATSUCKER) return "FloatSucker";
	if(eventID == OBJECT::SUCKER) return "Sucker";
	if(eventID == OBJECT::CAT) return "Cat";
	if(eventID == OBJECT::PACMANGHOST) return "PacmanGhost";
	if(eventID == OBJECT::MOTH) return "Moth";
	if(eventID == OBJECT::WITCH) return "Witch";
	return "Monster";
}

/// <summary>
/// Returns the display/JSON name for a boss event id.
/// </summary>
string NdeDefeatBossName(int eventID)
{
	if(eventID == OBJECT::XMASBILSY) return "XmasBilsy";
	if(eventID == OBJECT::BILSY) return "Bilsy";
	if(eventID == OBJECT::BOLLY) return "Bolly";
	if(eventID == OBJECT::BUBBA) return "Bubba";
	if(eventID == OBJECT::DEVANROBOT) return "Devan";
	if(eventID == OBJECT::DEVILDEVAN) return "DevilDevan";
	if(eventID == OBJECT::QUEEN) return "Queen";
	if(eventID == OBJECT::ROBOT) return "Robot";
	if(eventID == OBJECT::ROCKETTURTLE) return "RocketTurtle";
	if(eventID == OBJECT::TUFBOSS) return "TufBoss";
	if(eventID == OBJECT::TWEEDLEBOSS) return "Tweedle";
	if(eventID == OBJECT::UTERUS) return "Uterus";
	return "Boss";
}

/// <summary>
/// Returns the display/JSON character type name for an NPC.
/// </summary>
const int NDE_NPC_TYPE_CHAR = 0;
const int NDE_NPC_TYPE_MONSTER = 1;
const int NDE_NPC_TYPE_BOSS = 2;
const int NDE_NPC_TYPE_STATIC = 3;
const int NDE_NPC_TYPE_OBJECT = 4;

int NdeNpcTypeCategory(npcBunniesCore::npcBunny &in npc)
{
	if(npcBunniesAnimations::IsNpcStaticType(npc.type)) return NDE_NPC_TYPE_STATIC;
	if(npcBunniesAnimations::IsNpcObjectType(npc.type)) return NDE_NPC_TYPE_OBJECT;
	if(npcBunniesAnimations::IsNpcBossType(npc.type)) return NDE_NPC_TYPE_BOSS;
	if(npcBunniesAnimations::IsNpcMonsterType(npc.type)) return NDE_NPC_TYPE_MONSTER;
	return NDE_NPC_TYPE_CHAR;
}

string NdeNpcTypeName(npcBunniesCore::npcBunny &in npc)
{
	int category = NdeNpcTypeCategory(npc);
	if(category == NDE_NPC_TYPE_STATIC) return "Static: Static";
	if(category == NDE_NPC_TYPE_OBJECT)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerObjectTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerObjectTypes[i]) return "Object: " + npcBunniesEditorCore::NpcDesignerObjectNames[i];
		string objectFallback = NdeMonsterAdjustmentName(npc.type);
		if(objectFallback != "") return "Object: " + objectFallback;
		return "Object";
	}
	if(category == NDE_NPC_TYPE_MONSTER)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerMonsterTypes[i]) return "Monster: " + npcBunniesEditorCore::NpcDesignerMonsterNames[i];
		string monsterFallback = NdeMonsterAdjustmentName(npc.type);
		if(monsterFallback != "") return "Monster: " + monsterFallback;
		return "Monster";
	}
	if(category == NDE_NPC_TYPE_BOSS)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerBossTypes[i]) return "Boss: " + npcBunniesEditorCore::NpcDesignerBossNames[i];
		return "Boss";
	}
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerNpcNames.length; i++) if(npc.type == npcBunniesAnimations::getTypeFromTypeName(npcBunniesEditorCore::NpcDesignerNpcNames[i])) return "Char: " + npcBunniesEditorCore::NpcDesignerNpcNames[i];
	return "Char: Jazz";
}

string NdeNpcTypeFileName(npcBunniesCore::npcBunny &in npc)
{
	int category = NdeNpcTypeCategory(npc);
	if(category == NDE_NPC_TYPE_STATIC) return "Static: Static";
	if(category == NDE_NPC_TYPE_OBJECT)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerObjectTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerObjectTypes[i]) return "Object: " + npcBunniesEditorCore::NpcDesignerObjectNames[i];
		string objectFileFallback = NdeMonsterAdjustmentName(npc.type);
		if(objectFileFallback != "") return "Object: " + objectFileFallback;
		return "Object";
	}
	if(category == NDE_NPC_TYPE_MONSTER)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerMonsterTypes[i]) return "Monster: " + npcBunniesEditorCore::NpcDesignerMonsterNames[i];
		string monsterFileFallback = NdeMonsterAdjustmentName(npc.type);
		if(monsterFileFallback != "") return "Monster: " + monsterFileFallback;
		return "Monster";
	}
	if(category == NDE_NPC_TYPE_BOSS)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerBossTypes[i]) return "Boss: " + npcBunniesEditorCore::NpcDesignerBossNames[i];
		return "Boss";
	}
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerNpcNames.length; i++) if(npc.type == npcBunniesAnimations::getTypeFromTypeName(npcBunniesEditorCore::NpcDesignerNpcNames[i])) return npcBunniesEditorCore::NpcDesignerNpcNames[i];
	return "Jazz";
}
bool NdeSimpleTypeAvailableForChoice(int type)
{
	return true;
}

void NdeLoadSimpleAnimSetForDesigner(int type)
{
	int animSet = npcBunniesAnimations::GetNpcSimpleAnimSet(type);
	if(animSet >= 0 && jjAnimSets[animSet].firstAnim == 0) jjAnimSets[animSet].load();
}

int NdeNpcTypeCount(int category)
{
	if(category == NDE_NPC_TYPE_STATIC) return 1;
	if(category == NDE_NPC_TYPE_CHAR) return int(npcBunniesEditorCore::NpcDesignerNpcNames.length);
	if(category == NDE_NPC_TYPE_OBJECT) return int(npcBunniesEditorCore::NpcDesignerObjectTypes.length);
	int count = 0;
	if(category == NDE_NPC_TYPE_MONSTER)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++) if(NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerMonsterTypes[i])) count++;
	}
	else
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++) if(NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerBossTypes[i])) count++;
	}
	return count;
}

int NdeNpcTypeAt(int category, int index)
{
	if(category == NDE_NPC_TYPE_STATIC) return npcBunniesAnimations::NpcStatic::Static;
	if(category == NDE_NPC_TYPE_CHAR) return npcBunniesAnimations::getTypeFromTypeName(npcBunniesEditorCore::NpcDesignerNpcNames[index]);
	if(category == NDE_NPC_TYPE_OBJECT) return npcBunniesEditorCore::NpcDesignerObjectTypes[npcBunniesEditorCore::NdeClamp(index, 0, int(npcBunniesEditorCore::NpcDesignerObjectTypes.length) - 1)];
	int seen = 0;
	if(category == NDE_NPC_TYPE_MONSTER)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++)
		{
			if(!NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerMonsterTypes[i])) continue;
			if(seen == index) return npcBunniesEditorCore::NpcDesignerMonsterTypes[i];
			seen++;
		}
		return npcBunniesEditorCore::NpcDesignerMonsterTypes.length > 0 ? npcBunniesEditorCore::NpcDesignerMonsterTypes[0] : npcBunniesCore::NpcAnim::JAZZ;
	}
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++)
	{
		if(!NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerBossTypes[i])) continue;
		if(seen == index) return npcBunniesEditorCore::NpcDesignerBossTypes[i];
		seen++;
	}
	return npcBunniesEditorCore::NpcDesignerBossTypes.length > 0 ? npcBunniesEditorCore::NpcDesignerBossTypes[0] : npcBunniesCore::NpcAnim::JAZZ;
}

int NdeNpcTypeIndex(npcBunniesCore::npcBunny &in npc)
{
	int category = NdeNpcTypeCategory(npc);
	if(category == NDE_NPC_TYPE_STATIC) return 0;
	if(category == NDE_NPC_TYPE_CHAR)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerNpcNames.length; i++) if(npc.type == npcBunniesAnimations::getTypeFromTypeName(npcBunniesEditorCore::NpcDesignerNpcNames[i])) return int(i);
		return 0;
	}
	if(category == NDE_NPC_TYPE_OBJECT)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerObjectTypes.length; i++) if(npc.type == npcBunniesEditorCore::NpcDesignerObjectTypes[i]) return int(i);
		return 0;
	}
	int seen = 0;
	if(category == NDE_NPC_TYPE_MONSTER)
	{
		for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++)
		{
			if(npc.type == npcBunniesEditorCore::NpcDesignerMonsterTypes[i]) return seen;
			if(NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerMonsterTypes[i])) seen++;
		}
		return 0;
	}
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++)
	{
		if(npc.type == npcBunniesEditorCore::NpcDesignerBossTypes[i]) return seen;
		if(NdeSimpleTypeAvailableForChoice(npcBunniesEditorCore::NpcDesignerBossTypes[i])) seen++;
	}
	return 0;
}

void NdeSetNpcType(npcBunniesCore::npcBunny npc, int type)
{
	npc.type = type;
	npcBunniesAnimations::LoadNpcSimpleAnimSet(npc.type);
	if(npcBunniesAnimations::IsNpcSimpleAnimType(npc.type)) NdeLoadSimpleAnimSetForDesigner(npc.type);
	if(npcBunniesAnimations::IsNpcStaticType(npc.type))
	{
		npc.moveable = false;
		npc.fightingAvailable = false;
		npc.fightingEnabled = false;
		npc.playerFollowing = -1;
		npc.guideActive = false;
		npc.guideActivePathIndex = -1;
	}
	npcBunniesCore::npcBunnies[npcBunniesEditorCore::NpcDesignerSelectedNpc] = npc;
	npcBunniesEditorSerialization::NdeSaveNpcFile();
}

void NdeCycleNpcTypeCategory(npcBunniesCore::npcBunny npc)
{
	int category = NdeNpcTypeCategory(npc) + 1;
	if(category > NDE_NPC_TYPE_OBJECT) category = NDE_NPC_TYPE_CHAR;
	int count = NdeNpcTypeCount(category);
	if(count <= 0)
	{
		category = NDE_NPC_TYPE_STATIC;
		count = 1;
	}
	NdeSetNpcType(npc, NdeNpcTypeAt(category, 0));
}
void NdeCycleNpcCharacter(npcBunniesCore::npcBunny npc, int delta)
{
	int category = NdeNpcTypeCategory(npc);
	int count = NdeNpcTypeCount(category);
	if(count <= 0) return;
	int index = NdeNpcTypeIndex(npc) + delta;
	while(index < 0) index += count;
	while(index >= count) index -= count;
	NdeSetNpcType(npc, NdeNpcTypeAt(category, index));
}

string NdeSimpleTypeFileName(int type)
{
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerMonsterTypes.length; i++) if(type == npcBunniesEditorCore::NpcDesignerMonsterTypes[i]) return "Monster: " + npcBunniesEditorCore::NpcDesignerMonsterNames[i];
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerBossTypes.length; i++) if(type == npcBunniesEditorCore::NpcDesignerBossTypes[i]) return "Boss: " + npcBunniesEditorCore::NpcDesignerBossNames[i];
	for(uint i = 0; i < npcBunniesEditorCore::NpcDesignerObjectTypes.length; i++) if(type == npcBunniesEditorCore::NpcDesignerObjectTypes[i]) return "Object: " + npcBunniesEditorCore::NpcDesignerObjectNames[i];
	string fallback = NdeMonsterAdjustmentName(type);
	if(fallback != "")
	{
		if(npcBunniesAnimations::IsNpcBossType(type)) return "Boss: " + fallback;
		if(npcBunniesAnimations::IsNpcObjectType(type)) return "Object: " + fallback;
		return "Monster: " + fallback;
	}
	return "";
}

string NdeMonsterAdjustmentName(int type)
{
	int index = npcMonsterAdjustments::FindByNpcType(type);
	if(index >= 0) return npcMonsterAdjustments::monsters[index].name;
	return "";
}

string NdeAdditionalMonsterAnimationsJson()
{
	string text = "\t\"AdditionalMonsterAnimations\": [";
	for(uint i = 0; i < npcBunniesCore::NPC_ADDITIONAL_MONSTER_ANIMATIONS.length; i++)
	{
		if(i > 0) text += ", ";
		text += "\"" + NdeSimpleTypeFileName(npcBunniesCore::NPC_ADDITIONAL_MONSTER_ANIMATIONS[i]) + "\"";
	}
	text += "],\n";
	return text;
}

int NdeSelectedLoadAnimType(int category)
{
	if(category == NDE_NPC_TYPE_BOSS) return npcBunniesEditorCore::NpcDesignerBossTypes[npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerLoadBossIndex, 0, int(npcBunniesEditorCore::NpcDesignerBossTypes.length) - 1)];
	return npcBunniesEditorCore::NpcDesignerMonsterTypes[npcBunniesEditorCore::NdeClamp(npcBunniesEditorCore::NpcDesignerLoadMonsterIndex, 0, int(npcBunniesEditorCore::NpcDesignerMonsterTypes.length) - 1)];
}

void NdeCycleLoadAnimSelection(int category, int delta)
{
	if(category == NDE_NPC_TYPE_BOSS)
	{
		npcBunniesEditorCore::NpcDesignerLoadBossIndex += delta;
		while(npcBunniesEditorCore::NpcDesignerLoadBossIndex < 0) npcBunniesEditorCore::NpcDesignerLoadBossIndex += int(npcBunniesEditorCore::NpcDesignerBossTypes.length);
		while(npcBunniesEditorCore::NpcDesignerLoadBossIndex >= int(npcBunniesEditorCore::NpcDesignerBossTypes.length)) npcBunniesEditorCore::NpcDesignerLoadBossIndex -= int(npcBunniesEditorCore::NpcDesignerBossTypes.length);
	}
	else
	{
		npcBunniesEditorCore::NpcDesignerLoadMonsterIndex += delta;
		while(npcBunniesEditorCore::NpcDesignerLoadMonsterIndex < 0) npcBunniesEditorCore::NpcDesignerLoadMonsterIndex += int(npcBunniesEditorCore::NpcDesignerMonsterTypes.length);
		while(npcBunniesEditorCore::NpcDesignerLoadMonsterIndex >= int(npcBunniesEditorCore::NpcDesignerMonsterTypes.length)) npcBunniesEditorCore::NpcDesignerLoadMonsterIndex -= int(npcBunniesEditorCore::NpcDesignerMonsterTypes.length);
	}
}

void NdeSetAdditionalSimpleAnim(int type, bool enabled)
{
	int index = npcBunniesCore::NPC_ADDITIONAL_MONSTER_ANIMATIONS.find(type);
	if(enabled)
	{
		if(index < 0)
		{
			if(npcBunniesAnimations::IsNpcSimpleAnimationLoaded(type)) { jjAlert("NPC Designer: this animation is already loaded by the level or script.", false); return; }
			npcBunniesCore::NPC_ADDITIONAL_MONSTER_ANIMATIONS.insertLast(type);
		}
		npcBunniesAnimations::LoadNpcSimpleAnimSet(type);
	}
	else if(index >= 0)
	{
		npcBunniesCore::NPC_ADDITIONAL_MONSTER_ANIMATIONS.removeAt(index);
		jjAlert("NPC Designer: unloaded additional animation entry. Already-loaded animsets stay available until level restart.", false);
	}
}

void NdeDrawLoadAdditionalAnimations(jjCANVAS@ canvas, int x, int y, int w, int h)
{
	int rowY = y + 48;
	canvas.drawString(x + 14, rowY - 24, "Load additional monsters", STRING::SMALL, STRING::NORMAL);
	int monsterType = NdeSelectedLoadAnimType(NDE_NPC_TYPE_MONSTER);
	int nameW = w - 206;
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_PREV, NDE_NPC_TYPE_MONSTER, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 36, rowY, nameW, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_NEXT, NDE_NPC_TYPE_MONSTER, 1, NdeSimpleTypeFileName(monsterType));
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 40 + nameW, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_NEXT, NDE_NPC_TYPE_MONSTER, 1, ">");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 134, rowY, 46, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_LOAD, NDE_NPC_TYPE_MONSTER, 0, "Load");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 82, rowY, 68, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_UNLOAD, NDE_NPC_TYPE_MONSTER, 0, "Unload"); rowY += 26;
	int bossType = NdeSelectedLoadAnimType(NDE_NPC_TYPE_BOSS);
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_PREV, NDE_NPC_TYPE_BOSS, -1, "<");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 36, rowY, nameW, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_NEXT, NDE_NPC_TYPE_BOSS, 1, NdeSimpleTypeFileName(bossType));
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 40 + nameW, rowY, 16, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_NEXT, NDE_NPC_TYPE_BOSS, 1, ">");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 134, rowY, 46, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_LOAD, NDE_NPC_TYPE_BOSS, 0, "Load");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + w - 82, rowY, 68, 16, npcBunniesEditorCore::NDE_ACT_LOAD_ANIM_UNLOAD, NDE_NPC_TYPE_BOSS, 0, "Unload");
	npcBunniesEditorCore::NdeDrawButton(canvas, x + 14, y + h - 30, w - 28, 16, npcBunniesEditorCore::NDE_ACT_BACK_MAIN, 0, 0, "Main Menu");
}

}