package traits import ( "sync" ) // TraitData represents a single trait that can be learned by players. // Converted from the C++ TraitData struct with all original fields preserved. type TraitData struct { SpellID uint32 // ID of the spell associated with this trait Level int8 // Required level to learn this trait ClassReq int8 // Required class (255 = any class) RaceReq int8 // Required race (255 = any race) IsTrait bool // Whether this is a regular trait IsInnate bool // Whether this is an innate racial ability IsFocusEffect bool // Whether this is a focus effect IsTraining bool // Whether this is a training ability Tier int8 // Spell tier for this trait Group int8 // Trait group/category (TRAITS_* constants) ItemID uint32 // Associated item ID (if any) } // MasterTraitList manages all available traits in the game. // Converted from the C++ MasterTraitList class with thread-safe operations. type MasterTraitList struct { traitList []TraitData // List of all available traits mutex sync.RWMutex // Protects concurrent access to trait list } // TraitLists contains organized trait data for a specific player. // Used to categorize traits by type for efficient processing. type TraitLists struct { // SortedTraitList organizes character traits by group and level // Map structure: [group][level] -> []*TraitData SortedTraitList map[int8]map[int8][]*TraitData // ClassTraining contains training abilities for the player's class // Map structure: [level] -> []*TraitData ClassTraining map[int8][]*TraitData // RaceTraits contains racial abilities (non-innate) // Map structure: [group] -> []*TraitData RaceTraits map[int8][]*TraitData // InnateRaceTraits contains innate racial abilities // Map structure: [group] -> []*TraitData InnateRaceTraits map[int8][]*TraitData // FocusEffects contains focus effect abilities // Map structure: [group] -> []*TraitData FocusEffects map[int8][]*TraitData } // TraitSelectionContext holds state during trait selection processing. type TraitSelectionContext struct { CollectTraits []*TraitData // All potential traits for selection TieredTraits []*TraitData // Traits in current tier selection PreviousMatchedSpells map[uint32]int8 // Previously matched spells and their groups FoundSpellMatch bool // Whether a spell match was found in current group GroupToApply int8 // Current group being processed TieredSelection bool // Whether tiered selection is enabled } // TraitPacketData contains data needed to build trait list packets. type TraitPacketData struct { // Character traits organized by level CharacterTraits []TraitLevelData // Class training abilities organized by level ClassTraining []TraitLevelData // Racial traits organized by group RacialTraits []RacialTraitGroup // Innate racial abilities InnateAbilities []TraitInfo // Focus effects (client version >= 1188) FocusEffects []TraitInfo // Selection availability RacialSelectionsAvailable int8 FocusSelectionsAvailable int8 } // TraitLevelData represents traits available at a specific level. type TraitLevelData struct { Level int8 // Required level for these traits SelectedLine int8 // Which trait is selected (255 = none, 0-4 = trait index) Traits []TraitInfo // Available traits at this level (max 5) } // RacialTraitGroup represents a group of racial traits. type RacialTraitGroup struct { GroupName string // Display name for this group Traits []TraitInfo // Traits in this group } // TraitInfo contains display information for a single trait. type TraitInfo struct { SpellID uint32 // Spell ID for this trait Name string // Display name Icon uint16 // Icon ID for display Icon2 uint16 // Secondary icon ID (backdrop) Selected bool // Whether player has selected this trait Unknown1 uint32 // Unknown field 1 (usually 1) Unknown2 uint32 // Unknown field 2 (usually 1) } // TraitSelectionRequest represents a request to select traits. type TraitSelectionRequest struct { PlayerID uint32 // ID of player making selection TraitSpells []uint32 // Spell IDs of traits being selected PacketType int8 // Type of trait selection packet } // TraitValidationResult contains the result of trait validation. type TraitValidationResult struct { Allowed bool // Whether the trait selection is allowed Reason string // Reason why not allowed (if applicable) UsedSlots int16 // Number of trait slots currently used MaxSlots int16 // Maximum trait slots available ClassicReq int16 // Classic level requirement (if applicable) } // TraitManagerStats tracks statistics for the trait system. type TraitManagerStats struct { TotalTraits int32 // Total number of traits in system TraitsByType map[string]int32 // Number of traits by type TraitsByGroup map[int8]int32 // Number of traits by group TraitsByLevel map[int8]int32 // Number of traits by level requirement PlayersWithTraits int32 // Number of players with trait selections } // PlayerTraitState represents a player's current trait selections and availability. type PlayerTraitState struct { PlayerID uint32 // Player ID Level int16 // Current player level Class int8 // Player's adventure class Race int8 // Player's race NeedTraitUpdate bool // Whether trait lists need refresh TraitLists *TraitLists // Organized trait lists SelectedTraits map[uint32]bool // Currently selected traits (spellID -> selected) AvailableSlots map[string]int16 // Available slots by trait type UsedSlots map[string]int16 // Used slots by trait type LastUpdate int64 // Timestamp of last update } // TraitSystemConfig holds configuration for the trait system. type TraitSystemConfig struct { TieringSelection bool // Enable tiered trait selection UseClassicLevelTable bool // Use classic EQ2 level requirements FocusSelectLevel int32 // Level interval for focus effects TrainingSelectLevel int32 // Level interval for training abilities RaceSelectLevel int32 // Level interval for racial abilities CharacterSelectLevel int32 // Level interval for character traits } // Copy creates a deep copy of a TraitData. func (td *TraitData) Copy() *TraitData { if td == nil { return nil } return &TraitData{ SpellID: td.SpellID, Level: td.Level, ClassReq: td.ClassReq, RaceReq: td.RaceReq, IsTrait: td.IsTrait, IsInnate: td.IsInnate, IsFocusEffect: td.IsFocusEffect, IsTraining: td.IsTraining, Tier: td.Tier, Group: td.Group, ItemID: td.ItemID, } } // IsUniversalTrait checks if this trait is available to all classes and races. func (td *TraitData) IsUniversalTrait() bool { return td.ClassReq == UniversalClassReq && td.RaceReq == UniversalRaceReq && td.IsTrait } // IsForClass checks if this trait is available for the specified class. func (td *TraitData) IsForClass(classID int8) bool { return td.ClassReq == UniversalClassReq || td.ClassReq == classID } // IsForRace checks if this trait is available for the specified race. func (td *TraitData) IsForRace(raceID int8) bool { return td.RaceReq == UniversalRaceReq || td.RaceReq == raceID } // GetTraitType returns a string description of the trait type. func (td *TraitData) GetTraitType() string { if td.IsFocusEffect { return "Focus Effect" } if td.IsTraining { return "Training" } if td.IsInnate { return "Innate Racial" } if td.RaceReq != UniversalRaceReq { return "Racial" } if td.IsTrait { return "Character Trait" } return "Unknown" } // Validate checks if the trait data is valid. func (td *TraitData) Validate() error { if td.SpellID == 0 { return ErrInvalidSpellID } if td.Level < 0 { return ErrInvalidLevel } if td.Group < 0 || td.Group > TraitsTradeskill { return ErrInvalidGroup } return nil } // NewTraitLists creates a new initialized TraitLists structure. func NewTraitLists() *TraitLists { return &TraitLists{ SortedTraitList: make(map[int8]map[int8][]*TraitData), ClassTraining: make(map[int8][]*TraitData), RaceTraits: make(map[int8][]*TraitData), InnateRaceTraits: make(map[int8][]*TraitData), FocusEffects: make(map[int8][]*TraitData), } } // Clear removes all trait data from the lists. func (tl *TraitLists) Clear() { tl.SortedTraitList = make(map[int8]map[int8][]*TraitData) tl.ClassTraining = make(map[int8][]*TraitData) tl.RaceTraits = make(map[int8][]*TraitData) tl.InnateRaceTraits = make(map[int8][]*TraitData) tl.FocusEffects = make(map[int8][]*TraitData) } // NewTraitSelectionContext creates a new trait selection context. func NewTraitSelectionContext(tieredSelection bool) *TraitSelectionContext { return &TraitSelectionContext{ CollectTraits: make([]*TraitData, 0), TieredTraits: make([]*TraitData, 0), PreviousMatchedSpells: make(map[uint32]int8), GroupToApply: UnassignedGroupID, TieredSelection: tieredSelection, } } // Reset clears the context for reuse. func (tsc *TraitSelectionContext) Reset() { tsc.CollectTraits = tsc.CollectTraits[:0] tsc.TieredTraits = tsc.TieredTraits[:0] tsc.PreviousMatchedSpells = make(map[uint32]int8) tsc.FoundSpellMatch = false tsc.GroupToApply = UnassignedGroupID } // NewPlayerTraitState creates a new player trait state. func NewPlayerTraitState(playerID uint32, level int16, classID, raceID int8) *PlayerTraitState { return &PlayerTraitState{ PlayerID: playerID, Level: level, Class: classID, Race: raceID, NeedTraitUpdate: true, TraitLists: NewTraitLists(), SelectedTraits: make(map[uint32]bool), AvailableSlots: make(map[string]int16), UsedSlots: make(map[string]int16), } } // UpdateLevel updates the player's level and marks traits for refresh. func (pts *PlayerTraitState) UpdateLevel(newLevel int16) { if pts.Level != newLevel { pts.Level = newLevel pts.NeedTraitUpdate = true } } // SelectTrait marks a trait as selected. func (pts *PlayerTraitState) SelectTrait(spellID uint32) { pts.SelectedTraits[spellID] = true } // UnselectTrait marks a trait as not selected. func (pts *PlayerTraitState) UnselectTrait(spellID uint32) { delete(pts.SelectedTraits, spellID) } // HasTrait checks if a trait is selected. func (pts *PlayerTraitState) HasTrait(spellID uint32) bool { return pts.SelectedTraits[spellID] } // GetSelectedTraitCount returns the number of selected traits. func (pts *PlayerTraitState) GetSelectedTraitCount() int { return len(pts.SelectedTraits) } // Common error types for trait system var ( ErrInvalidSpellID = NewTraitError("invalid spell ID") ErrInvalidLevel = NewTraitError("invalid level") ErrInvalidGroup = NewTraitError("invalid trait group") ErrInvalidPlayer = NewTraitError("invalid player") ErrTraitNotFound = NewTraitError("trait not found") ErrNotAllowed = NewTraitError("trait selection not allowed") ErrInsufficientLevel = NewTraitError("insufficient level") ErrMaxTraitsReached = NewTraitError("maximum traits reached") ) // TraitError represents an error in the trait system. type TraitError struct { message string } // NewTraitError creates a new trait error. func NewTraitError(message string) *TraitError { return &TraitError{message: message} } // Error returns the error message. func (e *TraitError) Error() string { return e.message } // IsTraitError checks if an error is a trait error. func IsTraitError(err error) bool { _, ok := err.(*TraitError) return ok }