simplify/ehance classes

This commit is contained in:
Sky Johnson 2025-08-23 18:09:48 -05:00
parent 32143aab1a
commit f99343f490
3 changed files with 1118 additions and 359 deletions

View File

@ -1,359 +0,0 @@
package classes
import (
"strings"
)
// classMap contains the mapping of class names to IDs
var classMap = map[string]int8{
"COMMONER": ClassCommoner,
"FIGHTER": ClassFighter,
"WARRIOR": ClassWarrior,
"GUARDIAN": ClassGuardian,
"BERSERKER": ClassBerserker,
"BRAWLER": ClassBrawler,
"MONK": ClassMonk,
"BRUISER": ClassBruiser,
"CRUSADER": ClassCrusader,
"SHADOWKNIGHT": ClassShadowknight,
"PALADIN": ClassPaladin,
"PRIEST": ClassPriest,
"CLERIC": ClassCleric,
"TEMPLAR": ClassTemplar,
"INQUISITOR": ClassInquisitor,
"DRUID": ClassDruid,
"WARDEN": ClassWarden,
"FURY": ClassFury,
"SHAMAN": ClassShaman,
"MYSTIC": ClassMystic,
"DEFILER": ClassDefiler,
"MAGE": ClassMage,
"SORCERER": ClassSorcerer,
"WIZARD": ClassWizard,
"WARLOCK": ClassWarlock,
"ENCHANTER": ClassEnchanter,
"ILLUSIONIST": ClassIllusionist,
"COERCER": ClassCoercer,
"SUMMONER": ClassSummoner,
"CONJUROR": ClassConjuror,
"NECROMANCER": ClassNecromancer,
"SCOUT": ClassScout,
"ROGUE": ClassRogue,
"SWASHBUCKLER": ClassSwashbuckler,
"BRIGAND": ClassBrigand,
"BARD": ClassBard,
"TROUBADOR": ClassTroubador,
"DIRGE": ClassDirge,
"PREDATOR": ClassPredator,
"RANGER": ClassRanger,
"ASSASSIN": ClassAssassin,
"ANIMALIST": ClassAnimalist,
"BEASTLORD": ClassBeastlord,
"SHAPER": ClassShaper,
"CHANNELER": ClassChanneler,
"ARTISAN": ClassArtisan,
"CRAFTSMAN": ClassCraftsman,
"PROVISIONER": ClassProvisioner,
"WOODWORKER": ClassWoodworker,
"CARPENTER": ClassCarpenter,
"OUTFITTER": ClassOutfitter,
"ARMORER": ClassArmorer,
"WEAPONSMITH": ClassWeaponsmith,
"TAILOR": ClassTailor,
"SCHOLAR": ClassScholar,
"JEWELER": ClassJeweler,
"SAGE": ClassSage,
"ALCHEMIST": ClassAlchemist,
}
// displayNameMap contains the display names for each class ID
var displayNameMap = map[int8]string{
ClassCommoner: "Commoner",
ClassFighter: "Fighter",
ClassWarrior: "Warrior",
ClassGuardian: "Guardian",
ClassBerserker: "Berserker",
ClassBrawler: "Brawler",
ClassMonk: "Monk",
ClassBruiser: "Bruiser",
ClassCrusader: "Crusader",
ClassShadowknight: "Shadowknight",
ClassPaladin: "Paladin",
ClassPriest: "Priest",
ClassCleric: "Cleric",
ClassTemplar: "Templar",
ClassInquisitor: "Inquisitor",
ClassDruid: "Druid",
ClassWarden: "Warden",
ClassFury: "Fury",
ClassShaman: "Shaman",
ClassMystic: "Mystic",
ClassDefiler: "Defiler",
ClassMage: "Mage",
ClassSorcerer: "Sorcerer",
ClassWizard: "Wizard",
ClassWarlock: "Warlock",
ClassEnchanter: "Enchanter",
ClassIllusionist: "Illusionist",
ClassCoercer: "Coercer",
ClassSummoner: "Summoner",
ClassConjuror: "Conjuror",
ClassNecromancer: "Necromancer",
ClassScout: "Scout",
ClassRogue: "Rogue",
ClassSwashbuckler: "Swashbuckler",
ClassBrigand: "Brigand",
ClassBard: "Bard",
ClassTroubador: "Troubador",
ClassDirge: "Dirge",
ClassPredator: "Predator",
ClassRanger: "Ranger",
ClassAssassin: "Assassin",
ClassAnimalist: "Animalist",
ClassBeastlord: "Beastlord",
ClassShaper: "Shaper",
ClassChanneler: "Channeler",
ClassArtisan: "Artisan",
ClassCraftsman: "Craftsman",
ClassProvisioner: "Provisioner",
ClassWoodworker: "Woodworker",
ClassCarpenter: "Carpenter",
ClassOutfitter: "Outfitter",
ClassArmorer: "Armorer",
ClassWeaponsmith: "Weaponsmith",
ClassTailor: "Tailor",
ClassScholar: "Scholar",
ClassJeweler: "Jeweler",
ClassSage: "Sage",
ClassAlchemist: "Alchemist",
}
// GetClassID returns the class ID for a given class name
// Converted from C++ Classes::GetClassID
func GetClassID(name string) int8 {
className := strings.ToUpper(strings.TrimSpace(name))
if classID, exists := classMap[className]; exists {
return classID
}
return -1 // Invalid class
}
// GetClassName returns the uppercase class name for a given ID
// Converted from C++ Classes::GetClassName
func GetClassName(classID int8) string {
// Search through class map to find the name
for name, id := range classMap {
if id == classID {
return name
}
}
return "" // Invalid class ID
}
// GetClassNameCase returns the friendly display name for a given class ID
// Converted from C++ Classes::GetClassNameCase
func GetClassNameCase(classID int8) string {
if displayName, exists := displayNameMap[classID]; exists {
return displayName
}
return "" // Invalid class ID
}
// GetBaseClass returns the base class ID for a given class
// Converted from C++ Classes::GetBaseClass
func GetBaseClass(classID int8) int8 {
if classID >= ClassWarrior && classID <= ClassPaladin {
return ClassFighter
}
if (classID >= ClassCleric && classID <= ClassDefiler) || (classID == ClassShaper || classID == ClassChanneler) {
return ClassPriest
}
if classID >= ClassSorcerer && classID <= ClassNecromancer {
return ClassMage
}
if classID >= ClassRogue && classID <= ClassBeastlord {
return ClassScout
}
return ClassCommoner // Default for unknown classes
}
// GetSecondaryBaseClass returns the secondary base class ID for specialized classes
// Converted from C++ Classes::GetSecondaryBaseClass
func GetSecondaryBaseClass(classID int8) int8 {
switch classID {
case ClassGuardian, ClassBerserker:
return ClassWarrior
case ClassMonk, ClassBruiser:
return ClassBrawler
case ClassShadowknight, ClassPaladin:
return ClassCrusader
case ClassTemplar, ClassInquisitor:
return ClassCleric
case ClassWarden, ClassFury:
return ClassDruid
case ClassMystic, ClassDefiler:
return ClassShaman
case ClassWizard, ClassWarlock:
return ClassSorcerer
case ClassIllusionist, ClassCoercer:
return ClassEnchanter
case ClassConjuror, ClassNecromancer:
return ClassSummoner
case ClassSwashbuckler, ClassBrigand:
return ClassRogue
case ClassTroubador, ClassDirge:
return ClassBard
case ClassRanger, ClassAssassin:
return ClassPredator
case ClassBeastlord:
return ClassAnimalist
case ClassChanneler:
return ClassShaper
}
return ClassCommoner // Default for unknown classes
}
// GetTSBaseClass returns the tradeskill base class ID
// Converted from C++ Classes::GetTSBaseClass
func 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
// Converted from C++ Classes::GetSecondaryTSBaseClass
func 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 IsValidClassID(classID int8) bool {
return classID >= MinClassID && classID <= MaxClassID
}
// IsAdventureClass checks if a class is an adventure class
func IsAdventureClass(classID int8) bool {
return classID >= ClassCommoner && classID <= ClassChanneler
}
// IsTradeskillClass checks if a class is a tradeskill class
func IsTradeskillClass(classID int8) bool {
return classID >= ClassArtisan && classID <= ClassAlchemist
}
// GetClassType returns the type of class (adventure, tradeskill, etc.)
func GetClassType(classID int8) string {
if IsAdventureClass(classID) {
return ClassTypeAdventure
}
if IsTradeskillClass(classID) {
return ClassTypeTradeskill
}
return ClassTypeSpecial
}
// GetAllClasses returns all class IDs and their display names
func GetAllClasses() map[int8]string {
result := make(map[int8]string)
for classID, displayName := range displayNameMap {
result[classID] = displayName
}
return result
}
// GetClassInfo returns comprehensive information about a class
func GetClassInfo(classID int8) map[string]any {
info := make(map[string]any)
if !IsValidClassID(classID) {
info["valid"] = false
return info
}
info["valid"] = true
info["class_id"] = classID
info["name"] = GetClassName(classID)
info["display_name"] = GetClassNameCase(classID)
info["base_class"] = GetBaseClass(classID)
info["secondary_base_class"] = GetSecondaryBaseClass(classID)
info["type"] = GetClassType(classID)
info["is_adventure"] = IsAdventureClass(classID)
info["is_tradeskill"] = IsTradeskillClass(classID)
return info
}
// GetClassHierarchy returns the full class hierarchy for a given class
func GetClassHierarchy(classID int8) []int8 {
if !IsValidClassID(classID) {
return nil
}
hierarchy := []int8{classID}
// Add secondary base class if it exists
secondary := GetSecondaryBaseClass(classID)
if secondary != ClassCommoner && secondary != classID {
hierarchy = append(hierarchy, secondary)
}
// Add base class
base := GetBaseClass(classID)
if base != ClassCommoner && base != classID {
// Check if base is already in hierarchy (from secondary)
found := false
for _, id := range hierarchy {
if id == base {
found = true
break
}
}
if !found {
hierarchy = append(hierarchy, base)
}
}
// 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 IsSameArchetype(classID1, classID2 int8) bool {
if !IsValidClassID(classID1) || !IsValidClassID(classID2) {
return false
}
return GetBaseClass(classID1) == GetBaseClass(classID2)
}
// GetArchetypeClasses returns all classes of a given archetype
func GetArchetypeClasses(archetypeID int8) []int8 {
var classes []int8
for classID := MinClassID; classID <= MaxClassID; classID++ {
if GetBaseClass(classID) == archetypeID {
classes = append(classes, classID)
}
}
return classes
}

540
internal/classes/classes.go Normal file
View File

@ -0,0 +1,540 @@
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)
}

View File

@ -0,0 +1,578 @@
package classes
import (
"strings"
"testing"
)
func TestClassBasics(t *testing.T) {
class := &Class{
ID: ClassWarrior,
Name: "WARRIOR",
DisplayName: "Warrior",
BaseClass: ClassFighter,
SecondaryBaseClass: ClassCommoner,
ClassType: ClassTypeAdventure,
IsAdventure: true,
IsTradeskill: false,
}
if class.GetID() != ClassWarrior {
t.Errorf("Expected ID %d, got %d", ClassWarrior, class.GetID())
}
if class.GetName() != "WARRIOR" {
t.Errorf("Expected name 'WARRIOR', got %s", class.GetName())
}
if class.GetDisplayName() != "Warrior" {
t.Errorf("Expected display name 'Warrior', got %s", class.GetDisplayName())
}
if class.GetBaseClass() != ClassFighter {
t.Errorf("Expected base class %d, got %d", ClassFighter, class.GetBaseClass())
}
if class.GetSecondaryBaseClass() != ClassCommoner {
t.Errorf("Expected secondary base class %d, got %d", ClassCommoner, class.GetSecondaryBaseClass())
}
if !class.IsValid() {
t.Error("Expected class to be valid")
}
// Test invalid class
invalidClass := &Class{ID: int8(99)}
if invalidClass.IsValid() {
t.Error("Expected invalid class to be invalid")
}
}
func TestManagerCreation(t *testing.T) {
manager := NewManager()
if manager == nil {
t.Fatal("Manager creation failed")
}
stats := manager.GetStatistics()
if stats["classes_loaded"].(int32) == 0 {
t.Error("Expected classes to be loaded during initialization")
}
if stats["total_classes"].(int32) != MaxClasses {
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
}
t.Logf("Manager initialized with %d classes", stats["total_classes"])
}
func TestGetClassID(t *testing.T) {
manager := NewManager()
// Test valid class names (C++ API compatibility)
testCases := map[string]int8{
"WARRIOR": ClassWarrior,
"GUARDIAN": ClassGuardian,
"TEMPLAR": ClassTemplar,
"WIZARD": ClassWizard,
"RANGER": ClassRanger,
"BEASTLORD": ClassBeastlord,
"CHANNELER": ClassChanneler,
"ARTISAN": ClassArtisan,
"PROVISIONER": ClassProvisioner,
"ALCHEMIST": ClassAlchemist,
"COMMONER": ClassCommoner,
}
for name, expectedID := range testCases {
id := manager.GetClassID(name)
if id != expectedID {
t.Errorf("GetClassID('%s'): expected %d, got %d", name, expectedID, id)
}
// Test case insensitivity and whitespace handling
id = manager.GetClassID(" " + strings.ToLower(name) + " ")
if id != expectedID {
t.Errorf("GetClassID('%s') with whitespace and lowercase: expected %d, got %d", name, expectedID, id)
}
}
// Test invalid class name
invalidID := manager.GetClassID("INVALID_CLASS")
if invalidID != -1 {
t.Errorf("Expected -1 for invalid class name, got %d", invalidID)
}
// Test empty string
emptyID := manager.GetClassID("")
if emptyID != -1 {
t.Errorf("Expected -1 for empty class name, got %d", emptyID)
}
}
func TestGetClassName(t *testing.T) {
manager := NewManager()
// Test valid class IDs (C++ API compatibility)
testCases := map[int8]string{
ClassWarrior: "WARRIOR",
ClassGuardian: "GUARDIAN",
ClassTemplar: "TEMPLAR",
ClassWizard: "WIZARD",
ClassRanger: "RANGER",
ClassBeastlord: "BEASTLORD",
ClassChanneler: "CHANNELER",
ClassArtisan: "ARTISAN",
ClassProvisioner: "PROVISIONER",
ClassAlchemist: "ALCHEMIST",
ClassCommoner: "COMMONER",
}
for classID, expectedName := range testCases {
name := manager.GetClassName(classID)
if name != expectedName {
t.Errorf("GetClassName(%d): expected '%s', got '%s'", classID, expectedName, name)
}
}
// Test invalid class ID
invalidName := manager.GetClassName(int8(99))
if invalidName != "" {
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidName)
}
}
func TestGetClassNameCase(t *testing.T) {
manager := NewManager()
// Test display names (C++ API compatibility)
testCases := map[int8]string{
ClassWarrior: "Warrior",
ClassGuardian: "Guardian",
ClassTemplar: "Templar",
ClassWizard: "Wizard",
ClassRanger: "Ranger",
ClassBeastlord: "Beastlord",
ClassChanneler: "Channeler",
ClassArtisan: "Artisan",
ClassProvisioner: "Provisioner",
ClassAlchemist: "Alchemist",
ClassCommoner: "Commoner",
}
for classID, expectedDisplayName := range testCases {
displayName := manager.GetClassNameCase(classID)
if displayName != expectedDisplayName {
t.Errorf("GetClassNameCase(%d): expected '%s', got '%s'", classID, expectedDisplayName, displayName)
}
}
// Test invalid class ID
invalidDisplayName := manager.GetClassNameCase(int8(99))
if invalidDisplayName != "" {
t.Errorf("Expected empty string for invalid class ID, got '%s'", invalidDisplayName)
}
}
func TestGetBaseClass(t *testing.T) {
manager := NewManager()
// Test base class mappings (C++ API compatibility)
testCases := map[int8]int8{
// Fighter archetype
ClassWarrior: ClassFighter,
ClassGuardian: ClassFighter,
ClassBerserker: ClassFighter,
ClassMonk: ClassFighter,
ClassShadowknight: ClassFighter,
ClassPaladin: ClassFighter,
// Priest archetype
ClassCleric: ClassPriest,
ClassTemplar: ClassPriest,
ClassInquisitor: ClassPriest,
ClassWarden: ClassPriest,
ClassFury: ClassPriest,
ClassMystic: ClassPriest,
ClassDefiler: ClassPriest,
ClassChanneler: ClassPriest,
// Mage archetype
ClassSorcerer: ClassMage,
ClassWizard: ClassMage,
ClassWarlock: ClassMage,
ClassIllusionist: ClassMage,
ClassCoercer: ClassMage,
ClassConjuror: ClassMage,
ClassNecromancer: ClassMage,
// Scout archetype
ClassRogue: ClassScout,
ClassSwashbuckler: ClassScout,
ClassBrigand: ClassScout,
ClassTroubador: ClassScout,
ClassDirge: ClassScout,
ClassRanger: ClassScout,
ClassAssassin: ClassScout,
ClassBeastlord: ClassScout,
// Base classes should return themselves
ClassFighter: ClassFighter,
ClassPriest: ClassPriest,
ClassMage: ClassMage,
ClassScout: ClassScout,
ClassCommoner: ClassCommoner,
}
for classID, expectedBase := range testCases {
base := manager.GetBaseClass(classID)
if base != expectedBase {
t.Errorf("GetBaseClass(%d): expected %d, got %d", classID, expectedBase, base)
}
}
// Test invalid class ID
invalidBase := manager.GetBaseClass(int8(99))
if invalidBase != ClassCommoner {
t.Errorf("Expected %d (Commoner) for invalid class ID, got %d", ClassCommoner, invalidBase)
}
}
func TestGetSecondaryBaseClass(t *testing.T) {
manager := NewManager()
// Test secondary base class mappings (C++ API compatibility)
testCases := map[int8]int8{
ClassGuardian: ClassWarrior,
ClassBerserker: ClassWarrior,
ClassMonk: ClassBrawler,
ClassBruiser: ClassBrawler,
ClassShadowknight: ClassCrusader,
ClassPaladin: ClassCrusader,
ClassTemplar: ClassCleric,
ClassInquisitor: ClassCleric,
ClassWarden: ClassDruid,
ClassFury: ClassDruid,
ClassMystic: ClassShaman,
ClassDefiler: ClassShaman,
ClassWizard: ClassSorcerer,
ClassWarlock: ClassSorcerer,
ClassIllusionist: ClassEnchanter,
ClassCoercer: ClassEnchanter,
ClassConjuror: ClassSummoner,
ClassNecromancer: ClassSummoner,
ClassSwashbuckler: ClassRogue,
ClassBrigand: ClassRogue,
ClassTroubador: ClassBard,
ClassDirge: ClassBard,
ClassRanger: ClassPredator,
ClassAssassin: ClassPredator,
ClassBeastlord: ClassAnimalist,
ClassChanneler: ClassShaper,
}
for classID, expectedSecondary := range testCases {
secondary := manager.GetSecondaryBaseClass(classID)
if secondary != expectedSecondary {
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", classID, expectedSecondary, secondary)
}
}
// Test base classes (should return Commoner)
baseClasses := []int8{ClassCommoner, ClassFighter, ClassPriest, ClassMage, ClassScout}
for _, classID := range baseClasses {
secondary := manager.GetSecondaryBaseClass(classID)
if secondary != ClassCommoner {
t.Errorf("GetSecondaryBaseClass(%d): expected %d (Commoner), got %d", classID, ClassCommoner, secondary)
}
}
}
func TestClassValidation(t *testing.T) {
manager := NewManager()
// Test valid class IDs
validClasses := []int8{
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
ClassChanneler, ClassArtisan, ClassAlchemist,
}
for _, classID := range validClasses {
if !manager.IsValidClassID(classID) {
t.Errorf("Expected class ID %d to be valid", classID)
}
}
// Test invalid class IDs
invalidClasses := []int8{-1, -10, 58, 99, 127}
for _, classID := range invalidClasses {
if manager.IsValidClassID(classID) {
t.Errorf("Expected class ID %d to be invalid", classID)
}
}
// Test boundary conditions
if !manager.IsValidClassID(MinClassID) {
t.Errorf("Expected MinClassID (%d) to be valid", MinClassID)
}
if !manager.IsValidClassID(MaxClassID) {
t.Errorf("Expected MaxClassID (%d) to be valid", MaxClassID)
}
}
func TestClassTypeChecking(t *testing.T) {
manager := NewManager()
// Test adventure classes
adventureClasses := []int8{
ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian,
ClassTemplar, ClassWizard, ClassRanger, ClassBeastlord,
ClassChanneler,
}
for _, classID := range adventureClasses {
if !manager.IsAdventureClass(classID) {
t.Errorf("Expected class ID %d to be an adventure class", classID)
}
if manager.IsTradeskillClass(classID) {
t.Errorf("Expected class ID %d to not be a tradeskill class", classID)
}
if manager.GetClassType(classID) != ClassTypeAdventure {
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeAdventure, manager.GetClassType(classID))
}
}
// Test tradeskill classes
tradeskillClasses := []int8{
ClassArtisan, ClassCraftsman, ClassProvisioner,
ClassOutfitter, ClassArmorer, ClassScholar,
ClassJeweler, ClassAlchemist,
}
for _, classID := range tradeskillClasses {
if manager.IsAdventureClass(classID) {
t.Errorf("Expected class ID %d to not be an adventure class", classID)
}
if !manager.IsTradeskillClass(classID) {
t.Errorf("Expected class ID %d to be a tradeskill class", classID)
}
if manager.GetClassType(classID) != ClassTypeTradeskill {
t.Errorf("Expected class ID %d to have type '%s', got '%s'", classID, ClassTypeTradeskill, manager.GetClassType(classID))
}
}
}
func TestClassHierarchy(t *testing.T) {
manager := NewManager()
// Test Guardian hierarchy: Guardian -> Warrior -> Fighter -> Commoner
guardianHierarchy := manager.GetClassHierarchy(ClassGuardian)
expectedGuardian := []int8{ClassGuardian, ClassWarrior, ClassFighter, ClassCommoner}
if len(guardianHierarchy) != len(expectedGuardian) {
t.Errorf("Guardian hierarchy length: expected %d, got %d", len(expectedGuardian), len(guardianHierarchy))
} else {
for i, expected := range expectedGuardian {
if i < len(guardianHierarchy) && guardianHierarchy[i] != expected {
t.Errorf("Guardian hierarchy[%d]: expected %d, got %d", i, expected, guardianHierarchy[i])
}
}
}
// Test Templar hierarchy: Templar -> Cleric -> Priest -> Commoner
templarHierarchy := manager.GetClassHierarchy(ClassTemplar)
expectedTemplar := []int8{ClassTemplar, ClassCleric, ClassPriest, ClassCommoner}
if len(templarHierarchy) != len(expectedTemplar) {
t.Errorf("Templar hierarchy length: expected %d, got %d", len(expectedTemplar), len(templarHierarchy))
} else {
for i, expected := range expectedTemplar {
if i < len(templarHierarchy) && templarHierarchy[i] != expected {
t.Errorf("Templar hierarchy[%d]: expected %d, got %d", i, expected, templarHierarchy[i])
}
}
}
// Test Commoner hierarchy: just Commoner
commonerHierarchy := manager.GetClassHierarchy(ClassCommoner)
if len(commonerHierarchy) != 1 || commonerHierarchy[0] != ClassCommoner {
t.Errorf("Commoner hierarchy: expected [%d], got %v", ClassCommoner, commonerHierarchy)
}
// Test invalid class
invalidHierarchy := manager.GetClassHierarchy(int8(99))
if invalidHierarchy != nil {
t.Errorf("Expected nil hierarchy for invalid class, got %v", invalidHierarchy)
}
}
func TestArchetypeOperations(t *testing.T) {
manager := NewManager()
// Test same archetype checking
if !manager.IsSameArchetype(ClassWarrior, ClassGuardian) {
t.Error("Expected Warrior and Guardian to be same archetype (Fighter)")
}
if !manager.IsSameArchetype(ClassTemplar, ClassMystic) {
t.Error("Expected Templar and Mystic to be same archetype (Priest)")
}
if manager.IsSameArchetype(ClassWarrior, ClassTemplar) {
t.Error("Expected Warrior and Templar to be different archetypes")
}
// Test archetype class listing
fighterClasses := manager.GetArchetypeClasses(ClassFighter)
expectedFighterCount := 10 // Including base Fighter class (Fighter, Warrior, Guardian, Berserker, Brawler, Monk, Bruiser, Crusader, Shadowknight, Paladin)
if len(fighterClasses) != expectedFighterCount {
t.Errorf("Expected %d Fighter archetype classes, got %d", expectedFighterCount, len(fighterClasses))
}
// Verify Fighter is in the list
foundFighter := false
for _, classID := range fighterClasses {
if classID == ClassFighter {
foundFighter = true
break
}
}
if !foundFighter {
t.Error("Expected Fighter base class to be in Fighter archetype list")
}
}
func TestTradeskillClassOperations(t *testing.T) {
manager := NewManager()
// Test tradeskill base class operations (C++ API compatibility)
// Note: These functions use specific C++ logic with offsets
// Test with adventure class (should return as-is for most)
warriorTS := manager.GetTSBaseClass(ClassWarrior)
if warriorTS != ClassWarrior {
t.Errorf("Expected GetTSBaseClass(%d) to return %d, got %d", ClassWarrior, ClassWarrior, warriorTS)
}
// Test secondary tradeskill operations
warriorSecondaryTS := manager.GetSecondaryTSBaseClass(ClassWarrior)
// This should follow the C++ logic with +42 offset calculations
t.Logf("GetSecondaryTSBaseClass(ClassWarrior) = %d", warriorSecondaryTS)
}
func TestManagerStatistics(t *testing.T) {
manager := NewManager()
// Perform some operations to generate statistics
manager.GetClassID("WARRIOR")
manager.GetClassName(ClassTemplar)
manager.GetClassNameCase(ClassRanger)
manager.GetBaseClass(ClassGuardian)
manager.GetClass(ClassBeastlord)
manager.GetClassID("INVALID_CLASS") // This should increment cache misses
stats := manager.GetStatistics()
// Check basic statistics
if stats["classes_loaded"].(int32) == 0 {
t.Error("Expected classes to be loaded")
}
if stats["lookups_performed"].(int32) == 0 {
t.Error("Expected lookups to be performed")
}
if stats["total_classes"].(int32) != MaxClasses {
t.Errorf("Expected %d total classes, got %d", MaxClasses, stats["total_classes"])
}
// Check adventure vs tradeskill counts
adventureCount := stats["adventure_classes"].(int32)
tradeskillCount := stats["tradeskill_classes"].(int32)
if adventureCount == 0 {
t.Error("Expected some adventure classes")
}
if tradeskillCount == 0 {
t.Error("Expected some tradeskill classes")
}
t.Logf("Statistics: %+v", stats)
}
func TestGlobalFunctions(t *testing.T) {
// Test global functions that should initialize manager automatically
// Test basic operations
id := GetClassID("WARRIOR")
if id != ClassWarrior {
t.Errorf("GetClassID('WARRIOR'): expected %d, got %d", ClassWarrior, id)
}
name := GetClassName(ClassTemplar)
if name != "TEMPLAR" {
t.Errorf("GetClassName(%d): expected 'TEMPLAR', got '%s'", ClassTemplar, name)
}
displayName := GetClassNameCase(ClassRanger)
if displayName != "Ranger" {
t.Errorf("GetClassNameCase(%d): expected 'Ranger', got '%s'", ClassRanger, displayName)
}
base := GetBaseClass(ClassGuardian)
if base != ClassFighter {
t.Errorf("GetBaseClass(%d): expected %d, got %d", ClassGuardian, ClassFighter, base)
}
secondary := GetSecondaryBaseClass(ClassGuardian)
if secondary != ClassWarrior {
t.Errorf("GetSecondaryBaseClass(%d): expected %d, got %d", ClassGuardian, ClassWarrior, secondary)
}
// Test utility functions
if !IsValidClassID(ClassBeastlord) {
t.Errorf("Expected ClassBeastlord (%d) to be valid", ClassBeastlord)
}
if !IsAdventureClass(ClassChanneler) {
t.Errorf("Expected ClassChanneler (%d) to be adventure class", ClassChanneler)
}
if !IsTradeskillClass(ClassAlchemist) {
t.Errorf("Expected ClassAlchemist (%d) to be tradeskill class", ClassAlchemist)
}
classType := GetClassType(ClassWarrior)
if classType != ClassTypeAdventure {
t.Errorf("Expected ClassWarrior type to be '%s', got '%s'", ClassTypeAdventure, classType)
}
// Test collection functions
allClasses := GetAllClasses()
if len(allClasses) != MaxClasses {
t.Errorf("Expected %d classes in GetAllClasses(), got %d", MaxClasses, len(allClasses))
}
info := GetClassInfo(ClassTemplar)
if !info["valid"].(bool) {
t.Error("Expected ClassTemplar info to be valid")
}
hierarchy := GetClassHierarchy(ClassGuardian)
if len(hierarchy) == 0 {
t.Error("Expected non-empty hierarchy for ClassGuardian")
}
if !IsSameArchetype(ClassWarrior, ClassGuardian) {
t.Error("Expected Warrior and Guardian to be same archetype")
}
fighterClasses := GetArchetypeClasses(ClassFighter)
if len(fighterClasses) == 0 {
t.Error("Expected non-empty list of Fighter archetype classes")
}
}