round out achievement manager and initial pass on packets
This commit is contained in:
parent
d24ec376a8
commit
c637793dee
@ -3,6 +3,7 @@ package achievements
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
)
|
||||
@ -435,3 +436,214 @@ func (a *Achievement) saveRewards(tx *sql.Tx) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAllAchievements loads all achievements from database into a master list
|
||||
func LoadAllAchievements(db *database.Database, masterList *MasterList) error {
|
||||
query := `SELECT id, achievement_id, title, uncompleted_text, completed_text,
|
||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||
unknown3a, unknown3b, max_version
|
||||
FROM achievements ORDER BY achievement_id`
|
||||
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var achievements []*Achievement
|
||||
|
||||
for rows.Next() {
|
||||
achievement := &Achievement{
|
||||
db: db,
|
||||
isNew: false,
|
||||
}
|
||||
|
||||
var hideInt int
|
||||
err := rows.Scan(
|
||||
&achievement.ID, &achievement.AchievementID, &achievement.Title,
|
||||
&achievement.UncompletedText, &achievement.CompletedText,
|
||||
&achievement.Category, &achievement.Expansion, &achievement.Icon,
|
||||
&achievement.PointValue, &achievement.QtyRequired, &hideInt,
|
||||
&achievement.Unknown3A, &achievement.Unknown3B, &achievement.MaxVersion,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan achievement: %w", err)
|
||||
}
|
||||
|
||||
achievement.Hide = hideInt != 0
|
||||
achievements = append(achievements, achievement)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return fmt.Errorf("failed to iterate rows: %w", err)
|
||||
}
|
||||
|
||||
// Load requirements and rewards for each achievement
|
||||
for _, achievement := range achievements {
|
||||
if err := achievement.loadRequirements(); err != nil {
|
||||
return fmt.Errorf("failed to load requirements for achievement %d: %w", achievement.AchievementID, err)
|
||||
}
|
||||
|
||||
if err := achievement.loadRewards(); err != nil {
|
||||
return fmt.Errorf("failed to load rewards for achievement %d: %w", achievement.AchievementID, err)
|
||||
}
|
||||
|
||||
// Add to master list
|
||||
masterList.AddAchievement(achievement)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPlayerAchievements loads all achievements for a specific player
|
||||
func LoadPlayerAchievements(db *database.Database, characterID uint32, playerList *PlayerList) error {
|
||||
// For now, we load all achievements for the player (matching C++ behavior)
|
||||
// In the future, this could be optimized to only load unlocked achievements
|
||||
query := `SELECT id, achievement_id, title, uncompleted_text, completed_text,
|
||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||
unknown3a, unknown3b, max_version
|
||||
FROM achievements ORDER BY achievement_id`
|
||||
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
achievement := &Achievement{
|
||||
db: db,
|
||||
isNew: false,
|
||||
}
|
||||
|
||||
var hideInt int
|
||||
err := rows.Scan(
|
||||
&achievement.ID, &achievement.AchievementID, &achievement.Title,
|
||||
&achievement.UncompletedText, &achievement.CompletedText,
|
||||
&achievement.Category, &achievement.Expansion, &achievement.Icon,
|
||||
&achievement.PointValue, &achievement.QtyRequired, &hideInt,
|
||||
&achievement.Unknown3A, &achievement.Unknown3B, &achievement.MaxVersion,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan achievement: %w", err)
|
||||
}
|
||||
|
||||
achievement.Hide = hideInt != 0
|
||||
|
||||
// Load requirements and rewards
|
||||
if err := achievement.loadRequirements(); err != nil {
|
||||
return fmt.Errorf("failed to load requirements: %w", err)
|
||||
}
|
||||
|
||||
if err := achievement.loadRewards(); err != nil {
|
||||
return fmt.Errorf("failed to load rewards: %w", err)
|
||||
}
|
||||
|
||||
// Add to player list
|
||||
playerList.AddAchievement(achievement)
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
// LoadPlayerAchievementUpdates loads player achievement progress from database
|
||||
func LoadPlayerAchievementUpdates(db *database.Database, characterID uint32, updateList *PlayerUpdateList) error {
|
||||
query := `SELECT achievement_id, completed_date FROM character_achievements WHERE char_id = ?`
|
||||
|
||||
rows, err := db.Query(query, characterID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
update := NewUpdate()
|
||||
var completedDate int64
|
||||
|
||||
err := rows.Scan(&update.ID, &completedDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan update: %w", err)
|
||||
}
|
||||
|
||||
if completedDate > 0 {
|
||||
update.CompletedDate = time.Unix(completedDate, 0)
|
||||
}
|
||||
|
||||
// Load update items for this achievement
|
||||
if err := loadPlayerAchievementUpdateItems(db, characterID, update); err != nil {
|
||||
return fmt.Errorf("failed to load update items: %w", err)
|
||||
}
|
||||
|
||||
updateList.AddUpdate(update)
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
// loadPlayerAchievementUpdateItems loads update items for a specific achievement update
|
||||
func loadPlayerAchievementUpdateItems(db *database.Database, characterID uint32, update *Update) error {
|
||||
query := `SELECT achievement_id, items FROM character_achievements_items WHERE char_id = ? AND achievement_id = ?`
|
||||
|
||||
rows, err := db.Query(query, characterID, update.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute query: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var updateItem UpdateItem
|
||||
err := rows.Scan(&updateItem.AchievementID, &updateItem.ItemUpdate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan update item: %w", err)
|
||||
}
|
||||
update.AddUpdateItem(updateItem)
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
// SavePlayerAchievementUpdate saves a player's achievement progress to database
|
||||
func SavePlayerAchievementUpdate(db *database.Database, characterID uint32, update *Update) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
var completedDate int64
|
||||
if !update.CompletedDate.IsZero() {
|
||||
completedDate = update.CompletedDate.Unix()
|
||||
}
|
||||
|
||||
// Insert or update achievement progress
|
||||
query := `INSERT OR REPLACE INTO character_achievements (char_id, achievement_id, completed_date)
|
||||
VALUES (?, ?, ?)`
|
||||
|
||||
_, err = tx.Exec(query, characterID, update.ID, completedDate)
|
||||
if 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 = ?`
|
||||
_, err = tx.Exec(deleteQuery, characterID, update.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete existing update items: %w", err)
|
||||
}
|
||||
|
||||
// Insert new update items
|
||||
if len(update.UpdateItems) > 0 {
|
||||
insertQuery := `INSERT INTO character_achievements_items (char_id, achievement_id, items) VALUES (?, ?, ?)`
|
||||
for _, item := range update.UpdateItems {
|
||||
_, err = tx.Exec(insertQuery, characterID, item.AchievementID, item.ItemUpdate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert update item: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
return tx.Commit()
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ package world
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/achievements"
|
||||
"eq2emu/internal/database"
|
||||
"eq2emu/internal/packets"
|
||||
)
|
||||
|
||||
// AchievementManager manages achievements for the world server
|
||||
@ -117,10 +119,16 @@ func (am *AchievementManager) UpdateProgress(characterID int32, achievementID ui
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,6 +279,11 @@ func (am *AchievementManager) RemovePlayerManager(characterID int32) {
|
||||
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()
|
||||
@ -286,6 +299,371 @@ func (am *AchievementManager) GetStatistics() map[string]any {
|
||||
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...")
|
||||
|
Loading…
x
Reference in New Issue
Block a user