feat: add /use command for inventory items with effects, implement XP boosts, and enhance scheduler for temporary role removal.

This commit is contained in:
syntaxbullet
2025-12-15 23:22:51 +01:00
parent 1d4263e178
commit d3ade218ec
6 changed files with 230 additions and 140 deletions

View File

@@ -0,0 +1,95 @@
import { createCommand } from "@/lib/utils";
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { userService } from "@/modules/user/user.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { inventory, items } from "@/db/schema";
import { eq, and, like } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import type { ItemUsageData } from "@/lib/types";
export const use = createCommand({
data: new SlashCommandBuilder()
.setName("use")
.setDescription("Use an item from your inventory")
.addNumberOption(option =>
option.setName("item")
.setDescription("The item to use")
.setRequired(true)
.setAutocomplete(true)
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) {
if (interaction.isAutocomplete()) {
const focusedValue = interaction.options.getFocused();
const userId = interaction.user.id;
// Fetch owned items that are usable
const userInventory = await DrizzleClient.query.inventory.findMany({
where: eq(inventory.userId, BigInt(userId)),
with: {
item: true
},
limit: 10
});
const filtered = userInventory.filter(entry => {
const matchName = entry.item.name.toLowerCase().includes(focusedValue.toLowerCase());
const usageData = entry.item.usageData as ItemUsageData | null;
const isUsable = usageData && usageData.effects && usageData.effects.length > 0;
return matchName && isUsable;
});
await interaction.respond(
filtered.map(entry => ({ name: `${entry.item.name} (${entry.quantity})`, value: entry.item.id }))
);
}
return;
}
await interaction.deferReply();
const itemId = interaction.options.getNumber("item", true);
const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
try {
const result = await inventoryService.useItem(user.id, itemId);
// Check for side effects like Role assignment that need Discord API access
// The service returns the usageData, so we can re-check simple effects or just check the results log?
// Actually, we put "TEMP_ROLE" inside results log, AND we can check usageData here for strict role assignment if we want to separate concerns.
// But for now, let's rely on the service to have handled database state, and we handle Discord state here if needed?
// WAIT - I put the role assignment placeholder in the service but it returned a result string.
// The service cannot assign the role directly because it doesn't have the member object easily (requires fetching).
// So we should iterate results or usageData here.
const usageData = result.usageData;
if (usageData) {
for (const effect of usageData.effects) {
if (effect.type === 'TEMP_ROLE') {
try {
const member = await interaction.guild?.members.fetch(user.id);
if (member) {
await member.roles.add(effect.roleId);
}
} catch (e) {
console.error("Failed to assign role in /use command:", e);
result.results.push("⚠️ Failed to assign role (Check bot permissions)");
}
}
}
}
const embed = createSuccessEmbed(
result.results.map(r => `${r}`).join("\n"),
`Used ${result.usageData.effects.length > 0 ? 'Item' : 'Item'}` // Generic title, improves below
);
embed.setTitle("Item Used!");
await interaction.editReply({ embeds: [embed] });
} catch (error: any) {
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
}
}
});