add achievements package

This commit is contained in:
Sky Johnson 2025-07-30 09:38:58 -05:00
parent fc82f97cb6
commit 4bae02bec0
5 changed files with 925 additions and 0 deletions

View 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 = &timestamp
}
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
})
}

View 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

View 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
}

View 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
}

View 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
}