Files
discord-rpg-concept/src/modules/leveling/leveling.service.ts

107 lines
3.6 KiB
TypeScript

import { users, userTimers } from "@/db/schema";
import { eq, sql, and } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { GameConfig } from "@/config/game";
export const levelingService = {
// Calculate XP required for a specific level
getXpForLevel: (level: number) => {
return Math.floor(GameConfig.leveling.base * Math.pow(level, GameConfig.leveling.exponent));
},
// Pure XP addition - No cooldown checks
addXp: async (id: string, amount: bigint, tx?: any) => {
const execute = async (txFn: any) => {
// Get current state
const user = await txFn.query.users.findFirst({
where: eq(users.id, BigInt(id)),
});
if (!user) throw new Error("User not found");
let newXp = (user.xp ?? 0n) + amount;
let currentLevel = user.level ?? 1;
let levelUp = false;
// Check for level up loop
let xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel));
while (newXp >= xpForNextLevel) {
newXp -= xpForNextLevel;
currentLevel++;
levelUp = true;
xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel));
}
// Update user
const [updatedUser] = await txFn.update(users)
.set({
xp: newXp,
level: currentLevel,
})
.where(eq(users.id, BigInt(id)))
.returning();
return { user: updatedUser, levelUp, currentLevel };
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
})
}
},
// Handle chat XP with cooldowns
processChatXp: async (id: string, tx?: any) => {
const execute = async (txFn: any) => {
// check if an xp cooldown is in place
const cooldown = await txFn.query.userTimers.findFirst({
where: and(
eq(userTimers.userId, BigInt(id)),
eq(userTimers.type, 'COOLDOWN'),
eq(userTimers.key, 'chat_xp')
),
});
const now = new Date();
if (cooldown && cooldown.expiresAt > now) {
return { awarded: false, reason: 'cooldown' };
}
// Calculate random XP
const amount = BigInt(Math.floor(Math.random() * (GameConfig.leveling.chat.maxXp - GameConfig.leveling.chat.minXp + 1)) + GameConfig.leveling.chat.minXp);
// Add XP
const result = await levelingService.addXp(id, amount, txFn);
// Update/Set Cooldown
const nextReadyAt = new Date(now.getTime() + GameConfig.leveling.chat.cooldownMs);
await txFn.insert(userTimers)
.values({
userId: BigInt(id),
type: 'COOLDOWN',
key: 'chat_xp',
expiresAt: nextReadyAt,
})
.onConflictDoUpdate({
target: [userTimers.userId, userTimers.type, userTimers.key],
set: { expiresAt: nextReadyAt },
});
return { awarded: true, amount, ...result };
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
})
}
}
};