package common import ( "fmt" "strings" "sync" ) // VisualState represents a visual animation state type VisualState struct { id int name string } // NewVisualState creates a new visual state func NewVisualState(id int, name string) *VisualState { return &VisualState{ id: id, name: name, } } // GetID returns the visual state ID func (vs *VisualState) GetID() int { return vs.id } // GetName returns the visual state name func (vs *VisualState) GetName() string { return vs.name } // Emote represents an emote with visual state and messages type Emote struct { name string visualState int32 message string targetedMessage string } // NewEmote creates a new emote func NewEmote(name string, visualState int32, message, targetedMessage string) *Emote { return &Emote{ name: name, visualState: visualState, message: message, targetedMessage: targetedMessage, } } // GetName returns the emote name func (e *Emote) GetName() string { return e.name } // GetVisualState returns the visual state ID func (e *Emote) GetVisualState() int32 { return e.visualState } // GetMessage returns the emote message func (e *Emote) GetMessage() string { return e.message } // GetTargetedMessage returns the targeted emote message func (e *Emote) GetTargetedMessage() string { return e.targetedMessage } // VersionRange represents a min/max version range type VersionRange struct { minVersion int32 maxVersion int32 } // NewVersionRange creates a new version range func NewVersionRange(min, max int32) *VersionRange { return &VersionRange{ minVersion: min, maxVersion: max, } } // GetMinVersion returns the minimum version func (vr *VersionRange) GetMinVersion() int32 { return vr.minVersion } // GetMaxVersion returns the maximum version func (vr *VersionRange) GetMaxVersion() int32 { return vr.maxVersion } // InRange checks if a version is within this range func (vr *VersionRange) InRange(version int32) bool { return version >= vr.minVersion && (vr.maxVersion == 0 || version <= vr.maxVersion) } // EmoteVersionRange manages emotes across different client versions type EmoteVersionRange struct { name string versionMap map[*VersionRange]*Emote mutex sync.RWMutex } // NewEmoteVersionRange creates a new emote version range func NewEmoteVersionRange(name string) *EmoteVersionRange { return &EmoteVersionRange{ name: name, versionMap: make(map[*VersionRange]*Emote), } } // GetName returns the emote range name func (evr *EmoteVersionRange) GetName() string { return evr.name } // AddVersionRange adds an emote for a specific version range func (evr *EmoteVersionRange) AddVersionRange(minVersion, maxVersion int32, name string, visualState int32, message, targetedMessage string) error { evr.mutex.Lock() defer evr.mutex.Unlock() // Check for duplicate ranges for vr := range evr.versionMap { if evr.rangesOverlap(vr, minVersion, maxVersion) { return fmt.Errorf("duplicate emote mapping of %s with range min %d max %d, existing found with range min %d max %d", evr.name, minVersion, maxVersion, vr.minVersion, vr.maxVersion) } } vr := NewVersionRange(minVersion, maxVersion) emote := NewEmote(name, visualState, message, targetedMessage) evr.versionMap[vr] = emote return nil } // rangesOverlap checks if two version ranges overlap func (evr *EmoteVersionRange) rangesOverlap(existing *VersionRange, minVersion, maxVersion int32) bool { // Check various overlap conditions if existing.minVersion <= minVersion && maxVersion <= existing.maxVersion { return true } if existing.minVersion <= minVersion && existing.maxVersion == 0 { return true } if existing.minVersion == 0 && maxVersion <= existing.maxVersion { return true } return false } // FindEmoteByVersion finds the emote for a specific client version func (evr *EmoteVersionRange) FindEmoteByVersion(version int32) *Emote { evr.mutex.RLock() defer evr.mutex.RUnlock() for vr, emote := range evr.versionMap { if vr.InRange(version) { return emote } } return nil } // VisualStates manages all visual states, emotes, and spell visuals type VisualStates struct { visualStateMap map[string]*VisualState emoteMap map[string]*EmoteVersionRange emoteMapID map[int32]*EmoteVersionRange spellMap map[string]*EmoteVersionRange spellMapID map[int32]*EmoteVersionRange mutex sync.RWMutex } // NewVisualStates creates a new visual states manager func NewVisualStates() *VisualStates { return &VisualStates{ visualStateMap: make(map[string]*VisualState), emoteMap: make(map[string]*EmoteVersionRange), emoteMapID: make(map[int32]*EmoteVersionRange), spellMap: make(map[string]*EmoteVersionRange), spellMapID: make(map[int32]*EmoteVersionRange), } } // InsertVisualState adds a visual state func (vs *VisualStates) InsertVisualState(state *VisualState) { vs.mutex.Lock() defer vs.mutex.Unlock() vs.visualStateMap[state.name] = state } // FindVisualState finds a visual state by name func (vs *VisualStates) FindVisualState(name string) *VisualState { vs.mutex.RLock() defer vs.mutex.RUnlock() return vs.visualStateMap[name] } // InsertEmoteRange adds an emote range func (vs *VisualStates) InsertEmoteRange(emote *EmoteVersionRange, animationID int32) { vs.mutex.Lock() defer vs.mutex.Unlock() vs.emoteMap[emote.name] = emote vs.emoteMapID[animationID] = emote } // FindEmoteRange finds an emote range by name func (vs *VisualStates) FindEmoteRange(name string) *EmoteVersionRange { vs.mutex.RLock() defer vs.mutex.RUnlock() return vs.emoteMap[name] } // FindEmote finds an emote by name and version func (vs *VisualStates) FindEmote(name string, version int32) *Emote { vs.mutex.RLock() defer vs.mutex.RUnlock() if emoteRange, exists := vs.emoteMap[name]; exists { return emoteRange.FindEmoteByVersion(version) } return nil } // FindEmoteRangeByID finds an emote range by ID func (vs *VisualStates) FindEmoteRangeByID(id int32) *EmoteVersionRange { vs.mutex.RLock() defer vs.mutex.RUnlock() return vs.emoteMapID[id] } // FindEmoteByID finds an emote by visual ID and version func (vs *VisualStates) FindEmoteByID(visualID int32, version int32) *Emote { vs.mutex.RLock() defer vs.mutex.RUnlock() if emoteRange, exists := vs.emoteMapID[visualID]; exists { return emoteRange.FindEmoteByVersion(version) } return nil } // InsertSpellVisualRange adds a spell visual range func (vs *VisualStates) InsertSpellVisualRange(emote *EmoteVersionRange, spellVisualID int32) { vs.mutex.Lock() defer vs.mutex.Unlock() vs.spellMap[emote.name] = emote vs.spellMapID[spellVisualID] = emote } // FindSpellVisualRange finds a spell visual range by name func (vs *VisualStates) FindSpellVisualRange(name string) *EmoteVersionRange { vs.mutex.RLock() defer vs.mutex.RUnlock() return vs.spellMap[name] } // FindSpellVisualRangeByID finds a spell visual range by ID func (vs *VisualStates) FindSpellVisualRangeByID(id int32) *EmoteVersionRange { vs.mutex.RLock() defer vs.mutex.RUnlock() return vs.spellMapID[id] } // FindSpellVisual finds a spell visual by name and version func (vs *VisualStates) FindSpellVisual(name string, version int32) *Emote { vs.mutex.RLock() defer vs.mutex.RUnlock() if spellRange, exists := vs.spellMap[name]; exists { return spellRange.FindEmoteByVersion(version) } return nil } // FindSpellVisualByID finds a spell visual by ID and version func (vs *VisualStates) FindSpellVisualByID(visualID int32, version int32) *Emote { vs.mutex.RLock() defer vs.mutex.RUnlock() if spellRange, exists := vs.spellMapID[visualID]; exists { return spellRange.FindEmoteByVersion(version) } return nil } // ClearVisualStates clears all visual states func (vs *VisualStates) ClearVisualStates() { vs.mutex.Lock() defer vs.mutex.Unlock() vs.visualStateMap = make(map[string]*VisualState) } // ClearEmotes clears all emotes func (vs *VisualStates) ClearEmotes() { vs.mutex.Lock() defer vs.mutex.Unlock() vs.emoteMap = make(map[string]*EmoteVersionRange) vs.emoteMapID = make(map[int32]*EmoteVersionRange) } // ClearSpellVisuals clears all spell visuals func (vs *VisualStates) ClearSpellVisuals() { vs.mutex.Lock() defer vs.mutex.Unlock() vs.spellMap = make(map[string]*EmoteVersionRange) vs.spellMapID = make(map[int32]*EmoteVersionRange) } // Reset clears all data func (vs *VisualStates) Reset() { vs.ClearVisualStates() vs.ClearEmotes() vs.ClearSpellVisuals() } // GetEmoteList returns a list of all emote names func (vs *VisualStates) GetEmoteList() []string { vs.mutex.RLock() defer vs.mutex.RUnlock() names := make([]string, 0, len(vs.emoteMap)) for name := range vs.emoteMap { names = append(names, name) } return names } // GetVisualStateList returns a list of all visual state names func (vs *VisualStates) GetVisualStateList() []string { vs.mutex.RLock() defer vs.mutex.RUnlock() names := make([]string, 0, len(vs.visualStateMap)) for name := range vs.visualStateMap { names = append(names, name) } return names } // FindEmoteByPartialName finds emotes that contain the partial name func (vs *VisualStates) FindEmoteByPartialName(partial string) []*EmoteVersionRange { vs.mutex.RLock() defer vs.mutex.RUnlock() partial = strings.ToLower(partial) results := make([]*EmoteVersionRange, 0) for name, emote := range vs.emoteMap { if strings.Contains(strings.ToLower(name), partial) { results = append(results, emote) } } return results }