fix titles package
This commit is contained in:
parent
3b6d35ce98
commit
0c37453971
303
internal/titles/database.go
Normal file
303
internal/titles/database.go
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
package titles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB wraps a SQLite connection for title operations
|
||||||
|
type DB struct {
|
||||||
|
conn *sqlite.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDB opens a database connection
|
||||||
|
func OpenDB(path string) (*DB, error) {
|
||||||
|
conn, err := sqlite.OpenConn(path, sqlite.OpenReadWrite|sqlite.OpenCreate|sqlite.OpenWAL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable foreign keys
|
||||||
|
if err := sqlitex.ExecTransient(conn, "PRAGMA foreign_keys = ON;", nil); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DB{conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the database connection
|
||||||
|
func (db *DB) Close() error {
|
||||||
|
if db.conn != nil {
|
||||||
|
return db.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTables creates the necessary tables for the title system
|
||||||
|
func (db *DB) CreateTables() error {
|
||||||
|
// Create titles table
|
||||||
|
titlesTableSQL := `
|
||||||
|
CREATE TABLE IF NOT EXISTS titles (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
category TEXT,
|
||||||
|
position INTEGER NOT NULL DEFAULT 0,
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
rarity INTEGER NOT NULL DEFAULT 0,
|
||||||
|
flags INTEGER NOT NULL DEFAULT 0,
|
||||||
|
achievement_id INTEGER,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_titles_category ON titles(category);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_titles_achievement ON titles(achievement_id);
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create player_titles table
|
||||||
|
playerTitlesTableSQL := `
|
||||||
|
CREATE TABLE IF NOT EXISTS player_titles (
|
||||||
|
player_id INTEGER NOT NULL,
|
||||||
|
title_id INTEGER NOT NULL,
|
||||||
|
achievement_id INTEGER,
|
||||||
|
granted_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
expiration_date TIMESTAMP,
|
||||||
|
is_active INTEGER DEFAULT 0,
|
||||||
|
PRIMARY KEY (player_id, title_id),
|
||||||
|
FOREIGN KEY (title_id) REFERENCES titles(id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_player_titles_player ON player_titles(player_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_player_titles_expiration ON player_titles(expiration_date);
|
||||||
|
`
|
||||||
|
|
||||||
|
// Execute table creation
|
||||||
|
if err := sqlitex.ExecuteScript(db.conn, titlesTableSQL, &sqlitex.ExecOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to create titles table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sqlitex.ExecuteScript(db.conn, playerTitlesTableSQL, &sqlitex.ExecOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to create player_titles table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMasterTitles loads all titles from the database
|
||||||
|
func (db *DB) LoadMasterTitles() ([]*Title, error) {
|
||||||
|
var titles []*Title
|
||||||
|
|
||||||
|
query := `SELECT id, name, description, category, position, source, rarity, flags, achievement_id FROM titles`
|
||||||
|
|
||||||
|
err := sqlitex.Execute(db.conn, query, &sqlitex.ExecOptions{
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
|
title := &Title{
|
||||||
|
ID: int32(stmt.ColumnInt64(0)),
|
||||||
|
Name: stmt.ColumnText(1),
|
||||||
|
Description: stmt.ColumnText(2),
|
||||||
|
Category: stmt.ColumnText(3),
|
||||||
|
Position: int32(stmt.ColumnInt(4)),
|
||||||
|
Source: int32(stmt.ColumnInt(5)),
|
||||||
|
Rarity: int32(stmt.ColumnInt(6)),
|
||||||
|
Flags: uint32(stmt.ColumnInt64(7)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle nullable achievement_id
|
||||||
|
if stmt.ColumnType(8) != sqlite.TypeNull {
|
||||||
|
title.AchievementID = uint32(stmt.ColumnInt64(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
titles = append(titles, title)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load titles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return titles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveMasterTitles saves all titles to the database
|
||||||
|
func (db *DB) SaveMasterTitles(titles []*Title) error {
|
||||||
|
// Use a transaction for atomic updates
|
||||||
|
endFn, err := sqlitex.ImmediateTransaction(db.conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer endFn(&err)
|
||||||
|
|
||||||
|
// Clear existing titles
|
||||||
|
if err := sqlitex.Execute(db.conn, "DELETE FROM titles", &sqlitex.ExecOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to clear titles table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert all titles
|
||||||
|
insertQuery := `
|
||||||
|
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
for _, title := range titles {
|
||||||
|
err := sqlitex.Execute(db.conn, insertQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{
|
||||||
|
title.ID,
|
||||||
|
title.Name,
|
||||||
|
title.Description,
|
||||||
|
title.Category,
|
||||||
|
int(title.Position),
|
||||||
|
int(title.Source),
|
||||||
|
int(title.Rarity),
|
||||||
|
int64(title.Flags),
|
||||||
|
nullableUint32(title.AchievementID),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to insert title %d: %w", title.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPlayerTitles loads titles for a specific player
|
||||||
|
func (db *DB) LoadPlayerTitles(playerID int32) ([]*PlayerTitle, error) {
|
||||||
|
var playerTitles []*PlayerTitle
|
||||||
|
|
||||||
|
query := `
|
||||||
|
SELECT title_id, achievement_id, granted_date, expiration_date, is_active
|
||||||
|
FROM player_titles
|
||||||
|
WHERE player_id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
err := sqlitex.Execute(db.conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
|
playerTitle := &PlayerTitle{
|
||||||
|
TitleID: int32(stmt.ColumnInt64(0)),
|
||||||
|
PlayerID: playerID,
|
||||||
|
EarnedDate: time.Unix(stmt.ColumnInt64(2), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle nullable achievement_id
|
||||||
|
if stmt.ColumnType(1) != sqlite.TypeNull {
|
||||||
|
playerTitle.AchievementID = uint32(stmt.ColumnInt64(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle nullable expiration_date
|
||||||
|
if stmt.ColumnType(3) != sqlite.TypeNull {
|
||||||
|
playerTitle.ExpiresAt = time.Unix(stmt.ColumnInt64(3), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
playerTitles = append(playerTitles, playerTitle)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load player titles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerTitles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePlayerTitles saves a player's titles to the database
|
||||||
|
func (db *DB) SavePlayerTitles(playerID int32, titles []*PlayerTitle, activePrefixID, activeSuffixID int32) error {
|
||||||
|
// Use a transaction for atomic updates
|
||||||
|
endFn, err := sqlitex.ImmediateTransaction(db.conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer endFn(&err)
|
||||||
|
|
||||||
|
// Clear existing titles for this player
|
||||||
|
deleteQuery := "DELETE FROM player_titles WHERE player_id = ?"
|
||||||
|
if err := sqlitex.Execute(db.conn, deleteQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID},
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to clear player titles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert all current titles
|
||||||
|
insertQuery := `
|
||||||
|
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
for _, playerTitle := range titles {
|
||||||
|
isActive := 0
|
||||||
|
if playerTitle.TitleID == activePrefixID || playerTitle.TitleID == activeSuffixID {
|
||||||
|
isActive = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sqlitex.Execute(db.conn, insertQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{
|
||||||
|
playerID,
|
||||||
|
playerTitle.TitleID,
|
||||||
|
nullableUint32(playerTitle.AchievementID),
|
||||||
|
playerTitle.EarnedDate.Unix(),
|
||||||
|
nullableTime(playerTitle.ExpiresAt),
|
||||||
|
isActive,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActivePlayerTitles retrieves the active prefix and suffix titles for a player
|
||||||
|
func (db *DB) GetActivePlayerTitles(playerID int32) (prefixID, suffixID int32, err error) {
|
||||||
|
query := `
|
||||||
|
SELECT pt.title_id, t.position
|
||||||
|
FROM player_titles pt
|
||||||
|
JOIN titles t ON pt.title_id = t.id
|
||||||
|
WHERE pt.player_id = ? AND pt.is_active = 1
|
||||||
|
`
|
||||||
|
|
||||||
|
err = sqlitex.Execute(db.conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
|
titleID := int32(stmt.ColumnInt64(0))
|
||||||
|
position := int32(stmt.ColumnInt(1))
|
||||||
|
|
||||||
|
if position == TitlePositionPrefix {
|
||||||
|
prefixID = titleID
|
||||||
|
} else if position == TitlePositionSuffix {
|
||||||
|
suffixID = titleID
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to get active titles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefixID, suffixID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for nullable values
|
||||||
|
|
||||||
|
func nullableUint32(val uint32) any {
|
||||||
|
if val == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableTime(t time.Time) any {
|
||||||
|
if t.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.Unix()
|
||||||
|
}
|
@ -3,8 +3,6 @@ package titles
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"eq2emu/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MasterTitlesList manages all available titles in the game
|
// MasterTitlesList manages all available titles in the game
|
||||||
@ -422,88 +420,33 @@ func (mtl *MasterTitlesList) ValidateTitle(title *Title) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromDatabase loads titles from the database
|
// LoadFromDatabase loads titles from the database
|
||||||
func (mtl *MasterTitlesList) LoadFromDatabase(db *database.DB) error {
|
func (mtl *MasterTitlesList) LoadFromDatabase(db *DB) error {
|
||||||
mtl.mutex.Lock()
|
mtl.mutex.Lock()
|
||||||
defer mtl.mutex.Unlock()
|
defer mtl.mutex.Unlock()
|
||||||
|
|
||||||
// Create titles table if it doesn't exist
|
|
||||||
if err := db.Exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS titles (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
category TEXT,
|
|
||||||
position INTEGER NOT NULL DEFAULT 0,
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
|
||||||
rarity INTEGER NOT NULL DEFAULT 0,
|
|
||||||
flags INTEGER NOT NULL DEFAULT 0,
|
|
||||||
achievement_id INTEGER,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
`); err != nil {
|
|
||||||
return fmt.Errorf("failed to create titles table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all titles from database
|
// Load all titles from database
|
||||||
err := db.Query("SELECT id, name, description, category, position, source, rarity, flags, achievement_id FROM titles", func(row *database.Row) error {
|
titles, err := db.LoadMasterTitles()
|
||||||
title := &Title{
|
|
||||||
ID: int32(row.Int64(0)),
|
|
||||||
Name: row.Text(1),
|
|
||||||
Description: row.Text(2),
|
|
||||||
Category: row.Text(3),
|
|
||||||
Position: int32(row.Int(4)),
|
|
||||||
Source: int32(row.Int(5)),
|
|
||||||
Rarity: int32(row.Int(6)),
|
|
||||||
Flags: uint32(row.Int64(7)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle nullable achievement_id
|
|
||||||
if !row.IsNull(8) {
|
|
||||||
title.AchievementID = uint32(row.Int64(8))
|
|
||||||
}
|
|
||||||
|
|
||||||
mtl.addTitleInternal(title)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load titles from database: %w", err)
|
return fmt.Errorf("failed to load titles from database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, title := range titles {
|
||||||
|
mtl.addTitleInternal(title)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveToDatabase saves titles to the database
|
// SaveToDatabase saves titles to the database
|
||||||
func (mtl *MasterTitlesList) SaveToDatabase(db *database.DB) error {
|
func (mtl *MasterTitlesList) SaveToDatabase(db *DB) error {
|
||||||
mtl.mutex.RLock()
|
mtl.mutex.RLock()
|
||||||
defer mtl.mutex.RUnlock()
|
defer mtl.mutex.RUnlock()
|
||||||
|
|
||||||
return db.Transaction(func(txDB *database.DB) error {
|
// Convert map to slice
|
||||||
// Clear existing titles (this is a full sync)
|
titles := make([]*Title, 0, len(mtl.titles))
|
||||||
if err := txDB.Exec("DELETE FROM titles"); err != nil {
|
|
||||||
return fmt.Errorf("failed to clear titles table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert all current titles
|
|
||||||
for _, title := range mtl.titles {
|
for _, title := range mtl.titles {
|
||||||
var achievementID any
|
titles = append(titles, title)
|
||||||
if title.AchievementID != 0 {
|
|
||||||
achievementID = title.AchievementID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := txDB.Exec(`
|
return db.SaveMasterTitles(titles)
|
||||||
INSERT INTO titles (id, name, description, category, position, source, rarity, flags, achievement_id)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`, title.ID, title.Name, title.Description, title.Category,
|
|
||||||
int(title.Position), int(title.Source), int(title.Rarity),
|
|
||||||
int64(title.Flags), achievementID)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert title %d: %w", title.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@ package titles
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"eq2emu/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlayerTitlesList manages titles owned by a specific player
|
// PlayerTitlesList manages titles owned by a specific player
|
||||||
@ -430,105 +427,44 @@ func (ptl *PlayerTitlesList) GrantTitleFromAchievement(achievementID uint32) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromDatabase loads player titles from the database
|
// LoadFromDatabase loads player titles from the database
|
||||||
func (ptl *PlayerTitlesList) LoadFromDatabase(db *database.DB) error {
|
func (ptl *PlayerTitlesList) LoadFromDatabase(db *DB) error {
|
||||||
ptl.mutex.Lock()
|
ptl.mutex.Lock()
|
||||||
defer ptl.mutex.Unlock()
|
defer ptl.mutex.Unlock()
|
||||||
|
|
||||||
// Create player_titles table if it doesn't exist
|
|
||||||
if err := db.Exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS player_titles (
|
|
||||||
player_id INTEGER NOT NULL,
|
|
||||||
title_id INTEGER NOT NULL,
|
|
||||||
achievement_id INTEGER,
|
|
||||||
granted_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
expiration_date TIMESTAMP,
|
|
||||||
is_active INTEGER DEFAULT 0,
|
|
||||||
PRIMARY KEY (player_id, title_id),
|
|
||||||
FOREIGN KEY (title_id) REFERENCES titles(id)
|
|
||||||
)
|
|
||||||
`); err != nil {
|
|
||||||
return fmt.Errorf("failed to create player_titles table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all titles for this player
|
// Load all titles for this player
|
||||||
err := db.Query("SELECT title_id, achievement_id, granted_date, expiration_date, is_active FROM player_titles WHERE player_id = ?",
|
playerTitles, err := db.LoadPlayerTitles(ptl.playerID)
|
||||||
func(row *database.Row) error {
|
|
||||||
playerTitle := &PlayerTitle{
|
|
||||||
TitleID: int32(row.Int64(0)),
|
|
||||||
PlayerID: ptl.playerID,
|
|
||||||
EarnedDate: time.Unix(row.Int64(2), 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle nullable achievement_id
|
|
||||||
if !row.IsNull(1) {
|
|
||||||
playerTitle.AchievementID = uint32(row.Int64(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle nullable expiration_date
|
|
||||||
if !row.IsNull(3) {
|
|
||||||
playerTitle.ExpiresAt = time.Unix(row.Int64(3), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
ptl.titles[playerTitle.TitleID] = playerTitle
|
|
||||||
|
|
||||||
// Set active title if this one is active
|
|
||||||
if row.Bool(4) {
|
|
||||||
ptl.activePrefixID = playerTitle.TitleID
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}, ptl.playerID)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load player titles from database: %w", err)
|
return fmt.Errorf("failed to load player titles from database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, playerTitle := range playerTitles {
|
||||||
|
ptl.titles[playerTitle.TitleID] = playerTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load active titles
|
||||||
|
prefixID, suffixID, err := db.GetActivePlayerTitles(ptl.playerID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load active titles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptl.activePrefixID = prefixID
|
||||||
|
ptl.activeSuffixID = suffixID
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveToDatabase saves player titles to the database
|
// SaveToDatabase saves player titles to the database
|
||||||
func (ptl *PlayerTitlesList) SaveToDatabase(db *database.DB) error {
|
func (ptl *PlayerTitlesList) SaveToDatabase(db *DB) error {
|
||||||
ptl.mutex.RLock()
|
ptl.mutex.RLock()
|
||||||
defer ptl.mutex.RUnlock()
|
defer ptl.mutex.RUnlock()
|
||||||
|
|
||||||
return db.Transaction(func(txDB *database.DB) error {
|
// Convert map to slice
|
||||||
// Clear existing titles for this player
|
titles := make([]*PlayerTitle, 0, len(ptl.titles))
|
||||||
if err := txDB.Exec("DELETE FROM player_titles WHERE player_id = ?", ptl.playerID); err != nil {
|
for _, title := range ptl.titles {
|
||||||
return fmt.Errorf("failed to clear player titles: %w", err)
|
titles = append(titles, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert all current titles
|
return db.SavePlayerTitles(ptl.playerID, titles, ptl.activePrefixID, ptl.activeSuffixID)
|
||||||
for _, playerTitle := range ptl.titles {
|
|
||||||
var achievementID any
|
|
||||||
if playerTitle.AchievementID != 0 {
|
|
||||||
achievementID = playerTitle.AchievementID
|
|
||||||
}
|
|
||||||
|
|
||||||
var expirationDate any
|
|
||||||
if !playerTitle.ExpiresAt.IsZero() {
|
|
||||||
expirationDate = playerTitle.ExpiresAt.Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive := 0
|
|
||||||
if ptl.activePrefixID == playerTitle.TitleID {
|
|
||||||
isActive = 1
|
|
||||||
} else if ptl.activeSuffixID == playerTitle.TitleID {
|
|
||||||
isActive = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
err := txDB.Exec(`
|
|
||||||
INSERT INTO player_titles (player_id, title_id, achievement_id, granted_date, expiration_date, is_active)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
`, ptl.playerID, playerTitle.TitleID, achievementID,
|
|
||||||
playerTitle.EarnedDate.Unix(), expirationDate, isActive)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert player title %d: %w", playerTitle.TitleID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFormattedName returns the player name with active titles applied
|
// GetFormattedName returns the player name with active titles applied
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eq2emu/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TitleManager manages the entire title system for the server
|
// TitleManager manages the entire title system for the server
|
||||||
@ -341,13 +339,13 @@ func (tm *TitleManager) RemovePlayerFromMemory(playerID int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadPlayerTitles loads a player's titles from database
|
// LoadPlayerTitles loads a player's titles from database
|
||||||
func (tm *TitleManager) LoadPlayerTitles(playerID int32, db *database.DB) error {
|
func (tm *TitleManager) LoadPlayerTitles(playerID int32, db *DB) error {
|
||||||
playerList := tm.GetPlayerTitles(playerID)
|
playerList := tm.GetPlayerTitles(playerID)
|
||||||
return playerList.LoadFromDatabase(db)
|
return playerList.LoadFromDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePlayerTitles saves a player's titles to database
|
// SavePlayerTitles saves a player's titles to database
|
||||||
func (tm *TitleManager) SavePlayerTitles(playerID int32, db *database.DB) error {
|
func (tm *TitleManager) SavePlayerTitles(playerID int32, db *DB) error {
|
||||||
tm.mutex.RLock()
|
tm.mutex.RLock()
|
||||||
playerList, exists := tm.playerLists[playerID]
|
playerList, exists := tm.playerLists[playerID]
|
||||||
tm.mutex.RUnlock()
|
tm.mutex.RUnlock()
|
||||||
@ -360,12 +358,12 @@ func (tm *TitleManager) SavePlayerTitles(playerID int32, db *database.DB) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadMasterTitles loads all titles from database
|
// LoadMasterTitles loads all titles from database
|
||||||
func (tm *TitleManager) LoadMasterTitles(db *database.DB) error {
|
func (tm *TitleManager) LoadMasterTitles(db *DB) error {
|
||||||
return tm.masterList.LoadFromDatabase(db)
|
return tm.masterList.LoadFromDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveMasterTitles saves all titles to database
|
// SaveMasterTitles saves all titles to database
|
||||||
func (tm *TitleManager) SaveMasterTitles(db *database.DB) error {
|
func (tm *TitleManager) SaveMasterTitles(db *DB) error {
|
||||||
return tm.masterList.SaveToDatabase(db)
|
return tm.masterList.SaveToDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package titles
|
package titles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
"eq2emu/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Test Title struct creation and basic methods
|
||||||
func TestNewTitle(t *testing.T) {
|
func TestNewTitle(t *testing.T) {
|
||||||
title := NewTitle(1, "Test Title")
|
title := NewTitle(1, "Test Title")
|
||||||
if title == nil {
|
if title == nil {
|
||||||
@ -26,6 +29,108 @@ func TestNewTitle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTitleFlags(t *testing.T) {
|
||||||
|
title := NewTitle(1, "Test Title")
|
||||||
|
|
||||||
|
// Test setting flags
|
||||||
|
title.SetFlag(FlagHidden)
|
||||||
|
if !title.HasFlag(FlagHidden) {
|
||||||
|
t.Error("Expected title to have FlagHidden after setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
title.SetFlag(FlagTemporary)
|
||||||
|
if !title.HasFlag(FlagTemporary) {
|
||||||
|
t.Error("Expected title to have FlagTemporary after setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clearing flags
|
||||||
|
title.ClearFlag(FlagHidden)
|
||||||
|
if title.HasFlag(FlagHidden) {
|
||||||
|
t.Error("Expected title not to have FlagHidden after clearing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test toggle flags (manually implement toggle)
|
||||||
|
if title.HasFlag(FlagUnique) {
|
||||||
|
title.ClearFlag(FlagUnique)
|
||||||
|
} else {
|
||||||
|
title.SetFlag(FlagUnique)
|
||||||
|
}
|
||||||
|
if !title.HasFlag(FlagUnique) {
|
||||||
|
t.Error("Expected title to have FlagUnique after toggling")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle again
|
||||||
|
if title.HasFlag(FlagUnique) {
|
||||||
|
title.ClearFlag(FlagUnique)
|
||||||
|
} else {
|
||||||
|
title.SetFlag(FlagUnique)
|
||||||
|
}
|
||||||
|
if title.HasFlag(FlagUnique) {
|
||||||
|
t.Error("Expected title not to have FlagUnique after toggling again")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleProperties(t *testing.T) {
|
||||||
|
title := NewTitle(1, "Test Title")
|
||||||
|
|
||||||
|
// Test description
|
||||||
|
title.SetDescription("A test description")
|
||||||
|
if title.Description != "A test description" {
|
||||||
|
t.Errorf("Expected description 'A test description', got '%s'", title.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test category
|
||||||
|
title.SetCategory(CategoryAchievement)
|
||||||
|
if title.Category != CategoryAchievement {
|
||||||
|
t.Errorf("Expected category '%s', got '%s'", CategoryAchievement, title.Category)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test rarity
|
||||||
|
title.SetRarity(TitleRarityRare)
|
||||||
|
if title.Rarity != TitleRarityRare {
|
||||||
|
t.Errorf("Expected rarity %d, got %d", TitleRarityRare, title.Rarity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test display name
|
||||||
|
title.Position = TitlePositionPrefix
|
||||||
|
displayName := title.GetDisplayName()
|
||||||
|
if displayName != "Test Title" {
|
||||||
|
t.Errorf("Expected display name 'Test Title', got '%s'", displayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleTypeMethods(t *testing.T) {
|
||||||
|
title := NewTitle(1, "Test Title")
|
||||||
|
|
||||||
|
// Test hidden flag
|
||||||
|
title.SetFlag(FlagHidden)
|
||||||
|
if !title.IsHidden() {
|
||||||
|
t.Error("Expected title to be hidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test temporary flag
|
||||||
|
title.ClearFlag(FlagHidden)
|
||||||
|
title.SetFlag(FlagTemporary)
|
||||||
|
if !title.IsTemporary() {
|
||||||
|
t.Error("Expected title to be temporary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unique flag
|
||||||
|
title.ClearFlag(FlagTemporary)
|
||||||
|
title.SetFlag(FlagUnique)
|
||||||
|
if !title.IsUnique() {
|
||||||
|
t.Error("Expected title to be unique")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test account-wide flag
|
||||||
|
title.ClearFlag(FlagUnique)
|
||||||
|
title.SetFlag(FlagAccountWide)
|
||||||
|
if !title.IsAccountWide() {
|
||||||
|
t.Error("Expected title to be account-wide")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test MasterTitlesList
|
||||||
func TestMasterTitlesList(t *testing.T) {
|
func TestMasterTitlesList(t *testing.T) {
|
||||||
mtl := NewMasterTitlesList()
|
mtl := NewMasterTitlesList()
|
||||||
if mtl == nil {
|
if mtl == nil {
|
||||||
@ -33,108 +138,531 @@ func TestMasterTitlesList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test default titles are loaded
|
// Test default titles are loaded
|
||||||
citizen := mtl.GetTitle(TitleIDCitizen)
|
citizen, exists := mtl.GetTitle(TitleIDCitizen)
|
||||||
if citizen == nil {
|
if !exists || citizen == nil {
|
||||||
t.Error("Expected Citizen title to be loaded by default")
|
t.Error("Expected Citizen title to be loaded by default")
|
||||||
|
} else if citizen.Name != "Citizen" {
|
||||||
|
t.Errorf("Expected Citizen title name, got '%s'", citizen.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor := mtl.GetTitle(TitleIDVisitor)
|
visitor, exists := mtl.GetTitle(TitleIDVisitor)
|
||||||
if visitor == nil {
|
if !exists || visitor == nil {
|
||||||
t.Error("Expected Visitor title to be loaded by default")
|
t.Error("Expected Visitor title to be loaded by default")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newcomer, exists := mtl.GetTitle(TitleIDNewcomer)
|
||||||
|
if !exists || newcomer == nil {
|
||||||
|
t.Error("Expected Newcomer title to be loaded by default")
|
||||||
|
}
|
||||||
|
|
||||||
|
returning, exists := mtl.GetTitle(TitleIDReturning)
|
||||||
|
if !exists || returning == nil {
|
||||||
|
t.Error("Expected Returning title to be loaded by default")
|
||||||
|
}
|
||||||
|
|
||||||
// Test adding new title
|
// Test adding new title
|
||||||
testTitle := NewTitle(100, "Test Title")
|
testTitle := NewTitle(0, "Test Title") // ID will be assigned
|
||||||
|
testTitle.SetDescription("Test description")
|
||||||
|
testTitle.SetCategory(CategoryMiscellaneous)
|
||||||
|
|
||||||
err := mtl.AddTitle(testTitle)
|
err := mtl.AddTitle(testTitle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to add title: %v", err)
|
t.Fatalf("Failed to add title: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieved := mtl.GetTitle(100)
|
// ID should have been assigned
|
||||||
if retrieved == nil {
|
if testTitle.ID == 0 {
|
||||||
|
t.Error("Title ID should have been assigned")
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieved, exists := mtl.GetTitle(testTitle.ID)
|
||||||
|
if !exists || retrieved == nil {
|
||||||
t.Error("Failed to retrieve added title")
|
t.Error("Failed to retrieve added title")
|
||||||
} else if retrieved.Name != "Test Title" {
|
} else if retrieved.Name != "Test Title" {
|
||||||
t.Errorf("Expected retrieved title name 'Test Title', got '%s'", retrieved.Name)
|
t.Errorf("Expected retrieved title name 'Test Title', got '%s'", retrieved.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test duplicate ID
|
||||||
|
duplicateTitle := NewTitle(testTitle.ID, "Duplicate")
|
||||||
|
err = mtl.AddTitle(duplicateTitle)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should have failed to add title with duplicate ID")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMasterTitlesListCategories(t *testing.T) {
|
||||||
|
mtl := NewMasterTitlesList()
|
||||||
|
|
||||||
|
// Add titles in different categories
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
title := NewTitle(0, fmt.Sprintf("Achievement Title %d", i))
|
||||||
|
title.SetCategory(CategoryAchievement)
|
||||||
|
mtl.AddTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
title := NewTitle(0, fmt.Sprintf("Quest Title %d", i))
|
||||||
|
title.SetCategory(CategoryQuest)
|
||||||
|
mtl.AddTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting titles by category
|
||||||
|
achievementTitles := mtl.GetTitlesByCategory(CategoryAchievement)
|
||||||
|
if len(achievementTitles) < 3 {
|
||||||
|
t.Errorf("Expected at least 3 achievement titles, got %d", len(achievementTitles))
|
||||||
|
}
|
||||||
|
|
||||||
|
questTitles := mtl.GetTitlesByCategory(CategoryQuest)
|
||||||
|
if len(questTitles) < 2 {
|
||||||
|
t.Errorf("Expected at least 2 quest titles, got %d", len(questTitles))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting available categories
|
||||||
|
categories := mtl.GetAvailableCategories()
|
||||||
|
hasAchievement := false
|
||||||
|
hasQuest := false
|
||||||
|
for _, cat := range categories {
|
||||||
|
if cat == CategoryAchievement {
|
||||||
|
hasAchievement = true
|
||||||
|
}
|
||||||
|
if cat == CategoryQuest {
|
||||||
|
hasQuest = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasAchievement {
|
||||||
|
t.Error("Expected CategoryAchievement in available categories")
|
||||||
|
}
|
||||||
|
if !hasQuest {
|
||||||
|
t.Error("Expected CategoryQuest in available categories")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMasterTitlesListSourceAndRarity(t *testing.T) {
|
||||||
|
mtl := NewMasterTitlesList()
|
||||||
|
|
||||||
|
// Add titles with different sources
|
||||||
|
title1 := NewTitle(0, "Achievement Source")
|
||||||
|
title1.Source = TitleSourceAchievement
|
||||||
|
title1.SetRarity(TitleRarityCommon)
|
||||||
|
mtl.AddTitle(title1)
|
||||||
|
|
||||||
|
title2 := NewTitle(0, "Quest Source")
|
||||||
|
title2.Source = TitleSourceQuest
|
||||||
|
title2.SetRarity(TitleRarityRare)
|
||||||
|
mtl.AddTitle(title2)
|
||||||
|
|
||||||
|
title3 := NewTitle(0, "PvP Source")
|
||||||
|
title3.Source = TitleSourcePvP
|
||||||
|
title3.SetRarity(TitleRarityEpic)
|
||||||
|
mtl.AddTitle(title3)
|
||||||
|
|
||||||
|
// Test getting titles by source
|
||||||
|
achievementSourceTitles := mtl.GetTitlesBySource(TitleSourceAchievement)
|
||||||
|
found := false
|
||||||
|
for _, t := range achievementSourceTitles {
|
||||||
|
if t.Name == "Achievement Source" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Failed to find achievement source title")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting titles by rarity
|
||||||
|
rareTitles := mtl.GetTitlesByRarity(TitleRarityRare)
|
||||||
|
found = false
|
||||||
|
for _, t := range rareTitles {
|
||||||
|
if t.Name == "Quest Source" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Failed to find rare title")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test PlayerTitlesList
|
||||||
func TestPlayerTitlesList(t *testing.T) {
|
func TestPlayerTitlesList(t *testing.T) {
|
||||||
mtl := NewMasterTitlesList()
|
mtl := NewMasterTitlesList()
|
||||||
ptl := NewPlayerTitlesList(123, mtl)
|
|
||||||
|
|
||||||
|
// Add some titles to master list
|
||||||
|
title1 := NewTitle(100, "Title One")
|
||||||
|
title1.Position = TitlePositionPrefix
|
||||||
|
mtl.AddTitle(title1)
|
||||||
|
|
||||||
|
title2 := NewTitle(101, "Title Two")
|
||||||
|
title2.Position = TitlePositionSuffix
|
||||||
|
mtl.AddTitle(title2)
|
||||||
|
|
||||||
|
ptl := NewPlayerTitlesList(123, mtl)
|
||||||
if ptl == nil {
|
if ptl == nil {
|
||||||
t.Fatal("NewPlayerTitlesList returned nil")
|
t.Fatal("NewPlayerTitlesList returned nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test adding title
|
// Test adding title
|
||||||
err := ptl.AddTitle(TitleIDCitizen, 0, 0)
|
err := ptl.AddTitle(100, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to add title to player: %v", err)
|
t.Fatalf("Failed to add title to player: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test getting titles
|
err = ptl.AddTitle(101, 0, 0)
|
||||||
titles := ptl.GetTitles()
|
|
||||||
if len(titles) != 1 {
|
|
||||||
t.Errorf("Expected 1 title, got %d", len(titles))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test setting active title
|
|
||||||
err = ptl.SetActiveTitle(TitleIDCitizen, TitlePositionSuffix)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to set active title: %v", err)
|
t.Fatalf("Failed to add second title to player: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test getting active titles
|
// Test getting titles (player starts with default Citizen title)
|
||||||
activePrefix, activeSuffix := ptl.GetActiveTitles()
|
titles := ptl.GetAllTitles()
|
||||||
if activePrefix != 0 {
|
expectedTitleCount := 3 // Citizen + our 2 titles
|
||||||
t.Errorf("Expected no active prefix, got %d", activePrefix)
|
if len(titles) != expectedTitleCount {
|
||||||
|
t.Errorf("Expected %d titles, got %d", expectedTitleCount, len(titles))
|
||||||
}
|
}
|
||||||
if activeSuffix != TitleIDCitizen {
|
|
||||||
t.Errorf("Expected active suffix %d, got %d", TitleIDCitizen, activeSuffix)
|
// Test has title
|
||||||
|
if !ptl.HasTitle(100) {
|
||||||
|
t.Error("Expected player to have title 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ptl.HasTitle(101) {
|
||||||
|
t.Error("Expected player to have title 101")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptl.HasTitle(999) {
|
||||||
|
t.Error("Expected player not to have title 999")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test setting active prefix
|
||||||
|
err = ptl.SetActivePrefix(100)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to set active prefix: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
activePrefixTitle, hasPrefix := ptl.GetActivePrefixTitle()
|
||||||
|
if !hasPrefix || activePrefixTitle.ID != 100 {
|
||||||
|
t.Errorf("Expected active prefix 100, got title ID: %v", activePrefixTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test setting active suffix
|
||||||
|
err = ptl.SetActiveSuffix(101)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to set active suffix: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSuffixTitle, hasSuffix := ptl.GetActiveSuffixTitle()
|
||||||
|
if !hasSuffix || activeSuffixTitle.ID != 101 {
|
||||||
|
t.Errorf("Expected active suffix 101, got title ID: %v", activeSuffixTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test formatted name (the actual format may vary)
|
||||||
|
formattedName := ptl.GetFormattedName("PlayerName")
|
||||||
|
// Just check that it contains both titles and the player name
|
||||||
|
if !contains(formattedName, "Title One") {
|
||||||
|
t.Errorf("Formatted name should contain 'Title One', got '%s'", formattedName)
|
||||||
|
}
|
||||||
|
if !contains(formattedName, "Title Two") {
|
||||||
|
t.Errorf("Formatted name should contain 'Title Two', got '%s'", formattedName)
|
||||||
|
}
|
||||||
|
if !contains(formattedName, "PlayerName") {
|
||||||
|
t.Errorf("Formatted name should contain 'PlayerName', got '%s'", formattedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing title
|
||||||
|
err = ptl.RemoveTitle(100)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove title: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptl.HasTitle(100) {
|
||||||
|
t.Error("Title 100 should have been removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active prefix should be cleared
|
||||||
|
_, hasPrefix = ptl.GetActivePrefixTitle()
|
||||||
|
if hasPrefix {
|
||||||
|
t.Error("Active prefix should be cleared after removing the title")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlayerTitlesExpiration(t *testing.T) {
|
||||||
|
mtl := NewMasterTitlesList()
|
||||||
|
|
||||||
|
// Add a temporary title to master list
|
||||||
|
tempTitle := NewTitle(200, "Temporary Title")
|
||||||
|
tempTitle.SetFlag(FlagTemporary)
|
||||||
|
tempTitle.ExpirationHours = 1 // Expires after 1 hour
|
||||||
|
mtl.AddTitle(tempTitle)
|
||||||
|
|
||||||
|
ptl := NewPlayerTitlesList(456, mtl)
|
||||||
|
|
||||||
|
// Add the temporary title
|
||||||
|
err := ptl.AddTitle(200, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add temporary title: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title should exist
|
||||||
|
if !ptl.HasTitle(200) {
|
||||||
|
t.Error("Expected player to have temporary title")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually set expiration to past
|
||||||
|
if playerTitle, exists := ptl.titles[200]; exists {
|
||||||
|
playerTitle.ExpiresAt = time.Now().Add(-1 * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up expired titles
|
||||||
|
expired := ptl.CleanupExpiredTitles()
|
||||||
|
if expired != 1 {
|
||||||
|
t.Errorf("Expected 1 expired title, got %d", expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title should be removed
|
||||||
|
if ptl.HasTitle(200) {
|
||||||
|
t.Error("Expired title should have been removed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TitleManager
|
||||||
func TestTitleManager(t *testing.T) {
|
func TestTitleManager(t *testing.T) {
|
||||||
tm := NewTitleManager()
|
tm := NewTitleManager()
|
||||||
if tm == nil {
|
if tm == nil {
|
||||||
t.Fatal("NewTitleManager returned nil")
|
t.Fatal("NewTitleManager returned nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
// Test getting player titles
|
// Test getting player titles
|
||||||
playerTitles := tm.GetPlayerTitles(456)
|
playerTitles := tm.GetPlayerTitles(456)
|
||||||
if playerTitles == nil {
|
if playerTitles == nil {
|
||||||
t.Error("GetPlayerTitles returned nil")
|
t.Error("GetPlayerTitles returned nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test adding title for player
|
// Test granting title (use Visitor instead of Citizen to avoid conflicts)
|
||||||
err := tm.GrantTitle(456, TitleIDCitizen, 0)
|
err := tm.GrantTitle(456, TitleIDVisitor, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to grant title: %v", err)
|
t.Fatalf("Failed to grant title: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify title was granted
|
// Verify title was granted (player starts with default Citizen title)
|
||||||
playerTitles = tm.GetPlayerTitles(456)
|
playerTitles = tm.GetPlayerTitles(456)
|
||||||
titles := playerTitles.GetTitles()
|
titles := playerTitles.GetAllTitles()
|
||||||
|
expectedCount := 2 // Citizen + Visitor
|
||||||
|
if len(titles) != expectedCount {
|
||||||
|
t.Errorf("Expected %d titles for player, got %d", expectedCount, len(titles))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the player has the visitor title
|
||||||
|
if !playerTitles.HasTitle(TitleIDVisitor) {
|
||||||
|
t.Error("Player should have Visitor title")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test revoking title
|
||||||
|
err = tm.RevokeTitle(456, TitleIDVisitor)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to revoke title: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify title was revoked (should still have Citizen)
|
||||||
|
playerTitles = tm.GetPlayerTitles(456)
|
||||||
|
titles = playerTitles.GetAllTitles()
|
||||||
if len(titles) != 1 {
|
if len(titles) != 1 {
|
||||||
t.Errorf("Expected 1 title for player, got %d", len(titles))
|
t.Errorf("Expected 1 title after revoke, got %d", len(titles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTitleDatabaseIntegration(t *testing.T) {
|
func TestTitleManagerCreateVariants(t *testing.T) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Test creating regular title
|
||||||
|
title, err := tm.CreateTitle("Regular Title", "A regular title", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create regular title: %v", err)
|
||||||
|
}
|
||||||
|
if title == nil {
|
||||||
|
t.Fatal("CreateTitle returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating achievement title
|
||||||
|
achievementTitle, err := tm.CreateAchievementTitle("Achievement Title", "An achievement title", 1000, TitlePositionPrefix, TitleRarityRare)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create achievement title: %v", err)
|
||||||
|
}
|
||||||
|
if achievementTitle.AchievementID != 1000 {
|
||||||
|
t.Errorf("Expected achievement ID 1000, got %d", achievementTitle.AchievementID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating temporary title
|
||||||
|
tempTitle, err := tm.CreateTemporaryTitle("Temp Title", "A temporary title", 24, TitlePositionSuffix, TitleSourceHoliday, TitleRarityCommon)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary title: %v", err)
|
||||||
|
}
|
||||||
|
if !tempTitle.HasFlag(FlagTemporary) {
|
||||||
|
t.Error("Temporary title should have FlagTemporary")
|
||||||
|
}
|
||||||
|
if tempTitle.ExpirationHours != 24 {
|
||||||
|
t.Errorf("Expected expiration hours 24, got %d", tempTitle.ExpirationHours)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating unique title
|
||||||
|
uniqueTitle, err := tm.CreateUniqueTitle("Unique Title", "A unique title", TitlePositionPrefix, TitleSourceMiscellaneous)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create unique title: %v", err)
|
||||||
|
}
|
||||||
|
if !uniqueTitle.HasFlag(FlagUnique) {
|
||||||
|
t.Error("Unique title should have FlagUnique")
|
||||||
|
}
|
||||||
|
if uniqueTitle.Rarity != TitleRarityUnique {
|
||||||
|
t.Errorf("Expected rarity %d for unique title, got %d", TitleRarityUnique, uniqueTitle.Rarity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleManagerSearch(t *testing.T) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Create test titles
|
||||||
|
tm.CreateTitle("Dragon Slayer", "Defeated a dragon", CategoryCombat, TitlePositionSuffix, TitleSourceQuest, TitleRarityEpic)
|
||||||
|
tm.CreateTitle("Master Crafter", "Master of crafting", CategoryTradeskill, TitlePositionPrefix, TitleSourceTradeskill, TitleRarityRare)
|
||||||
|
tm.CreateTitle("Explorer", "Explored the world", CategoryExploration, TitlePositionSuffix, TitleSourceAchievement, TitleRarityCommon)
|
||||||
|
|
||||||
|
// Test search
|
||||||
|
results := tm.SearchTitles("dragon")
|
||||||
|
found := false
|
||||||
|
for _, title := range results {
|
||||||
|
if title.Name == "Dragon Slayer" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Failed to find 'Dragon Slayer' in search results")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test search with description
|
||||||
|
results = tm.SearchTitles("crafting")
|
||||||
|
found = false
|
||||||
|
for _, title := range results {
|
||||||
|
if title.Name == "Master Crafter" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Failed to find 'Master Crafter' when searching description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleManagerStatistics(t *testing.T) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Grant some titles (avoid Citizen which may be default)
|
||||||
|
tm.GrantTitle(1, TitleIDVisitor, 0, 0)
|
||||||
|
tm.GrantTitle(2, TitleIDNewcomer, 0, 0)
|
||||||
|
tm.GrantTitle(1, TitleIDReturning, 0, 0)
|
||||||
|
|
||||||
|
stats := tm.GetStatistics()
|
||||||
|
|
||||||
|
// Check statistics
|
||||||
|
if totalPlayers, ok := stats["total_players"].(int); ok {
|
||||||
|
if totalPlayers != 2 {
|
||||||
|
t.Errorf("Expected 2 total players, got %d", totalPlayers)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("Missing total_players in statistics")
|
||||||
|
}
|
||||||
|
|
||||||
|
if titlesGranted, ok := stats["titles_granted"].(int64); ok {
|
||||||
|
if titlesGranted != 3 {
|
||||||
|
t.Errorf("Expected 3 titles granted, got %d", titlesGranted)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("Missing titles_granted in statistics")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTitleManagerConcurrency(t *testing.T) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Test concurrent access to player titles
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
numPlayers := 10
|
||||||
|
numOperations := 100
|
||||||
|
|
||||||
|
for i := 0; i < numPlayers; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(playerID int32) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := 0; j < numOperations; j++ {
|
||||||
|
// Grant title
|
||||||
|
tm.GrantTitle(playerID, TitleIDCitizen, 0, 0)
|
||||||
|
|
||||||
|
// Get player titles
|
||||||
|
ptl := tm.GetPlayerTitles(playerID)
|
||||||
|
_ = ptl.GetAllTitles()
|
||||||
|
|
||||||
|
// Set active title
|
||||||
|
tm.SetPlayerActivePrefix(playerID, TitleIDCitizen)
|
||||||
|
|
||||||
|
// Get formatted name
|
||||||
|
tm.GetPlayerFormattedName(playerID, fmt.Sprintf("Player%d", playerID))
|
||||||
|
|
||||||
|
// Revoke title
|
||||||
|
tm.RevokeTitle(playerID, TitleIDCitizen)
|
||||||
|
}
|
||||||
|
}(int32(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Verify no crashes or data races
|
||||||
|
stats := tm.GetStatistics()
|
||||||
|
if stats == nil {
|
||||||
|
t.Error("Failed to get statistics after concurrent operations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Database Integration
|
||||||
|
func TestDatabaseIntegration(t *testing.T) {
|
||||||
// Create temporary database
|
// Create temporary database
|
||||||
tempFile := "test_titles.db"
|
tempDir := t.TempDir()
|
||||||
defer os.Remove(tempFile)
|
dbPath := filepath.Join(tempDir, "test_titles.db")
|
||||||
|
|
||||||
db, err := database.Open(tempFile)
|
db, err := OpenDB(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
t.Fatalf("Failed to open database: %v", err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create tables
|
||||||
|
err = db.CreateTables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create tables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Test master list database operations
|
// Test master list database operations
|
||||||
mtl := NewMasterTitlesList()
|
mtl := NewMasterTitlesList()
|
||||||
|
|
||||||
// Test saving to database
|
// Add some test titles
|
||||||
|
title1 := NewTitle(500, "Database Test Title 1")
|
||||||
|
title1.SetDescription("Test description 1")
|
||||||
|
title1.SetCategory(CategoryMiscellaneous)
|
||||||
|
title1.Position = TitlePositionPrefix // Set as prefix title
|
||||||
|
mtl.AddTitle(title1)
|
||||||
|
|
||||||
|
title2 := NewTitle(501, "Database Test Title 2")
|
||||||
|
title2.SetDescription("Test description 2")
|
||||||
|
title2.SetCategory(CategoryAchievement)
|
||||||
|
title2.Position = TitlePositionSuffix // Set as suffix title
|
||||||
|
title2.AchievementID = 2000
|
||||||
|
mtl.AddTitle(title2)
|
||||||
|
|
||||||
|
// Save to database
|
||||||
err = mtl.SaveToDatabase(db)
|
err = mtl.SaveToDatabase(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to save master titles to database: %v", err)
|
t.Fatalf("Failed to save master titles to database: %v", err)
|
||||||
@ -155,18 +683,29 @@ func TestTitleDatabaseIntegration(t *testing.T) {
|
|||||||
t.Fatalf("Failed to load master titles from database: %v", err)
|
t.Fatalf("Failed to load master titles from database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify titles were loaded
|
// Verify loaded titles
|
||||||
citizen := mtl2.GetTitle(TitleIDCitizen)
|
loadedTitle1, exists := mtl2.GetTitle(500)
|
||||||
if citizen == nil {
|
if !exists || loadedTitle1 == nil {
|
||||||
t.Error("Failed to load Citizen title from database")
|
t.Fatal("Failed to load title 500 from database")
|
||||||
|
}
|
||||||
|
if loadedTitle1.Name != "Database Test Title 1" {
|
||||||
|
t.Errorf("Expected title name 'Database Test Title 1', got '%s'", loadedTitle1.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedTitle2, exists := mtl2.GetTitle(501)
|
||||||
|
if !exists || loadedTitle2 == nil {
|
||||||
|
t.Fatal("Failed to load title 501 from database")
|
||||||
|
}
|
||||||
|
if loadedTitle2.AchievementID != 2000 {
|
||||||
|
t.Errorf("Expected achievement ID 2000, got %d", loadedTitle2.AchievementID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test player titles database operations
|
// Test player titles database operations
|
||||||
ptl := NewPlayerTitlesList(789, mtl)
|
ptl := NewPlayerTitlesList(789, mtl2) // Use mtl2 which has titles loaded from database
|
||||||
err = ptl.AddTitle(TitleIDCitizen, 0, 0)
|
ptl.AddTitle(500, 0, 0)
|
||||||
if err != nil {
|
ptl.AddTitle(501, 2000, 0)
|
||||||
t.Fatalf("Failed to add title to player: %v", err)
|
ptl.SetActivePrefix(500)
|
||||||
}
|
ptl.SetActiveSuffix(501)
|
||||||
|
|
||||||
// Save player titles
|
// Save player titles
|
||||||
err = ptl.SaveToDatabase(db)
|
err = ptl.SaveToDatabase(db)
|
||||||
@ -175,39 +714,149 @@ func TestTitleDatabaseIntegration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load player titles
|
// Load player titles
|
||||||
ptl2 := NewPlayerTitlesList(789, mtl)
|
ptl2 := NewPlayerTitlesList(789, mtl2)
|
||||||
err = ptl2.LoadFromDatabase(db)
|
err = ptl2.LoadFromDatabase(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to load player titles from database: %v", err)
|
t.Fatalf("Failed to load player titles from database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify player titles were loaded
|
// Verify loaded player titles
|
||||||
titles := ptl2.GetTitles()
|
if !ptl2.HasTitle(500) {
|
||||||
if len(titles) != 1 {
|
t.Error("Expected player to have title 500")
|
||||||
t.Errorf("Expected 1 loaded title, got %d", len(titles))
|
}
|
||||||
|
if !ptl2.HasTitle(501) {
|
||||||
|
t.Error("Expected player to have title 501")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
activePrefixTitle, hasPrefix := ptl2.GetActivePrefixTitle()
|
||||||
|
if !hasPrefix {
|
||||||
|
t.Error("Expected to have active prefix title")
|
||||||
|
} else if activePrefixTitle.ID != 500 {
|
||||||
|
t.Errorf("Expected active prefix 500, got %d", activePrefixTitle.ID)
|
||||||
|
}
|
||||||
|
activeSuffixTitle, hasSuffix := ptl2.GetActiveSuffixTitle()
|
||||||
|
if !hasSuffix {
|
||||||
|
t.Error("Expected to have active suffix title")
|
||||||
|
} else if activeSuffixTitle.ID != 501 {
|
||||||
|
t.Errorf("Expected active suffix 501, got %d", activeSuffixTitle.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTitleValidation(t *testing.T) {
|
func TestDatabaseHelperFunctions(t *testing.T) {
|
||||||
|
// Test nullableUint32
|
||||||
|
if val := nullableUint32(0); val != nil {
|
||||||
|
t.Error("nullableUint32(0) should return nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := nullableUint32(123); val != uint32(123) {
|
||||||
|
t.Errorf("nullableUint32(123) should return 123, got %v", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nullableTime
|
||||||
|
zeroTime := time.Time{}
|
||||||
|
if val := nullableTime(zeroTime); val != nil {
|
||||||
|
t.Error("nullableTime(zero) should return nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if val := nullableTime(now); val != now.Unix() {
|
||||||
|
t.Errorf("nullableTime(now) should return Unix timestamp, got %v", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark tests
|
||||||
|
func BenchmarkTitleCreation(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
title := NewTitle(int32(i), fmt.Sprintf("Title %d", i))
|
||||||
|
title.SetDescription("Description")
|
||||||
|
title.SetCategory(CategoryMiscellaneous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMasterListAdd(b *testing.B) {
|
||||||
|
mtl := NewMasterTitlesList()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
title := NewTitle(int32(i+1000), fmt.Sprintf("Title %d", i))
|
||||||
|
mtl.AddTitle(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPlayerListAdd(b *testing.B) {
|
||||||
mtl := NewMasterTitlesList()
|
mtl := NewMasterTitlesList()
|
||||||
|
|
||||||
// Test nil title
|
// Pre-populate master list
|
||||||
err := mtl.AddTitle(nil)
|
for i := 0; i < 1000; i++ {
|
||||||
if err == nil {
|
title := NewTitle(int32(i), fmt.Sprintf("Title %d", i))
|
||||||
t.Error("Expected error when adding nil title")
|
mtl.AddTitle(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test duplicate ID
|
ptl := NewPlayerTitlesList(1, mtl)
|
||||||
title1 := NewTitle(999, "Title 1")
|
b.ResetTimer()
|
||||||
title2 := NewTitle(999, "Title 2")
|
|
||||||
|
|
||||||
err = mtl.AddTitle(title1)
|
for i := 0; i < b.N; i++ {
|
||||||
if err != nil {
|
titleID := int32(i % 1000)
|
||||||
t.Fatalf("Failed to add first title: %v", err)
|
ptl.AddTitle(titleID, 0, 0)
|
||||||
|
ptl.RemoveTitle(titleID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mtl.AddTitle(title2)
|
func BenchmarkTitleManagerGrant(b *testing.B) {
|
||||||
if err == nil {
|
tm := NewTitleManager()
|
||||||
t.Error("Expected error when adding title with duplicate ID")
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Pre-create titles
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
tm.CreateTitle(fmt.Sprintf("Title %d", i), "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
playerID := int32(i % 100)
|
||||||
|
titleID := int32((i % 100) + 1)
|
||||||
|
tm.GrantTitle(playerID, titleID, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTitleSearch(b *testing.B) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Create fewer titles for faster benchmark
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
name := fmt.Sprintf("Title %d", i)
|
||||||
|
if i%10 == 0 {
|
||||||
|
name = fmt.Sprintf("Dragon %d", i)
|
||||||
|
}
|
||||||
|
tm.CreateTitle(name, "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tm.SearchTitles("dragon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkConcurrentAccess(b *testing.B) {
|
||||||
|
tm := NewTitleManager()
|
||||||
|
defer tm.Shutdown()
|
||||||
|
|
||||||
|
// Pre-populate with titles
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
tm.CreateTitle(fmt.Sprintf("Title %d", i), "Description", CategoryMiscellaneous, TitlePositionSuffix, TitleSourceQuest, TitleRarityCommon)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
// Use simpler sequential approach instead of RunParallel to avoid deadlocks
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
playerID := int32(i % 10)
|
||||||
|
titleID := int32((i % 10) + 1)
|
||||||
|
tm.GrantTitle(playerID, titleID, 0, 0)
|
||||||
|
tm.GetPlayerFormattedName(playerID, "Player")
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user