Skip to content

Arquitectura del Sistema - Aureum

Esta documentación está dirigida a desarrolladores. Para información sobre el juego dirigida a jugadores, ver Primeros pasos, Combate, Items y Hechizos.

Sistema de Atributos y Modificadores

Arquitectura Base vs. Calculado

El sistema de atributos sigue una arquitectura robusta que separa claramente los valores base de los valores finales calculados:

┌─────────────────────────────────────────┐
│         BaseAttributes                  │
│  (Valores base del personaje)           │
│  - hp: 100                              │
│  - maxHp: 100                           │
│  - mana: 50                             │
│  - maxMana: 50                          │
│  (NUNCA se modifican directamente)     │
└─────────────────────────────────────────┘

              │ + modificadores

┌─────────────────────────────────────────┐
│      EquippedItems (Map)                │
│  - Slot1: Item con +20 maxHp           │
│  - Slot2: Item con +10 maxMana         │
└─────────────────────────────────────────┘

              │ getAllModifiers()

┌─────────────────────────────────────────┐
│      ActiveBuffs (Map)                  │
│  - Buff1: +5 damage (expira en 30s)    │
│  - Buff2: +10% crit (expira en 60s)    │
└─────────────────────────────────────────┘

              │ calculateSetBonuses()

┌─────────────────────────────────────────┐
│      ItemSets                           │
│  - warrior_basic: 3 piezas → bonos     │
└─────────────────────────────────────────┘

              │ getFinalAttributes()

┌─────────────────────────────────────────┐
│      Atributos Finales (Cacheado)       │
│  - maxHp: 100 + 20 + 30 = 150          │
│  - maxMana: 50 + 10 = 60                │
│  - hp: min(100, 150) = 100              │
└─────────────────────────────────────────┘

Sistema de Cache Inteligente

Los atributos finales se calculan dinámicamente, pero se cachean para optimizar el rendimiento:

  • Cache automático: Los atributos finales se cachean después del primer cálculo.
  • Invalidación inteligente: El cache se invalida automáticamente cuando:
    • Se equipa o desequipa un item.
    • Se aplica o remueve un buff.
    • Se registran nuevos sets de items.
  • Versión de modificadores: Cada cambio incrementa un contador de versión que permite verificar si el cache es válido.

Mantenimiento de Porcentaje de Recursos

Cuando se equipa o desequipa un item que cambia los máximos de HP/Mana/Stamina, el sistema mantiene el porcentaje actual:

Ejemplo:

  • Estado inicial: 50/150 HP (33%)
  • Equipas item que aumenta maxHp a 200
  • Resultado: 66/200 HP (mantiene 33%)

Implementación:

typescript
const oldMaxHp = player.getFinalAttributes().maxHp;
// ... equipar/desequipar item ...
const newMaxHp = player.getFinalAttributes().maxHp;
const hpPercentage = player.baseAttributes.hp / oldMaxHp;
player.baseAttributes.hp = Math.min(newMaxHp, Math.floor(oldMaxHp * hpPercentage));

Persistencia de Datos

Regla crítica: En la base de datos solo se guardan valores base, nunca valores con modificadores.

  • Al guardar: Se guarda player.baseAttributes.hp (sin modificadores).
  • Al cargar: Se carga como HP base y luego se calculan los atributos finales con los items equipados.
  • Razón: Evita que los bonos de items se acumulen incorrectamente al recargar el personaje.

Sistema de Buffs Temporales

Los buffs son aplicados por consumibles y hechizos (ver efectos buff / debuff / status_effect), y también pueden venir de auras toggle que tiquean cada N ms. Los modificadores que aplican son del mismo tipo que los de items.

Características

  • Duración: Cada buff tiene un timestamp de expiración (expiresAt).
  • Auto-limpieza: Los buffs expirados se eliminan automáticamente en cada tick del servidor.
  • Modificadores: Los buffs aplican modificadores igual que los items.
  • Fuente: Cada buff puede tener una fuente identificada (item, spell, consumable, etc.).

Ciclo de Vida

  1. Aplicación: player.applyBuff(buff) - Agrega el buff y marca el cache como inválido.
  2. Cálculo: Los buffs activos se incluyen en getAllModifiers().
  3. Expiración: En cada tick, cleanupExpiredBuffs() remueve los buffs expirados.
  4. Remoción manual: player.removeBuff(buffId) - Remueve un buff específico.

Ejemplo de Uso

typescript
// Aplicar buff de poción de fuerza (30 segundos)
player.applyBuff({
  id: 'potion_strength',
  name: 'Poción de Fuerza',
  modifiers: { minDamage: 5, maxDamage: 10 },
  expiresAt: Date.now() + 30000,
  source: 'consumable'
});

// El buff se aplica automáticamente en getFinalAttributes()
// Después de 30 segundos, se remueve automáticamente

Sistema de Sets de Items

Funcionamiento

  1. Definición: Los sets se definen en shared/data/itemSets.ts.
  2. Agrupación: Los items tienen un campo setId que los agrupa.
  3. Conteo: El servidor cuenta automáticamente cuántas piezas de cada set están equipadas.
  4. Bonos: Se aplican todos los bonos que cumplan el requisito de piezas.

Ejemplo

Si tienes 3 piezas del Set del Guerrero:

  • Obtienes el bono de 2 piezas: +30 HP, +5 Defensa Física
  • Obtienes el bono de 3 piezas: +50 HP, +10 Defensa Física, +3-5 Daño
  • Total: +80 HP, +15 Defensa Física, +3-5 Daño

Acceso desde Cliente

Los sets están disponibles tanto en el servidor como en el cliente:

typescript
import { getAllItemSets, getItemSet } from '@shared/data/itemSets';

// Obtener todos los sets
const allSets = getAllItemSets();

// Obtener un set específico
const warriorSet = getItemSet('warrior_basic');

// Verificar cuántas piezas están equipadas
function getEquippedSetPieces(equippedItems: Map<string, Item>, setId: string): number {
  let count = 0;
  for (const item of equippedItems.values()) {
    if (item.setId === setId) {
      count++;
    }
  }
  return count;
}

Sistema de Renderizado de Cascos

Separación Head vs. Helm

  • headId: ID de la cabeza del personaje (determinada por raza/sexo, siempre visible).
  • helmId: ID del casco equipado (se dibuja encima de la cabeza).

Gráficos

Los cascos usan sprites en /assets/head/headX.png donde X es el helmId:

  • head1.png - Casco de Guerrero
  • head2.png - Casco de Hierro

Renderizado

  1. Se dibuja primero la cabeza base (headId).
  2. Si hay un casco equipado (helmId > 0), se dibuja encima en la misma posición.
  3. Ambos sprites se actualizan cuando cambia la dirección del personaje.

Optimizaciones Implementadas

Cache de Atributos Finales

  • Problema: Calcular atributos finales en cada acceso es costoso.
  • Solución: Cache con invalidación automática basada en versión de modificadores.
  • Beneficio: Mejora significativa del rendimiento en cálculos frecuentes.

Cálculo Dinámico de Modificadores

  • Ventaja: No hay que "deshacer" modificadores manualmente.
  • Funcionamiento: Los modificadores se calculan desde cero cada vez, sumando solo los items/buffs/sets activos.
  • Resultado: Imposible que los modificadores se acumulen incorrectamente.

Limpieza Automática de Buffs

  • Problema: Buffs expirados ocuparían memoria indefinidamente.
  • Solución: Auto-limpieza en cada tick del servidor.
  • Beneficio: Gestión automática de recursos sin intervención manual.

Mejores Prácticas

Para Desarrolladores

  1. Nunca modificar baseAttributes directamente con valores calculados.
  2. Siempre usar getFinalAttributes() para obtener valores con modificadores.
  3. Guardar solo valores base en la base de datos.
  4. Invalidar el cache cuando se modifiquen items o buffs (automático en los métodos públicos).

Para Diseñadores de Items

  1. Los modificadores se suman: Si dos items dan +10 HP cada uno, el total es +20 HP.
  2. Los sets otorgan bonos adicionales: Se suman a los modificadores individuales de los items.
  3. Los buffs temporales se suman: Se combinan con modificadores de items y sets.

Flujo Completo de Cálculo

1. Player tiene baseAttributes: { hp: 100, maxHp: 100 }
2. Equipa item con +20 maxHp
   → getAllModifiers() suma: { hp: 20 }
   → getFinalAttributes() calcula: { hp: 100, maxHp: 120 }
   → Cache se guarda
3. Aplica buff con +10 maxHp (30 segundos)
   → getAllModifiers() suma: { hp: 20 + 10 }
   → getFinalAttributes() calcula: { hp: 100, maxHp: 130 }
   → Cache se invalida y recalcula
4. Equipa 3 piezas del Set del Guerrero
   → calculateSetBonuses() suma bonos de 2 y 3 piezas
   → getAllModifiers() incluye bonos del set
   → getFinalAttributes() calcula con todos los modificadores
5. Buff expira (30 segundos pasaron)
   → cleanupExpiredBuffs() remueve el buff
   → Cache se invalida
   → getFinalAttributes() recalcula sin el buff

Wiki del jugador de Aureum.