eq2go/internal/traits/traits_test.go

585 lines
14 KiB
Go

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)
}
}