Modelo de Dominio - vykymoon/rpg-character-creator GitHub Wiki

Paquete: com.proyecto.rpg.model. Diagrama de clases completo en UML.png (raíz del repositorio).

classDiagram
    class Character {
        -String id
        -String name
        -Race race
        -CharacterClass characterClass
        -List~Skill~ skills
        -List~Outfit~ outfits
        -int strength
        -int dexterity
        -int intelligence
        -int vitality
        +recalculateStats()
        +clone() Character
    }
    class Race { id, name, description, baseStrength.. baseVitality }
    class CharacterClass { id, name, description, bonusStrength.. bonusVitality }
    class Skill { id, name, description, manaCost, allowedRaces, allowedClasses }
    class Outfit { id, name, slot, spriteRef, allowedRaces, allowedClasses }
    class CatalogRestriction { <<package-private>> }
    Character --> Race
    Character --> CharacterClass
    Character --> "0..*" Skill
    Character --> "0..*" Outfit
    Skill ..> CatalogRestriction
    Outfit ..> CatalogRestriction

Character

Personaje jugable. Implementa Cloneable (patrón Prototype).

  • id: UUID generado en el constructor. Es la clave con la que el DAO guarda, busca y elimina.
  • Stats derivados (strength, dexterity, intelligence, vitality): se calculan en recalculateStats():
stat final = stat base de la raza + bonus de la clase
  • clone(): copia superficial de los campos, pero con nuevo id y listas nuevas de habilidades y vestuario. Race, CharacterClass, Skill y Outfit se comparten porque son datos de catálogo inmutables.

Race y CharacterClass

Objetos inmutables (todos los campos final) creados solo por sus Factories.

  • Race define 4 stats base; CharacterClass define 4 bonificaciones.
  • equals/hashCode por id: permite que el ComboBox del wizard reselecione correctamente la raza o clase cuando el objeto proviene de otra instancia (por ejemplo, al releer un personaje desde JSON).

Ejemplo de cálculo — Elfo Mago:

FUE DES INT VIT
Elfo (base) 3 7 6 4
Mago (bonus) +0 +2 +6 +1
Personaje 3 9 12 5

Skill y Outfit

Ambos tienen listas allowedRaces y allowedClasses (lista vacía o null = disponible para todos).

Clase Campos propios
Skill manaCost
Outfit slot (armadura, casco, capa, escudo, botas) y spriteRef (ruta del PNG)

Ambas exponen:

  • isAvailableFor(race, characterClass): true solo si pasa la restricción de raza y la de clase.
  • getRequirementLabel(): texto corto del requisito (p. ej. "Solo Mago, Druida") o null.

Cada una tiene un constructor "suelto" sin restricciones, usado por las plantillas de CharacterPrototype.

CatalogRestriction

Clase de utilidad package-private que concentra la regla de restricción para que no se duplique entre Skill y Outfit:

  • Lista null o vacía ⇒ sin restricción. El caso null existe porque los personajes guardados antes de esta función no tienen el campo en characters.json y Gson lo deja en null.
  • Las entradas se comparan con el id o con el nombre visible, ignorando mayúsculas y espacios ("elfo" y "Elfo" funcionan igual). Así, renombrar una raza en el JSON no rompe las restricciones en silencio.

Dónde se valida cada regla

Regla Dónde
La UI oculta lo que no aplica Step3SkillsController, Step4OutfitController (filtran con isAvailableFor)
Validación dura (lanza IllegalStateException) CharacterBuilder.addSkill / addOutfit
Nombre, raza y clase obligatorios CharacterBuilder.build()
Nombre duplicado CharacterDAO.existsByName (sin distinguir mayúsculas)