686 lines
23 KiB
Go
686 lines
23 KiB
Go
package world
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"eq2emu/internal/achievements"
|
|
"eq2emu/internal/database"
|
|
"eq2emu/internal/packets"
|
|
)
|
|
|
|
// AchievementManager manages achievements for the world server
|
|
type AchievementManager struct {
|
|
masterList *achievements.MasterList
|
|
playerManagers map[int32]*achievements.PlayerManager // CharacterID -> PlayerManager
|
|
database *database.Database
|
|
world *World // Reference to world server for notifications
|
|
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewAchievementManager creates a new achievement manager
|
|
func NewAchievementManager(db *database.Database) *AchievementManager {
|
|
return &AchievementManager{
|
|
masterList: achievements.NewMasterList(),
|
|
playerManagers: make(map[int32]*achievements.PlayerManager),
|
|
database: db,
|
|
world: nil, // Set by world server after creation
|
|
}
|
|
}
|
|
|
|
// SetWorld sets the world server reference for notifications
|
|
func (am *AchievementManager) SetWorld(world *World) {
|
|
am.world = world
|
|
}
|
|
|
|
// LoadAchievements loads all achievements from database
|
|
func (am *AchievementManager) LoadAchievements() error {
|
|
fmt.Println("Loading master achievement list...")
|
|
|
|
err := achievements.LoadAllAchievements(am.database, am.masterList)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load achievements: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Loaded %d achievements\n", am.masterList.Size())
|
|
return nil
|
|
}
|
|
|
|
// GetPlayerManager gets or creates a player achievement manager
|
|
func (am *AchievementManager) GetPlayerManager(characterID int32) *achievements.PlayerManager {
|
|
am.mutex.RLock()
|
|
playerMgr, exists := am.playerManagers[characterID]
|
|
am.mutex.RUnlock()
|
|
|
|
if exists {
|
|
return playerMgr
|
|
}
|
|
|
|
// Create new player manager and load data
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
// Double-check after acquiring write lock
|
|
if playerMgr, exists := am.playerManagers[characterID]; exists {
|
|
return playerMgr
|
|
}
|
|
|
|
playerMgr = achievements.NewPlayerManager()
|
|
am.playerManagers[characterID] = playerMgr
|
|
|
|
// Load player achievement data from database
|
|
go am.loadPlayerAchievements(characterID, playerMgr)
|
|
|
|
return playerMgr
|
|
}
|
|
|
|
// loadPlayerAchievements loads achievement data for a specific player
|
|
func (am *AchievementManager) loadPlayerAchievements(characterID int32, playerMgr *achievements.PlayerManager) {
|
|
// Load player achievements
|
|
err := achievements.LoadPlayerAchievements(am.database, uint32(characterID), playerMgr.Achievements)
|
|
if err != nil {
|
|
fmt.Printf("Error loading achievements for character %d: %v\n", characterID, err)
|
|
}
|
|
|
|
// Load player progress
|
|
err = achievements.LoadPlayerAchievementUpdates(am.database, uint32(characterID), playerMgr.Updates)
|
|
if err != nil {
|
|
fmt.Printf("Error loading achievement progress for character %d: %v\n", characterID, err)
|
|
}
|
|
}
|
|
|
|
// UpdateProgress updates player progress for an achievement
|
|
func (am *AchievementManager) UpdateProgress(characterID int32, achievementID uint32, progress uint32) error {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return fmt.Errorf("failed to get player manager for character %d", characterID)
|
|
}
|
|
|
|
// Update progress
|
|
playerMgr.Updates.UpdateProgress(achievementID, progress)
|
|
|
|
// Check if achievement is completed
|
|
achievement := am.masterList.GetAchievement(achievementID)
|
|
if achievement != nil {
|
|
completed, err := playerMgr.CheckRequirements(achievement)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check requirements: %w", err)
|
|
}
|
|
|
|
if completed && !playerMgr.Updates.IsCompleted(achievementID) {
|
|
// Complete the achievement
|
|
playerMgr.Updates.CompleteAchievement(achievementID)
|
|
|
|
// Save progress to database
|
|
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
|
|
|
// Trigger achievement completion event
|
|
go am.onAchievementCompleted(characterID, achievement)
|
|
|
|
// Send achievement update packet to client
|
|
go am.sendAchievementUpdateToClient(characterID)
|
|
|
|
fmt.Printf("Character %d completed achievement: %s\n", characterID, achievement.Title)
|
|
} else if progress > 0 {
|
|
// Save progress update to database
|
|
go am.savePlayerProgress(characterID, achievementID, playerMgr)
|
|
|
|
// Send achievement update packet for progress update
|
|
go am.sendAchievementUpdateToClient(characterID)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// savePlayerProgress saves player achievement progress to database
|
|
func (am *AchievementManager) savePlayerProgress(characterID int32, achievementID uint32, playerMgr *achievements.PlayerManager) {
|
|
update := playerMgr.Updates.GetUpdate(achievementID)
|
|
if update == nil {
|
|
return
|
|
}
|
|
|
|
err := achievements.SavePlayerAchievementUpdate(am.database, uint32(characterID), update)
|
|
if err != nil {
|
|
fmt.Printf("Error saving achievement progress for character %d, achievement %d: %v\n",
|
|
characterID, achievementID, err)
|
|
}
|
|
}
|
|
|
|
// onAchievementCompleted handles achievement completion events
|
|
func (am *AchievementManager) onAchievementCompleted(characterID int32, achievement *achievements.Achievement) {
|
|
// Award points
|
|
if achievement.PointValue > 0 {
|
|
// Increment player's achievement points
|
|
fmt.Printf("Character %d earned %d achievement points\n", characterID, achievement.PointValue)
|
|
}
|
|
|
|
// Process rewards
|
|
for _, reward := range achievement.Rewards {
|
|
am.processReward(characterID, reward)
|
|
}
|
|
|
|
// Notify other systems about achievement completion
|
|
am.notifyAchievementCompleted(characterID, achievement.ID)
|
|
}
|
|
|
|
// notifyAchievementCompleted notifies other systems about achievement completion
|
|
func (am *AchievementManager) notifyAchievementCompleted(characterID int32, achievementID uint32) {
|
|
// Notify title system if available
|
|
if am.world != nil && am.world.titleMgr != nil {
|
|
integrationMgr := am.world.titleMgr.GetIntegrationManager()
|
|
if integrationMgr != nil {
|
|
achievementIntegration := integrationMgr.GetAchievementIntegration()
|
|
if achievementIntegration != nil {
|
|
err := achievementIntegration.OnAchievementCompleted(characterID, achievementID)
|
|
if err != nil {
|
|
fmt.Printf("Error processing achievement completion for titles: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// processReward processes an achievement reward
|
|
func (am *AchievementManager) processReward(characterID int32, reward achievements.Reward) {
|
|
// Basic reward processing - extend based on reward types
|
|
switch reward.Reward {
|
|
case "title":
|
|
// Award title
|
|
fmt.Printf("Character %d earned a title reward\n", characterID)
|
|
case "item":
|
|
// Award item
|
|
fmt.Printf("Character %d earned an item reward\n", characterID)
|
|
case "experience":
|
|
// Award experience
|
|
fmt.Printf("Character %d earned experience reward\n", characterID)
|
|
default:
|
|
fmt.Printf("Character %d earned reward: %s\n", characterID, reward.Reward)
|
|
}
|
|
}
|
|
|
|
// GetAchievement gets an achievement by ID from master list
|
|
func (am *AchievementManager) GetAchievement(achievementID uint32) *achievements.Achievement {
|
|
return am.masterList.GetAchievement(achievementID)
|
|
}
|
|
|
|
// GetAchievementsByCategory gets achievements filtered by category
|
|
func (am *AchievementManager) GetAchievementsByCategory(category string) []*achievements.Achievement {
|
|
return am.masterList.GetAchievementsByCategory(category)
|
|
}
|
|
|
|
// GetAchievementsByExpansion gets achievements filtered by expansion
|
|
func (am *AchievementManager) GetAchievementsByExpansion(expansion string) []*achievements.Achievement {
|
|
return am.masterList.GetAchievementsByExpansion(expansion)
|
|
}
|
|
|
|
// GetPlayerProgress gets player's progress for an achievement
|
|
func (am *AchievementManager) GetPlayerProgress(characterID int32, achievementID uint32) uint32 {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return 0
|
|
}
|
|
|
|
return playerMgr.Updates.GetProgress(achievementID)
|
|
}
|
|
|
|
// IsPlayerCompleted checks if player has completed an achievement
|
|
func (am *AchievementManager) IsPlayerCompleted(characterID int32, achievementID uint32) bool {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return false
|
|
}
|
|
|
|
return playerMgr.Updates.IsCompleted(achievementID)
|
|
}
|
|
|
|
// GetPlayerCompletedAchievements gets all completed achievement IDs for a player
|
|
func (am *AchievementManager) GetPlayerCompletedAchievements(characterID int32) []uint32 {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return nil
|
|
}
|
|
|
|
return playerMgr.Updates.GetCompletedAchievements()
|
|
}
|
|
|
|
// GetPlayerInProgressAchievements gets all in-progress achievement IDs for a player
|
|
func (am *AchievementManager) GetPlayerInProgressAchievements(characterID int32) []uint32 {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return nil
|
|
}
|
|
|
|
return playerMgr.Updates.GetInProgressAchievements()
|
|
}
|
|
|
|
// GetCompletionPercentage gets completion percentage for player's achievement
|
|
func (am *AchievementManager) GetCompletionPercentage(characterID int32, achievementID uint32) float64 {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return 0.0
|
|
}
|
|
|
|
achievement := am.masterList.GetAchievement(achievementID)
|
|
if achievement == nil {
|
|
return 0.0
|
|
}
|
|
|
|
return playerMgr.GetCompletionStatus(achievement)
|
|
}
|
|
|
|
// RemovePlayerManager removes a player manager (called when player logs out)
|
|
func (am *AchievementManager) RemovePlayerManager(characterID int32) {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
delete(am.playerManagers, characterID)
|
|
}
|
|
|
|
// GetMasterList returns the master achievement list
|
|
func (am *AchievementManager) GetMasterList() *achievements.MasterList {
|
|
return am.masterList
|
|
}
|
|
|
|
// GetStatistics returns achievement system statistics
|
|
func (am *AchievementManager) GetStatistics() map[string]any {
|
|
am.mutex.RLock()
|
|
defer am.mutex.RUnlock()
|
|
|
|
stats := map[string]any{
|
|
"total_achievements": am.masterList.Size(),
|
|
"online_players": len(am.playerManagers),
|
|
"categories": am.masterList.GetCategories(),
|
|
"expansions": am.masterList.GetExpansions(),
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// CreateAchievementUpdatePacket creates an achievement update packet for a player
|
|
func (am *AchievementManager) CreateAchievementUpdatePacket(characterID int32, version uint32) ([]byte, error) {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return nil, fmt.Errorf("player manager not found for character %d", characterID)
|
|
}
|
|
|
|
updates := playerMgr.Updates.GetAllUpdates()
|
|
|
|
// Build the packet data map according to the AchievementUpdate.xml structure
|
|
achievementArray := make([]map[string]any, 0, len(updates))
|
|
|
|
for achievementID, update := range updates {
|
|
var completedDate uint32
|
|
if !update.CompletedDate.IsZero() {
|
|
completedDate = uint32(update.CompletedDate.Unix())
|
|
}
|
|
|
|
// Build item array for this achievement
|
|
itemArray := make([]map[string]any, 0, len(update.UpdateItems))
|
|
for _, item := range update.UpdateItems {
|
|
itemArray = append(itemArray, map[string]any{
|
|
"item_update": item.ItemUpdate,
|
|
})
|
|
}
|
|
|
|
achievementData := map[string]any{
|
|
"achievement_id": achievementID,
|
|
"completed_date": completedDate,
|
|
"num_items": uint8(len(update.UpdateItems)),
|
|
"item_array": itemArray,
|
|
}
|
|
|
|
achievementArray = append(achievementArray, achievementData)
|
|
}
|
|
|
|
packetData := map[string]any{
|
|
"unknown1": uint8(0),
|
|
"num_achievements": uint16(len(updates)),
|
|
"achievement_array": achievementArray,
|
|
}
|
|
|
|
// Build the packet using the packet system
|
|
packetBytes, err := packets.BuildPacket("AchievementUpdate", packetData, version, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build achievement update packet: %w", err)
|
|
}
|
|
|
|
return packetBytes, nil
|
|
}
|
|
|
|
// SendAchievementUpdateToPlayer sends achievement update packet to a player
|
|
func (am *AchievementManager) SendAchievementUpdateToPlayer(characterID int32, clientVersion int32) error {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return fmt.Errorf("player manager not found for character %d", characterID)
|
|
}
|
|
|
|
// Create the packet data
|
|
packetData, err := am.CreateAchievementUpdatePacket(characterID, uint32(clientVersion))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create achievement update packet: %w", err)
|
|
}
|
|
|
|
// TODO: Send packet to player through world server client connection
|
|
// This would typically use the world server's client manager
|
|
if am.world != nil {
|
|
// Get client opcode for this version
|
|
clientOpcode := packets.InternalToClient(packets.OP_AchievementUpdateMsg, clientVersion)
|
|
if clientOpcode == 0 {
|
|
return fmt.Errorf("no client opcode mapping for achievement update in version %d", clientVersion)
|
|
}
|
|
|
|
fmt.Printf("Would send achievement update packet to character %d (opcode: %d, size: %d bytes)\n",
|
|
characterID, clientOpcode, len(packetData))
|
|
|
|
// In a real implementation:
|
|
// return am.world.SendPacketToClient(characterID, clientOpcode, packetData)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateCharacterAchievementsPacket creates a character achievements packet (master list) for a player
|
|
func (am *AchievementManager) CreateCharacterAchievementsPacket(characterID int32, version uint32) ([]byte, error) {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return nil, fmt.Errorf("player manager not found for character %d", characterID)
|
|
}
|
|
|
|
// Get all achievements from master list
|
|
allAchievements := am.masterList.GetAllAchievements()
|
|
|
|
// Build achievement array according to CharacterAchievements.xml structure
|
|
achievementArray := make([]map[string]any, 0, len(allAchievements))
|
|
|
|
for _, achievement := range allAchievements {
|
|
// Build requirements array
|
|
itemArray := make([]map[string]any, 0, len(achievement.Requirements))
|
|
for _, req := range achievement.Requirements {
|
|
itemArray = append(itemArray, map[string]any{
|
|
"item_name": req.Name,
|
|
"item_qty_req": req.QtyRequired,
|
|
})
|
|
}
|
|
|
|
// Build rewards array
|
|
rewardArray := make([]map[string]any, 0, len(achievement.Rewards))
|
|
for _, reward := range achievement.Rewards {
|
|
rewardData := map[string]any{
|
|
"reward_item": reward.Reward,
|
|
}
|
|
|
|
// Add unknown4 field for version 57032+
|
|
if version >= 57032 {
|
|
rewardData["unknown4"] = uint32(0)
|
|
}
|
|
|
|
rewardArray = append(rewardArray, rewardData)
|
|
}
|
|
|
|
// Build achievement data based on version
|
|
achievementData := map[string]any{
|
|
"achievement_id": achievement.AchievementID,
|
|
"title": achievement.Title,
|
|
"uncompleted_text": achievement.UncompletedText,
|
|
"completed_text": achievement.CompletedText,
|
|
"category": achievement.Category,
|
|
"expansion": achievement.Expansion,
|
|
"icon": achievement.Icon,
|
|
"point_value": achievement.PointValue,
|
|
"qty_req": achievement.QtyRequired,
|
|
"hide_achievement": uint8(0), // Convert bool to uint8
|
|
}
|
|
|
|
if achievement.Hide {
|
|
achievementData["hide_achievement"] = uint8(1)
|
|
}
|
|
|
|
// Handle version-specific fields
|
|
switch {
|
|
case version >= 57032:
|
|
achievementData["unknown3"] = [2]uint32{achievement.Unknown3A, achievement.Unknown3B}
|
|
achievementData["num_items"] = uint8(len(achievement.Requirements))
|
|
achievementData["item_array"] = itemArray
|
|
achievementData["num_rewards"] = uint8(len(achievement.Rewards))
|
|
achievementData["reward_array"] = rewardArray
|
|
achievementData["num_reward_links"] = uint8(0) // TODO: Implement reward links if needed
|
|
achievementData["reward_link_array"] = []map[string]any{}
|
|
|
|
case version >= 1096:
|
|
achievementData["unknown3"] = [2]uint32{achievement.Unknown3A, achievement.Unknown3B}
|
|
achievementData["num_items"] = uint8(len(achievement.Requirements))
|
|
achievementData["item_array"] = itemArray
|
|
achievementData["num_rewards"] = uint8(len(achievement.Rewards))
|
|
achievementData["reward_array"] = rewardArray
|
|
|
|
case version >= 603:
|
|
achievementData["unknown3a"] = achievement.Unknown3A
|
|
achievementData["unknown3b"] = achievement.Unknown3B
|
|
achievementData["guild"] = uint8(0) // TODO: Implement guild achievements if needed
|
|
achievementData["num_items"] = uint8(len(achievement.Requirements))
|
|
achievementData["item_array"] = itemArray
|
|
achievementData["num_reward_links"] = uint8(0) // TODO: Implement reward links if needed
|
|
achievementData["reward_link_array"] = []map[string]any{}
|
|
}
|
|
|
|
achievementArray = append(achievementArray, achievementData)
|
|
}
|
|
|
|
packetData := map[string]any{
|
|
"num_achievements": uint16(len(allAchievements)),
|
|
"achievement_array": achievementArray,
|
|
}
|
|
|
|
// Build the packet using the packet system
|
|
packetBytes, err := packets.BuildPacket("CharacterAchievements", packetData, version, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build character achievements packet: %w", err)
|
|
}
|
|
|
|
return packetBytes, nil
|
|
}
|
|
|
|
// SendCharacterAchievementsToPlayer sends the master achievement list to a player
|
|
func (am *AchievementManager) SendCharacterAchievementsToPlayer(characterID int32, clientVersion int32) error {
|
|
// Create the packet data
|
|
packetData, err := am.CreateCharacterAchievementsPacket(characterID, uint32(clientVersion))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create character achievements packet: %w", err)
|
|
}
|
|
|
|
// TODO: Send packet to player through world server client connection
|
|
if am.world != nil {
|
|
// Get client opcode for this version
|
|
clientOpcode := packets.InternalToClient(packets.OP_CharacterAchievements, clientVersion)
|
|
if clientOpcode == 0 {
|
|
return fmt.Errorf("no client opcode mapping for character achievements in version %d", clientVersion)
|
|
}
|
|
|
|
fmt.Printf("Would send character achievements packet to character %d (opcode: %d, size: %d bytes)\n",
|
|
characterID, clientOpcode, len(packetData))
|
|
|
|
// In a real implementation:
|
|
// return am.world.SendPacketToClient(characterID, clientOpcode, packetData)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AwardAchievementPoints awards achievement points to a player
|
|
func (am *AchievementManager) AwardAchievementPoints(characterID int32, points uint32) error {
|
|
// TODO: Integrate with player character system to award achievement points
|
|
// This would typically update the player's total achievement points
|
|
fmt.Printf("Character %d awarded %d achievement points\n", characterID, points)
|
|
return nil
|
|
}
|
|
|
|
// ProcessAchievementTrigger processes an achievement trigger for a player
|
|
func (am *AchievementManager) ProcessAchievementTrigger(characterID int32, triggerType string, value uint32) error {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return fmt.Errorf("player manager not found for character %d", characterID)
|
|
}
|
|
|
|
// Get all achievements and check if any match the trigger
|
|
allAchievements := am.masterList.GetAllAchievements()
|
|
|
|
for _, achievement := range allAchievements {
|
|
// Check requirements to see if any match the trigger
|
|
for _, requirement := range achievement.Requirements {
|
|
if requirement.Name == triggerType {
|
|
// Update progress for this achievement
|
|
currentProgress := playerMgr.Updates.GetProgress(achievement.AchievementID)
|
|
newProgress := currentProgress + value
|
|
|
|
// Ensure we don't exceed the requirement
|
|
if newProgress > requirement.QtyRequired {
|
|
newProgress = requirement.QtyRequired
|
|
}
|
|
|
|
// Update the progress
|
|
err := am.UpdateProgress(characterID, achievement.AchievementID, newProgress)
|
|
if err != nil {
|
|
fmt.Printf("Error updating progress for achievement %d: %v\n", achievement.AchievementID, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetPlayerAchievementPoints returns total achievement points for a player
|
|
func (am *AchievementManager) GetPlayerAchievementPoints(characterID int32) uint32 {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return 0
|
|
}
|
|
|
|
var totalPoints uint32
|
|
completedAchievements := playerMgr.Updates.GetCompletedAchievements()
|
|
|
|
for _, achievementID := range completedAchievements {
|
|
achievement := am.masterList.GetAchievement(achievementID)
|
|
if achievement != nil {
|
|
totalPoints += achievement.PointValue
|
|
}
|
|
}
|
|
|
|
return totalPoints
|
|
}
|
|
|
|
// RefreshPlayerAchievements refreshes a player's achievement data
|
|
func (am *AchievementManager) RefreshPlayerAchievements(characterID int32) error {
|
|
// Remove existing player manager
|
|
am.RemovePlayerManager(characterID)
|
|
|
|
// This will create a new manager and load fresh data
|
|
am.GetPlayerManager(characterID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAchievementProgress returns detailed progress information for a player's achievement
|
|
func (am *AchievementManager) GetAchievementProgress(characterID int32, achievementID uint32) map[string]any {
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return nil
|
|
}
|
|
|
|
achievement := am.masterList.GetAchievement(achievementID)
|
|
if achievement == nil {
|
|
return nil
|
|
}
|
|
|
|
progress := playerMgr.Updates.GetProgress(achievementID)
|
|
completed := playerMgr.Updates.IsCompleted(achievementID)
|
|
completionPercentage := am.GetCompletionPercentage(characterID, achievementID)
|
|
|
|
result := map[string]any{
|
|
"achievement_id": achievementID,
|
|
"title": achievement.Title,
|
|
"category": achievement.Category,
|
|
"current_progress": progress,
|
|
"required_progress": achievement.QtyRequired,
|
|
"completed": completed,
|
|
"completion_percentage": completionPercentage,
|
|
"point_value": achievement.PointValue,
|
|
}
|
|
|
|
if completed {
|
|
completedDate := playerMgr.Updates.GetCompletedDate(achievementID)
|
|
if !completedDate.IsZero() {
|
|
result["completed_date"] = completedDate.Format(time.RFC3339)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// sendAchievementUpdateToClient sends achievement update to client with version detection
|
|
func (am *AchievementManager) sendAchievementUpdateToClient(characterID int32) {
|
|
// TODO: Get client version from world server client connection
|
|
// For now, use a common version (1096 is a common EQ2 client version)
|
|
defaultClientVersion := int32(1096)
|
|
|
|
err := am.SendAchievementUpdateToPlayer(characterID, defaultClientVersion)
|
|
if err != nil {
|
|
fmt.Printf("Failed to send achievement update to character %d: %v\n", characterID, err)
|
|
}
|
|
}
|
|
|
|
// LoadAndSendInitialAchievements loads and sends all achievements to a newly connected player
|
|
func (am *AchievementManager) LoadAndSendInitialAchievements(characterID int32, clientVersion int32) error {
|
|
// Ensure player manager is loaded
|
|
playerMgr := am.GetPlayerManager(characterID)
|
|
if playerMgr == nil {
|
|
return fmt.Errorf("failed to create player manager for character %d", characterID)
|
|
}
|
|
|
|
// Send master achievement list first
|
|
err := am.SendCharacterAchievementsToPlayer(characterID, clientVersion)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send character achievements: %w", err)
|
|
}
|
|
|
|
// Then send current progress
|
|
err = am.SendAchievementUpdateToPlayer(characterID, clientVersion)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send achievement updates: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Sent initial achievement data to character %d\n", characterID)
|
|
return nil
|
|
}
|
|
|
|
// HandleAchievementTriggerEvent processes achievement triggers from game events
|
|
func (am *AchievementManager) HandleAchievementTriggerEvent(characterID int32, triggerType string, value uint32) {
|
|
err := am.ProcessAchievementTrigger(characterID, triggerType, value)
|
|
if err != nil {
|
|
fmt.Printf("Error processing achievement trigger for character %d: %v\n", characterID, err)
|
|
}
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the achievement manager
|
|
func (am *AchievementManager) Shutdown() {
|
|
fmt.Println("Shutting down achievement manager...")
|
|
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
// Save all player progress before shutdown
|
|
for characterID, playerMgr := range am.playerManagers {
|
|
for _, achievementID := range playerMgr.Updates.GetInProgressAchievements() {
|
|
am.savePlayerProgress(characterID, achievementID, playerMgr)
|
|
}
|
|
}
|
|
|
|
// Clear player managers
|
|
am.playerManagers = make(map[int32]*achievements.PlayerManager)
|
|
|
|
fmt.Println("Achievement manager shutdown complete")
|
|
}
|