package heroic_ops import ( "fmt" "eq2emu/internal/database" ) // NewHeroicOPStarter creates a new heroic opportunity starter func NewHeroicOPStarter(db *database.Database) *HeroicOPStarter { return &HeroicOPStarter{ db: db, isNew: true, Abilities: [6]int16{}, SaveNeeded: false, } } // LoadHeroicOPStarter loads a heroic opportunity starter by ID func LoadHeroicOPStarter(db *database.Database, id int32) (*HeroicOPStarter, error) { starter := &HeroicOPStarter{ db: db, isNew: false, } query := "SELECT id, start_class, starter_icon, ability1, ability2, ability3, ability4, ability5, ability6, name, description FROM heroic_op_starters WHERE id = ?" err := db.QueryRow(query, id).Scan( &starter.ID, &starter.StartClass, &starter.StarterIcon, &starter.Abilities[0], &starter.Abilities[1], &starter.Abilities[2], &starter.Abilities[3], &starter.Abilities[4], &starter.Abilities[5], &starter.Name, &starter.Description, ) if err != nil { return nil, fmt.Errorf("failed to load heroic op starter: %w", err) } starter.SaveNeeded = false return starter, nil } // GetID returns the starter ID func (hos *HeroicOPStarter) GetID() int32 { hos.mu.RLock() defer hos.mu.RUnlock() return hos.ID } // Save persists the starter to the database func (hos *HeroicOPStarter) Save() error { hos.mu.Lock() defer hos.mu.Unlock() if !hos.SaveNeeded { return nil } if hos.isNew { // Insert new record query := `INSERT INTO heroic_op_starters (id, start_class, starter_icon, ability1, ability2, ability3, ability4, ability5, ability6, name, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` _, err := hos.db.Exec(query, hos.ID, hos.StartClass, hos.StarterIcon, hos.Abilities[0], hos.Abilities[1], hos.Abilities[2], hos.Abilities[3], hos.Abilities[4], hos.Abilities[5], hos.Name, hos.Description, ) if err != nil { return fmt.Errorf("failed to insert heroic op starter: %w", err) } hos.isNew = false } else { // Update existing record query := `UPDATE heroic_op_starters SET start_class = ?, starter_icon = ?, ability1 = ?, ability2 = ?, ability3 = ?, ability4 = ?, ability5 = ?, ability6 = ?, name = ?, description = ? WHERE id = ?` _, err := hos.db.Exec(query, hos.StartClass, hos.StarterIcon, hos.Abilities[0], hos.Abilities[1], hos.Abilities[2], hos.Abilities[3], hos.Abilities[4], hos.Abilities[5], hos.Name, hos.Description, hos.ID, ) if err != nil { return fmt.Errorf("failed to update heroic op starter: %w", err) } } hos.SaveNeeded = false return nil } // Delete removes the starter from the database func (hos *HeroicOPStarter) Delete() error { hos.mu.Lock() defer hos.mu.Unlock() if hos.isNew { return nil // Nothing to delete } query := "DELETE FROM heroic_op_starters WHERE id = ?" _, err := hos.db.Exec(query, hos.ID) if err != nil { return fmt.Errorf("failed to delete heroic op starter: %w", err) } return nil } // Reload refreshes the starter data from the database func (hos *HeroicOPStarter) Reload() error { hos.mu.Lock() defer hos.mu.Unlock() if hos.isNew { return fmt.Errorf("cannot reload unsaved heroic op starter") } query := "SELECT start_class, starter_icon, ability1, ability2, ability3, ability4, ability5, ability6, name, description FROM heroic_op_starters WHERE id = ?" err := hos.db.QueryRow(query, hos.ID).Scan( &hos.StartClass, &hos.StarterIcon, &hos.Abilities[0], &hos.Abilities[1], &hos.Abilities[2], &hos.Abilities[3], &hos.Abilities[4], &hos.Abilities[5], &hos.Name, &hos.Description, ) if err != nil { return fmt.Errorf("failed to reload heroic op starter: %w", err) } hos.SaveNeeded = false return nil } // Copy creates a deep copy of the starter func (hos *HeroicOPStarter) Copy() *HeroicOPStarter { hos.mu.RLock() defer hos.mu.RUnlock() newStarter := &HeroicOPStarter{ ID: hos.ID, StartClass: hos.StartClass, StarterIcon: hos.StarterIcon, Abilities: hos.Abilities, // Arrays are copied by value Name: hos.Name, Description: hos.Description, db: hos.db, isNew: true, // Copy is always new unless explicitly saved SaveNeeded: false, } return newStarter } // GetAbility returns the ability icon at the specified position func (hos *HeroicOPStarter) GetAbility(position int) int16 { hos.mu.RLock() defer hos.mu.RUnlock() if position < 0 || position >= MaxAbilities { return AbilityIconNone } return hos.Abilities[position] } // SetAbility sets the ability icon at the specified position func (hos *HeroicOPStarter) SetAbility(position int, abilityIcon int16) bool { hos.mu.Lock() defer hos.mu.Unlock() if position < 0 || position >= MaxAbilities { return false } hos.Abilities[position] = abilityIcon hos.SaveNeeded = true return true } // IsComplete checks if the starter chain is complete (has completion marker) func (hos *HeroicOPStarter) IsComplete(position int) bool { hos.mu.RLock() defer hos.mu.RUnlock() if position < 0 || position >= MaxAbilities { return false } return hos.Abilities[position] == AbilityIconAny } // CanInitiate checks if the specified class can initiate this starter func (hos *HeroicOPStarter) CanInitiate(playerClass int8) bool { hos.mu.RLock() defer hos.mu.RUnlock() return hos.StartClass == ClassAny || hos.StartClass == playerClass } // MatchesAbility checks if the given ability matches the current position func (hos *HeroicOPStarter) MatchesAbility(position int, abilityIcon int16) bool { hos.mu.RLock() defer hos.mu.RUnlock() if position < 0 || position >= MaxAbilities { return false } requiredAbility := hos.Abilities[position] // Wildcard matches any ability if requiredAbility == AbilityIconAny { return true } // Exact match required return requiredAbility == abilityIcon } // Validate checks if the starter is properly configured func (hos *HeroicOPStarter) Validate() error { hos.mu.RLock() defer hos.mu.RUnlock() if hos.ID <= 0 { return fmt.Errorf("invalid starter ID: %d", hos.ID) } if hos.StarterIcon <= 0 { return fmt.Errorf("invalid starter icon: %d", hos.StarterIcon) } // Check for at least one non-zero ability hasAbility := false for _, ability := range hos.Abilities { if ability != AbilityIconNone { hasAbility = true break } } if !hasAbility { return fmt.Errorf("starter must have at least one ability") } return nil } // SetID sets the starter ID func (hos *HeroicOPStarter) SetID(id int32) { hos.mu.Lock() defer hos.mu.Unlock() hos.ID = id hos.SaveNeeded = true } // SetStartClass sets the start class func (hos *HeroicOPStarter) SetStartClass(startClass int8) { hos.mu.Lock() defer hos.mu.Unlock() hos.StartClass = startClass hos.SaveNeeded = true } // SetStarterIcon sets the starter icon func (hos *HeroicOPStarter) SetStarterIcon(icon int16) { hos.mu.Lock() defer hos.mu.Unlock() hos.StarterIcon = icon hos.SaveNeeded = true } // SetName sets the starter name func (hos *HeroicOPStarter) SetName(name string) { hos.mu.Lock() defer hos.mu.Unlock() hos.Name = name hos.SaveNeeded = true } // SetDescription sets the starter description func (hos *HeroicOPStarter) SetDescription(description string) { hos.mu.Lock() defer hos.mu.Unlock() hos.Description = description hos.SaveNeeded = true }