feat: Implement cumulative XP leveling system with new helper functions and update XP bar to show progress within the current level.

This commit is contained in:
syntaxbullet
2025-12-24 18:52:40 +01:00
parent 8c28fe60fc
commit eaaf569f4f
2 changed files with 47 additions and 20 deletions

View File

@@ -97,9 +97,12 @@ export async function generateStudentIdCard(data: StudentCardData): Promise<Buff
ctx.restore(); ctx.restore();
// Draw XP Bar // Draw XP Bar
const xpForNextLevel = levelingService.getXpForLevel(data.level); const xpForThisLevel = levelingService.getXpForNextLevel(data.level); // The total size of the current level bucket
const xpAtStartOfLevel = levelingService.getXpToReachLevel(data.level); // The accumulated XP when this level started
const currentLevelProgress = Number(data.xp) - xpAtStartOfLevel; // How much XP into this level
const xpBarMaxWidth = 382; const xpBarMaxWidth = 382;
const xpBarWidth = xpBarMaxWidth * Number(data.xp) / Number(xpForNextLevel); const xpBarWidth = Math.max(0, Math.min(xpBarMaxWidth, xpBarMaxWidth * currentLevelProgress / xpForThisLevel));
const xpBarHeight = 3; const xpBarHeight = 3;
ctx.save(); ctx.save();
ctx.fillStyle = '#B3AD93'; ctx.fillStyle = '#B3AD93';

View File

@@ -5,12 +5,42 @@ import { config } from "@/lib/config";
import type { Transaction } from "@/lib/types"; import type { Transaction } from "@/lib/types";
export const levelingService = { export const levelingService = {
// Calculate XP required for a specific level // Calculate total XP required to REACH a specific level (Cumulative)
getXpForLevel: (level: number) => { // Level 1 = 0 XP
return Math.floor(config.leveling.base * Math.pow(level, config.leveling.exponent)); // Level 2 = Base * (1^Exp)
// Level 3 = Level 2 + Base * (2^Exp)
// ...
getXpToReachLevel: (level: number) => {
let total = 0;
for (let l = 1; l < level; l++) {
total += Math.floor(config.leveling.base * Math.pow(l, config.leveling.exponent));
}
return total;
}, },
// Pure XP addition - No cooldown checks // Calculate level from Total XP
getLevelFromXp: (totalXp: bigint) => {
let level = 1;
let xp = Number(totalXp);
while (true) {
// XP needed to complete current level and reach next
const xpForNext = Math.floor(config.leveling.base * Math.pow(level, config.leveling.exponent));
if (xp < xpForNext) {
return level;
}
xp -= xpForNext;
level++;
}
},
// Get XP needed to complete the current level (for calculating next level threshold in isolation)
// Used internally or for display
getXpForNextLevel: (currentLevel: number) => {
return Math.floor(config.leveling.base * Math.pow(currentLevel, config.leveling.exponent));
},
// Cumulative XP addition
addXp: async (id: string, amount: bigint, tx?: Transaction) => { addXp: async (id: string, amount: bigint, tx?: Transaction) => {
return await withTransaction(async (txFn) => { return await withTransaction(async (txFn) => {
// Get current state // Get current state
@@ -20,30 +50,24 @@ export const levelingService = {
if (!user) throw new Error("User not found"); if (!user) throw new Error("User not found");
let newXp = (user.xp ?? 0n) + amount; const currentXp = user.xp ?? 0n;
let currentLevel = user.level ?? 1; const newXp = currentXp + amount;
let levelUp = false;
// Check for level up loop // Calculate new level based on TOTAL accumulated XP
let xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel)); const newLevel = levelingService.getLevelFromXp(newXp);
const currentLevel = user.level ?? 1;
while (newXp >= xpForNextLevel) { const levelUp = newLevel > currentLevel;
newXp -= xpForNextLevel;
currentLevel++;
levelUp = true;
xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel));
}
// Update user // Update user
const [updatedUser] = await txFn.update(users) const [updatedUser] = await txFn.update(users)
.set({ .set({
xp: newXp, xp: newXp,
level: currentLevel, level: newLevel,
}) })
.where(eq(users.id, BigInt(id))) .where(eq(users.id, BigInt(id)))
.returning(); .returning();
return { user: updatedUser, levelUp, currentLevel }; return { user: updatedUser, levelUp, currentLevel: newLevel };
}, tx); }, tx);
}, },