import type { RowDataPacket } from 'mysql2' import { db, DB } from './db' const IMG = '/ns-themes/ns-ryu/ns-images' // Clases (orden del original) → slug de color + etiqueta. export const CLASS_ORDER = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11] export const CLASS_INFO: Record = { 1: { key: 'warrior', label: 'Guerrero' }, 2: { key: 'paladin', label: 'Paladín' }, 3: { key: 'hunter', label: 'Cazador' }, 4: { key: 'rogue', label: 'Pícaro' }, 5: { key: 'priest', label: 'Sacerdote' }, 6: { key: 'death-knight', label: 'C. de la M.' }, 7: { key: 'shaman', label: 'Chamán' }, 8: { key: 'mage', label: 'Mago' }, 9: { key: 'warlock', label: 'Brujo' }, 11: { key: 'druid', label: 'Druida' }, } // Razas → slug + etiqueta + facción. Orden del original para «Primeros del reino». export const RACE_ORDER = [7, 10, 11, 3, 1, 4, 2, 6, 8, 5] export const RACE_INFO: Record = { 1: { key: 'human', label: 'Humano', faction: 'alliance' }, 2: { key: 'orc', label: 'Orco', faction: 'horde' }, 3: { key: 'dwarf', label: 'Enano', faction: 'alliance' }, 4: { key: 'night-elf', label: 'Elfo de la noche', faction: 'alliance' }, 5: { key: 'undead', label: 'No-muerto', faction: 'horde' }, 6: { key: 'tauren', label: 'Tauren', faction: 'horde' }, 7: { key: 'gnome', label: 'Gnomo', faction: 'alliance' }, 8: { key: 'troll', label: 'Trol', faction: 'horde' }, 10: { key: 'blood-elf', label: 'Elfo de sangre', faction: 'horde' }, 11: { key: 'draenei', label: 'Draenei', faction: 'alliance' }, } export function classChest(id: number) { return `${IMG}/ns-classes/${CLASS_INFO[id]?.key ?? 'warrior'}-chest.webp` } export function classMedium(id: number) { return `${IMG}/ns-classes/${CLASS_INFO[id]?.key ?? 'warrior'}-medium.jpg` } export function raceBig(race: number, gender: number) { return `${IMG}/ns-races/big-${RACE_INFO[race]?.key ?? 'human'}-${gender === 0 ? 'male' : 'female'}.webp` } export const ALLIANCE_ICON = `${IMG}/ns-icons/alliance-no-border.webp` export const HORDE_ICON = `${IMG}/ns-icons/horde-no-border.webp` export const ACH80_ICON = `${IMG}/ns-icons/achievement-level-80.jpg` // Logro «Nivel 80» (logros de nivel WotLK: 6=Nv10, 7=Nv20 … 12=Nv70, 13=Nv80). const ACH_LEVEL80 = 13 export interface ClassStat { id: number; key: string; total: number; alliance: number; horde: number } export interface FirstEntry { icon: string; label: string; name: string; classKey: string; date: string } export interface AchRow { name: string; race: number; class: number; classKey: string; value: number } export interface HonorRow { name: string; race: number; class: number; classKey: string; total: number; today: number } export interface PlayersData { classStats: ClassStat[] totals: { total: number; alliance: number; horde: number } firsts: FirstEntry[] topAch: AchRow[] topHonor: HonorRow[] } function fmtTs(ts: number): string { const d = new Date(ts * 1000) const p = (n: number) => String(n).padStart(2, '0') return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())} ${p(d.getDate())}-${p(d.getMonth() + 1)}-${d.getFullYear()}` } export async function getPlayersData(): Promise { const c = db(DB.characters) // 1) Personajes creados por clase (y facción). const classStats: ClassStat[] = CLASS_ORDER.map((id) => ({ id, key: CLASS_INFO[id].key, total: 0, alliance: 0, horde: 0 })) const totals = { total: 0, alliance: 0, horde: 0 } try { const [rows] = await c.query('SELECT class, race, COUNT(*) AS n FROM characters GROUP BY class, race') for (const r of rows) { const stat = classStats.find((s) => s.id === r.class) const n = Number(r.n) const faction = RACE_INFO[r.race]?.faction totals.total += n if (faction === 'alliance') totals.alliance += n else if (faction === 'horde') totals.horde += n if (stat) { stat.total += n if (faction === 'alliance') stat.alliance += n else if (faction === 'horde') stat.horde += n } } } catch { /* BD de personajes puede no estar poblada */ } // 2) Primeros del reino - Nivel 80 (logro 20, por fecha). const firsts: FirstEntry[] = [] try { const [rows] = await c.query( `SELECT ca.date AS date, ch.name AS name, ch.class AS class, ch.race AS race, ch.gender AS gender FROM character_achievement ca JOIN characters ch ON ch.guid = ca.guid WHERE ca.achievement = ? ORDER BY ca.date ASC`, [ACH_LEVEL80], ) if (rows.length) { const overall = rows[0] firsts.push({ icon: ACH80_ICON, label: 'Nivel 80', name: overall.name, classKey: CLASS_INFO[overall.class]?.key ?? '', date: fmtTs(Number(overall.date)) }) // Primero por clase. for (const id of CLASS_ORDER) { const row = rows.find((r) => r.class === id) if (row) firsts.push({ icon: classMedium(id), label: `${CLASS_INFO[id].label} nivel 80`, name: row.name, classKey: CLASS_INFO[id].key, date: fmtTs(Number(row.date)) }) } // Primero por raza. for (const race of RACE_ORDER) { const row = rows.find((r) => r.race === race) if (row) firsts.push({ icon: raceBig(race, Number(row.gender)), label: `${RACE_INFO[race].label} nivel 80`, name: row.name, classKey: CLASS_INFO[row.class]?.key ?? '', date: fmtTs(Number(row.date)) }) } } } catch { /* ignore */ } // 3) Top de PUNTOS de logros (SUMA de points vía acore_world.achievement_points, // poblada desde el DB2 Achievement del build 3.4.3). let topAch: AchRow[] = [] try { const [rows] = await c.query( `SELECT ch.name AS name, ch.race AS race, ch.class AS class, SUM(ap.points) AS pts FROM character_achievement ca JOIN characters ch ON ch.guid = ca.guid JOIN acore_world.achievement_points ap ON ap.id = ca.achievement GROUP BY ca.guid, ch.name, ch.race, ch.class ORDER BY pts DESC LIMIT 10`, ) topAch = rows.map((r) => ({ name: r.name, race: r.race, class: r.class, classKey: CLASS_INFO[r.class]?.key ?? '', value: Number(r.pts) })) } catch { /* ignore */ } // 4) Top de muertes con honor. let topHonor: HonorRow[] = [] try { const [rows] = await c.query( 'SELECT name, race, class, totalKills, todayKills FROM characters ORDER BY totalKills DESC LIMIT 10', ) topHonor = rows.map((r) => ({ name: r.name, race: r.race, class: r.class, classKey: CLASS_INFO[r.class]?.key ?? '', total: Number(r.totalKills), today: Number(r.todayKills) })) } catch { /* ignore */ } return { classStats, totals, firsts, topAch, topHonor } }