add achievements package
This commit is contained in:
parent
fc82f97cb6
commit
4bae02bec0
301
internal/achievements/database.go
Normal file
301
internal/achievements/database.go
Normal file
@ -0,0 +1,301 @@
|
||||
package achievements
|
||||
|
||||
import (
|
||||
"eq2emu/internal/database"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoadAllAchievements loads all achievements from database into master list
|
||||
func LoadAllAchievements(db *database.DB, masterList *MasterList) error {
|
||||
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||
unknown3a, unknown3b FROM achievements`
|
||||
|
||||
err := db.Query(query, func(row *database.Row) error {
|
||||
achievement := NewAchievement()
|
||||
achievement.ID = uint32(row.Int(0))
|
||||
achievement.Title = row.Text(1)
|
||||
achievement.UncompletedText = row.Text(2)
|
||||
achievement.CompletedText = row.Text(3)
|
||||
achievement.Category = row.Text(4)
|
||||
achievement.Expansion = row.Text(5)
|
||||
achievement.Icon = uint16(row.Int(6))
|
||||
achievement.PointValue = uint32(row.Int(7))
|
||||
achievement.QtyRequired = uint32(row.Int(8))
|
||||
achievement.Hide = row.Bool(9)
|
||||
achievement.Unknown3A = uint32(row.Int(10))
|
||||
achievement.Unknown3B = uint32(row.Int(11))
|
||||
|
||||
// Load requirements and rewards
|
||||
if err := loadAchievementRequirements(db, achievement); err != nil {
|
||||
return fmt.Errorf("failed to load requirements for achievement %d: %w", achievement.ID, err)
|
||||
}
|
||||
|
||||
if err := loadAchievementRewards(db, achievement); err != nil {
|
||||
return fmt.Errorf("failed to load rewards for achievement %d: %w", achievement.ID, err)
|
||||
}
|
||||
|
||||
if !masterList.AddAchievement(achievement) {
|
||||
return fmt.Errorf("duplicate achievement ID: %d", achievement.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// loadAchievementRequirements loads requirements for a specific achievement
|
||||
func loadAchievementRequirements(db *database.DB, achievement *Achievement) error {
|
||||
query := `SELECT achievement_id, name, qty_req
|
||||
FROM achievements_requirements
|
||||
WHERE achievement_id = ?`
|
||||
|
||||
return db.Query(query, func(row *database.Row) error {
|
||||
req := Requirement{
|
||||
AchievementID: uint32(row.Int(0)),
|
||||
Name: row.Text(1),
|
||||
QtyRequired: uint32(row.Int(2)),
|
||||
}
|
||||
achievement.AddRequirement(req)
|
||||
return nil
|
||||
}, achievement.ID)
|
||||
}
|
||||
|
||||
// loadAchievementRewards loads rewards for a specific achievement
|
||||
func loadAchievementRewards(db *database.DB, achievement *Achievement) error {
|
||||
query := `SELECT achievement_id, reward
|
||||
FROM achievements_rewards
|
||||
WHERE achievement_id = ?`
|
||||
|
||||
return db.Query(query, func(row *database.Row) error {
|
||||
reward := Reward{
|
||||
AchievementID: uint32(row.Int(0)),
|
||||
Reward: row.Text(1),
|
||||
}
|
||||
achievement.AddReward(reward)
|
||||
return nil
|
||||
}, achievement.ID)
|
||||
}
|
||||
|
||||
// LoadPlayerAchievements loads player achievements from database
|
||||
func LoadPlayerAchievements(db *database.DB, playerID uint32, playerList *PlayerList) error {
|
||||
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||
unknown3a, unknown3b FROM achievements`
|
||||
|
||||
err := db.Query(query, func(row *database.Row) error {
|
||||
achievement := NewAchievement()
|
||||
achievement.ID = uint32(row.Int(0))
|
||||
achievement.Title = row.Text(1)
|
||||
achievement.UncompletedText = row.Text(2)
|
||||
achievement.CompletedText = row.Text(3)
|
||||
achievement.Category = row.Text(4)
|
||||
achievement.Expansion = row.Text(5)
|
||||
achievement.Icon = uint16(row.Int(6))
|
||||
achievement.PointValue = uint32(row.Int(7))
|
||||
achievement.QtyRequired = uint32(row.Int(8))
|
||||
achievement.Hide = row.Bool(9)
|
||||
achievement.Unknown3A = uint32(row.Int(10))
|
||||
achievement.Unknown3B = uint32(row.Int(11))
|
||||
|
||||
// Load requirements and rewards
|
||||
if err := loadAchievementRequirements(db, achievement); err != nil {
|
||||
return fmt.Errorf("failed to load requirements: %w", err)
|
||||
}
|
||||
|
||||
if err := loadAchievementRewards(db, achievement); err != nil {
|
||||
return fmt.Errorf("failed to load rewards: %w", err)
|
||||
}
|
||||
|
||||
if !playerList.AddAchievement(achievement) {
|
||||
return fmt.Errorf("duplicate achievement ID: %d", achievement.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadPlayerAchievementUpdates loads player achievement progress from database
|
||||
func LoadPlayerAchievementUpdates(db *database.DB, playerID uint32, updateList *PlayerUpdateList) error {
|
||||
query := `SELECT char_id, achievement_id, completed_date
|
||||
FROM character_achievements
|
||||
WHERE char_id = ?`
|
||||
|
||||
return db.Query(query, func(row *database.Row) error {
|
||||
update := NewUpdate()
|
||||
update.ID = uint32(row.Int(1))
|
||||
|
||||
// Convert completed_date from Unix timestamp
|
||||
if !row.IsNull(2) {
|
||||
timestamp := row.Int64(2)
|
||||
update.CompletedDate = time.Unix(timestamp, 0)
|
||||
}
|
||||
|
||||
// Load update items
|
||||
if err := loadPlayerAchievementUpdateItems(db, playerID, update); err != nil {
|
||||
return fmt.Errorf("failed to load update items: %w", err)
|
||||
}
|
||||
|
||||
if !updateList.AddUpdate(update) {
|
||||
return fmt.Errorf("duplicate achievement update ID: %d", update.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, playerID)
|
||||
}
|
||||
|
||||
// loadPlayerAchievementUpdateItems loads progress items for an achievement update
|
||||
func loadPlayerAchievementUpdateItems(db *database.DB, playerID uint32, update *Update) error {
|
||||
query := `SELECT achievement_id, items
|
||||
FROM character_achievements_items
|
||||
WHERE char_id = ? AND achievement_id = ?`
|
||||
|
||||
return db.Query(query, func(row *database.Row) error {
|
||||
item := UpdateItem{
|
||||
AchievementID: uint32(row.Int(0)),
|
||||
ItemUpdate: uint32(row.Int(1)),
|
||||
}
|
||||
update.AddUpdateItem(item)
|
||||
return nil
|
||||
}, playerID, update.ID)
|
||||
}
|
||||
|
||||
// SavePlayerAchievementUpdate saves or updates player achievement progress
|
||||
func SavePlayerAchievementUpdate(db *database.DB, playerID uint32, update *Update) error {
|
||||
return db.Transaction(func(tx *database.DB) error {
|
||||
// Save or update main achievement record
|
||||
query := `INSERT OR REPLACE INTO character_achievements
|
||||
(char_id, achievement_id, completed_date) VALUES (?, ?, ?)`
|
||||
|
||||
var completedDate *int64
|
||||
if !update.CompletedDate.IsZero() {
|
||||
timestamp := update.CompletedDate.Unix()
|
||||
completedDate = ×tamp
|
||||
}
|
||||
|
||||
if err := tx.Exec(query, playerID, update.ID, completedDate); err != nil {
|
||||
return fmt.Errorf("failed to save achievement update: %w", err)
|
||||
}
|
||||
|
||||
// Delete existing update items
|
||||
deleteQuery := `DELETE FROM character_achievements_items
|
||||
WHERE char_id = ? AND achievement_id = ?`
|
||||
if err := tx.Exec(deleteQuery, playerID, update.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete old update items: %w", err)
|
||||
}
|
||||
|
||||
// Insert new update items
|
||||
itemQuery := `INSERT INTO character_achievements_items
|
||||
(char_id, achievement_id, items) VALUES (?, ?, ?)`
|
||||
for _, item := range update.UpdateItems {
|
||||
if err := tx.Exec(itemQuery, playerID, item.AchievementID, item.ItemUpdate); err != nil {
|
||||
return fmt.Errorf("failed to save update item: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeletePlayerAchievementUpdate removes player achievement progress from database
|
||||
func DeletePlayerAchievementUpdate(db *database.DB, playerID uint32, achievementID uint32) error {
|
||||
return db.Transaction(func(tx *database.DB) error {
|
||||
// Delete main achievement record
|
||||
query := `DELETE FROM character_achievements
|
||||
WHERE char_id = ? AND achievement_id = ?`
|
||||
if err := tx.Exec(query, playerID, achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete achievement update: %w", err)
|
||||
}
|
||||
|
||||
// Delete update items
|
||||
itemQuery := `DELETE FROM character_achievements_items
|
||||
WHERE char_id = ? AND achievement_id = ?`
|
||||
if err := tx.Exec(itemQuery, playerID, achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete update items: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveAchievement saves or updates an achievement in the database
|
||||
func SaveAchievement(db *database.DB, achievement *Achievement) error {
|
||||
return db.Transaction(func(tx *database.DB) error {
|
||||
// Save main achievement record
|
||||
query := `INSERT OR REPLACE INTO achievements
|
||||
(achievement_id, title, uncompleted_text, completed_text,
|
||||
category, expansion, icon, point_value, qty_req,
|
||||
hide_achievement, unknown3a, unknown3b)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
if err := tx.Exec(query, achievement.ID, achievement.Title,
|
||||
achievement.UncompletedText, achievement.CompletedText,
|
||||
achievement.Category, achievement.Expansion, achievement.Icon,
|
||||
achievement.PointValue, achievement.QtyRequired, achievement.Hide,
|
||||
achievement.Unknown3A, achievement.Unknown3B); err != nil {
|
||||
return fmt.Errorf("failed to save achievement: %w", err)
|
||||
}
|
||||
|
||||
// Delete existing requirements and rewards
|
||||
if err := tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", achievement.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete old requirements: %w", err)
|
||||
}
|
||||
if err := tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", achievement.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete old rewards: %w", err)
|
||||
}
|
||||
|
||||
// Insert requirements
|
||||
reqQuery := `INSERT INTO achievements_requirements
|
||||
(achievement_id, name, qty_req) VALUES (?, ?, ?)`
|
||||
for _, req := range achievement.Requirements {
|
||||
if err := tx.Exec(reqQuery, req.AchievementID, req.Name, req.QtyRequired); err != nil {
|
||||
return fmt.Errorf("failed to save requirement: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert rewards
|
||||
rewardQuery := `INSERT INTO achievements_rewards
|
||||
(achievement_id, reward) VALUES (?, ?)`
|
||||
for _, reward := range achievement.Rewards {
|
||||
if err := tx.Exec(rewardQuery, reward.AchievementID, reward.Reward); err != nil {
|
||||
return fmt.Errorf("failed to save reward: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAchievement removes an achievement and all related records from database
|
||||
func DeleteAchievement(db *database.DB, achievementID uint32) error {
|
||||
return db.Transaction(func(tx *database.DB) error {
|
||||
// Delete main achievement
|
||||
if err := tx.Exec("DELETE FROM achievements WHERE achievement_id = ?", achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete achievement: %w", err)
|
||||
}
|
||||
|
||||
// Delete requirements
|
||||
if err := tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete requirements: %w", err)
|
||||
}
|
||||
|
||||
// Delete rewards
|
||||
if err := tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete rewards: %w", err)
|
||||
}
|
||||
|
||||
// Delete player progress (optional - might want to preserve history)
|
||||
if err := tx.Exec("DELETE FROM character_achievements WHERE achievement_id = ?", achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete player achievements: %w", err)
|
||||
}
|
||||
if err := tx.Exec("DELETE FROM character_achievements_items WHERE achievement_id = ?", achievementID); err != nil {
|
||||
return fmt.Errorf("failed to delete player achievement items: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
32
internal/achievements/doc.go
Normal file
32
internal/achievements/doc.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Package achievements provides a complete achievement system for EQ2Emulator servers.
|
||||
//
|
||||
// The package includes:
|
||||
// - Achievement definitions with requirements and rewards
|
||||
// - Master achievement list for server-wide management
|
||||
// - Player-specific achievement tracking and progress
|
||||
// - Database operations for persistence
|
||||
//
|
||||
// Basic usage:
|
||||
//
|
||||
// // Create master list and load from database
|
||||
// masterList := achievements.NewMasterList()
|
||||
// db, _ := database.Open("world.db")
|
||||
// achievements.LoadAllAchievements(db, masterList)
|
||||
//
|
||||
// // Create player manager
|
||||
// playerMgr := achievements.NewPlayerManager()
|
||||
// achievements.LoadPlayerAchievements(db, playerID, playerMgr.Achievements)
|
||||
// achievements.LoadPlayerAchievementUpdates(db, playerID, playerMgr.Updates)
|
||||
//
|
||||
// // Update player progress
|
||||
// playerMgr.Updates.UpdateProgress(achievementID, newProgress)
|
||||
//
|
||||
// // Check completion
|
||||
// if playerMgr.Updates.IsCompleted(achievementID) {
|
||||
// // Handle completed achievement
|
||||
// }
|
||||
//
|
||||
// // Save progress
|
||||
// update := playerMgr.Updates.GetUpdate(achievementID)
|
||||
// achievements.SavePlayerAchievementUpdate(db, playerID, update)
|
||||
package achievements
|
197
internal/achievements/master.go
Normal file
197
internal/achievements/master.go
Normal file
@ -0,0 +1,197 @@
|
||||
package achievements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MasterList manages the global list of all achievements
|
||||
type MasterList struct {
|
||||
achievements map[uint32]*Achievement
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMasterList creates a new master achievement list
|
||||
func NewMasterList() *MasterList {
|
||||
return &MasterList{
|
||||
achievements: make(map[uint32]*Achievement),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAchievement adds an achievement to the master list
|
||||
// Returns false if achievement with same ID already exists
|
||||
func (m *MasterList) AddAchievement(achievement *Achievement) bool {
|
||||
if achievement == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.achievements[achievement.ID]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
m.achievements[achievement.ID] = achievement
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAchievement retrieves an achievement by ID
|
||||
// Returns nil if not found
|
||||
func (m *MasterList) GetAchievement(id uint32) *Achievement {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
return m.achievements[id]
|
||||
}
|
||||
|
||||
// GetAchievementClone retrieves a cloned copy of an achievement by ID
|
||||
// Returns nil if not found. Safe for modification without affecting master list
|
||||
func (m *MasterList) GetAchievementClone(id uint32) *Achievement {
|
||||
m.mutex.RLock()
|
||||
achievement := m.achievements[id]
|
||||
m.mutex.RUnlock()
|
||||
|
||||
if achievement == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return achievement.Clone()
|
||||
}
|
||||
|
||||
// GetAllAchievements returns a map of all achievements (read-only access)
|
||||
// The returned map should not be modified
|
||||
func (m *MasterList) GetAllAchievements() map[uint32]*Achievement {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
// Return copy of map to prevent external modification
|
||||
result := make(map[uint32]*Achievement, len(m.achievements))
|
||||
for id, achievement := range m.achievements {
|
||||
result[id] = achievement
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAchievementsByCategory returns achievements filtered by category
|
||||
func (m *MasterList) GetAchievementsByCategory(category string) []*Achievement {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
var result []*Achievement
|
||||
for _, achievement := range m.achievements {
|
||||
if achievement.Category == category {
|
||||
result = append(result, achievement)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAchievementsByExpansion returns achievements filtered by expansion
|
||||
func (m *MasterList) GetAchievementsByExpansion(expansion string) []*Achievement {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
var result []*Achievement
|
||||
for _, achievement := range m.achievements {
|
||||
if achievement.Expansion == expansion {
|
||||
result = append(result, achievement)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RemoveAchievement removes an achievement from the master list
|
||||
// Returns true if achievement was found and removed
|
||||
func (m *MasterList) RemoveAchievement(id uint32) bool {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.achievements[id]; !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(m.achievements, id)
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateAchievement updates an existing achievement
|
||||
// Returns error if achievement doesn't exist
|
||||
func (m *MasterList) UpdateAchievement(achievement *Achievement) error {
|
||||
if achievement == nil {
|
||||
return fmt.Errorf("achievement cannot be nil")
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.achievements[achievement.ID]; !exists {
|
||||
return fmt.Errorf("achievement with ID %d does not exist", achievement.ID)
|
||||
}
|
||||
|
||||
m.achievements[achievement.ID] = achievement
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear removes all achievements from the master list
|
||||
func (m *MasterList) Clear() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.achievements = make(map[uint32]*Achievement)
|
||||
}
|
||||
|
||||
// Size returns the number of achievements in the master list
|
||||
func (m *MasterList) Size() int {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
return len(m.achievements)
|
||||
}
|
||||
|
||||
// Exists checks if an achievement with given ID exists
|
||||
func (m *MasterList) Exists(id uint32) bool {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
_, exists := m.achievements[id]
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetCategories returns all unique categories
|
||||
func (m *MasterList) GetCategories() []string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
categories := make(map[string]bool)
|
||||
for _, achievement := range m.achievements {
|
||||
if achievement.Category != "" {
|
||||
categories[achievement.Category] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(categories))
|
||||
for category := range categories {
|
||||
result = append(result, category)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetExpansions returns all unique expansions
|
||||
func (m *MasterList) GetExpansions() []string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
expansions := make(map[string]bool)
|
||||
for _, achievement := range m.achievements {
|
||||
if achievement.Expansion != "" {
|
||||
expansions[achievement.Expansion] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(expansions))
|
||||
for expansion := range expansions {
|
||||
result = append(result, expansion)
|
||||
}
|
||||
return result
|
||||
}
|
282
internal/achievements/player.go
Normal file
282
internal/achievements/player.go
Normal file
@ -0,0 +1,282 @@
|
||||
package achievements
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PlayerList manages achievements for a specific player
|
||||
type PlayerList struct {
|
||||
achievements map[uint32]*Achievement
|
||||
}
|
||||
|
||||
// PlayerUpdateList manages achievement updates/progress for a specific player
|
||||
type PlayerUpdateList struct {
|
||||
updates map[uint32]*Update
|
||||
}
|
||||
|
||||
// NewPlayerList creates a new player achievement list
|
||||
func NewPlayerList() *PlayerList {
|
||||
return &PlayerList{
|
||||
achievements: make(map[uint32]*Achievement),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPlayerUpdateList creates a new player achievement update list
|
||||
func NewPlayerUpdateList() *PlayerUpdateList {
|
||||
return &PlayerUpdateList{
|
||||
updates: make(map[uint32]*Update),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAchievement adds an achievement to the player's list
|
||||
// Returns false if achievement with same ID already exists
|
||||
func (p *PlayerList) AddAchievement(achievement *Achievement) bool {
|
||||
if achievement == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, exists := p.achievements[achievement.ID]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
p.achievements[achievement.ID] = achievement
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAchievement retrieves an achievement by ID
|
||||
// Returns nil if not found
|
||||
func (p *PlayerList) GetAchievement(id uint32) *Achievement {
|
||||
return p.achievements[id]
|
||||
}
|
||||
|
||||
// GetAllAchievements returns all player achievements
|
||||
func (p *PlayerList) GetAllAchievements() map[uint32]*Achievement {
|
||||
result := make(map[uint32]*Achievement, len(p.achievements))
|
||||
for id, achievement := range p.achievements {
|
||||
result[id] = achievement
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RemoveAchievement removes an achievement from the player's list
|
||||
// Returns true if achievement was found and removed
|
||||
func (p *PlayerList) RemoveAchievement(id uint32) bool {
|
||||
if _, exists := p.achievements[id]; !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(p.achievements, id)
|
||||
return true
|
||||
}
|
||||
|
||||
// HasAchievement checks if player has a specific achievement
|
||||
func (p *PlayerList) HasAchievement(id uint32) bool {
|
||||
_, exists := p.achievements[id]
|
||||
return exists
|
||||
}
|
||||
|
||||
// Clear removes all achievements from the player's list
|
||||
func (p *PlayerList) Clear() {
|
||||
p.achievements = make(map[uint32]*Achievement)
|
||||
}
|
||||
|
||||
// Size returns the number of achievements in the player's list
|
||||
func (p *PlayerList) Size() int {
|
||||
return len(p.achievements)
|
||||
}
|
||||
|
||||
// GetAchievementsByCategory returns player achievements filtered by category
|
||||
func (p *PlayerList) GetAchievementsByCategory(category string) []*Achievement {
|
||||
var result []*Achievement
|
||||
for _, achievement := range p.achievements {
|
||||
if achievement.Category == category {
|
||||
result = append(result, achievement)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AddUpdate adds an achievement update to the player's list
|
||||
// Returns false if update with same ID already exists
|
||||
func (p *PlayerUpdateList) AddUpdate(update *Update) bool {
|
||||
if update == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, exists := p.updates[update.ID]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
p.updates[update.ID] = update
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUpdate retrieves an achievement update by ID
|
||||
// Returns nil if not found
|
||||
func (p *PlayerUpdateList) GetUpdate(id uint32) *Update {
|
||||
return p.updates[id]
|
||||
}
|
||||
|
||||
// GetAllUpdates returns all player achievement updates
|
||||
func (p *PlayerUpdateList) GetAllUpdates() map[uint32]*Update {
|
||||
result := make(map[uint32]*Update, len(p.updates))
|
||||
for id, update := range p.updates {
|
||||
result[id] = update
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UpdateProgress updates or creates achievement progress
|
||||
func (p *PlayerUpdateList) UpdateProgress(achievementID uint32, itemUpdate uint32) {
|
||||
update := p.updates[achievementID]
|
||||
if update == nil {
|
||||
update = NewUpdate()
|
||||
update.ID = achievementID
|
||||
p.updates[achievementID] = update
|
||||
}
|
||||
|
||||
// Add or update the progress item
|
||||
found := false
|
||||
for i := range update.UpdateItems {
|
||||
if update.UpdateItems[i].AchievementID == achievementID {
|
||||
update.UpdateItems[i].ItemUpdate = itemUpdate
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
update.AddUpdateItem(UpdateItem{
|
||||
AchievementID: achievementID,
|
||||
ItemUpdate: itemUpdate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteAchievement marks an achievement as completed
|
||||
func (p *PlayerUpdateList) CompleteAchievement(achievementID uint32) {
|
||||
update := p.updates[achievementID]
|
||||
if update == nil {
|
||||
update = NewUpdate()
|
||||
update.ID = achievementID
|
||||
p.updates[achievementID] = update
|
||||
}
|
||||
update.CompletedDate = time.Now()
|
||||
}
|
||||
|
||||
// IsCompleted checks if an achievement is completed
|
||||
func (p *PlayerUpdateList) IsCompleted(achievementID uint32) bool {
|
||||
update := p.updates[achievementID]
|
||||
return update != nil && !update.CompletedDate.IsZero()
|
||||
}
|
||||
|
||||
// GetCompletedDate returns the completion date for an achievement
|
||||
// Returns zero time if not completed
|
||||
func (p *PlayerUpdateList) GetCompletedDate(achievementID uint32) time.Time {
|
||||
update := p.updates[achievementID]
|
||||
if update == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return update.CompletedDate
|
||||
}
|
||||
|
||||
// GetProgress returns the current progress for an achievement
|
||||
// Returns 0 if no progress found
|
||||
func (p *PlayerUpdateList) GetProgress(achievementID uint32) uint32 {
|
||||
update := p.updates[achievementID]
|
||||
if update == nil || len(update.UpdateItems) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Return the first matching update item's progress
|
||||
for _, item := range update.UpdateItems {
|
||||
if item.AchievementID == achievementID {
|
||||
return item.ItemUpdate
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RemoveUpdate removes an achievement update from the player's list
|
||||
// Returns true if update was found and removed
|
||||
func (p *PlayerUpdateList) RemoveUpdate(id uint32) bool {
|
||||
if _, exists := p.updates[id]; !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(p.updates, id)
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear removes all updates from the player's list
|
||||
func (p *PlayerUpdateList) Clear() {
|
||||
p.updates = make(map[uint32]*Update)
|
||||
}
|
||||
|
||||
// Size returns the number of updates in the player's list
|
||||
func (p *PlayerUpdateList) Size() int {
|
||||
return len(p.updates)
|
||||
}
|
||||
|
||||
// GetCompletedAchievements returns all completed achievement IDs
|
||||
func (p *PlayerUpdateList) GetCompletedAchievements() []uint32 {
|
||||
var completed []uint32
|
||||
for id, update := range p.updates {
|
||||
if !update.CompletedDate.IsZero() {
|
||||
completed = append(completed, id)
|
||||
}
|
||||
}
|
||||
return completed
|
||||
}
|
||||
|
||||
// GetInProgressAchievements returns all in-progress achievement IDs
|
||||
func (p *PlayerUpdateList) GetInProgressAchievements() []uint32 {
|
||||
var inProgress []uint32
|
||||
for id, update := range p.updates {
|
||||
if update.CompletedDate.IsZero() && len(update.UpdateItems) > 0 {
|
||||
inProgress = append(inProgress, id)
|
||||
}
|
||||
}
|
||||
return inProgress
|
||||
}
|
||||
|
||||
// PlayerManager combines achievement list and update list for a player
|
||||
type PlayerManager struct {
|
||||
Achievements *PlayerList
|
||||
Updates *PlayerUpdateList
|
||||
}
|
||||
|
||||
// NewPlayerManager creates a new player manager
|
||||
func NewPlayerManager() *PlayerManager {
|
||||
return &PlayerManager{
|
||||
Achievements: NewPlayerList(),
|
||||
Updates: NewPlayerUpdateList(),
|
||||
}
|
||||
}
|
||||
|
||||
// CheckRequirements validates if player meets achievement requirements
|
||||
// This is a basic implementation - extend as needed for specific game logic
|
||||
func (pm *PlayerManager) CheckRequirements(achievement *Achievement) (bool, error) {
|
||||
if achievement == nil {
|
||||
return false, fmt.Errorf("achievement cannot be nil")
|
||||
}
|
||||
|
||||
// Basic implementation - check if we have progress >= required quantity
|
||||
progress := pm.Updates.GetProgress(achievement.ID)
|
||||
return progress >= achievement.QtyRequired, nil
|
||||
}
|
||||
|
||||
// GetCompletionStatus returns completion percentage for an achievement
|
||||
func (pm *PlayerManager) GetCompletionStatus(achievement *Achievement) float64 {
|
||||
if achievement == nil || achievement.QtyRequired == 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
progress := pm.Updates.GetProgress(achievement.ID)
|
||||
if progress >= achievement.QtyRequired {
|
||||
return 100.0
|
||||
}
|
||||
|
||||
return (float64(progress) / float64(achievement.QtyRequired)) * 100.0
|
||||
}
|
113
internal/achievements/types.go
Normal file
113
internal/achievements/types.go
Normal file
@ -0,0 +1,113 @@
|
||||
package achievements
|
||||
|
||||
import "time"
|
||||
|
||||
// Requirement represents a single achievement requirement
|
||||
type Requirement struct {
|
||||
AchievementID uint32 `json:"achievement_id"`
|
||||
Name string `json:"name"`
|
||||
QtyRequired uint32 `json:"qty_required"`
|
||||
}
|
||||
|
||||
// Reward represents a single achievement reward
|
||||
type Reward struct {
|
||||
AchievementID uint32 `json:"achievement_id"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
|
||||
// Achievement represents a complete achievement definition
|
||||
type Achievement struct {
|
||||
ID uint32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
UncompletedText string `json:"uncompleted_text"`
|
||||
CompletedText string `json:"completed_text"`
|
||||
Category string `json:"category"`
|
||||
Expansion string `json:"expansion"`
|
||||
Icon uint16 `json:"icon"`
|
||||
PointValue uint32 `json:"point_value"`
|
||||
QtyRequired uint32 `json:"qty_required"`
|
||||
Hide bool `json:"hide"`
|
||||
Unknown3A uint32 `json:"unknown3a"`
|
||||
Unknown3B uint32 `json:"unknown3b"`
|
||||
Requirements []Requirement `json:"requirements"`
|
||||
Rewards []Reward `json:"rewards"`
|
||||
}
|
||||
|
||||
// UpdateItem represents a single achievement progress update
|
||||
type UpdateItem struct {
|
||||
AchievementID uint32 `json:"achievement_id"`
|
||||
ItemUpdate uint32 `json:"item_update"`
|
||||
}
|
||||
|
||||
// Update represents achievement completion/progress data
|
||||
type Update struct {
|
||||
ID uint32 `json:"id"`
|
||||
CompletedDate time.Time `json:"completed_date"`
|
||||
UpdateItems []UpdateItem `json:"update_items"`
|
||||
}
|
||||
|
||||
// NewAchievement creates a new achievement with empty slices
|
||||
func NewAchievement() *Achievement {
|
||||
return &Achievement{
|
||||
Requirements: make([]Requirement, 0),
|
||||
Rewards: make([]Reward, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// NewUpdate creates a new achievement update with empty slices
|
||||
func NewUpdate() *Update {
|
||||
return &Update{
|
||||
UpdateItems: make([]UpdateItem, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// AddRequirement adds a requirement to the achievement
|
||||
func (a *Achievement) AddRequirement(req Requirement) {
|
||||
a.Requirements = append(a.Requirements, req)
|
||||
}
|
||||
|
||||
// AddReward adds a reward to the achievement
|
||||
func (a *Achievement) AddReward(reward Reward) {
|
||||
a.Rewards = append(a.Rewards, reward)
|
||||
}
|
||||
|
||||
// AddUpdateItem adds an update item to the achievement update
|
||||
func (u *Update) AddUpdateItem(item UpdateItem) {
|
||||
u.UpdateItems = append(u.UpdateItems, item)
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the achievement
|
||||
func (a *Achievement) Clone() *Achievement {
|
||||
clone := &Achievement{
|
||||
ID: a.ID,
|
||||
Title: a.Title,
|
||||
UncompletedText: a.UncompletedText,
|
||||
CompletedText: a.CompletedText,
|
||||
Category: a.Category,
|
||||
Expansion: a.Expansion,
|
||||
Icon: a.Icon,
|
||||
PointValue: a.PointValue,
|
||||
QtyRequired: a.QtyRequired,
|
||||
Hide: a.Hide,
|
||||
Unknown3A: a.Unknown3A,
|
||||
Unknown3B: a.Unknown3B,
|
||||
Requirements: make([]Requirement, len(a.Requirements)),
|
||||
Rewards: make([]Reward, len(a.Rewards)),
|
||||
}
|
||||
|
||||
copy(clone.Requirements, a.Requirements)
|
||||
copy(clone.Rewards, a.Rewards)
|
||||
return clone
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the achievement update
|
||||
func (u *Update) Clone() *Update {
|
||||
clone := &Update{
|
||||
ID: u.ID,
|
||||
CompletedDate: u.CompletedDate,
|
||||
UpdateItems: make([]UpdateItem, len(u.UpdateItems)),
|
||||
}
|
||||
|
||||
copy(clone.UpdateItems, u.UpdateItems)
|
||||
return clone
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user