package traits import ( "fmt" "testing" ) func TestTraitData(t *testing.T) { trait := &TraitData{ SpellID: 12345, Level: 10, ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, IsInnate: false, IsFocusEffect: false, IsTraining: false, Tier: 1, Group: TraitsCombat, ItemID: 0, } // Test Copy method copied := trait.Copy() if copied == nil { t.Fatal("Copy returned nil") } if copied.SpellID != trait.SpellID { t.Errorf("Expected SpellID %d, got %d", trait.SpellID, copied.SpellID) } if copied.Level != trait.Level { t.Errorf("Expected Level %d, got %d", trait.Level, copied.Level) } // Test Copy with nil var nilTrait *TraitData copiedNil := nilTrait.Copy() if copiedNil != nil { t.Error("Copy of nil should return nil") } // Test IsUniversalTrait if !trait.IsUniversalTrait() { t.Error("Trait should be universal") } // Test IsForClass if !trait.IsForClass(5) { t.Error("Universal trait should be available for any class") } // Test IsForRace if !trait.IsForRace(3) { t.Error("Universal trait should be available for any race") } // Test GetTraitType traitType := trait.GetTraitType() if traitType != "Character Trait" { t.Errorf("Expected 'Character Trait', got '%s'", traitType) } // Test Validate err := trait.Validate() if err != nil { t.Errorf("Valid trait should pass validation: %v", err) } // Test invalid trait invalidTrait := &TraitData{ SpellID: 0, // Invalid Level: -1, // Invalid Group: 10, // Invalid } err = invalidTrait.Validate() if err == nil { t.Error("Invalid trait should fail validation") } } func TestMasterTraitList(t *testing.T) { masterList := NewMasterTraitList() if masterList == nil { t.Fatal("NewMasterTraitList returned nil") } // Test initial state if masterList.Size() != 0 { t.Error("New master list should be empty") } // Test AddTrait trait := &TraitData{ SpellID: 12345, Level: 10, ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, IsInnate: false, IsFocusEffect: false, IsTraining: false, Tier: 1, Group: TraitsCombat, ItemID: 67890, } err := masterList.AddTrait(trait) if err != nil { t.Fatalf("AddTrait failed: %v", err) } if masterList.Size() != 1 { t.Errorf("Expected size 1 after adding trait, got %d", masterList.Size()) } // Test GetTrait retrieved := masterList.GetTrait(12345) if retrieved == nil { t.Fatal("GetTrait returned nil") } if retrieved.SpellID != 12345 { t.Errorf("Expected SpellID 12345, got %d", retrieved.SpellID) } // Test GetTraitByItemID retrievedByItem := masterList.GetTraitByItemID(67890) if retrievedByItem == nil { t.Fatal("GetTraitByItemID returned nil") } if retrievedByItem.ItemID != 67890 { t.Errorf("Expected ItemID 67890, got %d", retrievedByItem.ItemID) } // Test GetTrait with non-existent ID nonExistent := masterList.GetTrait(99999) if nonExistent != nil { t.Error("GetTrait should return nil for non-existent trait") } // Test AddTrait with nil err = masterList.AddTrait(nil) if err == nil { t.Error("AddTrait should fail with nil trait") } // Test DestroyTraits masterList.DestroyTraits() if masterList.Size() != 0 { t.Error("Size should be 0 after DestroyTraits") } } func TestPlayerTraitState(t *testing.T) { playerState := NewPlayerTraitState(12345, 25, 1, 2) if playerState == nil { t.Fatal("NewPlayerTraitState returned nil") } if playerState.PlayerID != 12345 { t.Errorf("Expected PlayerID 12345, got %d", playerState.PlayerID) } // Test UpdateLevel playerState.UpdateLevel(30) if playerState.Level != 30 { t.Errorf("Expected level 30, got %d", playerState.Level) } if !playerState.NeedTraitUpdate { t.Error("Should need trait update after level change") } // Test trait selection playerState.SelectTrait(11111) if !playerState.HasTrait(11111) { t.Error("Player should have selected trait") } if playerState.GetSelectedTraitCount() != 1 { t.Errorf("Expected 1 selected trait, got %d", playerState.GetSelectedTraitCount()) } // Test trait unselection playerState.UnselectTrait(11111) if playerState.HasTrait(11111) { t.Error("Player should not have unselected trait") } if playerState.GetSelectedTraitCount() != 0 { t.Errorf("Expected 0 selected traits, got %d", playerState.GetSelectedTraitCount()) } } func TestTraitLists(t *testing.T) { traitLists := NewTraitLists() if traitLists == nil { t.Fatal("NewTraitLists returned nil") } // Test initial state if len(traitLists.SortedTraitList) != 0 { t.Error("SortedTraitList should be empty initially") } // Test Clear traitLists.SortedTraitList[0] = make(map[int8][]*TraitData) traitLists.Clear() if len(traitLists.SortedTraitList) != 0 { t.Error("SortedTraitList should be empty after Clear") } } func TestTraitSelectionContext(t *testing.T) { context := NewTraitSelectionContext(true) if context == nil { t.Fatal("NewTraitSelectionContext returned nil") } if !context.TieredSelection { t.Error("TieredSelection should be true") } if context.GroupToApply != UnassignedGroupID { t.Error("GroupToApply should be UnassignedGroupID initially") } // Test Reset context.GroupToApply = 5 context.FoundSpellMatch = true context.Reset() if context.GroupToApply != UnassignedGroupID { t.Error("GroupToApply should be reset to UnassignedGroupID") } if context.FoundSpellMatch { t.Error("FoundSpellMatch should be reset to false") } } func TestTraitSystemConfig(t *testing.T) { config := &TraitSystemConfig{ TieringSelection: true, UseClassicLevelTable: false, FocusSelectLevel: 9, TrainingSelectLevel: 10, RaceSelectLevel: 10, CharacterSelectLevel: 4, } if !config.TieringSelection { t.Error("TieringSelection should be true") } if config.FocusSelectLevel != 9 { t.Errorf("Expected FocusSelectLevel 9, got %d", config.FocusSelectLevel) } } func TestGenerateTraitLists(t *testing.T) { masterList := NewMasterTraitList() // Add test traits traits := []*TraitData{ { SpellID: 1001, Level: 10, ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, Group: TraitsCombat, }, { SpellID: 1002, Level: 15, ClassReq: 5, // Specific class IsTraining: true, Group: TraitsAttributes, }, { SpellID: 1003, Level: 20, RaceReq: 3, // Specific race Group: TraitsNoncombat, }, { SpellID: 1004, Level: 25, RaceReq: 3, IsInnate: true, Group: TraitsPools, }, { SpellID: 1005, Level: 30, ClassReq: 5, IsFocusEffect: true, Group: TraitsResist, }, } for _, trait := range traits { masterList.AddTrait(trait) } playerState := NewPlayerTraitState(12345, 50, 5, 3) // Test GenerateTraitLists success := masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID) if !success { t.Fatal("GenerateTraitLists should succeed") } // Check that traits were categorized correctly // Should have 1 character trait (universal) characterTraitFound := false for _, levelMap := range playerState.TraitLists.SortedTraitList { if len(levelMap) > 0 { characterTraitFound = true break } } if !characterTraitFound { t.Error("Should have character traits") } // Should have 1 class training trait if len(playerState.TraitLists.ClassTraining) == 0 { t.Error("Should have class training traits") } // Should have 1 racial trait if len(playerState.TraitLists.RaceTraits) == 0 { t.Error("Should have racial traits") } // Should have 1 innate racial trait if len(playerState.TraitLists.InnateRaceTraits) == 0 { t.Error("Should have innate racial traits") } // Should have 1 focus effect if len(playerState.TraitLists.FocusEffects) == 0 { t.Error("Should have focus effects") } } func TestIsPlayerAllowedTrait(t *testing.T) { masterList := NewMasterTraitList() playerState := NewPlayerTraitState(12345, 20, 5, 3) config := &TraitSystemConfig{ TieringSelection: false, UseClassicLevelTable: false, FocusSelectLevel: 9, TrainingSelectLevel: 10, RaceSelectLevel: 10, CharacterSelectLevel: 4, } trait := &TraitData{ SpellID: 1001, Level: 10, ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, Group: TraitsCombat, } masterList.AddTrait(trait) // Generate trait lists masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID) // Test trait allowance allowed := masterList.IsPlayerAllowedTrait(playerState, trait, config) if !allowed { t.Error("Player should be allowed this trait") } } func TestTraitManager(t *testing.T) { masterList := NewMasterTraitList() config := &TraitSystemConfig{ TieringSelection: false, UseClassicLevelTable: false, FocusSelectLevel: 9, TrainingSelectLevel: 10, RaceSelectLevel: 10, CharacterSelectLevel: 4, } manager := NewTraitManager(masterList, config) if manager == nil { t.Fatal("NewTraitManager returned nil") } // Test GetPlayerState playerState := manager.GetPlayerState(12345, 25, 5, 3) if playerState == nil { t.Fatal("GetPlayerState returned nil") } if playerState.PlayerID != 12345 { t.Errorf("Expected PlayerID 12345, got %d", playerState.PlayerID) } // Test level update playerState2 := manager.GetPlayerState(12345, 30, 5, 3) if playerState2.Level != 30 { t.Errorf("Expected level 30, got %d", playerState2.Level) } // Test ClearPlayerState manager.ClearPlayerState(12345) // Should create new state after clearing playerState3 := manager.GetPlayerState(12345, 25, 5, 3) if playerState3 == playerState { t.Error("Should create new state after clearing") } } func TestTraitPacketHelper(t *testing.T) { helper := NewTraitPacketHelper() if helper == nil { t.Fatal("NewTraitPacketHelper returned nil") } // Test FormatTraitFieldName fieldName := helper.FormatTraitFieldName("trait", 2, "_icon") if fieldName != "trait2_icon" { t.Errorf("Expected 'trait2_icon', got '%s'", fieldName) } // Test GetPacketTypeForTrait trait := &TraitData{ ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, IsTraining: false, } packetType := helper.GetPacketTypeForTrait(trait, 5, 3) if packetType != PacketTypeCharacterTrait { t.Errorf("Expected PacketTypeCharacterTrait (%d), got %d", PacketTypeCharacterTrait, packetType) } // Test CalculateAvailableSelections available := helper.CalculateAvailableSelections(30, 2, 10) if available != 1 { t.Errorf("Expected 1 available selection, got %d", available) } // Test GetClassicLevelRequirement levelReq := helper.GetClassicLevelRequirement(PersonalTraitLevelLimits, 2) if levelReq != PersonalTraitLevelLimits[2] { t.Errorf("Expected %d, got %d", PersonalTraitLevelLimits[2], levelReq) } // Test BuildEmptyTraitSlot emptySlot := helper.BuildEmptyTraitSlot() if emptySlot.SpellID != EmptyTraitID { t.Errorf("Expected EmptyTraitID (%d), got %d", EmptyTraitID, emptySlot.SpellID) } // Test CountSelectedTraits traits := []TraitInfo{ {Selected: true}, {Selected: false}, {Selected: true}, } count := helper.CountSelectedTraits(traits) if count != 2 { t.Errorf("Expected 2 selected traits, got %d", count) } } func TestTraitErrors(t *testing.T) { // Test TraitError err := NewTraitError("test error") if err == nil { t.Fatal("NewTraitError returned nil") } if err.Error() != "test error" { t.Errorf("Expected 'test error', got '%s'", err.Error()) } // Test IsTraitError if !IsTraitError(err) { t.Error("Should identify as trait error") } // Test with non-trait error if IsTraitError(fmt.Errorf("not a trait error")) { t.Error("Should not identify as trait error") } } func TestConstants(t *testing.T) { // Test trait group constants if TraitsAttributes != 0 { t.Errorf("Expected TraitsAttributes to be 0, got %d", TraitsAttributes) } if TraitsTradeskill != 5 { t.Errorf("Expected TraitsTradeskill to be 5, got %d", TraitsTradeskill) } // Test level limits if len(PersonalTraitLevelLimits) != 9 { t.Errorf("Expected 9 personal trait level limits, got %d", len(PersonalTraitLevelLimits)) } if PersonalTraitLevelLimits[1] != 8 { t.Errorf("Expected first personal trait at level 8, got %d", PersonalTraitLevelLimits[1]) } // Test trait group names if TraitGroupNames[TraitsAttributes] != "Attributes" { t.Errorf("Expected 'Attributes', got '%s'", TraitGroupNames[TraitsAttributes]) } // Test constants if MaxTraitsPerLine != 5 { t.Errorf("Expected MaxTraitsPerLine to be 5, got %d", MaxTraitsPerLine) } if UniversalClassReq != -1 { t.Errorf("Expected UniversalClassReq to be -1, got %d", UniversalClassReq) } } func BenchmarkMasterTraitListAccess(b *testing.B) { masterList := NewMasterTraitList() // Add test traits for i := 0; i < 1000; i++ { trait := &TraitData{ SpellID: uint32(i + 1000), Level: int8((i % 50) + 1), Group: int8(i % 6), } masterList.AddTrait(trait) } b.ResetTimer() for i := 0; i < b.N; i++ { masterList.GetTrait(uint32((i % 1000) + 1000)) } } func BenchmarkGenerateTraitLists(b *testing.B) { masterList := NewMasterTraitList() // Add test traits for i := 0; i < 100; i++ { trait := &TraitData{ SpellID: uint32(i + 1000), Level: int8((i % 50) + 1), ClassReq: UniversalClassReq, RaceReq: UniversalRaceReq, IsTrait: true, Group: int8(i % 6), } masterList.AddTrait(trait) } playerState := NewPlayerTraitState(12345, 50, 5, 3) b.ResetTimer() for i := 0; i < b.N; i++ { masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID) } }