476 lines
12 KiB
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
|
|
}
|