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();
// 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 xpBarWidth = xpBarMaxWidth * Number(data.xp) / Number(xpForNextLevel);
const xpBarWidth = Math.max(0, Math.min(xpBarMaxWidth, xpBarMaxWidth * currentLevelProgress / xpForThisLevel));
const xpBarHeight = 3;
ctx.save();
ctx.fillStyle = '#B3AD93';

View File

@@ -5,12 +5,42 @@ import { config } from "@/lib/config";
import type { Transaction } from "@/lib/types";
export const levelingService = {
// Calculate XP required for a specific level
getXpForLevel: (level: number) => {
return Math.floor(config.leveling.base * Math.pow(level, config.leveling.exponent));
// Calculate total XP required to REACH a specific level (Cumulative)
// Level 1 = 0 XP
// 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) => {
return await withTransaction(async (txFn) => {
// Get current state
@@ -20,30 +50,24 @@ export const levelingService = {
if (!user) throw new Error("User not found");
let newXp = (user.xp ?? 0n) + amount;
let currentLevel = user.level ?? 1;
let levelUp = false;
const currentXp = user.xp ?? 0n;
const newXp = currentXp + amount;
// Check for level up loop
let xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel));
while (newXp >= xpForNextLevel) {
newXp -= xpForNextLevel;
currentLevel++;
levelUp = true;
xpForNextLevel = BigInt(levelingService.getXpForLevel(currentLevel));
}
// Calculate new level based on TOTAL accumulated XP
const newLevel = levelingService.getLevelFromXp(newXp);
const currentLevel = user.level ?? 1;
const levelUp = newLevel > currentLevel;
// Update user
const [updatedUser] = await txFn.update(users)
.set({
xp: newXp,
level: currentLevel,
level: newLevel,
})
.where(eq(users.id, BigInt(id)))
.returning();
return { user: updatedUser, levelUp, currentLevel };
return { user: updatedUser, levelUp, currentLevel: newLevel };
}, tx);
},