diff --git a/internal/classes/classes_test.go b/internal/classes/classes_test.go new file mode 100644 index 0000000..dc7e784 --- /dev/null +++ b/internal/classes/classes_test.go @@ -0,0 +1,1714 @@ +package classes + +import ( + "math/rand" + "strings" + "sync" + "testing" + "time" +) + +// Mock entity implementations for testing +type MockClassAware struct { + classID int8 + mutex sync.RWMutex +} + +func (m *MockClassAware) GetClass() int8 { + m.mutex.RLock() + defer m.mutex.RUnlock() + return m.classID +} + +func (m *MockClassAware) SetClass(classID int8) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.classID = classID +} + +type MockEntityWithClass struct { + *MockClassAware + id int32 + name string + level int8 +} + +func (m *MockEntityWithClass) GetID() int32 { + return m.id +} + +func (m *MockEntityWithClass) GetName() string { + return m.name +} + +func (m *MockEntityWithClass) GetLevel() int8 { + return m.level +} + +func NewMockClassAware(classID int8) *MockClassAware { + return &MockClassAware{classID: classID} +} + +func NewMockEntityWithClass(id int32, name string, level int8, classID int8) *MockEntityWithClass { + return &MockEntityWithClass{ + MockClassAware: NewMockClassAware(classID), + id: id, + name: name, + level: level, + } +} + +// Core Classes tests +func TestNewClasses(t *testing.T) { + classes := NewClasses() + if classes == nil { + t.Fatal("NewClasses returned nil") + } + + if len(classes.classMap) == 0 { + t.Error("classMap should not be empty after initialization") + } + + if len(classes.displayNameMap) == 0 { + t.Error("displayNameMap should not be empty after initialization") + } +} + +func TestGetClassID(t *testing.T) { + classes := NewClasses() + + tests := []struct { + name string + input string + expectedID int8 + shouldBeValid bool + }{ + {"Valid class name", "WARRIOR", ClassWarrior, true}, + {"Valid class name lowercase", "warrior", ClassWarrior, true}, + {"Valid class name with spaces", " WARRIOR ", ClassWarrior, true}, + {"Invalid class name", "INVALID", -1, false}, + {"Empty string", "", -1, false}, + {"Valid priest class", "CLERIC", ClassCleric, true}, + {"Valid mage class", "WIZARD", ClassWizard, true}, + {"Valid scout class", "RANGER", ClassRanger, true}, + {"Valid tradeskill class", "ARMORER", ClassArmorer, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := classes.GetClassID(tt.input) + if tt.shouldBeValid { + if result != tt.expectedID { + t.Errorf("GetClassID(%s) = %d, want %d", tt.input, result, tt.expectedID) + } + } else { + if result != -1 { + t.Errorf("GetClassID(%s) should return -1 for invalid input, got %d", tt.input, result) + } + } + }) + } +} + +func TestGetClassName(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + expectedName string + }{ + {ClassWarrior, ClassNameWarrior}, + {ClassCleric, ClassNameCleric}, + {ClassWizard, ClassNameWizard}, + {ClassRanger, ClassNameRanger}, + {ClassArmorer, ClassNameArmorer}, + {-1, ""}, // Invalid class ID + {100, ""}, // Invalid class ID + } + + for _, tt := range tests { + t.Run("GetClassName", func(t *testing.T) { + result := classes.GetClassName(tt.classID) + if result != tt.expectedName { + t.Errorf("GetClassName(%d) = %s, want %s", tt.classID, result, tt.expectedName) + } + }) + } +} + +func TestGetClassNameCase(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + expectedDisplay string + }{ + {ClassWarrior, DisplayNameWarrior}, + {ClassCleric, DisplayNameCleric}, + {ClassWizard, DisplayNameWizard}, + {ClassRanger, DisplayNameRanger}, + {ClassArmorer, DisplayNameArmorer}, + {-1, ""}, // Invalid class ID + {100, ""}, // Invalid class ID + } + + for _, tt := range tests { + t.Run("GetClassNameCase", func(t *testing.T) { + result := classes.GetClassNameCase(tt.classID) + if result != tt.expectedDisplay { + t.Errorf("GetClassNameCase(%d) = %s, want %s", tt.classID, result, tt.expectedDisplay) + } + }) + } +} + +func TestGetBaseClass(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + expectedBase int8 + }{ + {ClassWarrior, ClassFighter}, + {ClassGuardian, ClassFighter}, + {ClassBerserker, ClassFighter}, + {ClassCleric, ClassPriest}, + {ClassTemplar, ClassPriest}, + {ClassWizard, ClassMage}, + {ClassWarlock, ClassMage}, + {ClassRanger, ClassScout}, + {ClassAssassin, ClassScout}, + {ClassShaper, ClassPriest}, + {ClassChanneler, ClassPriest}, + {ClassCommoner, ClassCommoner}, + {-1, ClassCommoner}, // Invalid defaults to Commoner + } + + for _, tt := range tests { + t.Run("GetBaseClass", func(t *testing.T) { + result := classes.GetBaseClass(tt.classID) + if result != tt.expectedBase { + t.Errorf("GetBaseClass(%d) = %d, want %d", tt.classID, result, tt.expectedBase) + } + }) + } +} + +func TestGetSecondaryBaseClass(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + expectedSecondaryBase 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}, + {ClassCommoner, ClassCommoner}, // Base classes return Commoner + {ClassFighter, ClassCommoner}, + } + + for _, tt := range tests { + t.Run("GetSecondaryBaseClass", func(t *testing.T) { + result := classes.GetSecondaryBaseClass(tt.classID) + if result != tt.expectedSecondaryBase { + t.Errorf("GetSecondaryBaseClass(%d) = %d, want %d", tt.classID, result, tt.expectedSecondaryBase) + } + }) + } +} + +func TestIsValidClassID(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + valid bool + }{ + {ClassCommoner, true}, + {ClassWarrior, true}, + {ClassAlchemist, true}, + {MinClassID, true}, + {MaxClassID, true}, + {-1, false}, + {MaxClassID + 1, false}, + {100, false}, + } + + for _, tt := range tests { + t.Run("IsValidClassID", func(t *testing.T) { + result := classes.IsValidClassID(tt.classID) + if result != tt.valid { + t.Errorf("IsValidClassID(%d) = %t, want %t", tt.classID, result, tt.valid) + } + }) + } +} + +func TestGetAllClasses(t *testing.T) { + classes := NewClasses() + allClasses := classes.GetAllClasses() + + if len(allClasses) == 0 { + t.Error("GetAllClasses should not return empty map") + } + + // Check that all classes have valid display names + for classID, displayName := range allClasses { + if displayName == "" { + t.Errorf("Class %d has empty display name", classID) + } + + if !classes.IsValidClassID(classID) { + t.Errorf("Invalid class ID in GetAllClasses: %d", classID) + } + } + + // Verify we have the expected number of classes + expectedCount := MaxClasses + if len(allClasses) != expectedCount { + t.Errorf("GetAllClasses returned %d classes, expected %d", len(allClasses), expectedCount) + } +} + +func TestIsAdventureClass(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + adventure bool + }{ + {ClassCommoner, true}, + {ClassFighter, true}, + {ClassWarrior, true}, + {ClassChanneler, true}, + {ClassArtisan, false}, + {ClassCraftsman, false}, + {ClassAlchemist, false}, + } + + for _, tt := range tests { + t.Run("IsAdventureClass", func(t *testing.T) { + result := classes.IsAdventureClass(tt.classID) + if result != tt.adventure { + t.Errorf("IsAdventureClass(%d) = %t, want %t", tt.classID, result, tt.adventure) + } + }) + } +} + +func TestIsTradeskillClass(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + tradeskill bool + }{ + {ClassCommoner, false}, + {ClassWarrior, false}, + {ClassChanneler, false}, + {ClassArtisan, true}, + {ClassCraftsman, true}, + {ClassAlchemist, true}, + } + + for _, tt := range tests { + t.Run("IsTradeskillClass", func(t *testing.T) { + result := classes.IsTradeskillClass(tt.classID) + if result != tt.tradeskill { + t.Errorf("IsTradeskillClass(%d) = %t, want %t", tt.classID, result, tt.tradeskill) + } + }) + } +} + +func TestGetClassType(t *testing.T) { + classes := NewClasses() + + tests := []struct { + classID int8 + expectedType string + }{ + {ClassWarrior, ClassTypeAdventure}, + {ClassCleric, ClassTypeAdventure}, + {ClassChanneler, ClassTypeAdventure}, + {ClassArtisan, ClassTypeTradeskill}, + {ClassCraftsman, ClassTypeTradeskill}, + {ClassAlchemist, ClassTypeTradeskill}, + {-1, ClassTypeSpecial}, // Invalid class + {100, ClassTypeSpecial}, // Invalid class + } + + for _, tt := range tests { + t.Run("GetClassType", func(t *testing.T) { + result := classes.GetClassType(tt.classID) + if result != tt.expectedType { + t.Errorf("GetClassType(%d) = %s, want %s", tt.classID, result, tt.expectedType) + } + }) + } +} + +func TestGetClassCount(t *testing.T) { + classes := NewClasses() + count := classes.GetClassCount() + + if count != MaxClasses { + t.Errorf("GetClassCount() = %d, want %d", count, MaxClasses) + } +} + +func TestGetClassInfo(t *testing.T) { + classes := NewClasses() + + // Test valid class + info := classes.GetClassInfo(ClassWarrior) + if !info["valid"].(bool) { + t.Error("GetClassInfo should indicate valid class") + } + + if info["class_id"] != ClassWarrior { + t.Error("GetClassInfo should return correct class_id") + } + + if info["name"] != ClassNameWarrior { + t.Error("GetClassInfo should return correct name") + } + + if info["display_name"] != DisplayNameWarrior { + t.Error("GetClassInfo should return correct display_name") + } + + // Test invalid class + invalidInfo := classes.GetClassInfo(-1) + if invalidInfo["valid"].(bool) { + t.Error("GetClassInfo should indicate invalid class for -1") + } +} + +func TestGetGlobalClasses(t *testing.T) { + classes1 := GetGlobalClasses() + classes2 := GetGlobalClasses() + + if classes1 != classes2 { + t.Error("GetGlobalClasses should return the same instance (singleton)") + } + + if classes1 == nil { + t.Error("GetGlobalClasses should not return nil") + } +} + +// ClassUtils tests +func TestNewClassUtils(t *testing.T) { + utils := NewClassUtils() + if utils == nil { + t.Fatal("NewClassUtils returned nil") + } + + if utils.classes == nil { + t.Error("ClassUtils should have classes instance") + } +} + +func TestParseClassName(t *testing.T) { + utils := NewClassUtils() + + tests := []struct { + name string + input string + expected int8 + }{ + {"Exact match", "WARRIOR", ClassWarrior}, + {"Case insensitive", "warrior", ClassWarrior}, + {"With spaces", " Warrior ", ClassWarrior}, + {"With underscores", "SHADOW_KNIGHT", ClassShadowknight}, + {"With dashes", "SHADOW-KNIGHT", ClassShadowknight}, + {"Friendly name", "Warrior", ClassWarrior}, + {"Empty string", "", -1}, + {"Invalid name", "INVALID", -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := utils.ParseClassName(tt.input) + if result != tt.expected { + t.Errorf("ParseClassName(%s) = %d, want %d", tt.input, result, tt.expected) + } + }) + } +} + +func TestFormatClassName(t *testing.T) { + utils := NewClassUtils() + + tests := []struct { + classID int8 + format string + expected string + }{ + {ClassWarrior, "display", DisplayNameWarrior}, + {ClassWarrior, "friendly", DisplayNameWarrior}, + {ClassWarrior, "upper", ClassNameWarrior}, + {ClassWarrior, "lowercase", strings.ToLower(ClassNameWarrior)}, + {ClassWarrior, "default", DisplayNameWarrior}, + {ClassWarrior, "", DisplayNameWarrior}, + } + + for _, tt := range tests { + t.Run("FormatClassName", func(t *testing.T) { + result := utils.FormatClassName(tt.classID, tt.format) + if result != tt.expected { + t.Errorf("FormatClassName(%d, %s) = %s, want %s", tt.classID, tt.format, result, tt.expected) + } + }) + } +} + +func TestGetRandomClassByType(t *testing.T) { + utils := NewClassUtils() + + // Test adventure classes + adventureClass := utils.GetRandomClassByType(ClassTypeAdventure) + classes := GetGlobalClasses() + if !classes.IsAdventureClass(adventureClass) { + t.Errorf("GetRandomClassByType(adventure) returned non-adventure class: %d", adventureClass) + } + + // Test tradeskill classes + tradeskillClass := utils.GetRandomClassByType(ClassTypeTradeskill) + if !classes.IsTradeskillClass(tradeskillClass) { + t.Errorf("GetRandomClassByType(tradeskill) returned non-tradeskill class: %d", tradeskillClass) + } + + // Test invalid type + invalidTypeClass := utils.GetRandomClassByType("invalid") + if invalidTypeClass != DefaultClassID { + t.Errorf("GetRandomClassByType(invalid) should return DefaultClassID, got %d", invalidTypeClass) + } +} + +func TestGetRandomAdventureClass(t *testing.T) { + utils := NewClassUtils() + classes := GetGlobalClasses() + + for i := 0; i < 10; i++ { + adventureClass := utils.GetRandomAdventureClass() + if !classes.IsAdventureClass(adventureClass) { + t.Errorf("GetRandomAdventureClass() returned non-adventure class: %d", adventureClass) + } + } +} + +func TestGetRandomTradeskillClass(t *testing.T) { + utils := NewClassUtils() + classes := GetGlobalClasses() + + for i := 0; i < 10; i++ { + tradeskillClass := utils.GetRandomTradeskillClass() + if !classes.IsTradeskillClass(tradeskillClass) { + t.Errorf("GetRandomTradeskillClass() returned non-tradeskill class: %d", tradeskillClass) + } + } +} + +func TestValidateClassForRace(t *testing.T) { + utils := NewClassUtils() + + // Currently all classes are valid for all races + if !utils.ValidateClassForRace(ClassWarrior, 1) { + t.Error("ValidateClassForRace should return true for valid class") + } + + if utils.ValidateClassForRace(-1, 1) { + t.Error("ValidateClassForRace should return false for invalid class") + } +} + +func TestGetClassDescription(t *testing.T) { + utils := NewClassUtils() + + description := utils.GetClassDescription(ClassWarrior) + if description == "" { + t.Error("GetClassDescription should return non-empty description for Warrior") + } + + if !strings.Contains(strings.ToLower(description), "weapon") { + t.Error("Warrior description should contain 'weapon'") + } + + unknownDescription := utils.GetClassDescription(-1) + if unknownDescription == "" { + t.Error("GetClassDescription should return default description for invalid class") + } +} + +func TestGetClassProgression(t *testing.T) { + utils := NewClassUtils() + + // Test final class progression + guardianProgression := utils.GetClassProgression(ClassGuardian) + expected := []int8{ClassCommoner, ClassFighter, ClassWarrior, ClassGuardian} + + if len(guardianProgression) != len(expected) { + t.Errorf("Guardian progression length = %d, want %d", len(guardianProgression), len(expected)) + } + + for i, classID := range expected { + if i < len(guardianProgression) && guardianProgression[i] != classID { + t.Errorf("Guardian progression[%d] = %d, want %d", i, guardianProgression[i], classID) + } + } + + // Test base class progression (should be shorter) + fighterProgression := utils.GetClassProgression(ClassFighter) + if len(fighterProgression) >= len(guardianProgression) { + t.Error("Fighter progression should be shorter than Guardian progression") + } + + // Test commoner progression + commonerProgression := utils.GetClassProgression(ClassCommoner) + if len(commonerProgression) != 1 || commonerProgression[0] != ClassCommoner { + t.Error("Commoner progression should only contain Commoner") + } +} + +func TestGetClassesForBaseClass(t *testing.T) { + utils := NewClassUtils() + + fighterClasses := utils.GetClassesForBaseClass(ClassFighter) + if len(fighterClasses) == 0 { + t.Error("Should find fighter classes") + } + + // Verify all returned classes have Fighter as base class + classes := GetGlobalClasses() + for _, classID := range fighterClasses { + if classes.GetBaseClass(classID) != ClassFighter { + t.Errorf("Class %d should have Fighter as base class", classID) + } + } +} + +func TestGetClassesBySecondaryBase(t *testing.T) { + utils := NewClassUtils() + + warriorClasses := utils.GetClassesBySecondaryBase(ClassWarrior) + if len(warriorClasses) == 0 { + t.Error("Should find classes with Warrior as secondary base") + } + + // Verify all returned classes have Warrior as secondary base class + classes := GetGlobalClasses() + for _, classID := range warriorClasses { + if classes.GetSecondaryBaseClass(classID) != ClassWarrior { + t.Errorf("Class %d should have Warrior as secondary base class", classID) + } + } +} + +func TestGetClassesByPattern(t *testing.T) { + utils := NewClassUtils() + + // Search for classes containing "war" + warClasses := utils.GetClassesByPattern("war") + if len(warClasses) == 0 { + t.Error("Should find classes matching 'war'") + } + + // Verify matches + classes := GetGlobalClasses() + for _, classID := range warClasses { + displayName := classes.GetClassNameCase(classID) + if !strings.Contains(strings.ToLower(displayName), "war") { + t.Errorf("Class %s should contain 'war'", displayName) + } + } + + // Test case insensitive search + warClassesUpper := utils.GetClassesByPattern("WAR") + if len(warClassesUpper) != len(warClasses) { + t.Error("Pattern search should be case insensitive") + } +} + +func TestValidateClassTransition(t *testing.T) { + utils := NewClassUtils() + + tests := []struct { + name string + fromClass int8 + toClass int8 + shouldAllow bool + }{ + {"Commoner to Fighter", ClassCommoner, ClassFighter, true}, + {"Fighter to Warrior", ClassFighter, ClassWarrior, true}, + {"Warrior to Guardian", ClassWarrior, ClassGuardian, true}, + {"Same class", ClassWarrior, ClassWarrior, false}, + {"Regression", ClassGuardian, ClassWarrior, false}, + {"Invalid from", -1, ClassWarrior, false}, + {"Invalid to", ClassWarrior, -1, false}, + {"Incompatible paths", ClassWarrior, ClassCleric, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + valid, _ := utils.ValidateClassTransition(tt.fromClass, tt.toClass) + if valid != tt.shouldAllow { + t.Errorf("ValidateClassTransition(%d, %d) = %t, want %t", tt.fromClass, tt.toClass, valid, tt.shouldAllow) + } + }) + } +} + +func TestGetClassAliases(t *testing.T) { + utils := NewClassUtils() + + // Test class with known aliases + skAliases := utils.GetClassAliases(ClassShadowknight) + if len(skAliases) == 0 { + t.Error("Shadowknight should have aliases") + } + + // Should contain the alias "SK" + foundSK := false + for _, alias := range skAliases { + if alias == "SK" { + foundSK = true + break + } + } + if !foundSK { + t.Error("Shadowknight aliases should include 'SK'") + } + + // Test class without specific aliases + commonerAliases := utils.GetClassAliases(ClassCommoner) + if len(commonerAliases) == 0 { + t.Error("All classes should have at least their official names as aliases") + } +} + +func TestGetClassStatistics(t *testing.T) { + utils := NewClassUtils() + + stats := utils.GetClassStatistics() + + if stats["total_classes"] == nil { + t.Error("Statistics should include total_classes") + } + + if stats["adventure_classes"] == nil { + t.Error("Statistics should include adventure_classes") + } + + if stats["tradeskill_classes"] == nil { + t.Error("Statistics should include tradeskill_classes") + } + + totalClasses := stats["total_classes"].(int) + adventureClasses := stats["adventure_classes"].(int) + tradeskillClasses := stats["tradeskill_classes"].(int) + + if totalClasses != adventureClasses + tradeskillClasses { + t.Errorf("Total classes (%d) should equal adventure (%d) + tradeskill (%d)", + totalClasses, adventureClasses, tradeskillClasses) + } +} + +func TestFormatClassList(t *testing.T) { + utils := NewClassUtils() + + classIDs := []int8{ClassWarrior, ClassCleric, ClassWizard} + result := utils.FormatClassList(classIDs, ", ") + + expected := "Warrior, Cleric, Wizard" + if result != expected { + t.Errorf("FormatClassList() = %s, want %s", result, expected) + } + + // Test empty list + emptyResult := utils.FormatClassList([]int8{}, ", ") + if emptyResult != "" { + t.Error("FormatClassList with empty list should return empty string") + } +} + +func TestGetEQClassName(t *testing.T) { + utils := NewClassUtils() + + // Currently just returns display name + result := utils.GetEQClassName(ClassWarrior, 50) + expected := DisplayNameWarrior + if result != expected { + t.Errorf("GetEQClassName() = %s, want %s", result, expected) + } +} + +func TestGetStartingClass(t *testing.T) { + utils := NewClassUtils() + + startingClass := utils.GetStartingClass() + if startingClass != ClassCommoner { + t.Errorf("GetStartingClass() = %d, want %d", startingClass, ClassCommoner) + } +} + +func TestIsBaseClass(t *testing.T) { + utils := NewClassUtils() + + tests := []struct { + classID int8 + isBase bool + }{ + {ClassFighter, true}, + {ClassPriest, true}, + {ClassMage, true}, + {ClassScout, true}, + {ClassWarrior, false}, + {ClassCleric, false}, + {ClassCommoner, false}, + } + + for _, tt := range tests { + t.Run("IsBaseClass", func(t *testing.T) { + result := utils.IsBaseClass(tt.classID) + if result != tt.isBase { + t.Errorf("IsBaseClass(%d) = %t, want %t", tt.classID, result, tt.isBase) + } + }) + } +} + +func TestIsSecondaryBaseClass(t *testing.T) { + utils := NewClassUtils() + + // Warrior is a secondary base class (for Guardian, Berserker) + if !utils.IsSecondaryBaseClass(ClassWarrior) { + t.Error("Warrior should be a secondary base class") + } + + // Guardian is not a secondary base class + if utils.IsSecondaryBaseClass(ClassGuardian) { + t.Error("Guardian should not be a secondary base class") + } +} + +// ClassIntegration tests +func TestNewClassIntegration(t *testing.T) { + integration := NewClassIntegration() + if integration == nil { + t.Fatal("NewClassIntegration returned nil") + } + + if integration.classes == nil { + t.Error("ClassIntegration should have classes instance") + } + + if integration.utils == nil { + t.Error("ClassIntegration should have utils instance") + } +} + +func TestValidateEntityClass(t *testing.T) { + integration := NewClassIntegration() + + // Test valid entity + validEntity := NewMockClassAware(ClassWarrior) + valid, message, info := integration.ValidateEntityClass(validEntity) + + if !valid { + t.Error("ValidateEntityClass should return true for valid class") + } + + if message != "Valid class" { + t.Errorf("ValidateEntityClass message = %s, want 'Valid class'", message) + } + + if info == nil { + t.Error("ValidateEntityClass should return class info") + } + + // Test invalid entity + invalidEntity := NewMockClassAware(-1) + valid, message, info = integration.ValidateEntityClass(invalidEntity) + + if valid { + t.Error("ValidateEntityClass should return false for invalid class") + } + + if info != nil { + t.Error("ValidateEntityClass should return nil info for invalid class") + } +} + +func TestGetEntityClassInfo(t *testing.T) { + integration := NewClassIntegration() + + entity := NewMockEntityWithClass(100, "TestEntity", 25, ClassWarrior) + info := integration.GetEntityClassInfo(entity) + + if info["entity_id"] != int32(100) { + t.Error("GetEntityClassInfo should include entity_id") + } + + if info["entity_name"] != "TestEntity" { + t.Error("GetEntityClassInfo should include entity_name") + } + + if info["entity_level"] != int8(25) { + t.Error("GetEntityClassInfo should include entity_level") + } + + if info["class"] == nil { + t.Error("GetEntityClassInfo should include class information") + } + + if info["description"] == nil { + t.Error("GetEntityClassInfo should include description") + } +} + +func TestChangeEntityClass(t *testing.T) { + integration := NewClassIntegration() + + entity := NewMockClassAware(ClassCommoner) + + // Valid class change + err := integration.ChangeEntityClass(entity, ClassFighter) + if err != nil { + t.Errorf("ChangeEntityClass should allow valid transition: %v", err) + } + + if entity.GetClass() != ClassFighter { + t.Error("Entity class should be updated after successful change") + } + + // Invalid class change + err = integration.ChangeEntityClass(entity, -1) + if err == nil { + t.Error("ChangeEntityClass should reject invalid class") + } + + // Should not change class on error + if entity.GetClass() != ClassFighter { + t.Error("Entity class should not change on failed transition") + } +} + +func TestGetRandomClassForEntity(t *testing.T) { + integration := NewClassIntegration() + classes := GetGlobalClasses() + + adventureClass := integration.GetRandomClassForEntity(ClassTypeAdventure) + if !classes.IsAdventureClass(adventureClass) { + t.Error("GetRandomClassForEntity should return adventure class when requested") + } + + tradeskillClass := integration.GetRandomClassForEntity(ClassTypeTradeskill) + if !classes.IsTradeskillClass(tradeskillClass) { + t.Error("GetRandomClassForEntity should return tradeskill class when requested") + } +} + +func TestCheckClassCompatibility(t *testing.T) { + integration := NewClassIntegration() + + entity1 := NewMockClassAware(ClassWarrior) + entity2 := NewMockClassAware(ClassCleric) + + // Currently all valid classes are compatible + if !integration.CheckClassCompatibility(entity1, entity2) { + t.Error("CheckClassCompatibility should return true for valid classes") + } + + // Same class should be compatible + entity3 := NewMockClassAware(ClassWarrior) + if !integration.CheckClassCompatibility(entity1, entity3) { + t.Error("CheckClassCompatibility should return true for same classes") + } + + // Invalid class should not be compatible + invalidEntity := NewMockClassAware(-1) + if integration.CheckClassCompatibility(entity1, invalidEntity) { + t.Error("CheckClassCompatibility should return false for invalid class") + } +} + +func TestFormatEntityClass(t *testing.T) { + integration := NewClassIntegration() + + entity := NewMockEntityWithClass(100, "TestEntity", 25, ClassWarrior) + + // Test default format + result := integration.FormatEntityClass(entity, "display") + expected := DisplayNameWarrior + if result != expected { + t.Errorf("FormatEntityClass() = %s, want %s", result, expected) + } + + // Test EQ format + eqResult := integration.FormatEntityClass(entity, "eq") + if eqResult == "" { + t.Error("FormatEntityClass with eq format should not be empty") + } +} + +func TestGetEntityBaseClass(t *testing.T) { + integration := NewClassIntegration() + + entity := NewMockClassAware(ClassWarrior) + baseClass := integration.GetEntityBaseClass(entity) + + if baseClass != ClassFighter { + t.Errorf("GetEntityBaseClass() = %d, want %d", baseClass, ClassFighter) + } +} + +func TestGetEntitySecondaryBaseClass(t *testing.T) { + integration := NewClassIntegration() + + entity := NewMockClassAware(ClassGuardian) + secondaryBase := integration.GetEntitySecondaryBaseClass(entity) + + if secondaryBase != ClassWarrior { + t.Errorf("GetEntitySecondaryBaseClass() = %d, want %d", secondaryBase, ClassWarrior) + } +} + +func TestIsEntityAdventureClass(t *testing.T) { + integration := NewClassIntegration() + + adventureEntity := NewMockClassAware(ClassWarrior) + if !integration.IsEntityAdventureClass(adventureEntity) { + t.Error("IsEntityAdventureClass should return true for adventure class") + } + + tradeskillEntity := NewMockClassAware(ClassArmorer) + if integration.IsEntityAdventureClass(tradeskillEntity) { + t.Error("IsEntityAdventureClass should return false for tradeskill class") + } +} + +func TestIsEntityTradeskillClass(t *testing.T) { + integration := NewClassIntegration() + + tradeskillEntity := NewMockClassAware(ClassArmorer) + if !integration.IsEntityTradeskillClass(tradeskillEntity) { + t.Error("IsEntityTradeskillClass should return true for tradeskill class") + } + + adventureEntity := NewMockClassAware(ClassWarrior) + if integration.IsEntityTradeskillClass(adventureEntity) { + t.Error("IsEntityTradeskillClass should return false for adventure class") + } +} + +func TestGetEntitiesByClass(t *testing.T) { + integration := NewClassIntegration() + + entities := []ClassAware{ + NewMockClassAware(ClassWarrior), + NewMockClassAware(ClassCleric), + NewMockClassAware(ClassWarrior), + NewMockClassAware(ClassWizard), + } + + warriors := integration.GetEntitiesByClass(entities, ClassWarrior) + if len(warriors) != 2 { + t.Errorf("GetEntitiesByClass should find 2 warriors, found %d", len(warriors)) + } + + clerics := integration.GetEntitiesByClass(entities, ClassCleric) + if len(clerics) != 1 { + t.Errorf("GetEntitiesByClass should find 1 cleric, found %d", len(clerics)) + } +} + +func TestGetEntitiesByBaseClass(t *testing.T) { + integration := NewClassIntegration() + + entities := []ClassAware{ + NewMockClassAware(ClassWarrior), // Fighter base + NewMockClassAware(ClassGuardian), // Fighter base + NewMockClassAware(ClassCleric), // Priest base + NewMockClassAware(ClassWizard), // Mage base + } + + fighters := integration.GetEntitiesByBaseClass(entities, ClassFighter) + if len(fighters) != 2 { + t.Errorf("GetEntitiesByBaseClass should find 2 fighters, found %d", len(fighters)) + } +} + +func TestGetEntitiesByClassType(t *testing.T) { + integration := NewClassIntegration() + + entities := []ClassAware{ + NewMockClassAware(ClassWarrior), // Adventure + NewMockClassAware(ClassCleric), // Adventure + NewMockClassAware(ClassArmorer), // Tradeskill + NewMockClassAware(ClassAlchemist), // Tradeskill + } + + adventurers := integration.GetEntitiesByClassType(entities, ClassTypeAdventure) + if len(adventurers) != 2 { + t.Errorf("GetEntitiesByClassType should find 2 adventure classes, found %d", len(adventurers)) + } + + crafters := integration.GetEntitiesByClassType(entities, ClassTypeTradeskill) + if len(crafters) != 2 { + t.Errorf("GetEntitiesByClassType should find 2 tradeskill classes, found %d", len(crafters)) + } +} + +func TestIntegrationValidateClassForRace(t *testing.T) { + integration := NewClassIntegration() + + // Valid class and race + valid, message := integration.ValidateClassForRace(ClassWarrior, 1) + if !valid { + t.Error("ValidateClassForRace should return true for valid class") + } + if message != "" { + t.Error("ValidateClassForRace should return empty message for valid combination") + } + + // Invalid class + valid, message = integration.ValidateClassForRace(-1, 1) + if valid { + t.Error("ValidateClassForRace should return false for invalid class") + } + if message == "" { + t.Error("ValidateClassForRace should return error message for invalid class") + } +} + +func TestGetClassStartingStats(t *testing.T) { + integration := NewClassIntegration() + + // Test fighter class stats + fighterStats := integration.GetClassStartingStats(ClassWarrior) + if len(fighterStats) == 0 { + t.Error("GetClassStartingStats should return stats") + } + + // Fighters should have bonus strength + if fighterStats["strength"] <= 50 { + t.Error("Fighters should have strength bonus") + } + + // Test priest class stats + priestStats := integration.GetClassStartingStats(ClassCleric) + if priestStats["wisdom"] <= 50 { + t.Error("Priests should have wisdom bonus") + } + + // Test mage class stats + mageStats := integration.GetClassStartingStats(ClassWizard) + if mageStats["intelligence"] <= 50 { + t.Error("Mages should have intelligence bonus") + } + + // Test scout class stats + scoutStats := integration.GetClassStartingStats(ClassRanger) + if scoutStats["agility"] <= 50 { + t.Error("Scouts should have agility bonus") + } +} + +func TestCreateClassSpecificEntity(t *testing.T) { + integration := NewClassIntegration() + + entityData := integration.CreateClassSpecificEntity(ClassWarrior) + if entityData == nil { + t.Fatal("CreateClassSpecificEntity should not return nil for valid class") + } + + if entityData["class_id"] != ClassWarrior { + t.Errorf("Entity data class_id = %v, want %v", entityData["class_id"], ClassWarrior) + } + + if entityData["class_name"] != DisplayNameWarrior { + t.Error("Entity data should include correct class_name") + } + + if entityData["starting_stats"] == nil { + t.Error("Entity data should include starting_stats") + } + + if entityData["progression"] == nil { + t.Error("Entity data should include progression") + } + + // Test invalid class + invalidData := integration.CreateClassSpecificEntity(-1) + if invalidData != nil { + t.Error("CreateClassSpecificEntity should return nil for invalid class") + } +} + +func TestGetClassSelectionData(t *testing.T) { + integration := NewClassIntegration() + + selectionData := integration.GetClassSelectionData() + if selectionData == nil { + t.Fatal("GetClassSelectionData should not return nil") + } + + if selectionData["adventure_classes"] == nil { + t.Error("Selection data should include adventure_classes") + } + + if selectionData["statistics"] == nil { + t.Error("Selection data should include statistics") + } + + adventureClasses := selectionData["adventure_classes"].([]map[string]interface{}) + if len(adventureClasses) == 0 { + t.Error("Selection data should include adventure classes") + } + + // Verify no tradeskill classes in selection + for _, classData := range adventureClasses { + classType := classData["type"].(string) + if classType != ClassTypeAdventure { + t.Error("Class selection should only include adventure classes") + } + } +} + +func TestGetGlobalClassIntegration(t *testing.T) { + integration1 := GetGlobalClassIntegration() + integration2 := GetGlobalClassIntegration() + + if integration1 != integration2 { + t.Error("GetGlobalClassIntegration should return the same instance") + } + + if integration1 == nil { + t.Error("GetGlobalClassIntegration should not return nil") + } +} + +// ClassManager tests +func TestNewClassManager(t *testing.T) { + manager := NewClassManager() + if manager == nil { + t.Fatal("NewClassManager returned nil") + } + + if manager.classes == nil { + t.Error("ClassManager should have classes instance") + } + + if manager.utils == nil { + t.Error("ClassManager should have utils instance") + } + + if manager.integration == nil { + t.Error("ClassManager should have integration instance") + } + + if manager.classUsageStats == nil { + t.Error("ClassManager should initialize usage stats") + } +} + +func TestRegisterClassUsage(t *testing.T) { + manager := NewClassManager() + + // Register usage for valid class + manager.RegisterClassUsage(ClassWarrior) + + stats := manager.GetClassUsageStats() + if stats[ClassWarrior] != 1 { + t.Errorf("Usage count for Warrior should be 1, got %d", stats[ClassWarrior]) + } + + // Register more usage + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassCleric) + + stats = manager.GetClassUsageStats() + if stats[ClassWarrior] != 2 { + t.Errorf("Usage count for Warrior should be 2, got %d", stats[ClassWarrior]) + } + if stats[ClassCleric] != 1 { + t.Errorf("Usage count for Cleric should be 1, got %d", stats[ClassCleric]) + } + + // Register usage for invalid class (should be ignored) + manager.RegisterClassUsage(-1) + stats = manager.GetClassUsageStats() + if _, exists := stats[-1]; exists { + t.Error("Invalid class usage should not be registered") + } +} + +func TestGetMostPopularClass(t *testing.T) { + manager := NewClassManager() + + // No usage yet + mostPopular, maxUsage := manager.GetMostPopularClass() + if mostPopular != -1 || maxUsage != 0 { + t.Error("Most popular class should be -1 with 0 usage when no usage recorded") + } + + // Register some usage + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassCleric) + + mostPopular, maxUsage = manager.GetMostPopularClass() + if mostPopular != ClassWarrior || maxUsage != 2 { + t.Errorf("Most popular should be Warrior with 2 usage, got class %d with %d usage", mostPopular, maxUsage) + } +} + +func TestGetLeastPopularClass(t *testing.T) { + manager := NewClassManager() + + // No usage yet + leastPopular, minUsage := manager.GetLeastPopularClass() + if leastPopular != -1 || minUsage != -1 { + t.Error("Least popular class should be -1 with -1 usage when no usage recorded") + } + + // Register some usage + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassCleric) + + leastPopular, minUsage = manager.GetLeastPopularClass() + if leastPopular != ClassCleric || minUsage != 1 { + t.Errorf("Least popular should be Cleric with 1 usage, got class %d with %d usage", leastPopular, minUsage) + } +} + +func TestResetUsageStats(t *testing.T) { + manager := NewClassManager() + + // Register some usage + manager.RegisterClassUsage(ClassWarrior) + manager.RegisterClassUsage(ClassCleric) + + stats := manager.GetClassUsageStats() + if len(stats) == 0 { + t.Error("Should have usage stats before reset") + } + + // Reset stats + manager.ResetUsageStats() + + stats = manager.GetClassUsageStats() + if len(stats) != 0 { + t.Error("Usage stats should be empty after reset") + } +} + +func TestProcessClassCommand(t *testing.T) { + manager := NewClassManager() + + // Test list command + result, err := manager.ProcessClassCommand("list", []string{}) + if err != nil { + t.Errorf("List command should not error: %v", err) + } + if result == "" { + t.Error("List command should return results") + } + + // Test info command with valid class + result, err = manager.ProcessClassCommand("info", []string{"WARRIOR"}) + if err != nil { + t.Errorf("Info command should not error: %v", err) + } + if result == "" { + t.Error("Info command should return results") + } + + // Test info command without args + _, err = manager.ProcessClassCommand("info", []string{}) + if err == nil { + t.Error("Info command without args should error") + } + + // Test random command + result, err = manager.ProcessClassCommand("random", []string{}) + if err != nil { + t.Errorf("Random command should not error: %v", err) + } + if result == "" { + t.Error("Random command should return results") + } + + // Test stats command + result, err = manager.ProcessClassCommand("stats", []string{}) + if err != nil { + t.Errorf("Stats command should not error: %v", err) + } + if result == "" { + t.Error("Stats command should return results") + } + + // Test search command with pattern + result, err = manager.ProcessClassCommand("search", []string{"war"}) + if err != nil { + t.Errorf("Search command should not error: %v", err) + } + if result == "" { + t.Error("Search command should return results") + } + + // Test search command without args + _, err = manager.ProcessClassCommand("search", []string{}) + if err == nil { + t.Error("Search command without args should error") + } + + // Test progression command + result, err = manager.ProcessClassCommand("progression", []string{"GUARDIAN"}) + if err != nil { + t.Errorf("Progression command should not error: %v", err) + } + if result == "" { + t.Error("Progression command should return results") + } + + // Test unknown command + _, err = manager.ProcessClassCommand("unknown", []string{}) + if err == nil { + t.Error("Unknown command should error") + } +} + +func TestValidateEntityClasses(t *testing.T) { + manager := NewClassManager() + + entities := []ClassAware{ + NewMockClassAware(ClassWarrior), + NewMockClassAware(ClassCleric), + NewMockClassAware(-1), // Invalid + NewMockClassAware(ClassWizard), + NewMockClassAware(100), // Invalid + } + + results := manager.ValidateEntityClasses(entities) + + if results["total_entities"] != len(entities) { + t.Error("Validation results should include total entity count") + } + + if results["valid_count"] != 3 { + t.Errorf("Should find 3 valid entities, found %v", results["valid_count"]) + } + + if results["invalid_count"] != 2 { + t.Errorf("Should find 2 invalid entities, found %v", results["invalid_count"]) + } + + if results["class_distribution"] == nil { + t.Error("Validation results should include class distribution") + } + + if results["invalid_entities"] == nil { + t.Error("Validation results should include invalid entities list") + } +} + +func TestGetClassRecommendations(t *testing.T) { + manager := NewClassManager() + + // Test recommendations by class type + preferences := map[string]interface{}{ + "class_type": ClassTypeAdventure, + } + + recommendations := manager.GetClassRecommendations(preferences) + if len(recommendations) == 0 { + t.Error("Should get recommendations for adventure classes") + } + + classes := GetGlobalClasses() + for _, classID := range recommendations { + if !classes.IsAdventureClass(classID) { + t.Error("Adventure class recommendations should only include adventure classes") + } + } + + // Test recommendations by base class + basePreferences := map[string]interface{}{ + "base_class": ClassFighter, + } + + baseRecommendations := manager.GetClassRecommendations(basePreferences) + if len(baseRecommendations) == 0 { + t.Error("Should get recommendations for fighter base class") + } + + // Test recommendations by preferred stats + statPreferences := map[string]interface{}{ + "preferred_stats": []string{"strength", "stamina"}, + } + + statRecommendations := manager.GetClassRecommendations(statPreferences) + if len(statRecommendations) == 0 { + t.Error("Should get recommendations for preferred stats") + } + + // Test empty preferences (should get defaults) + emptyPreferences := map[string]interface{}{} + defaultRecommendations := manager.GetClassRecommendations(emptyPreferences) + if len(defaultRecommendations) == 0 { + t.Error("Should get default recommendations when no preferences given") + } +} + +func TestGetGlobalClassManager(t *testing.T) { + manager1 := GetGlobalClassManager() + manager2 := GetGlobalClassManager() + + if manager1 != manager2 { + t.Error("GetGlobalClassManager should return the same instance (singleton)") + } + + if manager1 == nil { + t.Error("GetGlobalClassManager should not return nil") + } +} + +// Constants tests +func TestClassConstants(t *testing.T) { + // Test class ID constants + if ClassCommoner != 0 { + t.Errorf("ClassCommoner = %d, want 0", ClassCommoner) + } + + if ClassFighter != 1 { + t.Errorf("ClassFighter = %d, want 1", ClassFighter) + } + + if ClassAlchemist != 57 { + t.Errorf("ClassAlchemist = %d, want 57", ClassAlchemist) + } + + // Test validation constants + if MinClassID != 0 { + t.Errorf("MinClassID = %d, want 0", MinClassID) + } + + if MaxClassID != 57 { + t.Errorf("MaxClassID = %d, want 57", MaxClassID) + } + + if MaxClasses != 58 { + t.Errorf("MaxClasses = %d, want 58", MaxClasses) + } + + // Test class type constants + if ClassTypeAdventure != "adventure" { + t.Errorf("ClassTypeAdventure = %s, want adventure", ClassTypeAdventure) + } + + if ClassTypeTradeskill != "tradeskill" { + t.Errorf("ClassTypeTradeskill = %s, want tradeskill", ClassTypeTradeskill) + } +} + +// Concurrency tests +func TestClassesConcurrency(t *testing.T) { + classes := NewClasses() + var wg sync.WaitGroup + + // Concurrent reads + for i := 0; i < 100; i++ { + wg.Add(1) + go func(classID int8) { + defer wg.Done() + classes.GetClassName(classID) + classes.GetClassNameCase(classID) + classes.GetBaseClass(classID) + classes.IsValidClassID(classID) + classes.GetAllClasses() + }(int8(i % MaxClasses)) + } + + wg.Wait() + + // Verify state consistency after concurrent access + allClasses := classes.GetAllClasses() + if len(allClasses) != MaxClasses { + t.Error("Concurrent access should not affect class data integrity") + } +} + +func TestClassManagerConcurrency(t *testing.T) { + manager := NewClassManager() + var wg sync.WaitGroup + + // Concurrent usage registration + for i := 0; i < 100; i++ { + wg.Add(1) + go func(classID int8) { + defer wg.Done() + manager.RegisterClassUsage(classID) + }(int8(i % 10)) // Use first 10 classes + } + + // Concurrent stats reading + for i := 0; i < 50; i++ { + wg.Add(1) + go func() { + defer wg.Done() + manager.GetClassUsageStats() + manager.GetMostPopularClass() + manager.GetLeastPopularClass() + }() + } + + wg.Wait() + + // Verify final state + stats := manager.GetClassUsageStats() + totalUsage := int32(0) + for _, count := range stats { + totalUsage += count + } + + if totalUsage != 100 { + t.Errorf("Total usage should be 100, got %d", totalUsage) + } +} + +func TestMockEntitiesConcurrency(t *testing.T) { + entity := NewMockClassAware(ClassWarrior) + var wg sync.WaitGroup + + // Concurrent reads and writes + for i := 0; i < 100; i++ { + wg.Add(1) + go func(classID int8) { + defer wg.Done() + entity.SetClass(classID) + entity.GetClass() + }(int8(i % MaxClasses)) + } + + wg.Wait() + + // Entity should have a valid class after concurrent access + finalClass := entity.GetClass() + classes := GetGlobalClasses() + if !classes.IsValidClassID(finalClass) { + t.Errorf("Final class %d should be valid after concurrent access", finalClass) + } +} + +// Benchmarks +func BenchmarkGetClassID(b *testing.B) { + classes := NewClasses() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + classes.GetClassID("WARRIOR") + } +} + +func BenchmarkGetClassName(b *testing.B) { + classes := NewClasses() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + classes.GetClassName(ClassWarrior) + } +} + +func BenchmarkGetBaseClass(b *testing.B) { + classes := NewClasses() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + classes.GetBaseClass(ClassGuardian) + } +} + +func BenchmarkGetAllClasses(b *testing.B) { + classes := NewClasses() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + classes.GetAllClasses() + } +} + +func BenchmarkParseClassName(b *testing.B) { + utils := NewClassUtils() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + utils.ParseClassName("WARRIOR") + } +} + +func BenchmarkGetRandomClassByType(b *testing.B) { + utils := NewClassUtils() + rand.Seed(time.Now().UnixNano()) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + utils.GetRandomClassByType(ClassTypeAdventure) + } +} + +func BenchmarkRegisterClassUsage(b *testing.B) { + manager := NewClassManager() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + manager.RegisterClassUsage(ClassWarrior) + } +} + +func BenchmarkValidateEntityClass(b *testing.B) { + integration := NewClassIntegration() + entity := NewMockClassAware(ClassWarrior) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + integration.ValidateEntityClass(entity) + } +} + +func BenchmarkMockClassAwareGetSet(b *testing.B) { + entity := NewMockClassAware(ClassWarrior) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + entity.SetClass(ClassCleric) + entity.GetClass() + } +} \ No newline at end of file diff --git a/internal/classes/constants.go b/internal/classes/constants.go index 4997382..fa9bd04 100644 --- a/internal/classes/constants.go +++ b/internal/classes/constants.go @@ -3,95 +3,95 @@ package classes // Adventure class ID constants converted from C++ classes.h const ( // Base classes - ClassCommoner = 0 - ClassFighter = 1 - ClassPriest = 11 - ClassMage = 21 - ClassScout = 31 + ClassCommoner = int8(0) + ClassFighter = int8(1) + ClassPriest = int8(11) + ClassMage = int8(21) + ClassScout = int8(31) // Fighter subclasses - ClassWarrior = 2 - ClassGuardian = 3 - ClassBerserker = 4 - ClassBrawler = 5 - ClassMonk = 6 - ClassBruiser = 7 - ClassCrusader = 8 - ClassShadowknight = 9 - ClassPaladin = 10 + ClassWarrior = int8(2) + ClassGuardian = int8(3) + ClassBerserker = int8(4) + ClassBrawler = int8(5) + ClassMonk = int8(6) + ClassBruiser = int8(7) + ClassCrusader = int8(8) + ClassShadowknight = int8(9) + ClassPaladin = int8(10) // Priest subclasses - ClassCleric = 12 - ClassTemplar = 13 - ClassInquisitor = 14 - ClassDruid = 15 - ClassWarden = 16 - ClassFury = 17 - ClassShaman = 18 - ClassMystic = 19 - ClassDefiler = 20 + ClassCleric = int8(12) + ClassTemplar = int8(13) + ClassInquisitor = int8(14) + ClassDruid = int8(15) + ClassWarden = int8(16) + ClassFury = int8(17) + ClassShaman = int8(18) + ClassMystic = int8(19) + ClassDefiler = int8(20) // Mage subclasses - ClassSorcerer = 22 - ClassWizard = 23 - ClassWarlock = 24 - ClassEnchanter = 25 - ClassIllusionist = 26 - ClassCoercer = 27 - ClassSummoner = 28 - ClassConjuror = 29 - ClassNecromancer = 30 + ClassSorcerer = int8(22) + ClassWizard = int8(23) + ClassWarlock = int8(24) + ClassEnchanter = int8(25) + ClassIllusionist = int8(26) + ClassCoercer = int8(27) + ClassSummoner = int8(28) + ClassConjuror = int8(29) + ClassNecromancer = int8(30) // Scout subclasses - ClassRogue = 32 - ClassSwashbuckler = 33 - ClassBrigand = 34 - ClassBard = 35 - ClassTroubador = 36 - ClassDirge = 37 - ClassPredator = 38 - ClassRanger = 39 - ClassAssassin = 40 - ClassAnimalist = 41 - ClassBeastlord = 42 + ClassRogue = int8(32) + ClassSwashbuckler = int8(33) + ClassBrigand = int8(34) + ClassBard = int8(35) + ClassTroubador = int8(36) + ClassDirge = int8(37) + ClassPredator = int8(38) + ClassRanger = int8(39) + ClassAssassin = int8(40) + ClassAnimalist = int8(41) + ClassBeastlord = int8(42) // Special classes - ClassShaper = 43 - ClassChanneler = 44 + ClassShaper = int8(43) + ClassChanneler = int8(44) ) // Tradeskill class ID constants const ( // Base tradeskill classes - ClassArtisan = 45 - ClassCraftsman = 46 - ClassOutfitter = 50 - ClassScholar = 54 + ClassArtisan = int8(45) + ClassCraftsman = int8(46) + ClassOutfitter = int8(50) + ClassScholar = int8(54) // Craftsman subclasses - ClassProvisioner = 47 - ClassWoodworker = 48 - ClassCarpenter = 49 + ClassProvisioner = int8(47) + ClassWoodworker = int8(48) + ClassCarpenter = int8(49) // Outfitter subclasses - ClassArmorer = 51 - ClassWeaponsmith = 52 - ClassTailor = 53 + ClassArmorer = int8(51) + ClassWeaponsmith = int8(52) + ClassTailor = int8(53) // Scholar subclasses - ClassJeweler = 55 - ClassSage = 56 - ClassAlchemist = 57 + ClassJeweler = int8(55) + ClassSage = int8(56) + ClassAlchemist = int8(57) ) // Class validation constants const ( - MaxClassID = 57 - MinClassID = 0 + MaxClassID = int8(57) + MinClassID = int8(0) DefaultClassID = ClassCommoner - ClassicMaxAdventureClass = 40 // Classic adventure classes (0-40) - ClassicMaxTradeskillClass = 13 // Classic tradeskill progression (0-13) - MaxClasses = 58 // Total number of classes + ClassicMaxAdventureClass = int8(40) // Classic adventure classes (0-40) + ClassicMaxTradeskillClass = int8(13) // Classic tradeskill progression (0-13) + MaxClasses = 58 // Total number of classes ) // Class type categories diff --git a/internal/classes/integration.go b/internal/classes/integration.go index dae7a70..38e6662 100644 --- a/internal/classes/integration.go +++ b/internal/classes/integration.go @@ -108,8 +108,8 @@ func (ci *ClassIntegration) CheckClassCompatibility(entity1, entity2 ClassAware) } // Check if they share the same base class (good for grouping) - base1 := ci.classes.GetBaseClass(class1) - base2 := ci.classes.GetBaseClass(class2) + // base1 := ci.classes.GetBaseClass(class1) + // base2 := ci.classes.GetBaseClass(class2) // Different base classes can group together (provides diversity) // Same base class provides synergy diff --git a/internal/classes/manager.go b/internal/classes/manager.go index 2f5ebe6..108de71 100644 --- a/internal/classes/manager.go +++ b/internal/classes/manager.go @@ -172,7 +172,7 @@ func (cm *ClassManager) handleInfoCommand(args []string) (string, error) { return fmt.Sprintf("Invalid class ID: %d", classID), nil } - result := fmt.Sprintf("Class Information:\n") + result := "Class Information:\n" result += fmt.Sprintf("ID: %d\n", classID) result += fmt.Sprintf("Name: %s\n", classInfo["display_name"]) result += fmt.Sprintf("Type: %s\n", classInfo["type"]) @@ -390,7 +390,7 @@ func (cm *ClassManager) GetClassRecommendations(preferences map[string]interface // Check for base class preference if baseClass, exists := preferences["base_class"]; exists { if baseClassID, ok := baseClass.(int8); ok { - subClasses := cm.utils.GetClassesByBaseClass(baseClassID) + subClasses := cm.utils.GetClassesForBaseClass(baseClassID) recommendations = append(recommendations, subClasses...) } } diff --git a/internal/classes/utils.go b/internal/classes/utils.go index 96b8b7b..f14d0a1 100644 --- a/internal/classes/utils.go +++ b/internal/classes/utils.go @@ -259,8 +259,8 @@ func (cu *ClassUtils) GetClassProgression(classID int8) []int8 { return progression } -// GetClasssByBaseClass returns all classes that belong to a base class -func (cu *ClassUtils) GetClasssByBaseClass(baseClassID int8) []int8 { +// GetClassesForBaseClass returns all classes that belong to a base class +func (cu *ClassUtils) GetClassesForBaseClass(baseClassID int8) []int8 { result := make([]int8, 0) allClasses := cu.classes.GetAllClasses()