eq2go/internal/alt_advancement/master_list.go

476 lines
12 KiB
Go

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]any {
mal.mutex.RLock()
defer mal.mutex.RUnlock()
stats := make(map[string]any)
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]any {
manl.mutex.RLock()
defer manl.mutex.RUnlock()
stats := make(map[string]any)
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
}