585 lines
14 KiB
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)
|
|
}
|
|
}
|