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