forked from syntaxbullet/aurorabot
feat: Implement structured lootbox results with image support and display referenced items in shop listings.
This commit is contained in:
@@ -86,7 +86,11 @@ export const handleLootbox: EffectHandler = async (userId, effect, txFn) => {
|
||||
|
||||
// Process Winner
|
||||
if (winner.type === LootType.NOTHING) {
|
||||
return winner.message || "You found nothing inside.";
|
||||
return {
|
||||
type: 'LOOTBOX_RESULT',
|
||||
rewardType: 'NOTHING',
|
||||
message: winner.message || "You found nothing inside."
|
||||
};
|
||||
}
|
||||
|
||||
if (winner.type === LootType.CURRENCY) {
|
||||
@@ -96,7 +100,12 @@ export const handleLootbox: EffectHandler = async (userId, effect, txFn) => {
|
||||
}
|
||||
if (amount > 0) {
|
||||
await economyService.modifyUserBalance(userId, BigInt(amount), TransactionType.LOOTBOX, 'Lootbox Reward', null, txFn);
|
||||
return winner.message || `You found ${amount} 🪙!`;
|
||||
return {
|
||||
type: 'LOOTBOX_RESULT',
|
||||
rewardType: 'CURRENCY',
|
||||
amount: amount,
|
||||
message: winner.message || `You found ${amount} 🪙!`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +116,12 @@ export const handleLootbox: EffectHandler = async (userId, effect, txFn) => {
|
||||
}
|
||||
if (amount > 0) {
|
||||
await levelingService.addXp(userId, BigInt(amount), txFn);
|
||||
return winner.message || `You gained ${amount} XP!`;
|
||||
return {
|
||||
type: 'LOOTBOX_RESULT',
|
||||
rewardType: 'XP',
|
||||
amount: amount,
|
||||
message: winner.message || `You gained ${amount} XP!`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +137,18 @@ export const handleLootbox: EffectHandler = async (userId, effect, txFn) => {
|
||||
where: (items: any, { eq }: any) => eq(items.id, winner.itemId!)
|
||||
});
|
||||
if (item) {
|
||||
return winner.message || `You found ${quantity > 1 ? quantity + 'x ' : ''}**${item.name}**!`;
|
||||
return {
|
||||
type: 'LOOTBOX_RESULT',
|
||||
rewardType: 'ITEM',
|
||||
amount: Number(quantity),
|
||||
item: {
|
||||
name: item.name,
|
||||
rarity: item.rarity,
|
||||
description: item.description,
|
||||
image: item.imageUrl || item.iconUrl
|
||||
},
|
||||
message: winner.message || `You found ${quantity > 1 ? quantity + 'x ' : ''}**${item.name}**!`
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch item name for lootbox message", e);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import type { Transaction } from "@shared/lib/types";
|
||||
|
||||
export type EffectHandler = (userId: string, effect: any, txFn: Transaction) => Promise<string>;
|
||||
export type EffectHandler = (userId: string, effect: any, txFn: Transaction) => Promise<any>;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
import { EmbedBuilder, AttachmentBuilder } from "discord.js";
|
||||
import type { ItemUsageData } from "@shared/lib/types";
|
||||
import { EffectType } from "@shared/lib/constants";
|
||||
import { resolveAssetUrl } from "@shared/lib/assets";
|
||||
import { resolveAssetUrl, isLocalAssetUrl } from "@shared/lib/assets";
|
||||
import { join } from "path";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
/**
|
||||
* Inventory entry with item details
|
||||
@@ -32,25 +34,107 @@ export function getInventoryEmbed(items: InventoryEntry[], username: string): Em
|
||||
/**
|
||||
* Creates an embed showing the results of using an item
|
||||
*/
|
||||
export function getItemUseResultEmbed(results: string[], item?: { name: string, iconUrl: string | null, usageData: any }): EmbedBuilder {
|
||||
const description = results.map(r => `• ${r}`).join("\n");
|
||||
export function getItemUseResultEmbed(results: any[], item?: { name: string, iconUrl: string | null, usageData: any }): { embed: EmbedBuilder, files: AttachmentBuilder[] } {
|
||||
const embed = new EmbedBuilder();
|
||||
const files: AttachmentBuilder[] = [];
|
||||
const otherMessages: string[] = [];
|
||||
let lootResult: any = null;
|
||||
|
||||
// Check if it was a lootbox
|
||||
const isLootbox = item?.usageData?.effects?.some((e: any) => e.type === EffectType.LOOTBOX);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(description)
|
||||
.setColor(isLootbox ? 0xFFD700 : 0x2ecc71); // Gold for lootbox, Green otherwise
|
||||
|
||||
if (isLootbox && item) {
|
||||
embed.setTitle(`🎁 ${item.name} Opened!`);
|
||||
const resolvedIconUrl = resolveAssetUrl(item.iconUrl);
|
||||
if (resolvedIconUrl) {
|
||||
embed.setThumbnail(resolvedIconUrl);
|
||||
for (const res of results) {
|
||||
if (typeof res === 'object' && res.type === 'LOOTBOX_RESULT') {
|
||||
lootResult = res;
|
||||
} else {
|
||||
otherMessages.push(typeof res === 'string' ? `• ${res}` : `• ${JSON.stringify(res)}`);
|
||||
}
|
||||
} else {
|
||||
embed.setTitle(item ? `✅ Used ${item.name}` : "✅ Item Used!");
|
||||
}
|
||||
|
||||
return embed;
|
||||
// Default Configuration
|
||||
const isLootbox = item?.usageData?.effects?.some((e: any) => e.type === EffectType.LOOTBOX);
|
||||
embed.setColor(isLootbox ? 0xFFD700 : 0x2ecc71); // Gold for lootbox, Green otherwise by default
|
||||
embed.setTimestamp();
|
||||
|
||||
if (lootResult) {
|
||||
embed.setTitle(`🎁 ${item?.name || "Lootbox"} Opened!`);
|
||||
|
||||
if (lootResult.rewardType === 'ITEM' && lootResult.item) {
|
||||
const i = lootResult.item;
|
||||
const amountStr = lootResult.amount > 1 ? `x${lootResult.amount}` : '';
|
||||
|
||||
// Rarity Colors
|
||||
const rarityColors: Record<string, number> = {
|
||||
'C': 0x95A5A6, // Gray
|
||||
'R': 0x3498DB, // Blue
|
||||
'SR': 0x9B59B6, // Purple
|
||||
'SSR': 0xF1C40F // Gold
|
||||
};
|
||||
|
||||
const rarityKey = i.rarity || 'C';
|
||||
if (rarityKey in rarityColors) {
|
||||
embed.setColor(rarityColors[rarityKey] ?? 0x95A5A6);
|
||||
} else {
|
||||
embed.setColor(0x95A5A6);
|
||||
}
|
||||
|
||||
if (i.image) {
|
||||
if (isLocalAssetUrl(i.image)) {
|
||||
const imagePath = join(process.cwd(), "bot/assets/graphics", i.image.replace(/^\/?assets\//, ""));
|
||||
if (existsSync(imagePath)) {
|
||||
const imageName = defaultName(i.image);
|
||||
if (!files.find(f => f.name === imageName)) {
|
||||
files.push(new AttachmentBuilder(imagePath, { name: imageName }));
|
||||
}
|
||||
embed.setImage(`attachment://${imageName}`);
|
||||
}
|
||||
} else {
|
||||
const imgUrl = resolveAssetUrl(i.image);
|
||||
if (imgUrl) embed.setImage(imgUrl);
|
||||
}
|
||||
}
|
||||
|
||||
embed.setDescription(`**You found ${i.name} ${amountStr}!**\n${i.description || '_'}`);
|
||||
embed.addFields({ name: 'Rarity', value: rarityKey, inline: true });
|
||||
|
||||
} else if (lootResult.rewardType === 'CURRENCY') {
|
||||
embed.setColor(0xF1C40F);
|
||||
embed.setDescription(`**You found ${lootResult.amount.toLocaleString()} 🪙 AU!**`);
|
||||
} else if (lootResult.rewardType === 'XP') {
|
||||
embed.setColor(0x2ECC71); // Green
|
||||
embed.setDescription(`**You gained ${lootResult.amount.toLocaleString()} XP!**`);
|
||||
} else {
|
||||
// Nothing or Message
|
||||
embed.setDescription(lootResult.message);
|
||||
embed.setColor(0x95A5A6); // Gray
|
||||
}
|
||||
|
||||
} else {
|
||||
// Standard item usage
|
||||
embed.setTitle(item ? `✅ Used ${item.name}` : "✅ Item Used!");
|
||||
embed.setDescription(otherMessages.join("\n") || "Effect applied.");
|
||||
|
||||
if (isLootbox && item && item.iconUrl) {
|
||||
if (isLocalAssetUrl(item.iconUrl)) {
|
||||
const iconPath = join(process.cwd(), "bot/assets/graphics", item.iconUrl.replace(/^\/?assets\//, ""));
|
||||
if (existsSync(iconPath)) {
|
||||
const iconName = defaultName(item.iconUrl);
|
||||
if (!files.find(f => f.name === iconName)) {
|
||||
files.push(new AttachmentBuilder(iconPath, { name: iconName }));
|
||||
}
|
||||
embed.setThumbnail(`attachment://${iconName}`);
|
||||
}
|
||||
} else {
|
||||
const resolvedIconUrl = resolveAssetUrl(item.iconUrl);
|
||||
if (resolvedIconUrl) embed.setThumbnail(resolvedIconUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (otherMessages.length > 0 && lootResult) {
|
||||
embed.addFields({ name: "Other Effects", value: otherMessages.join("\n") });
|
||||
}
|
||||
|
||||
return { embed, files };
|
||||
}
|
||||
|
||||
function defaultName(path: string): string {
|
||||
return path.split("/").pop() || "image.png";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user