eq2go/internal/traits/types.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
}