Foro: avatares por clase del personaje principal
Los avatares salían vacíos (el original usaba getAvatar, que no teníamos). Ahora cada post muestra el avatar de FusionCMS correspondiente a la CLASE del personaje de más nivel de la cuenta que postea, usando datos reales de acore_characters. - Se descargan 10 avatares (uno por clase jugable en WotLK 3.4.3: 1..9 y 11) a public/forum/avatars/class-<id>.gif. - resolvePosters() consulta la clase del personaje top de cada cuenta y mapea a su avatar; sin personaje (o acore_characters vacía) cae a guerrero por defecto. - La página de tema pinta el avatar en el recuadro lateral. Nota: la tabla characters de AzerothCore usa `guid` como clave (no `id`); ordenar por `id` lanzaba error y devolvía siempre el avatar por defecto. Corregido. Verificado en producción: una cuenta con Paladín de nivel máximo muestra class-2.gif. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@@ -101,7 +101,12 @@ export default async function TopicPage({
|
||||
<span className="username">{author?.name ?? `#${post.poster}`}</span>
|
||||
</div>
|
||||
<div className={`user_avatar${isStaff ? ' isStaff' : ''}`}>
|
||||
<span />
|
||||
<span
|
||||
style={{
|
||||
background: `url(${author?.avatar ?? '/forum/avatars/class-1.gif'}) no-repeat center`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="user_info">
|
||||
<h3 className="post_field">
|
||||
|
||||
@@ -85,6 +85,16 @@ export interface PosterInfo {
|
||||
isStaff: boolean
|
||||
joindate: string | null
|
||||
postCount: number
|
||||
avatar: string
|
||||
}
|
||||
|
||||
// Avatar por clase (FusionCMS) del personaje de más nivel de la cuenta. Solo clases
|
||||
// jugables en WotLK 3.4.3 (1..9 y 11). Sin personaje → guerrero por defecto.
|
||||
const AVATAR_DIR = '/forum/avatars'
|
||||
const AVATAR_CLASSES = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 11])
|
||||
function avatarForClass(classId: number | undefined): string {
|
||||
const c = classId && AVATAR_CLASSES.has(classId) ? classId : 1
|
||||
return `${AVATAR_DIR}/class-${c}.gif`
|
||||
}
|
||||
|
||||
/** Nivel GM mínimo para considerarse «staff» en el foro (badge y color). */
|
||||
@@ -362,6 +372,23 @@ export async function resolvePosters(ids: number[]): Promise<Map<number, PosterI
|
||||
if (unique.length === 0) return map
|
||||
|
||||
const placeholders = unique.map(() => '?').join(',')
|
||||
|
||||
// Clase del personaje de más nivel de cada cuenta (para el avatar). Se tolera que
|
||||
// acore_characters no exista o esté vacía: el avatar cae a guerrero por defecto.
|
||||
const classByAccount = new Map<number, number>()
|
||||
try {
|
||||
const [chars] = await db(DB.characters).query<RowDataPacket[]>(
|
||||
`SELECT account, class FROM characters WHERE account IN (${placeholders}) ORDER BY level DESC, guid ASC`,
|
||||
unique,
|
||||
)
|
||||
for (const c of chars) {
|
||||
const acc = Number(c.account)
|
||||
if (!classByAccount.has(acc)) classByAccount.set(acc, Number(c.class))
|
||||
}
|
||||
} catch {
|
||||
/* sin personajes: avatar por defecto */
|
||||
}
|
||||
|
||||
try {
|
||||
const [accs] = await db(DB.auth).query<RowDataPacket[]>(
|
||||
`SELECT id, username, joindate FROM account WHERE id IN (${placeholders})`,
|
||||
@@ -383,6 +410,7 @@ export async function resolvePosters(ids: number[]): Promise<Map<number, PosterI
|
||||
isStaff: gmlevel >= STAFF_GMLEVEL,
|
||||
joindate: a.joindate ? new Date(a.joindate).toISOString() : null,
|
||||
postCount: 0,
|
||||
avatar: avatarForClass(classByAccount.get(Number(a.id))),
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
@@ -391,7 +419,15 @@ export async function resolvePosters(ids: number[]): Promise<Map<number, PosterI
|
||||
// Cualquier id no resuelto (cuenta borrada) recibe un placeholder.
|
||||
for (const id of unique) {
|
||||
if (!map.has(id)) {
|
||||
map.set(id, { id, name: `#${id}`, gmlevel: 0, isStaff: false, joindate: null, postCount: 0 })
|
||||
map.set(id, {
|
||||
id,
|
||||
name: `#${id}`,
|
||||
gmlevel: 0,
|
||||
isStaff: false,
|
||||
joindate: null,
|
||||
postCount: 0,
|
||||
avatar: avatarForClass(classByAccount.get(id)),
|
||||
})
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |