Downloads containing npcBunniesShop.asc

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

File preview

namespace npcBunniesShop
{
class NpcShopItem
{
	string name = "";
	npcBunniesQuests::NpcReward@ item;
	int priceCoins = 0;
	int priceGems = 0;
	GEM::Color priceGemType = GEM::RED;
	int stock = -1;

	NpcShopItem()
	{
		@item = @npcBunniesQuests::NpcReward();
	}
}

int shopMoveTicks = 0;
int shopBuyTicks = 0;
bool shopInputReleased = true;

bool IsShopRewardAllowed(npcBunniesQuests::NpcReward@ reward)
{
	if(reward is null) return false;
	return reward.rewardType != npcBunniesQuests::RewardType::RewardCoins && reward.rewardType != npcBunniesQuests::RewardType::RewardGems && reward.rewardType != npcBunniesQuests::RewardType::RewardGuidePath;
}

string GetWeaponDisplayName(int weaponType)
{
	if(weaponType == WEAPON::BOUNCER) return "Bouncer";
	if(weaponType == WEAPON::ICE) return "Freezer";
	if(weaponType == WEAPON::SEEKER) return "Seeker";
	if(weaponType == WEAPON::RF) return "RF";
	if(weaponType == WEAPON::TOASTER) return "Toaster";
	if(weaponType == WEAPON::TNT) return "TNT";
	if(weaponType == WEAPON::GUN8) return "Pepper";
	if(weaponType == WEAPON::GUN9) return "Electro";
	return "Ammo";
}

string GetGemDisplayName(GEM::Color gemType)
{
	if(gemType == GEM::GREEN) return "green gems";
	if(gemType == GEM::BLUE) return "blue gems";
	if(gemType == GEM::PURPLE) return "purple gems";
	return "red gems";
}

string GetShopItemName(NpcShopItem@ item)
{
	if(item is null || item.item is null) return "Item";
	if(item.name != "") return item.name;
	npcBunniesQuests::NpcReward@ reward = item.item;
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardAmmo) return reward.amount + " " + GetWeaponDisplayName(reward.weaponType) + " ammo";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardPowerup) return GetWeaponDisplayName(reward.weaponType) + " powerup";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardFastFire) return "Fastfire";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTeleport) return "Teleport";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardTrigger) return "Trigger";
	if(reward.rewardType == npcBunniesQuests::RewardType::RewardMoveable) return "Moveable";
	return "Item";
}

bool IsShopItemInStock(NpcShopItem@ item)
{
	return !(item is null) && item.stock != 0;
}

string GetShopItemStockText(NpcShopItem@ item)
{
	if(item is null || item.stock < 0) return "";
	if(item.stock == 0) return "sold out";
	return "stock: " + item.stock;
}

string GetShopItemPriceText(NpcShopItem@ item)
{
	if(item is null) return "free";
	if(item.stock == 0) return "sold out";
	string text = "";
	if(item.priceCoins > 0) text = item.priceCoins + " coins";
	if(item.priceGems > 0)
	{
		if(text != "") text += " + ";
		text += item.priceGems + " " + GetGemDisplayName(item.priceGemType);
	}
	if(text == "") text = "free";
	string stockText = GetShopItemStockText(item);
	if(stockText != "") text += " (" + stockText + ")";
	return text;
}

string GetShopItemPriceQuestionText(NpcShopItem@ item)
{
	if(item is null) return "free";
	string text = "";
	if(item.priceCoins > 0) text = item.priceCoins + " coins";
	if(item.priceGems > 0)
	{
		if(text != "") text += " and ";
		text += item.priceGems + " " + GetGemDisplayName(item.priceGemType);
	}
	return text == "" ? "free" : text;
}

string GetPlayerShopCurrencyText(jjPLAYER@ player)
{
	if(player is null) return "";
	return "Red: " + player.gems[GEM::RED] + "   Green: " + player.gems[GEM::GREEN] + "   Blue: " + player.gems[GEM::BLUE] + "   Purple: " + player.gems[GEM::PURPLE] + "   Coins: " + player.coins;
}

int GetShopItemPriceIconWidth(NpcShopItem@ item)
{
	if(item is null || item.stock == 0) return jjGetStringWidth(item is null ? "free" : "sold out", STRING::SMALL, STRING::NORMAL);
	int width = 0;
	if(item.priceCoins > 0) width += npcBunniesAnimations::GetNpcItemAmountWidth(item.priceCoins);
	if(item.priceGems > 0)
	{
		if(width > 0) width += 12;
		width += npcBunniesAnimations::GetNpcItemAmountWidth(item.priceGems);
	}
	if(width == 0) width = jjGetStringWidth("free", STRING::SMALL, STRING::NORMAL);
	return width;
}

void DrawShopItemPriceIcons(jjCANVAS@ canvas, NpcShopItem@ item, int x, int y)
{
	if(item is null) { canvas.drawString(x, y, "free", STRING::SMALL, STRING::NORMAL); return; }
	if(item.stock == 0) { canvas.drawString(x, y, "sold out", STRING::SMALL, STRING::NORMAL); return; }
	if(item.priceCoins <= 0 && item.priceGems <= 0) { canvas.drawString(x, y, "free", STRING::SMALL, STRING::NORMAL); return; }
	if(item.priceCoins > 0)
	{
		npcBunniesAnimations::NpcUiAnimation coinIcon;
		npcBunniesAnimations::GetNpcUiIcon(npcBunniesAnimations::NpcUiIcon::Coin, coinIcon);
		npcBunniesAnimations::DrawNpcItemAmount(canvas, x, y, item.priceCoins, coinIcon);
		x += npcBunniesAnimations::GetNpcItemAmountWidth(item.priceCoins) + 12;
	}
	if(item.priceGems > 0)
	{
		npcBunniesAnimations::NpcUiAnimation gemIcon;
		npcBunniesAnimations::GetGemUiIcon(item.priceGemType, gemIcon);
		npcBunniesAnimations::DrawNpcItemAmount(canvas, x, y, item.priceGems, gemIcon);
	}
}
int GetNpcShopColumns(npcBunniesCore::npcBunny npc)
{
	return npc.shopColumns <= 1 ? 1 : 2;
}

int GetShopGemRedValue(GEM::Color gemType)
{
	if(gemType == GEM::GREEN) return 5;
	if(gemType == GEM::BLUE) return 10;
	return 1;
}

int GetShopItemGemPriceRedValue(NpcShopItem@ item)
{
	if(item is null || item.priceGems <= 0) return 0;
	return item.priceGems * GetShopGemRedValue(item.priceGemType);
}

int GetPlayerShopGemRedValue(jjPLAYER@ player)
{
	if(player is null) return 0;
	return player.gems[GEM::RED] + player.gems[GEM::PURPLE] + player.gems[GEM::GREEN] * 5 + player.gems[GEM::BLUE] * 10;
}

int SpendPlayerShopGemColor(jjPLAYER@ player, GEM::Color gemType, int gemValue, int remaining)
{
	while(remaining > 0 && player.gems[gemType] > 0)
	{
		player.gems[gemType] = player.gems[gemType] - 1;
		remaining -= gemValue;
	}
	if(remaining < 0)
	{
		player.gems[GEM::RED] = player.gems[GEM::RED] + -remaining;
		remaining = 0;
	}
	return remaining;
}

void SpendPlayerShopGems(jjPLAYER@ player, int redGemPrice)
{
	if(player is null || redGemPrice <= 0) return;
	int remaining = redGemPrice;
	remaining = SpendPlayerShopGemColor(player, GEM::RED, 1, remaining);
	remaining = SpendPlayerShopGemColor(player, GEM::PURPLE, 1, remaining);
	remaining = SpendPlayerShopGemColor(player, GEM::GREEN, 5, remaining);
	remaining = SpendPlayerShopGemColor(player, GEM::BLUE, 10, remaining);
}

bool CanAffordShopItem(NpcShopItem@ item, jjPLAYER@ player)
{
	if(item is null || player is null) return false;
	return player.coins >= item.priceCoins && GetPlayerShopGemRedValue(player) >= GetShopItemGemPriceRedValue(item);
}

void SpendShopItemPrice(NpcShopItem@ item, jjPLAYER@ player)
{
	if(item is null || player is null) return;
	if(item.priceCoins > 0) player.coins = player.coins >= item.priceCoins ? player.coins - item.priceCoins : 0;
	SpendPlayerShopGems(player, GetShopItemGemPriceRedValue(item));
}

bool IsValidShopWeaponType(int weaponType)
{
	return weaponType > 0 && weaponType < 10;
}

bool IsShopPowerupAtMax(npcBunniesQuests::NpcReward@ reward, jjPLAYER@ player)
{
	if(reward is null || player is null || reward.rewardType != npcBunniesQuests::RewardType::RewardPowerup) return false;
	if(!IsValidShopWeaponType(reward.weaponType)) return false;
	return player.ammo[reward.weaponType] >= 99;
}

void SetShopWeaponAmmo(jjPLAYER@ player, int weaponType, int amount)
{
	if(player is null || !IsValidShopWeaponType(weaponType)) return;
	int originalMaximum = jjWeapons[weaponType].maximum;
	if(amount > jjWeapons[weaponType].maximum) jjWeapons[weaponType].maximum = amount;
	player.ammo[weaponType] = amount;
	jjWeapons[weaponType].maximum = originalMaximum;
}

void GrantShopPowerupReward(npcBunniesQuests::NpcReward@ reward, jjPLAYER@ player)
{
	if(reward is null || player is null || reward.rewardType != npcBunniesQuests::RewardType::RewardPowerup) return;
	int weaponType = reward.weaponType;
	if(!IsValidShopWeaponType(weaponType)) return;
	int ammo = player.ammo[weaponType];
	if(ammo <= 0)
	{
		SetShopWeaponAmmo(player, weaponType, 20);
		player.powerup[weaponType] = true;
		return;
	}
	if(!player.powerup[weaponType])
	{
		player.powerup[weaponType] = true;
		return;
	}
	if(ammo < 50)
	{
		SetShopWeaponAmmo(player, weaponType, 50);
		return;
	}
	SetShopWeaponAmmo(player, weaponType, 99);
}

void GrantShopItemReward(NpcShopItem@ item, jjPLAYER@ player, int npcId)
{
	if(item is null || item.item is null) return;
	if(item.item.rewardType == npcBunniesQuests::RewardType::RewardPowerup) GrantShopPowerupReward(item.item, player);
	else npcBunniesQuests::GrantNpcReward(item.item, player, npcId);
}

void BuySelectedShopItem(int npcId, jjPLAYER@ player)
{
	if(player is null || npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
	jjPLAYER@ buyer = jjPlayers[player.playerID];
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcId];
	if(npc.shopItems.length == 0)
	{
		npc.shopMessage = "This NPC sells no items.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	if(npc.shopSelectedIndex < 0) npc.shopSelectedIndex = 0;
	if(npc.shopSelectedIndex >= int(npc.shopItems.length)) npc.shopSelectedIndex = int(npc.shopItems.length) - 1;
	NpcShopItem@ item = npc.shopItems[npc.shopSelectedIndex];
	NormalizeShopItemReward(item);
	if(!IsShopRewardAllowed(item.item))
	{
		npc.shopMessage = "This item cannot be sold.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	if(!IsShopItemInStock(item))
	{
		npc.shopMessage = "This item is sold out.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	if(IsShopPowerupAtMax(item.item, buyer))
	{
		npc.shopMessage = "Already at max.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	if(!CanAffordShopItem(item, buyer))
	{
		npc.shopMessage = "You cannot afford this.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	SpendShopItemPrice(item, buyer);
	GrantShopItemReward(item, buyer, npcId);
	if(item.stock > 0) item.stock--;
	npc.shopMessage = "Bought " + GetShopItemName(item) + ".";
	npcBunniesCore::npcBunnies[npcId] = npc;
}

void OpenShopPurchaseConfirm(int npcId)
{
	if(npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcId];
	if(npc.shopSelectedIndex < 0 || npc.shopSelectedIndex >= int(npc.shopItems.length)) return;
	if(!IsShopItemInStock(npc.shopItems[npc.shopSelectedIndex]))
	{
		npc.shopMessage = "This item is sold out.";
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}
	npc.shopConfirmOpen = true;
	npc.shopConfirmYes = true;
	npc.shopMessage = "";
	npcBunniesCore::npcBunnies[npcId] = npc;
}

void getShopControl(jjPLAYER@ play, int npcId)
{
	if(npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcId];
	int itemCount = int(npc.shopItems.length);
	int exitIndex = itemCount;
	int columns = GetNpcShopColumns(npc);
	if(npc.shopSelectedIndex < 0) npc.shopSelectedIndex = 0;
	if(npc.shopSelectedIndex > exitIndex) npc.shopSelectedIndex = exitIndex;
	if(!shopInputReleased)
	{
		if(!play.keySelect && !play.keyFire && !jjKey[0x0D]) shopInputReleased = true;
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}

	if(npc.shopConfirmOpen)
	{
		if(jjGameTicks > shopMoveTicks + 12)
		{
			if(play.keyLeft || play.keyRight)
			{
				npc.shopConfirmYes = !npc.shopConfirmYes;
				shopMoveTicks = jjGameTicks;
			}
		}
		if((play.keySelect || play.keyFire || jjKey[0x0D]) && jjGameTicks > shopBuyTicks + 20)
		{
			bool buy = npc.shopConfirmYes;
			npc.shopConfirmOpen = false;
			npcBunniesCore::npcBunnies[npcId] = npc;
			if(buy) BuySelectedShopItem(npcId, play);
			shopBuyTicks = jjGameTicks;
			shopInputReleased = false;
			npcBunniesMovement::lastKeyFire = jjGameTicks;
			npcBunniesMovement::keyTicks[13] = jjGameTicks;
			return;
		}
		if(jjKey[27]) npc.shopConfirmOpen = false;
		npcBunniesCore::npcBunnies[npcId] = npc;
		return;
	}

	if(jjGameTicks > shopMoveTicks + 12)
	{
		if(itemCount > 0 && npc.shopSelectedIndex < itemCount)
		{
			if(play.keyLeft && columns == 2 && npc.shopSelectedIndex % columns == 1) { npc.shopSelectedIndex--; shopMoveTicks = jjGameTicks; }
			else if(play.keyRight && columns == 2 && npc.shopSelectedIndex % columns == 0 && npc.shopSelectedIndex + 1 < itemCount) { npc.shopSelectedIndex++; shopMoveTicks = jjGameTicks; }
			else if(play.keyUp && npc.shopSelectedIndex - columns >= 0) { npc.shopSelectedIndex -= columns; shopMoveTicks = jjGameTicks; }
			else if(play.keyDown)
			{
				if(npc.shopSelectedIndex + columns < itemCount) npc.shopSelectedIndex += columns;
				else npc.shopSelectedIndex = exitIndex;
				shopMoveTicks = jjGameTicks;
			}
		}
		else if(play.keyUp && itemCount > 0)
		{
			int lastRowStart = ((itemCount - 1) / columns) * columns;
			npc.shopSelectedIndex = lastRowStart;
			shopMoveTicks = jjGameTicks;
		}
	}
	if((play.keySelect || play.keyFire || jjKey[0x0D]) && jjGameTicks > shopBuyTicks + 20)
	{
		if(npc.shopSelectedIndex == exitIndex)
		{
			npcBunniesHooks::exitConv();
			shopBuyTicks = jjGameTicks;
			return;
		}
		npcBunniesCore::npcBunnies[npcId] = npc;
		OpenShopPurchaseConfirm(npcId);
		shopBuyTicks = jjGameTicks;
		shopInputReleased = false;
		npcBunniesMovement::lastKeyFire = jjGameTicks;
		npcBunniesMovement::keyTicks[13] = jjGameTicks;
		return;
	}
	if(jjKey[27]) npcBunniesHooks::exitConv();
	npcBunniesCore::npcBunnies[npcId] = npc;
}

void DrawShopItem(jjCANVAS@ canvas, NpcShopItem@ item, int x, int y, int priceRight, bool selected)
{
	if(item is null) return;
	npcBunniesAnimations::NpcUiAnimation icon;
	GetShopItemAnimation(item, icon);
	npcBunniesAnimations::DrawNpcUiIcon(canvas, x, y + 10, icon, 0.75);
	jjTEXTAPPEARANCE appearance(selected ? STRING::SPIN : STRING::NORMAL);
	int nameX = x + 28;
	string itemName = GetShopItemName(item);
	canvas.drawString(nameX, y + 2, itemName, STRING::SMALL, appearance);
	DrawShopItemPriceIcons(canvas, item, priceRight - GetShopItemPriceIconWidth(item), y + 2);
}

void DrawShopConfirm(jjCANVAS@ canvas, npcBunniesCore::npcBunny npc)
{
	if(npc.shopSelectedIndex < 0 || npc.shopSelectedIndex >= int(npc.shopItems.length)) return;
	NpcShopItem@ item = npc.shopItems[npc.shopSelectedIndex];
	canvas.drawRectangle(120, 245, jjResolutionWidth - 240, 100, 0, SPRITE::SHADOW);
	canvas.drawString(0x8000, 258, "Buy " + GetShopItemName(item) + " for:", STRING::SMALL, STRING::NORMAL);
	int priceWidth = GetShopItemPriceIconWidth(item);
	DrawShopItemPriceIcons(canvas, item, (jjResolutionWidth - priceWidth) / 2, 280);
	canvas.drawString(0x8000 - 45, 318, "Yes", STRING::SMALL, npc.shopConfirmYes ? STRING::SPIN : STRING::NORMAL);
	canvas.drawString(0x8000 + 45, 318, "No", STRING::SMALL, npc.shopConfirmYes ? STRING::NORMAL : STRING::SPIN);
}

void DrawNpcShop(jjPLAYER@ play, jjCANVAS@ canvas, int npcId)
{
	if(npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
	getShopControl(play, npcId);
	if(npcBunniesMovement::convOn == -1) return;
	npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcId];
	int columns = GetNpcShopColumns(npc);
	int itemCount = int(npc.shopItems.length);
	int exitIndex = itemCount;
	int leftX = columns == 1 ? 210 : 135;
	int rightX = jjResolutionWidth / 2 + 20;
	int startY = 220;
	if(itemCount == 0) canvas.drawString(0x8000, 260, "This NPC sells no items.", STRING::SMALL, STRING::NORMAL);
	else
	{
		for(uint i = 0; i < npc.shopItems.length; i++)
		{
			int column = columns == 1 ? 0 : int(i) % columns;
			int row = columns == 1 ? int(i) : int(i) / columns;
			int itemX = column == 0 ? leftX : rightX;
			int priceRight = columns == 1 ? jjResolutionWidth - 120 : (column == 0 ? jjResolutionWidth / 2 - 36 : jjResolutionWidth - 36);
			DrawShopItem(canvas, npc.shopItems[i], itemX, startY + row * 32, priceRight, int(i) == npc.shopSelectedIndex && !npc.shopConfirmOpen);
		}
	}
	canvas.drawString(0x8000, 345, GetPlayerShopCurrencyText(play), STRING::SMALL, STRING::NORMAL);
	canvas.drawString(0x8000, 368, "Exit", STRING::SMALL, npc.shopSelectedIndex == exitIndex && !npc.shopConfirmOpen ? STRING::SPIN : STRING::NORMAL);
	if(npc.shopMessage != "") canvas.drawString(0x8000, 390, npc.shopMessage, STRING::SMALL, STRING::NORMAL);
	if(npc.shopConfirmOpen) DrawShopConfirm(canvas, npc);
}

int ParseNpcShopStock(array<string>@ tokens, array<int>@ pos)
{
	string value = npcBunniesParser::ReadNpcJsonString(tokens, pos);
	if(value == "" || jjRegexMatch(value, "inf", true) || jjRegexMatch(value, "infinite", true)) return -1;
	int stock = parseInt(value);
	return stock < 0 ? -1 : stock;
}
void ParseNpcShop(array<string>@ tokens, array<int>@ pos, npcBunniesCore::npcBunny &inout npc)
{
	if(pos[0] >= int(tokens.length) || tokens[pos[0]] != "{") { npcBunniesParser::SkipNpcJsonValue(tokens, pos); return; }
	pos[0]++;
	while(pos[0] < int(tokens.length) && tokens[pos[0]] != "}")
	{
		string key = tokens[pos[0]++]; if(pos[0] < int(tokens.length) && tokens[pos[0]] == ":") pos[0]++;
		if(jjRegexMatch(key, "Items", true) || jjRegexMatch(key, "ShopItems", true)) ParseNpcShopItems(tokens, pos, npc);
		else if(jjRegexMatch(key, "Columns", true) || jjRegexMatch(key, "DisplayColumns", true) || jjRegexMatch(key, "ItemColumns", true)) npc.shopColumns = npcBunniesParser::ReadNpcJsonInt(tokens, pos);
		else npcBunniesParser::SkipNpcJsonValue(tokens, pos);
		if(pos[0] < int(tokens.length) && tokens[pos[0]] == ",") pos[0]++;
	}
	if(pos[0] < int(tokens.length) && tokens[pos[0]] == "}") pos[0]++;
}

void ParseNpcShopItems(array<string>@ tokens, array<int>@ pos, npcBunniesCore::npcBunny &inout npc)
{
	if(pos[0] >= int(tokens.length) || tokens[pos[0]] != "[") { npcBunniesParser::SkipNpcJsonValue(tokens, pos); return; }
	pos[0]++;
	while(pos[0] < int(tokens.length) && tokens[pos[0]] != "]")
	{
		npc.shopItems.insertLast(ParseNpcShopItem(tokens, pos));
		if(pos[0] < int(tokens.length) && tokens[pos[0]] == ",") pos[0]++;
	}
	if(pos[0] < int(tokens.length) && tokens[pos[0]] == "]") pos[0]++;
}

NpcShopItem@ ParseNpcShopItem(array<string>@ tokens, array<int>@ pos)
{
	NpcShopItem@ item = @NpcShopItem();
	if(pos[0] >= int(tokens.length) || tokens[pos[0]] != "{") { npcBunniesParser::SkipNpcJsonValue(tokens, pos); return item; }
	pos[0]++;
	while(pos[0] < int(tokens.length) && tokens[pos[0]] != "}")
	{
		string key = tokens[pos[0]++]; if(pos[0] < int(tokens.length) && tokens[pos[0]] == ":") pos[0]++;
		if(jjRegexMatch(key, "Name", true)) item.name = npcBunniesParser::ReadNpcJsonString(tokens, pos);
		else if(jjRegexMatch(key, "Item", true) || jjRegexMatch(key, "Reward", true)) @item.item = npcBunniesParser::ParseReward(tokens, pos);
		else if(jjRegexMatch(key, "PriceCoins", true) || key == "Coins") item.priceCoins = npcBunniesParser::ReadNpcJsonInt(tokens, pos);
		else if(jjRegexMatch(key, "PriceGems", true) || key == "Gems") item.priceGems = npcBunniesParser::ReadNpcJsonInt(tokens, pos);
		else if(jjRegexMatch(key, "GemType", true)) item.priceGemType = npcBunniesQuests::ParseGemType(npcBunniesParser::ReadNpcJsonString(tokens, pos));
		else if(jjRegexMatch(key, "Stock", true)) item.stock = ParseNpcShopStock(tokens, pos);
		else if(jjRegexMatch(key, "Price", true)) ParseNpcShopPrice(tokens, pos, item);
		else npcBunniesParser::SkipNpcJsonValue(tokens, pos);
		if(pos[0] < int(tokens.length) && tokens[pos[0]] == ",") pos[0]++;
	}
	if(pos[0] < int(tokens.length) && tokens[pos[0]] == "}") pos[0]++;
	NormalizeShopItemReward(item);
	return item;
}

void ParseNpcShopPrice(array<string>@ tokens, array<int>@ pos, NpcShopItem@ item)
{
	if(pos[0] >= int(tokens.length) || tokens[pos[0]] != "{") { npcBunniesParser::SkipNpcJsonValue(tokens, pos); return; }
	pos[0]++;
	while(pos[0] < int(tokens.length) && tokens[pos[0]] != "}")
	{
		string key = tokens[pos[0]++]; if(pos[0] < int(tokens.length) && tokens[pos[0]] == ":") pos[0]++;
		if(jjRegexMatch(key, "Coins", true)) item.priceCoins = npcBunniesParser::ReadNpcJsonInt(tokens, pos);
		else if(jjRegexMatch(key, "Gems", true)) item.priceGems = npcBunniesParser::ReadNpcJsonInt(tokens, pos);
		else if(jjRegexMatch(key, "GemType", true)) item.priceGemType = npcBunniesQuests::ParseGemType(npcBunniesParser::ReadNpcJsonString(tokens, pos));
		else npcBunniesParser::SkipNpcJsonValue(tokens, pos);
		if(pos[0] < int(tokens.length) && tokens[pos[0]] == ",") pos[0]++;
	}
	if(pos[0] < int(tokens.length) && tokens[pos[0]] == "}") pos[0]++;
}

bool ShopItemNameContains(const string &in name, const string &in a, const string &in b = "", const string &in c = "")
{
	if(int(name.findFirst(a)) >= 0) return true;
	if(b != "" && int(name.findFirst(b)) >= 0) return true;
	if(c != "" && int(name.findFirst(c)) >= 0) return true;
	return false;
}

int GetShopItemWeaponTypeFromName(const string &in name)
{
	if(ShopItemNameContains(name, "Blaster", "blaster")) return WEAPON::BLASTER;
	if(ShopItemNameContains(name, "Bouncer", "bouncer")) return WEAPON::BOUNCER;
	if(ShopItemNameContains(name, "Freezer", "freezer") || ShopItemNameContains(name, "Ice", "ice")) return WEAPON::ICE;
	if(ShopItemNameContains(name, "Seeker", "seeker")) return WEAPON::SEEKER;
	if(ShopItemNameContains(name, "RF", "rf")) return WEAPON::RF;
	if(ShopItemNameContains(name, "Toaster", "toaster")) return WEAPON::TOASTER;
	if(ShopItemNameContains(name, "TNT", "tnt")) return WEAPON::TNT;
	if(ShopItemNameContains(name, "Pepper", "pepper")) return WEAPON::GUN8;
	if(ShopItemNameContains(name, "Electro", "electro")) return WEAPON::GUN9;
	return 0;
}

int GetShopItemIconIdFromName(const string &in name)
{
	int weaponType = GetShopItemWeaponTypeFromName(name);
	if(weaponType <= 0) return npcBunniesAnimations::NpcUiIcon::Unknown;
	if(ShopItemNameContains(name, "Powerup", "powerup", "Power-up") || ShopItemNameContains(name, "power-up", " PU", " pu")) return npcBunniesAnimations::GetPowerupRewardIconId(weaponType);
	if(ShopItemNameContains(name, "Ammo", "ammo") || ShopItemNameContains(name, "Bullet", "bullet") || ShopItemNameContains(name, "Bullets", "bullets")) return npcBunniesAnimations::GetAmmoRewardIconId(weaponType);
	return npcBunniesAnimations::GetAmmoRewardIconId(weaponType);
}

void NormalizeShopItemReward(NpcShopItem@ item)
{
	if(item is null || item.item is null || item.name == "") return;
	if(item.item.rewardType != npcBunniesQuests::RewardType::RewardCoins && item.item.rewardType != npcBunniesQuests::RewardType::RewardGems) return;
	int weaponType = GetShopItemWeaponTypeFromName(item.name);
	if(ShopItemNameContains(item.name, "Fastfire", "fastfire", "FastFire"))
	{
		item.item.rewardType = npcBunniesQuests::RewardType::RewardFastFire;
		if(item.item.amount <= 0) item.item.amount = 5;
		return;
	}
	if(weaponType <= 0) return;
	if(ShopItemNameContains(item.name, "Powerup", "powerup", "Power-up") || ShopItemNameContains(item.name, "power-up", " PU", " pu"))
	{
		item.item.rewardType = npcBunniesQuests::RewardType::RewardPowerup;
		item.item.weaponType = weaponType;
		item.item.amount = 1;
		return;
	}
	if(ShopItemNameContains(item.name, "Ammo", "ammo") || ShopItemNameContains(item.name, "Bullet", "bullet") || ShopItemNameContains(item.name, "Bullets", "bullets"))
	{
		item.item.rewardType = npcBunniesQuests::RewardType::RewardAmmo;
		item.item.weaponType = weaponType;
		if(item.item.amount <= 0) item.item.amount = 5;
	}
}

void GetShopItemAnimation(NpcShopItem@ item, npcBunniesAnimations::NpcUiAnimation &inout result)
{
	if(item is null || item.item is null) { npcBunniesAnimations::GetNpcUiIcon(npcBunniesAnimations::NpcUiIcon::Unknown, result); return; }
	NormalizeShopItemReward(item);
	if(item.item.iconSpecified) { npcBunniesAnimations::CopyNpcUiAnimation(item.item.icon, result); return; }
	if(item.name != "" && (item.item.rewardType == npcBunniesQuests::RewardType::RewardCoins || item.item.rewardType == npcBunniesQuests::RewardType::RewardGems))
	{
		int nameIcon = GetShopItemIconIdFromName(item.name);
		if(nameIcon != npcBunniesAnimations::NpcUiIcon::Unknown) { npcBunniesAnimations::GetNpcUiIcon(nameIcon, result); return; }
	}
	npcBunniesAnimations::GetRewardUiAnimation(item.item, result);
}

// END NPC SHOP REGION

}