eq2go/internal/heroic_ops/heroic_op_wheel.go
2025-08-08 14:31:44 -05:00

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
}