438 lines
12 KiB
Go
438 lines
12 KiB
Go
package achievements
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// Achievement represents a complete achievement with database operations
|
|
type Achievement struct {
|
|
// Database fields
|
|
ID uint32 `json:"id" db:"id"`
|
|
AchievementID uint32 `json:"achievement_id" db:"achievement_id"`
|
|
Title string `json:"title" db:"title"`
|
|
UncompletedText string `json:"uncompleted_text" db:"uncompleted_text"`
|
|
CompletedText string `json:"completed_text" db:"completed_text"`
|
|
Category string `json:"category" db:"category"`
|
|
Expansion string `json:"expansion" db:"expansion"`
|
|
Icon uint16 `json:"icon" db:"icon"`
|
|
PointValue uint32 `json:"point_value" db:"point_value"`
|
|
QtyRequired uint32 `json:"qty_req" db:"qty_req"`
|
|
Hide bool `json:"hide_achievement" db:"hide_achievement"`
|
|
Unknown3A uint32 `json:"unknown3a" db:"unknown3a"`
|
|
Unknown3B uint32 `json:"unknown3b" db:"unknown3b"`
|
|
MaxVersion uint32 `json:"max_version" db:"max_version"`
|
|
|
|
// Associated data
|
|
Requirements []Requirement `json:"requirements"`
|
|
Rewards []Reward `json:"rewards"`
|
|
|
|
// Database connection
|
|
db *database.Database
|
|
isNew bool
|
|
}
|
|
|
|
// New creates a new achievement with database connection
|
|
func New(db *database.Database) *Achievement {
|
|
return &Achievement{
|
|
Requirements: make([]Requirement, 0),
|
|
Rewards: make([]Reward, 0),
|
|
db: db,
|
|
isNew: true,
|
|
}
|
|
}
|
|
|
|
// Load loads an achievement by achievement_id
|
|
func Load(db *database.Database, achievementID uint32) (*Achievement, error) {
|
|
achievement := &Achievement{
|
|
db: db,
|
|
isNew: false,
|
|
}
|
|
|
|
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 WHERE achievement_id = ?`
|
|
|
|
var hideInt int
|
|
err := db.QueryRow(query, achievementID).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 {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("achievement not found: %d", achievementID)
|
|
}
|
|
return nil, fmt.Errorf("failed to load achievement: %w", err)
|
|
}
|
|
|
|
achievement.Hide = hideInt != 0
|
|
|
|
// Load requirements and rewards
|
|
if err := achievement.loadRequirements(); err != nil {
|
|
return nil, fmt.Errorf("failed to load requirements: %w", err)
|
|
}
|
|
|
|
if err := achievement.loadRewards(); err != nil {
|
|
return nil, fmt.Errorf("failed to load rewards: %w", err)
|
|
}
|
|
|
|
return achievement, nil
|
|
}
|
|
|
|
// LoadAll loads all achievements from database
|
|
func LoadAll(db *database.Database) ([]*Achievement, 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 nil, fmt.Errorf("failed to query achievements: %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 nil, fmt.Errorf("failed to scan achievement: %w", err)
|
|
}
|
|
|
|
achievement.Hide = hideInt != 0
|
|
|
|
// Load requirements and rewards
|
|
if err := achievement.loadRequirements(); err != nil {
|
|
return nil, fmt.Errorf("failed to load requirements for achievement %d: %w", achievement.AchievementID, err)
|
|
}
|
|
|
|
if err := achievement.loadRewards(); err != nil {
|
|
return nil, fmt.Errorf("failed to load rewards for achievement %d: %w", achievement.AchievementID, err)
|
|
}
|
|
|
|
achievements = append(achievements, achievement)
|
|
}
|
|
|
|
return achievements, rows.Err()
|
|
}
|
|
|
|
// Save saves the achievement to the database (insert if new, update if existing)
|
|
func (a *Achievement) Save() error {
|
|
if a.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
tx, err := a.db.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if a.isNew {
|
|
err = a.insert(tx)
|
|
} else {
|
|
err = a.update(tx)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save requirements and rewards
|
|
if err := a.saveRequirements(tx); err != nil {
|
|
return fmt.Errorf("failed to save requirements: %w", err)
|
|
}
|
|
|
|
if err := a.saveRewards(tx); err != nil {
|
|
return fmt.Errorf("failed to save rewards: %w", err)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// Delete removes the achievement and all associated data from the database
|
|
func (a *Achievement) Delete() error {
|
|
if a.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
if a.isNew {
|
|
return fmt.Errorf("cannot delete unsaved achievement")
|
|
}
|
|
|
|
tx, err := a.db.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete requirements (foreign key should cascade, but be explicit)
|
|
_, err = tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", a.AchievementID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete requirements: %w", err)
|
|
}
|
|
|
|
// Delete rewards
|
|
_, err = tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", a.AchievementID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete rewards: %w", err)
|
|
}
|
|
|
|
// Delete achievement
|
|
_, err = tx.Exec("DELETE FROM achievements WHERE achievement_id = ?", a.AchievementID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete achievement: %w", err)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// Reload reloads the achievement from the database
|
|
func (a *Achievement) Reload() error {
|
|
if a.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
if a.isNew {
|
|
return fmt.Errorf("cannot reload unsaved achievement")
|
|
}
|
|
|
|
reloaded, err := Load(a.db, a.AchievementID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy all fields from reloaded achievement
|
|
*a = *reloaded
|
|
return nil
|
|
}
|
|
|
|
// AddRequirement adds a requirement to this achievement
|
|
func (a *Achievement) AddRequirement(name string, qtyRequired uint32) {
|
|
req := Requirement{
|
|
AchievementID: a.AchievementID,
|
|
Name: name,
|
|
QtyRequired: qtyRequired,
|
|
}
|
|
a.Requirements = append(a.Requirements, req)
|
|
}
|
|
|
|
// AddReward adds a reward to this achievement
|
|
func (a *Achievement) AddReward(reward string) {
|
|
r := Reward{
|
|
AchievementID: a.AchievementID,
|
|
Reward: reward,
|
|
}
|
|
a.Rewards = append(a.Rewards, r)
|
|
}
|
|
|
|
// IsNew returns true if this is a new (unsaved) achievement
|
|
func (a *Achievement) IsNew() bool {
|
|
return a.isNew
|
|
}
|
|
|
|
// GetID returns the achievement ID (implements common.Identifiable interface)
|
|
func (a *Achievement) GetID() uint32 {
|
|
return a.AchievementID
|
|
}
|
|
|
|
// Clone creates a deep copy of the achievement
|
|
func (a *Achievement) Clone() *Achievement {
|
|
clone := &Achievement{
|
|
ID: a.ID,
|
|
AchievementID: a.AchievementID,
|
|
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,
|
|
MaxVersion: a.MaxVersion,
|
|
Requirements: make([]Requirement, len(a.Requirements)),
|
|
Rewards: make([]Reward, len(a.Rewards)),
|
|
db: a.db,
|
|
isNew: false,
|
|
}
|
|
|
|
copy(clone.Requirements, a.Requirements)
|
|
copy(clone.Rewards, a.Rewards)
|
|
return clone
|
|
}
|
|
|
|
// Private helper methods
|
|
|
|
func (a *Achievement) insert(tx *sql.Tx) error {
|
|
var query string
|
|
if a.db.GetType() == database.MySQL {
|
|
query = `INSERT INTO achievements
|
|
(achievement_id, title, uncompleted_text, completed_text, category,
|
|
expansion, icon, point_value, qty_req, hide_achievement,
|
|
unknown3a, unknown3b, max_version)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
} else {
|
|
query = `INSERT INTO achievements
|
|
(achievement_id, title, uncompleted_text, completed_text, category,
|
|
expansion, icon, point_value, qty_req, hide_achievement,
|
|
unknown3a, unknown3b, max_version)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
}
|
|
|
|
result, err := tx.Exec(query,
|
|
a.AchievementID, a.Title, a.UncompletedText, a.CompletedText,
|
|
a.Category, a.Expansion, a.Icon, a.PointValue, a.QtyRequired,
|
|
a.Hide, a.Unknown3A, a.Unknown3B, a.MaxVersion)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert achievement: %w", err)
|
|
}
|
|
|
|
// Get the auto-generated ID
|
|
if a.db.GetType() == database.MySQL {
|
|
id, err := result.LastInsertId()
|
|
if err == nil {
|
|
a.ID = uint32(id)
|
|
}
|
|
}
|
|
|
|
a.isNew = false
|
|
return nil
|
|
}
|
|
|
|
func (a *Achievement) update(tx *sql.Tx) error {
|
|
query := `UPDATE achievements SET
|
|
title = ?, uncompleted_text = ?, completed_text = ?, category = ?,
|
|
expansion = ?, icon = ?, point_value = ?, qty_req = ?,
|
|
hide_achievement = ?, unknown3a = ?, unknown3b = ?, max_version = ?
|
|
WHERE achievement_id = ?`
|
|
|
|
_, err := tx.Exec(query,
|
|
a.Title, a.UncompletedText, a.CompletedText, a.Category,
|
|
a.Expansion, a.Icon, a.PointValue, a.QtyRequired,
|
|
a.Hide, a.Unknown3A, a.Unknown3B, a.MaxVersion,
|
|
a.AchievementID)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update achievement: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *Achievement) loadRequirements() error {
|
|
query := `SELECT achievement_id, name, qty_req
|
|
FROM achievements_requirements
|
|
WHERE achievement_id = ?`
|
|
|
|
rows, err := a.db.Query(query, a.AchievementID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
a.Requirements = make([]Requirement, 0)
|
|
for rows.Next() {
|
|
var req Requirement
|
|
err := rows.Scan(&req.AchievementID, &req.Name, &req.QtyRequired)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Requirements = append(a.Requirements, req)
|
|
}
|
|
|
|
return rows.Err()
|
|
}
|
|
|
|
func (a *Achievement) loadRewards() error {
|
|
query := `SELECT achievement_id, reward
|
|
FROM achievements_rewards
|
|
WHERE achievement_id = ?`
|
|
|
|
rows, err := a.db.Query(query, a.AchievementID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
a.Rewards = make([]Reward, 0)
|
|
for rows.Next() {
|
|
var reward Reward
|
|
err := rows.Scan(&reward.AchievementID, &reward.Reward)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Rewards = append(a.Rewards, reward)
|
|
}
|
|
|
|
return rows.Err()
|
|
}
|
|
|
|
func (a *Achievement) saveRequirements(tx *sql.Tx) error {
|
|
// Delete existing requirements
|
|
_, err := tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", a.AchievementID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert new requirements
|
|
if len(a.Requirements) > 0 {
|
|
query := `INSERT INTO achievements_requirements (achievement_id, name, qty_req)
|
|
VALUES (?, ?, ?)`
|
|
for _, req := range a.Requirements {
|
|
_, err = tx.Exec(query, a.AchievementID, req.Name, req.QtyRequired)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *Achievement) saveRewards(tx *sql.Tx) error {
|
|
// Delete existing rewards
|
|
_, err := tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", a.AchievementID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert new rewards
|
|
if len(a.Rewards) > 0 {
|
|
query := `INSERT INTO achievements_rewards (achievement_id, reward)
|
|
VALUES (?, ?)`
|
|
for _, reward := range a.Rewards {
|
|
_, err = tx.Exec(query, a.AchievementID, reward.Reward)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|