eq2go/internal/classes/classes.go

540 lines
18 KiB
Go

package classes
import (
"strings"
"sync"
)
// Class represents a character class with all its properties
type Class struct {
ID int8 `json:"id"`
Name string `json:"name"` // Uppercase name (WARRIOR)
DisplayName string `json:"display_name"` // Friendly name (Warrior)
BaseClass int8 `json:"base_class"` // Primary archetype
SecondaryBaseClass int8 `json:"secondary_base"` // Secondary archetype
ClassType string `json:"type"` // adventure/tradeskill/special
IsAdventure bool `json:"is_adventure"`
IsTradeskill bool `json:"is_tradeskill"`
}
// GetID returns the class ID
func (c *Class) GetID() int8 {
return c.ID
}
// GetName returns the uppercase class name (C++ API compatibility)
func (c *Class) GetName() string {
return c.Name
}
// GetDisplayName returns the friendly display name
func (c *Class) GetDisplayName() string {
return c.DisplayName
}
// GetBaseClass returns the base archetype (C++ API compatibility)
func (c *Class) GetBaseClass() int8 {
return c.BaseClass
}
// GetSecondaryBaseClass returns the secondary archetype (C++ API compatibility)
func (c *Class) GetSecondaryBaseClass() int8 {
return c.SecondaryBaseClass
}
// IsValid returns true if the class ID is valid
func (c *Class) IsValid() bool {
return c.ID >= MinClassID && c.ID <= MaxClassID
}
// Manager provides centralized class management with caching and lookup optimization
type Manager struct {
classes map[int8]*Class // ID -> Class
nameToID map[string]int8 // Uppercase name -> ID
displayNameMap map[int8]string // ID -> Display name
mutex sync.RWMutex
// Statistics
stats struct {
ClassesLoaded int32
LookupsPerformed int32
CacheHits int32
CacheMisses int32
}
}
// NewManager creates a new class manager
func NewManager() *Manager {
manager := &Manager{
classes: make(map[int8]*Class),
nameToID: make(map[string]int8),
displayNameMap: make(map[int8]string),
}
// Initialize with all classes
manager.initializeClasses()
return manager
}
// initializeClasses populates the manager with all class data
func (m *Manager) initializeClasses() {
m.mutex.Lock()
defer m.mutex.Unlock()
// Define all classes with their properties
classDefinitions := []struct {
id int8
name string
displayName string
baseClass int8
secondaryBase int8
classType string
isAdventure bool
isTradeskill bool
}{
// Base classes
{ClassCommoner, "COMMONER", "Commoner", ClassCommoner, ClassCommoner, ClassTypeAdventure, true, false},
{ClassFighter, "FIGHTER", "Fighter", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
{ClassPriest, "PRIEST", "Priest", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
{ClassMage, "MAGE", "Mage", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
{ClassScout, "SCOUT", "Scout", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
// Fighter subclasses
{ClassWarrior, "WARRIOR", "Warrior", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
{ClassGuardian, "GUARDIAN", "Guardian", ClassFighter, ClassWarrior, ClassTypeAdventure, true, false},
{ClassBerserker, "BERSERKER", "Berserker", ClassFighter, ClassWarrior, ClassTypeAdventure, true, false},
{ClassBrawler, "BRAWLER", "Brawler", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
{ClassMonk, "MONK", "Monk", ClassFighter, ClassBrawler, ClassTypeAdventure, true, false},
{ClassBruiser, "BRUISER", "Bruiser", ClassFighter, ClassBrawler, ClassTypeAdventure, true, false},
{ClassCrusader, "CRUSADER", "Crusader", ClassFighter, ClassCommoner, ClassTypeAdventure, true, false},
{ClassShadowknight, "SHADOWKNIGHT", "Shadowknight", ClassFighter, ClassCrusader, ClassTypeAdventure, true, false},
{ClassPaladin, "PALADIN", "Paladin", ClassFighter, ClassCrusader, ClassTypeAdventure, true, false},
// Priest subclasses
{ClassCleric, "CLERIC", "Cleric", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
{ClassTemplar, "TEMPLAR", "Templar", ClassPriest, ClassCleric, ClassTypeAdventure, true, false},
{ClassInquisitor, "INQUISITOR", "Inquisitor", ClassPriest, ClassCleric, ClassTypeAdventure, true, false},
{ClassDruid, "DRUID", "Druid", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
{ClassWarden, "WARDEN", "Warden", ClassPriest, ClassDruid, ClassTypeAdventure, true, false},
{ClassFury, "FURY", "Fury", ClassPriest, ClassDruid, ClassTypeAdventure, true, false},
{ClassShaman, "SHAMAN", "Shaman", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
{ClassMystic, "MYSTIC", "Mystic", ClassPriest, ClassShaman, ClassTypeAdventure, true, false},
{ClassDefiler, "DEFILER", "Defiler", ClassPriest, ClassShaman, ClassTypeAdventure, true, false},
// Mage subclasses
{ClassSorcerer, "SORCERER", "Sorcerer", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
{ClassWizard, "WIZARD", "Wizard", ClassMage, ClassSorcerer, ClassTypeAdventure, true, false},
{ClassWarlock, "WARLOCK", "Warlock", ClassMage, ClassSorcerer, ClassTypeAdventure, true, false},
{ClassEnchanter, "ENCHANTER", "Enchanter", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
{ClassIllusionist, "ILLUSIONIST", "Illusionist", ClassMage, ClassEnchanter, ClassTypeAdventure, true, false},
{ClassCoercer, "COERCER", "Coercer", ClassMage, ClassEnchanter, ClassTypeAdventure, true, false},
{ClassSummoner, "SUMMONER", "Summoner", ClassMage, ClassCommoner, ClassTypeAdventure, true, false},
{ClassConjuror, "CONJUROR", "Conjuror", ClassMage, ClassSummoner, ClassTypeAdventure, true, false},
{ClassNecromancer, "NECROMANCER", "Necromancer", ClassMage, ClassSummoner, ClassTypeAdventure, true, false},
// Scout subclasses
{ClassRogue, "ROGUE", "Rogue", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
{ClassSwashbuckler, "SWASHBUCKLER", "Swashbuckler", ClassScout, ClassRogue, ClassTypeAdventure, true, false},
{ClassBrigand, "BRIGAND", "Brigand", ClassScout, ClassRogue, ClassTypeAdventure, true, false},
{ClassBard, "BARD", "Bard", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
{ClassTroubador, "TROUBADOR", "Troubador", ClassScout, ClassBard, ClassTypeAdventure, true, false},
{ClassDirge, "DIRGE", "Dirge", ClassScout, ClassBard, ClassTypeAdventure, true, false},
{ClassPredator, "PREDATOR", "Predator", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
{ClassRanger, "RANGER", "Ranger", ClassScout, ClassPredator, ClassTypeAdventure, true, false},
{ClassAssassin, "ASSASSIN", "Assassin", ClassScout, ClassPredator, ClassTypeAdventure, true, false},
{ClassAnimalist, "ANIMALIST", "Animalist", ClassScout, ClassCommoner, ClassTypeAdventure, true, false},
{ClassBeastlord, "BEASTLORD", "Beastlord", ClassScout, ClassAnimalist, ClassTypeAdventure, true, false},
// Special classes
{ClassShaper, "SHAPER", "Shaper", ClassPriest, ClassCommoner, ClassTypeAdventure, true, false},
{ClassChanneler, "CHANNELER", "Channeler", ClassPriest, ClassShaper, ClassTypeAdventure, true, false},
// Tradeskill classes
{ClassArtisan, "ARTISAN", "Artisan", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
{ClassCraftsman, "CRAFTSMAN", "Craftsman", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
{ClassProvisioner, "PROVISIONER", "Provisioner", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
{ClassWoodworker, "WOODWORKER", "Woodworker", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
{ClassCarpenter, "CARPENTER", "Carpenter", ClassArtisan, ClassCraftsman, ClassTypeTradeskill, false, true},
{ClassOutfitter, "OUTFITTER", "Outfitter", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
{ClassArmorer, "ARMORER", "Armorer", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
{ClassWeaponsmith, "WEAPONSMITH", "Weaponsmith", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
{ClassTailor, "TAILOR", "Tailor", ClassArtisan, ClassOutfitter, ClassTypeTradeskill, false, true},
{ClassScholar, "SCHOLAR", "Scholar", ClassArtisan, ClassCommoner, ClassTypeTradeskill, false, true},
{ClassJeweler, "JEWELER", "Jeweler", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
{ClassSage, "SAGE", "Sage", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
{ClassAlchemist, "ALCHEMIST", "Alchemist", ClassArtisan, ClassScholar, ClassTypeTradeskill, false, true},
}
// Create and store class objects
for _, def := range classDefinitions {
class := &Class{
ID: def.id,
Name: def.name,
DisplayName: def.displayName,
BaseClass: def.baseClass,
SecondaryBaseClass: def.secondaryBase,
ClassType: def.classType,
IsAdventure: def.isAdventure,
IsTradeskill: def.isTradeskill,
}
m.classes[def.id] = class
m.nameToID[def.name] = def.id
m.displayNameMap[def.id] = def.displayName
}
m.stats.ClassesLoaded = int32(len(classDefinitions))
}
// GetClass gets class by ID
func (m *Manager) GetClass(classID int8) *Class {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
if class, exists := m.classes[classID]; exists {
m.stats.CacheHits++
return class
}
m.stats.CacheMisses++
return nil
}
// GetClassID returns the class ID for a given class name (C++ API compatibility)
func (m *Manager) GetClassID(name string) int8 {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
className := strings.ToUpper(strings.TrimSpace(name))
if classID, exists := m.nameToID[className]; exists {
m.stats.CacheHits++
return classID
}
m.stats.CacheMisses++
return -1 // Invalid class
}
// GetClassName returns the uppercase class name for a given ID (C++ API compatibility)
func (m *Manager) GetClassName(classID int8) string {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
if class := m.classes[classID]; class != nil {
m.stats.CacheHits++
return class.Name
}
m.stats.CacheMisses++
return "" // Invalid class ID
}
// GetClassNameCase returns the friendly display name for a given class ID (C++ API compatibility)
func (m *Manager) GetClassNameCase(classID int8) string {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
if displayName, exists := m.displayNameMap[classID]; exists {
m.stats.CacheHits++
return displayName
}
m.stats.CacheMisses++
return "" // Invalid class ID
}
// GetBaseClass returns the base class ID for a given class (C++ API compatibility)
func (m *Manager) GetBaseClass(classID int8) int8 {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
if class := m.classes[classID]; class != nil {
m.stats.CacheHits++
return class.BaseClass
}
m.stats.CacheMisses++
return ClassCommoner // Default for unknown classes
}
// GetSecondaryBaseClass returns the secondary base class ID (C++ API compatibility)
func (m *Manager) GetSecondaryBaseClass(classID int8) int8 {
m.mutex.RLock()
defer m.mutex.RUnlock()
m.stats.LookupsPerformed++
if class := m.classes[classID]; class != nil {
m.stats.CacheHits++
return class.SecondaryBaseClass
}
m.stats.CacheMisses++
return ClassCommoner // Default for unknown classes
}
// GetTSBaseClass returns the tradeskill base class ID (C++ API compatibility)
func (m *Manager) GetTSBaseClass(classID int8) int8 {
// This function maps tradeskill class IDs to their base tradeskill progression levels
// The C++ code uses offset of 42 between adventure and tradeskill class systems
if classID+42 >= ClassArtisan {
return ClassArtisan - 44 // Returns 1 (base artisan level)
}
return classID // For non-tradeskill classes, return as-is
}
// GetSecondaryTSBaseClass returns the secondary tradeskill base class ID (C++ API compatibility)
func (m *Manager) GetSecondaryTSBaseClass(classID int8) int8 {
ret := classID + 42
if ret == ClassArtisan {
return ClassArtisan - 44
} else if ret >= ClassCraftsman && ret < ClassOutfitter {
return ClassCraftsman - 44
} else if ret >= ClassOutfitter && ret < ClassScholar {
return ClassOutfitter - 44
} else if ret >= ClassScholar {
return ClassScholar - 44
}
return classID
}
// IsValidClassID checks if a class ID is valid
func (m *Manager) IsValidClassID(classID int8) bool {
return classID >= MinClassID && classID <= MaxClassID
}
// IsAdventureClass checks if a class is an adventure class
func (m *Manager) IsAdventureClass(classID int8) bool {
if class := m.GetClass(classID); class != nil {
return class.IsAdventure
}
return false
}
// IsTradeskillClass checks if a class is a tradeskill class
func (m *Manager) IsTradeskillClass(classID int8) bool {
if class := m.GetClass(classID); class != nil {
return class.IsTradeskill
}
return false
}
// GetClassType returns the type of class (adventure, tradeskill, etc.)
func (m *Manager) GetClassType(classID int8) string {
if class := m.GetClass(classID); class != nil {
return class.ClassType
}
return ClassTypeSpecial
}
// GetAllClasses returns all class IDs and their display names
func (m *Manager) GetAllClasses() map[int8]string {
m.mutex.RLock()
defer m.mutex.RUnlock()
result := make(map[int8]string)
for classID, displayName := range m.displayNameMap {
result[classID] = displayName
}
return result
}
// GetClassInfo returns comprehensive information about a class
func (m *Manager) GetClassInfo(classID int8) map[string]any {
class := m.GetClass(classID)
info := make(map[string]any)
if class == nil {
info["valid"] = false
return info
}
info["valid"] = true
info["class_id"] = class.ID
info["name"] = class.Name
info["display_name"] = class.DisplayName
info["base_class"] = class.BaseClass
info["secondary_base_class"] = class.SecondaryBaseClass
info["type"] = class.ClassType
info["is_adventure"] = class.IsAdventure
info["is_tradeskill"] = class.IsTradeskill
return info
}
// GetClassHierarchy returns the full class hierarchy for a given class
func (m *Manager) GetClassHierarchy(classID int8) []int8 {
class := m.GetClass(classID)
if class == nil {
return nil
}
hierarchy := []int8{classID}
// Add secondary base class if it exists
if class.SecondaryBaseClass != ClassCommoner && class.SecondaryBaseClass != classID {
hierarchy = append(hierarchy, class.SecondaryBaseClass)
}
// Add base class
if class.BaseClass != ClassCommoner && class.BaseClass != classID {
// Check if base is already in hierarchy (from secondary)
found := false
for _, id := range hierarchy {
if id == class.BaseClass {
found = true
break
}
}
if !found {
hierarchy = append(hierarchy, class.BaseClass)
}
}
// Always add Commoner as the root
if classID != ClassCommoner {
hierarchy = append(hierarchy, ClassCommoner)
}
return hierarchy
}
// IsSameArchetype checks if two classes share the same base archetype
func (m *Manager) IsSameArchetype(classID1, classID2 int8) bool {
class1 := m.GetClass(classID1)
class2 := m.GetClass(classID2)
if class1 == nil || class2 == nil {
return false
}
return class1.BaseClass == class2.BaseClass
}
// GetArchetypeClasses returns all classes of a given archetype
func (m *Manager) GetArchetypeClasses(archetypeID int8) []int8 {
m.mutex.RLock()
defer m.mutex.RUnlock()
var classes []int8
for classID, class := range m.classes {
if class.BaseClass == archetypeID {
classes = append(classes, classID)
}
}
return classes
}
// GetStatistics returns current manager statistics
func (m *Manager) GetStatistics() map[string]interface{} {
m.mutex.RLock()
defer m.mutex.RUnlock()
return map[string]interface{}{
"classes_loaded": m.stats.ClassesLoaded,
"lookups_performed": m.stats.LookupsPerformed,
"cache_hits": m.stats.CacheHits,
"cache_misses": m.stats.CacheMisses,
"total_classes": int32(len(m.classes)),
"adventure_classes": m.countClassesByType(ClassTypeAdventure),
"tradeskill_classes": m.countClassesByType(ClassTypeTradeskill),
}
}
// countClassesByType counts classes of a specific type
func (m *Manager) countClassesByType(classType string) int32 {
count := int32(0)
for _, class := range m.classes {
if class.ClassType == classType {
count++
}
}
return count
}
// Global manager instance
var globalManager *Manager
// InitializeManager initializes the global class manager
func InitializeManager() {
globalManager = NewManager()
}
// GetManager returns the global class manager
func GetManager() *Manager {
if globalManager == nil {
InitializeManager()
}
return globalManager
}
// Global functions for C++ API compatibility
func GetClassID(name string) int8 {
return GetManager().GetClassID(name)
}
func GetClassName(classID int8) string {
return GetManager().GetClassName(classID)
}
func GetClassNameCase(classID int8) string {
return GetManager().GetClassNameCase(classID)
}
func GetBaseClass(classID int8) int8 {
return GetManager().GetBaseClass(classID)
}
func GetSecondaryBaseClass(classID int8) int8 {
return GetManager().GetSecondaryBaseClass(classID)
}
func GetTSBaseClass(classID int8) int8 {
return GetManager().GetTSBaseClass(classID)
}
func GetSecondaryTSBaseClass(classID int8) int8 {
return GetManager().GetSecondaryTSBaseClass(classID)
}
func IsValidClassID(classID int8) bool {
return GetManager().IsValidClassID(classID)
}
func IsAdventureClass(classID int8) bool {
return GetManager().IsAdventureClass(classID)
}
func IsTradeskillClass(classID int8) bool {
return GetManager().IsTradeskillClass(classID)
}
func GetClassType(classID int8) string {
return GetManager().GetClassType(classID)
}
func GetAllClasses() map[int8]string {
return GetManager().GetAllClasses()
}
func GetClassInfo(classID int8) map[string]any {
return GetManager().GetClassInfo(classID)
}
func GetClassHierarchy(classID int8) []int8 {
return GetManager().GetClassHierarchy(classID)
}
func IsSameArchetype(classID1, classID2 int8) bool {
return GetManager().IsSameArchetype(classID1, classID2)
}
func GetArchetypeClasses(archetypeID int8) []int8 {
return GetManager().GetArchetypeClasses(archetypeID)
}