eq2go/internal/titles/title.go

325 lines
9.0 KiB
Go

package titles
import (
"sync"
"time"
)
// Title represents a single character title with all its properties
type Title struct {
ID int32 `json:"id"` // Unique title identifier
Name string `json:"name"` // Display name of the title
Description string `json:"description"` // Description shown in UI
// Positioning and display
Position int32 `json:"position"` // TitlePositionPrefix or TitlePositionSuffix
DisplayFormat int32 `json:"display_format"` // How the title is formatted
Color uint32 `json:"color"` // Color code for display
// Classification
Source int32 `json:"source"` // How the title is obtained (TitleSource*)
Category string `json:"category"` // Category for organization
Rarity int32 `json:"rarity"` // Title rarity level
// Requirements and restrictions
Requirements []TitleRequirement `json:"requirements"` // What's needed to unlock
Flags uint32 `json:"flags"` // Various title flags
// Metadata
CreatedDate time.Time `json:"created_date"` // When title was added
LastModified time.Time `json:"last_modified"` // When title was last updated
MinLevel int32 `json:"min_level"` // Minimum character level
MaxLevel int32 `json:"max_level"` // Maximum character level (0 = no limit)
ExpansionID int32 `json:"expansion_id"` // Required expansion
AchievementID uint32 `json:"achievement_id"` // Associated achievement if any
// Expiration (for temporary titles)
ExpirationHours int32 `json:"expiration_hours"` // Hours until expiration (0 = permanent)
mutex sync.RWMutex // Thread safety
}
// TitleRequirement represents a single requirement for unlocking a title
type TitleRequirement struct {
Type int32 `json:"type"` // RequirementType* constant
Value int32 `json:"value"` // Required value/amount
StringValue string `json:"string_value"` // For string-based requirements
Description string `json:"description"` // Human-readable requirement description
}
// PlayerTitle represents a title owned by a player
type PlayerTitle struct {
TitleID int32 `json:"title_id"` // Reference to Title.ID
PlayerID int32 `json:"player_id"` // Character ID who owns it
EarnedDate time.Time `json:"earned_date"` // When the title was earned
ExpiresAt time.Time `json:"expires_at"` // When temporary title expires (zero for permanent)
IsActive bool `json:"is_active"` // Whether title is currently displayed
IsPrefix bool `json:"is_prefix"` // True if used as prefix, false for suffix
// Achievement context
AchievementID uint32 `json:"achievement_id"` // Achievement that granted this title
QuestID uint32 `json:"quest_id"` // Quest that granted this title
mutex sync.RWMutex // Thread safety
}
// NewTitle creates a new title with default values
func NewTitle(id int32, name string) *Title {
return &Title{
ID: id,
Name: name,
Position: TitlePositionSuffix,
DisplayFormat: DisplayFormatSimple,
Color: ColorCommon,
Source: TitleSourceMiscellaneous,
Category: CategoryMiscellaneous,
Rarity: TitleRarityCommon,
Requirements: make([]TitleRequirement, 0),
Flags: 0,
CreatedDate: time.Now(),
LastModified: time.Now(),
MinLevel: 1,
MaxLevel: 0, // No limit
ExpansionID: 0, // Base game
AchievementID: 0,
ExpirationHours: 0, // Permanent
}
}
// NewPlayerTitle creates a new player title entry
func NewPlayerTitle(titleID, playerID int32) *PlayerTitle {
return &PlayerTitle{
TitleID: titleID,
PlayerID: playerID,
EarnedDate: time.Now(),
ExpiresAt: time.Time{}, // Zero value = permanent
IsActive: false,
IsPrefix: false,
AchievementID: 0,
QuestID: 0,
}
}
// IsExpired checks if a temporary title has expired
func (pt *PlayerTitle) IsExpired() bool {
pt.mutex.RLock()
defer pt.mutex.RUnlock()
if pt.ExpiresAt.IsZero() {
return false // Permanent title
}
return time.Now().After(pt.ExpiresAt)
}
// SetExpiration sets when a temporary title expires
func (pt *PlayerTitle) SetExpiration(hours int32) {
pt.mutex.Lock()
defer pt.mutex.Unlock()
if hours <= 0 {
pt.ExpiresAt = time.Time{} // Make permanent
} else {
pt.ExpiresAt = time.Now().Add(time.Duration(hours) * time.Hour)
}
}
// Activate makes this title the active one for the player
func (pt *PlayerTitle) Activate(isPrefix bool) {
pt.mutex.Lock()
defer pt.mutex.Unlock()
pt.IsActive = true
pt.IsPrefix = isPrefix
}
// Deactivate removes this title from active display
func (pt *PlayerTitle) Deactivate() {
pt.mutex.Lock()
defer pt.mutex.Unlock()
pt.IsActive = false
}
// Clone creates a deep copy of the title
func (t *Title) Clone() *Title {
t.mutex.RLock()
defer t.mutex.RUnlock()
clone := &Title{
ID: t.ID,
Name: t.Name,
Description: t.Description,
Position: t.Position,
DisplayFormat: t.DisplayFormat,
Color: t.Color,
Source: t.Source,
Category: t.Category,
Rarity: t.Rarity,
Requirements: make([]TitleRequirement, len(t.Requirements)),
Flags: t.Flags,
CreatedDate: t.CreatedDate,
LastModified: t.LastModified,
MinLevel: t.MinLevel,
MaxLevel: t.MaxLevel,
ExpansionID: t.ExpansionID,
AchievementID: t.AchievementID,
ExpirationHours: t.ExpirationHours,
}
copy(clone.Requirements, t.Requirements)
return clone
}
// Clone creates a deep copy of the player title
func (pt *PlayerTitle) Clone() *PlayerTitle {
pt.mutex.RLock()
defer pt.mutex.RUnlock()
return &PlayerTitle{
TitleID: pt.TitleID,
PlayerID: pt.PlayerID,
EarnedDate: pt.EarnedDate,
ExpiresAt: pt.ExpiresAt,
IsActive: pt.IsActive,
IsPrefix: pt.IsPrefix,
AchievementID: pt.AchievementID,
QuestID: pt.QuestID,
}
}
// HasFlag checks if the title has a specific flag set
func (t *Title) HasFlag(flag uint32) bool {
t.mutex.RLock()
defer t.mutex.RUnlock()
return (t.Flags & flag) != 0
}
// SetFlag sets a specific flag on the title
func (t *Title) SetFlag(flag uint32) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Flags |= flag
t.LastModified = time.Now()
}
// ClearFlag removes a specific flag from the title
func (t *Title) ClearFlag(flag uint32) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Flags &^= flag
t.LastModified = time.Now()
}
// AddRequirement adds a new requirement to the title
func (t *Title) AddRequirement(reqType int32, value int32, stringValue, description string) {
t.mutex.Lock()
defer t.mutex.Unlock()
req := TitleRequirement{
Type: reqType,
Value: value,
StringValue: stringValue,
Description: description,
}
t.Requirements = append(t.Requirements, req)
t.LastModified = time.Now()
}
// ClearRequirements removes all requirements from the title
func (t *Title) ClearRequirements() {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Requirements = make([]TitleRequirement, 0)
t.LastModified = time.Now()
}
// IsHidden checks if the title is hidden from normal display
func (t *Title) IsHidden() bool {
return t.HasFlag(FlagHidden)
}
// IsAccountWide checks if the title is available to all characters on the account
func (t *Title) IsAccountWide() bool {
return t.HasFlag(FlagAccountWide)
}
// IsUnique checks if only one player can have this title
func (t *Title) IsUnique() bool {
return t.HasFlag(FlagUnique)
}
// IsTemporary checks if the title expires after a certain time
func (t *Title) IsTemporary() bool {
return t.HasFlag(FlagTemporary)
}
// IsGMOnly checks if the title is restricted to Game Masters
func (t *Title) IsGMOnly() bool {
return t.HasFlag(FlagGMOnly)
}
// GetDisplayName returns the formatted title name for display
func (t *Title) GetDisplayName() string {
t.mutex.RLock()
defer t.mutex.RUnlock()
switch t.DisplayFormat {
case DisplayFormatWithBrackets:
return "[" + t.Name + "]"
case DisplayFormatWithQuotes:
return "\"" + t.Name + "\""
case DisplayFormatWithCommas:
return "," + t.Name + ","
default:
return t.Name
}
}
// SetDescription updates the title description
func (t *Title) SetDescription(description string) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Description = description
t.LastModified = time.Now()
}
// SetCategory updates the title category
func (t *Title) SetCategory(category string) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Category = category
t.LastModified = time.Now()
}
// SetRarity updates the title rarity and adjusts color accordingly
func (t *Title) SetRarity(rarity int32) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.Rarity = rarity
// Update color based on rarity
switch rarity {
case TitleRarityCommon:
t.Color = ColorCommon
case TitleRarityUncommon:
t.Color = ColorUncommon
case TitleRarityRare:
t.Color = ColorRare
case TitleRarityEpic:
t.Color = ColorEpic
case TitleRarityLegendary:
t.Color = ColorLegendary
case TitleRarityUnique:
t.Color = ColorUnique
}
t.LastModified = time.Now()
}