package alt_advancement import ( "fmt" "sort" "time" ) // NewMasterAAList creates a new master AA list func NewMasterAAList() *MasterAAList { return &MasterAAList{ aaList: make([]*AltAdvanceData, 0), aaBySpellID: make(map[int32]*AltAdvanceData), aaByNodeID: make(map[int32]*AltAdvanceData), aaByGroup: make(map[int8][]*AltAdvanceData), totalLoaded: 0, lastLoadTime: time.Now(), } } // AddAltAdvancement adds an AA to the master list func (mal *MasterAAList) AddAltAdvancement(data *AltAdvanceData) error { if data == nil { return fmt.Errorf("data cannot be nil") } if !data.IsValid() { return fmt.Errorf("invalid AA data: spell_id=%d, node_id=%d", data.SpellID, data.NodeID) } mal.mutex.Lock() defer mal.mutex.Unlock() // Check for duplicates if _, exists := mal.aaBySpellID[data.SpellID]; exists { return fmt.Errorf("AA with spell ID %d already exists", data.SpellID) } if _, exists := mal.aaByNodeID[data.NodeID]; exists { return fmt.Errorf("AA with node ID %d already exists", data.NodeID) } // Add to main list mal.aaList = append(mal.aaList, data) // Add to lookup maps mal.aaBySpellID[data.SpellID] = data mal.aaByNodeID[data.NodeID] = data // Add to group map if mal.aaByGroup[data.Group] == nil { mal.aaByGroup[data.Group] = make([]*AltAdvanceData, 0) } mal.aaByGroup[data.Group] = append(mal.aaByGroup[data.Group], data) mal.totalLoaded++ return nil } // GetAltAdvancement returns an AA by spell ID func (mal *MasterAAList) GetAltAdvancement(spellID int32) *AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() if data, exists := mal.aaBySpellID[spellID]; exists { return data.Copy() } return nil } // GetAltAdvancementByNodeID returns an AA by node ID func (mal *MasterAAList) GetAltAdvancementByNodeID(nodeID int32) *AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() if data, exists := mal.aaByNodeID[nodeID]; exists { return data.Copy() } return nil } // GetAAsByGroup returns all AAs for a specific group/tab func (mal *MasterAAList) GetAAsByGroup(group int8) []*AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() if aaList, exists := mal.aaByGroup[group]; exists { // Return copies to prevent external modification result := make([]*AltAdvanceData, len(aaList)) for i, aa := range aaList { result[i] = aa.Copy() } return result } return []*AltAdvanceData{} } // GetAAsByClass returns AAs available for a specific class func (mal *MasterAAList) GetAAsByClass(classID int8) []*AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() var result []*AltAdvanceData for _, aa := range mal.aaList { // Check if AA is available for this class (0 means all classes) if aa.ClassReq == 0 || aa.ClassReq == classID { result = append(result, aa.Copy()) } } return result } // GetAAsByLevel returns AAs available at a specific level func (mal *MasterAAList) GetAAsByLevel(level int8) []*AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() var result []*AltAdvanceData for _, aa := range mal.aaList { if aa.MinLevel <= level { result = append(result, aa.Copy()) } } return result } // Size returns the total number of AAs func (mal *MasterAAList) Size() int { mal.mutex.RLock() defer mal.mutex.RUnlock() return len(mal.aaList) } // GetAllAAs returns all AAs (copies) func (mal *MasterAAList) GetAllAAs() []*AltAdvanceData { mal.mutex.RLock() defer mal.mutex.RUnlock() result := make([]*AltAdvanceData, len(mal.aaList)) for i, aa := range mal.aaList { result[i] = aa.Copy() } return result } // DestroyAltAdvancements clears all AA data func (mal *MasterAAList) DestroyAltAdvancements() { mal.mutex.Lock() defer mal.mutex.Unlock() mal.aaList = make([]*AltAdvanceData, 0) mal.aaBySpellID = make(map[int32]*AltAdvanceData) mal.aaByNodeID = make(map[int32]*AltAdvanceData) mal.aaByGroup = make(map[int8][]*AltAdvanceData) mal.totalLoaded = 0 } // SortAAsByGroup sorts AAs within each group by row and column func (mal *MasterAAList) SortAAsByGroup() { mal.mutex.Lock() defer mal.mutex.Unlock() for group := range mal.aaByGroup { sort.Slice(mal.aaByGroup[group], func(i, j int) bool { aaI := mal.aaByGroup[group][i] aaJ := mal.aaByGroup[group][j] // Sort by row first, then by column if aaI.Row != aaJ.Row { return aaI.Row < aaJ.Row } return aaI.Col < aaJ.Col }) } } // GetGroupCount returns the number of groups with AAs func (mal *MasterAAList) GetGroupCount() int { mal.mutex.RLock() defer mal.mutex.RUnlock() return len(mal.aaByGroup) } // GetGroups returns all group IDs that have AAs func (mal *MasterAAList) GetGroups() []int8 { mal.mutex.RLock() defer mal.mutex.RUnlock() groups := make([]int8, 0, len(mal.aaByGroup)) for group := range mal.aaByGroup { groups = append(groups, group) } sort.Slice(groups, func(i, j int) bool { return groups[i] < groups[j] }) return groups } // ValidateAAData validates all AA data for consistency func (mal *MasterAAList) ValidateAAData() []error { mal.mutex.RLock() defer mal.mutex.RUnlock() var errors []error for _, aa := range mal.aaList { if !aa.IsValid() { errors = append(errors, fmt.Errorf("invalid AA data: spell_id=%d, node_id=%d", aa.SpellID, aa.NodeID)) } // Validate prerequisites if aa.RankPrereqID > 0 { if _, exists := mal.aaByNodeID[aa.RankPrereqID]; !exists { errors = append(errors, fmt.Errorf("AA %d has invalid prerequisite node ID %d", aa.NodeID, aa.RankPrereqID)) } } // Validate positioning if aa.Col < MIN_AA_COL || aa.Col > MAX_AA_COL { errors = append(errors, fmt.Errorf("AA %d has invalid column %d", aa.NodeID, aa.Col)) } if aa.Row < MIN_AA_ROW || aa.Row > MAX_AA_ROW { errors = append(errors, fmt.Errorf("AA %d has invalid row %d", aa.NodeID, aa.Row)) } // Validate costs and ranks if aa.RankCost < MIN_RANK_COST || aa.RankCost > MAX_RANK_COST { errors = append(errors, fmt.Errorf("AA %d has invalid rank cost %d", aa.NodeID, aa.RankCost)) } if aa.MaxRank < MIN_MAX_RANK || aa.MaxRank > MAX_MAX_RANK { errors = append(errors, fmt.Errorf("AA %d has invalid max rank %d", aa.NodeID, aa.MaxRank)) } } return errors } // GetStats returns statistics about the master AA list func (mal *MasterAAList) GetStats() map[string]interface{} { mal.mutex.RLock() defer mal.mutex.RUnlock() stats := make(map[string]interface{}) stats[STAT_TOTAL_AAS_LOADED] = mal.totalLoaded stats["last_load_time"] = mal.lastLoadTime stats["groups_count"] = len(mal.aaByGroup) // Count AAs per group groupCounts := make(map[int8]int) for group, aaList := range mal.aaByGroup { groupCounts[group] = len(aaList) } stats[STAT_AAS_PER_TAB] = groupCounts return stats } // NewMasterAANodeList creates a new master AA node list func NewMasterAANodeList() *MasterAANodeList { return &MasterAANodeList{ nodeList: make([]*TreeNodeData, 0), nodesByClass: make(map[int32][]*TreeNodeData), nodesByTree: make(map[int32]*TreeNodeData), totalLoaded: 0, lastLoadTime: time.Now(), } } // AddTreeNode adds a tree node to the master list func (manl *MasterAANodeList) AddTreeNode(data *TreeNodeData) error { if data == nil { return fmt.Errorf("data cannot be nil") } manl.mutex.Lock() defer manl.mutex.Unlock() // Check for duplicates if _, exists := manl.nodesByTree[data.TreeID]; exists { return fmt.Errorf("tree node with tree ID %d already exists", data.TreeID) } // Add to main list manl.nodeList = append(manl.nodeList, data) // Add to lookup maps manl.nodesByTree[data.TreeID] = data // Add to class map if manl.nodesByClass[data.ClassID] == nil { manl.nodesByClass[data.ClassID] = make([]*TreeNodeData, 0) } manl.nodesByClass[data.ClassID] = append(manl.nodesByClass[data.ClassID], data) manl.totalLoaded++ return nil } // GetTreeNodes returns all tree nodes func (manl *MasterAANodeList) GetTreeNodes() []*TreeNodeData { manl.mutex.RLock() defer manl.mutex.RUnlock() // Return copies to prevent external modification result := make([]*TreeNodeData, len(manl.nodeList)) for i, node := range manl.nodeList { nodeCopy := *node result[i] = &nodeCopy } return result } // GetTreeNodesByClass returns tree nodes for a specific class func (manl *MasterAANodeList) GetTreeNodesByClass(classID int32) []*TreeNodeData { manl.mutex.RLock() defer manl.mutex.RUnlock() if nodeList, exists := manl.nodesByClass[classID]; exists { // Return copies to prevent external modification result := make([]*TreeNodeData, len(nodeList)) for i, node := range nodeList { nodeCopy := *node result[i] = &nodeCopy } return result } return []*TreeNodeData{} } // GetTreeNode returns a specific tree node by tree ID func (manl *MasterAANodeList) GetTreeNode(treeID int32) *TreeNodeData { manl.mutex.RLock() defer manl.mutex.RUnlock() if node, exists := manl.nodesByTree[treeID]; exists { nodeCopy := *node return &nodeCopy } return nil } // Size returns the total number of tree nodes func (manl *MasterAANodeList) Size() int { manl.mutex.RLock() defer manl.mutex.RUnlock() return len(manl.nodeList) } // DestroyTreeNodes clears all tree node data func (manl *MasterAANodeList) DestroyTreeNodes() { manl.mutex.Lock() defer manl.mutex.Unlock() manl.nodeList = make([]*TreeNodeData, 0) manl.nodesByClass = make(map[int32][]*TreeNodeData) manl.nodesByTree = make(map[int32]*TreeNodeData) manl.totalLoaded = 0 } // GetClassCount returns the number of classes with tree nodes func (manl *MasterAANodeList) GetClassCount() int { manl.mutex.RLock() defer manl.mutex.RUnlock() return len(manl.nodesByClass) } // GetClasses returns all class IDs that have tree nodes func (manl *MasterAANodeList) GetClasses() []int32 { manl.mutex.RLock() defer manl.mutex.RUnlock() classes := make([]int32, 0, len(manl.nodesByClass)) for classID := range manl.nodesByClass { classes = append(classes, classID) } sort.Slice(classes, func(i, j int) bool { return classes[i] < classes[j] }) return classes } // ValidateTreeNodes validates all tree node data for consistency func (manl *MasterAANodeList) ValidateTreeNodes() []error { manl.mutex.RLock() defer manl.mutex.RUnlock() var errors []error // Check for orphaned tree IDs treeIDMap := make(map[int32]bool) for _, node := range manl.nodeList { treeIDMap[node.TreeID] = true } // Check for duplicate class/tree combinations classTreeMap := make(map[string]bool) for _, node := range manl.nodeList { key := fmt.Sprintf("%d_%d", node.ClassID, node.TreeID) if classTreeMap[key] { errors = append(errors, fmt.Errorf("duplicate class/tree combination: class=%d, tree=%d", node.ClassID, node.TreeID)) } classTreeMap[key] = true } return errors } // GetStats returns statistics about the master node list func (manl *MasterAANodeList) GetStats() map[string]interface{} { manl.mutex.RLock() defer manl.mutex.RUnlock() stats := make(map[string]interface{}) stats[STAT_TOTAL_NODES_LOADED] = manl.totalLoaded stats["last_load_time"] = manl.lastLoadTime stats["classes_count"] = len(manl.nodesByClass) // Count nodes per class classCounts := make(map[int32]int) for classID, nodeList := range manl.nodesByClass { classCounts[classID] = len(nodeList) } stats["nodes_per_class"] = classCounts return stats } // BuildAATreeMap builds a map of AA tree configurations for a specific class func (manl *MasterAANodeList) BuildAATreeMap(classID int32) map[int8]int32 { nodes := manl.GetTreeNodesByClass(classID) treeMap := make(map[int8]int32) // Map each tab/group to its corresponding tree ID for i, node := range nodes { if i < 10 { // Limit to the number of defined AA tabs treeMap[int8(i)] = node.TreeID } } return treeMap } // GetTreeIDForTab returns the tree ID for a specific tab and class func (manl *MasterAANodeList) GetTreeIDForTab(classID int32, tab int8) int32 { nodes := manl.GetTreeNodesByClass(classID) if int(tab) < len(nodes) { return nodes[tab].TreeID } return 0 }