405 lines
9.5 KiB
Go
405 lines
9.5 KiB
Go
package heroic_ops
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// NewHeroicOPWheel creates a new heroic opportunity wheel
|
|
func NewHeroicOPWheel(db *database.Database) *HeroicOPWheel {
|
|
return &HeroicOPWheel{
|
|
db: db,
|
|
isNew: true,
|
|
Abilities: [6]int16{},
|
|
Chance: 1.0,
|
|
SaveNeeded: false,
|
|
}
|
|
}
|
|
|
|
// LoadHeroicOPWheel loads a heroic opportunity wheel by ID
|
|
func LoadHeroicOPWheel(db *database.Database, id int32) (*HeroicOPWheel, error) {
|
|
wheel := &HeroicOPWheel{
|
|
db: db,
|
|
isNew: false,
|
|
}
|
|
|
|
query := `SELECT id, starter_link_id, chain_order, shift_icon, chance, ability1, ability2, ability3, ability4, ability5, ability6,
|
|
spell_id, name, description, required_players FROM heroic_op_wheels WHERE id = ?`
|
|
err := db.QueryRow(query, id).Scan(
|
|
&wheel.ID,
|
|
&wheel.StarterLinkID,
|
|
&wheel.Order,
|
|
&wheel.ShiftIcon,
|
|
&wheel.Chance,
|
|
&wheel.Abilities[0],
|
|
&wheel.Abilities[1],
|
|
&wheel.Abilities[2],
|
|
&wheel.Abilities[3],
|
|
&wheel.Abilities[4],
|
|
&wheel.Abilities[5],
|
|
&wheel.SpellID,
|
|
&wheel.Name,
|
|
&wheel.Description,
|
|
&wheel.RequiredPlayers,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load heroic op wheel: %w", err)
|
|
}
|
|
|
|
wheel.SaveNeeded = false
|
|
return wheel, nil
|
|
}
|
|
|
|
// GetID returns the wheel ID
|
|
func (how *HeroicOPWheel) GetID() int32 {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
return how.ID
|
|
}
|
|
|
|
// Save persists the wheel to the database
|
|
func (how *HeroicOPWheel) Save() error {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
|
|
if !how.SaveNeeded {
|
|
return nil
|
|
}
|
|
|
|
if how.isNew {
|
|
// Insert new record
|
|
query := `INSERT INTO heroic_op_wheels (id, starter_link_id, chain_order, shift_icon, chance, ability1, ability2, ability3, ability4, ability5, ability6, spell_id, name, description, required_players)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
_, err := how.db.Exec(query,
|
|
how.ID,
|
|
how.StarterLinkID,
|
|
how.Order,
|
|
how.ShiftIcon,
|
|
how.Chance,
|
|
how.Abilities[0],
|
|
how.Abilities[1],
|
|
how.Abilities[2],
|
|
how.Abilities[3],
|
|
how.Abilities[4],
|
|
how.Abilities[5],
|
|
how.SpellID,
|
|
how.Name,
|
|
how.Description,
|
|
how.RequiredPlayers,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert heroic op wheel: %w", err)
|
|
}
|
|
how.isNew = false
|
|
} else {
|
|
// Update existing record
|
|
query := `UPDATE heroic_op_wheels SET starter_link_id = ?, chain_order = ?, shift_icon = ?, chance = ?, ability1 = ?, ability2 = ?, ability3 = ?, ability4 = ?, ability5 = ?, ability6 = ?, spell_id = ?, name = ?, description = ?, required_players = ? WHERE id = ?`
|
|
_, err := how.db.Exec(query,
|
|
how.StarterLinkID,
|
|
how.Order,
|
|
how.ShiftIcon,
|
|
how.Chance,
|
|
how.Abilities[0],
|
|
how.Abilities[1],
|
|
how.Abilities[2],
|
|
how.Abilities[3],
|
|
how.Abilities[4],
|
|
how.Abilities[5],
|
|
how.SpellID,
|
|
how.Name,
|
|
how.Description,
|
|
how.RequiredPlayers,
|
|
how.ID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update heroic op wheel: %w", err)
|
|
}
|
|
}
|
|
|
|
how.SaveNeeded = false
|
|
return nil
|
|
}
|
|
|
|
// Delete removes the wheel from the database
|
|
func (how *HeroicOPWheel) Delete() error {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
|
|
if how.isNew {
|
|
return nil // Nothing to delete
|
|
}
|
|
|
|
query := "DELETE FROM heroic_op_wheels WHERE id = ?"
|
|
_, err := how.db.Exec(query, how.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete heroic op wheel: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reload refreshes the wheel data from the database
|
|
func (how *HeroicOPWheel) Reload() error {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
|
|
if how.isNew {
|
|
return fmt.Errorf("cannot reload unsaved heroic op wheel")
|
|
}
|
|
|
|
query := `SELECT starter_link_id, chain_order, shift_icon, chance, ability1, ability2, ability3, ability4, ability5, ability6,
|
|
spell_id, name, description, required_players FROM heroic_op_wheels WHERE id = ?`
|
|
err := how.db.QueryRow(query, how.ID).Scan(
|
|
&how.StarterLinkID,
|
|
&how.Order,
|
|
&how.ShiftIcon,
|
|
&how.Chance,
|
|
&how.Abilities[0],
|
|
&how.Abilities[1],
|
|
&how.Abilities[2],
|
|
&how.Abilities[3],
|
|
&how.Abilities[4],
|
|
&how.Abilities[5],
|
|
&how.SpellID,
|
|
&how.Name,
|
|
&how.Description,
|
|
&how.RequiredPlayers,
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to reload heroic op wheel: %w", err)
|
|
}
|
|
|
|
how.SaveNeeded = false
|
|
return nil
|
|
}
|
|
|
|
// Copy creates a deep copy of the wheel
|
|
func (how *HeroicOPWheel) Copy() *HeroicOPWheel {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
newWheel := &HeroicOPWheel{
|
|
ID: how.ID,
|
|
StarterLinkID: how.StarterLinkID,
|
|
Order: how.Order,
|
|
ShiftIcon: how.ShiftIcon,
|
|
Chance: how.Chance,
|
|
Abilities: how.Abilities, // Arrays are copied by value
|
|
SpellID: how.SpellID,
|
|
Name: how.Name,
|
|
Description: how.Description,
|
|
RequiredPlayers: how.RequiredPlayers,
|
|
db: how.db,
|
|
isNew: true, // Copy is always new unless explicitly saved
|
|
SaveNeeded: false,
|
|
}
|
|
|
|
return newWheel
|
|
}
|
|
|
|
// GetAbility returns the ability icon at the specified position
|
|
func (how *HeroicOPWheel) GetAbility(position int) int16 {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
if position < 0 || position >= MaxAbilities {
|
|
return AbilityIconNone
|
|
}
|
|
|
|
return how.Abilities[position]
|
|
}
|
|
|
|
// SetAbility sets the ability icon at the specified position
|
|
func (how *HeroicOPWheel) SetAbility(position int, abilityIcon int16) bool {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
|
|
if position < 0 || position >= MaxAbilities {
|
|
return false
|
|
}
|
|
|
|
how.Abilities[position] = abilityIcon
|
|
how.SaveNeeded = true
|
|
return true
|
|
}
|
|
|
|
// IsOrdered checks if this wheel requires ordered completion
|
|
func (how *HeroicOPWheel) IsOrdered() bool {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
return how.Order >= WheelOrderOrdered
|
|
}
|
|
|
|
// HasShift checks if this wheel has a shift ability
|
|
func (how *HeroicOPWheel) HasShift() bool {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
return how.ShiftIcon > 0
|
|
}
|
|
|
|
// CanShift checks if shifting is possible with the given ability
|
|
func (how *HeroicOPWheel) CanShift(abilityIcon int16) bool {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
return how.ShiftIcon > 0 && how.ShiftIcon == abilityIcon
|
|
}
|
|
|
|
// GetNextRequiredAbility returns the next required ability for ordered wheels
|
|
func (how *HeroicOPWheel) GetNextRequiredAbility(countered [6]int8) int16 {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
if !how.IsOrdered() {
|
|
return AbilityIconNone // Any uncompleted ability works for unordered
|
|
}
|
|
|
|
// Find first uncompleted ability in order
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if countered[i] == 0 && how.Abilities[i] != AbilityIconNone {
|
|
return how.Abilities[i]
|
|
}
|
|
}
|
|
|
|
return AbilityIconNone
|
|
}
|
|
|
|
// CanUseAbility checks if an ability can be used on this wheel
|
|
func (how *HeroicOPWheel) CanUseAbility(abilityIcon int16, countered [6]int8) bool {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
// Check if this is a shift attempt
|
|
if how.CanShift(abilityIcon) {
|
|
return true
|
|
}
|
|
|
|
if how.IsOrdered() {
|
|
// For ordered wheels, only the next required ability can be used
|
|
nextRequired := how.GetNextRequiredAbility(countered)
|
|
return nextRequired == abilityIcon
|
|
} else {
|
|
// For unordered wheels, any uncompleted matching ability can be used
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if countered[i] == 0 && how.Abilities[i] == abilityIcon {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Validate checks if the wheel is properly configured
|
|
func (how *HeroicOPWheel) Validate() error {
|
|
how.mu.RLock()
|
|
defer how.mu.RUnlock()
|
|
|
|
if how.ID <= 0 {
|
|
return fmt.Errorf("invalid wheel ID: %d", how.ID)
|
|
}
|
|
|
|
if how.StarterLinkID <= 0 {
|
|
return fmt.Errorf("invalid starter link ID: %d", how.StarterLinkID)
|
|
}
|
|
|
|
if how.Chance < MinChance || how.Chance > MaxChance {
|
|
return fmt.Errorf("invalid chance: %f (must be %f-%f)", how.Chance, MinChance, MaxChance)
|
|
}
|
|
|
|
if how.SpellID <= 0 {
|
|
return fmt.Errorf("invalid spell ID: %d", how.SpellID)
|
|
}
|
|
|
|
// Check for at least one non-zero ability
|
|
hasAbility := false
|
|
for _, ability := range how.Abilities {
|
|
if ability != AbilityIconNone {
|
|
hasAbility = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasAbility {
|
|
return fmt.Errorf("wheel must have at least one ability")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetID sets the wheel ID
|
|
func (how *HeroicOPWheel) SetID(id int32) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.ID = id
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetStarterLinkID sets the starter link ID
|
|
func (how *HeroicOPWheel) SetStarterLinkID(id int32) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.StarterLinkID = id
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetOrder sets the wheel order
|
|
func (how *HeroicOPWheel) SetOrder(order int8) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.Order = order
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetShiftIcon sets the shift icon
|
|
func (how *HeroicOPWheel) SetShiftIcon(icon int16) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.ShiftIcon = icon
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetChance sets the wheel chance
|
|
func (how *HeroicOPWheel) SetChance(chance float32) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.Chance = chance
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetSpellID sets the spell ID
|
|
func (how *HeroicOPWheel) SetSpellID(id int32) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.SpellID = id
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetName sets the wheel name
|
|
func (how *HeroicOPWheel) SetName(name string) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.Name = name
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetDescription sets the wheel description
|
|
func (how *HeroicOPWheel) SetDescription(description string) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.Description = description
|
|
how.SaveNeeded = true
|
|
}
|
|
|
|
// SetRequiredPlayers sets the required players
|
|
func (how *HeroicOPWheel) SetRequiredPlayers(required int8) {
|
|
how.mu.Lock()
|
|
defer how.mu.Unlock()
|
|
how.RequiredPlayers = required
|
|
how.SaveNeeded = true
|
|
} |