704 lines
18 KiB
Go
704 lines
18 KiB
Go
package heroic_ops
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// NewHeroicOP creates a new heroic opportunity instance
|
|
func NewHeroicOP(db *database.Database, instanceID int64, encounterID int32) *HeroicOP {
|
|
return &HeroicOP{
|
|
ID: instanceID,
|
|
EncounterID: encounterID,
|
|
State: HOStateInactive,
|
|
StartTime: time.Now(),
|
|
Participants: make(map[int32]bool),
|
|
CurrentStarters: make([]int32, 0),
|
|
TotalTime: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
|
|
TimeRemaining: DefaultWheelTimerSeconds * 1000,
|
|
db: db,
|
|
isNew: true,
|
|
SaveNeeded: false,
|
|
}
|
|
}
|
|
|
|
// LoadHeroicOP loads a heroic opportunity instance by ID
|
|
func LoadHeroicOP(db *database.Database, instanceID int64) (*HeroicOP, error) {
|
|
ho := &HeroicOP{
|
|
db: db,
|
|
isNew: false,
|
|
}
|
|
|
|
// Load basic HO data
|
|
query := `SELECT id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time, time_remaining, total_time,
|
|
complete, shift_used, starter_progress, completed_by, spell_name, spell_description,
|
|
countered1, countered2, countered3, countered4, countered5, countered6
|
|
FROM heroic_op_instances WHERE id = ?`
|
|
|
|
var startTimeStr, wheelStartTimeStr string
|
|
err := db.QueryRow(query, instanceID).Scan(
|
|
&ho.ID,
|
|
&ho.EncounterID,
|
|
&ho.StarterID,
|
|
&ho.WheelID,
|
|
&ho.State,
|
|
&startTimeStr,
|
|
&wheelStartTimeStr,
|
|
&ho.TimeRemaining,
|
|
&ho.TotalTime,
|
|
&ho.Complete,
|
|
&ho.ShiftUsed,
|
|
&ho.StarterProgress,
|
|
&ho.CompletedBy,
|
|
&ho.SpellName,
|
|
&ho.SpellDescription,
|
|
&ho.Countered[0],
|
|
&ho.Countered[1],
|
|
&ho.Countered[2],
|
|
&ho.Countered[3],
|
|
&ho.Countered[4],
|
|
&ho.Countered[5],
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load heroic op instance: %w", err)
|
|
}
|
|
|
|
// Parse time fields
|
|
if startTimeStr != "" {
|
|
ho.StartTime, err = time.Parse(time.RFC3339, startTimeStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse start time: %w", err)
|
|
}
|
|
}
|
|
|
|
if wheelStartTimeStr != "" {
|
|
ho.WheelStartTime, err = time.Parse(time.RFC3339, wheelStartTimeStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse wheel start time: %w", err)
|
|
}
|
|
}
|
|
|
|
// Load participants
|
|
ho.Participants = make(map[int32]bool)
|
|
participantQuery := "SELECT character_id FROM heroic_op_participants WHERE instance_id = ?"
|
|
rows, err := db.Query(participantQuery, instanceID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load participants: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var characterID int32
|
|
if err := rows.Scan(&characterID); err != nil {
|
|
return nil, fmt.Errorf("failed to scan participant: %w", err)
|
|
}
|
|
ho.Participants[characterID] = true
|
|
}
|
|
|
|
// Load current starters
|
|
ho.CurrentStarters = make([]int32, 0)
|
|
starterQuery := "SELECT starter_id FROM heroic_op_current_starters WHERE instance_id = ?"
|
|
starterRows, err := db.Query(starterQuery, instanceID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load current starters: %w", err)
|
|
}
|
|
defer starterRows.Close()
|
|
|
|
for starterRows.Next() {
|
|
var starterID int32
|
|
if err := starterRows.Scan(&starterID); err != nil {
|
|
return nil, fmt.Errorf("failed to scan current starter: %w", err)
|
|
}
|
|
ho.CurrentStarters = append(ho.CurrentStarters, starterID)
|
|
}
|
|
|
|
ho.SaveNeeded = false
|
|
return ho, nil
|
|
}
|
|
|
|
// GetID returns the instance ID
|
|
func (ho *HeroicOP) GetID() int64 {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
return ho.ID
|
|
}
|
|
|
|
// Save persists the heroic op instance to the database
|
|
func (ho *HeroicOP) Save() error {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if !ho.SaveNeeded {
|
|
return nil
|
|
}
|
|
|
|
if ho.isNew {
|
|
// Insert new record
|
|
query := `INSERT INTO heroic_op_instances (id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time, time_remaining, total_time,
|
|
complete, shift_used, starter_progress, completed_by, spell_name, spell_description,
|
|
countered1, countered2, countered3, countered4, countered5, countered6)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
_, err := ho.db.Exec(query,
|
|
ho.ID,
|
|
ho.EncounterID,
|
|
ho.StarterID,
|
|
ho.WheelID,
|
|
ho.State,
|
|
ho.StartTime.Format(time.RFC3339),
|
|
ho.WheelStartTime.Format(time.RFC3339),
|
|
ho.TimeRemaining,
|
|
ho.TotalTime,
|
|
ho.Complete,
|
|
ho.ShiftUsed,
|
|
ho.StarterProgress,
|
|
ho.CompletedBy,
|
|
ho.SpellName,
|
|
ho.SpellDescription,
|
|
ho.Countered[0],
|
|
ho.Countered[1],
|
|
ho.Countered[2],
|
|
ho.Countered[3],
|
|
ho.Countered[4],
|
|
ho.Countered[5],
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert heroic op instance: %w", err)
|
|
}
|
|
ho.isNew = false
|
|
} else {
|
|
// Update existing record
|
|
query := `UPDATE heroic_op_instances SET encounter_id = ?, starter_id = ?, wheel_id = ?, state = ?, start_time = ?, wheel_start_time = ?,
|
|
time_remaining = ?, total_time = ?, complete = ?, shift_used = ?, starter_progress = ?, completed_by = ?,
|
|
spell_name = ?, spell_description = ?, countered1 = ?, countered2 = ?, countered3 = ?, countered4 = ?,
|
|
countered5 = ?, countered6 = ? WHERE id = ?`
|
|
|
|
_, err := ho.db.Exec(query,
|
|
ho.EncounterID,
|
|
ho.StarterID,
|
|
ho.WheelID,
|
|
ho.State,
|
|
ho.StartTime.Format(time.RFC3339),
|
|
ho.WheelStartTime.Format(time.RFC3339),
|
|
ho.TimeRemaining,
|
|
ho.TotalTime,
|
|
ho.Complete,
|
|
ho.ShiftUsed,
|
|
ho.StarterProgress,
|
|
ho.CompletedBy,
|
|
ho.SpellName,
|
|
ho.SpellDescription,
|
|
ho.Countered[0],
|
|
ho.Countered[1],
|
|
ho.Countered[2],
|
|
ho.Countered[3],
|
|
ho.Countered[4],
|
|
ho.Countered[5],
|
|
ho.ID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update heroic op instance: %w", err)
|
|
}
|
|
}
|
|
|
|
// Save participants
|
|
if err := ho.saveParticipants(); err != nil {
|
|
return fmt.Errorf("failed to save participants: %w", err)
|
|
}
|
|
|
|
// Save current starters
|
|
if err := ho.saveCurrentStarters(); err != nil {
|
|
return fmt.Errorf("failed to save current starters: %w", err)
|
|
}
|
|
|
|
ho.SaveNeeded = false
|
|
return nil
|
|
}
|
|
|
|
// saveParticipants saves the participants to the database (internal helper)
|
|
func (ho *HeroicOP) saveParticipants() error {
|
|
// Delete existing participants
|
|
deleteQuery := "DELETE FROM heroic_op_participants WHERE instance_id = ?"
|
|
_, err := ho.db.Exec(deleteQuery, ho.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert current participants
|
|
for characterID := range ho.Participants {
|
|
insertQuery := "INSERT INTO heroic_op_participants (instance_id, character_id) VALUES (?, ?)"
|
|
_, err := ho.db.Exec(insertQuery, ho.ID, characterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// saveCurrentStarters saves the current starters to the database (internal helper)
|
|
func (ho *HeroicOP) saveCurrentStarters() error {
|
|
// Delete existing current starters
|
|
deleteQuery := "DELETE FROM heroic_op_current_starters WHERE instance_id = ?"
|
|
_, err := ho.db.Exec(deleteQuery, ho.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert current starters
|
|
for _, starterID := range ho.CurrentStarters {
|
|
insertQuery := "INSERT INTO heroic_op_current_starters (instance_id, starter_id) VALUES (?, ?)"
|
|
_, err := ho.db.Exec(insertQuery, ho.ID, starterID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes the heroic op instance from the database
|
|
func (ho *HeroicOP) Delete() error {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if ho.isNew {
|
|
return nil // Nothing to delete
|
|
}
|
|
|
|
// Delete related records first (foreign key constraints)
|
|
deleteParticipants := "DELETE FROM heroic_op_participants WHERE instance_id = ?"
|
|
_, err := ho.db.Exec(deleteParticipants, ho.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete participants: %w", err)
|
|
}
|
|
|
|
deleteStarters := "DELETE FROM heroic_op_current_starters WHERE instance_id = ?"
|
|
_, err = ho.db.Exec(deleteStarters, ho.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete current starters: %w", err)
|
|
}
|
|
|
|
// Delete main record
|
|
query := "DELETE FROM heroic_op_instances WHERE id = ?"
|
|
_, err = ho.db.Exec(query, ho.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete heroic op instance: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reload refreshes the heroic op instance data from the database
|
|
func (ho *HeroicOP) Reload() error {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if ho.isNew {
|
|
return fmt.Errorf("cannot reload unsaved heroic op instance")
|
|
}
|
|
|
|
// Reload from database
|
|
reloaded, err := LoadHeroicOP(ho.db, ho.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy all fields except database connection and isNew flag
|
|
ho.EncounterID = reloaded.EncounterID
|
|
ho.StarterID = reloaded.StarterID
|
|
ho.WheelID = reloaded.WheelID
|
|
ho.State = reloaded.State
|
|
ho.StartTime = reloaded.StartTime
|
|
ho.WheelStartTime = reloaded.WheelStartTime
|
|
ho.TimeRemaining = reloaded.TimeRemaining
|
|
ho.TotalTime = reloaded.TotalTime
|
|
ho.Complete = reloaded.Complete
|
|
ho.Countered = reloaded.Countered
|
|
ho.ShiftUsed = reloaded.ShiftUsed
|
|
ho.StarterProgress = reloaded.StarterProgress
|
|
ho.Participants = reloaded.Participants
|
|
ho.CurrentStarters = reloaded.CurrentStarters
|
|
ho.CompletedBy = reloaded.CompletedBy
|
|
ho.SpellName = reloaded.SpellName
|
|
ho.SpellDescription = reloaded.SpellDescription
|
|
|
|
ho.SaveNeeded = false
|
|
return nil
|
|
}
|
|
|
|
// AddParticipant adds a character to the HO participants
|
|
func (ho *HeroicOP) AddParticipant(characterID int32) {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
ho.Participants[characterID] = true
|
|
ho.SaveNeeded = true
|
|
}
|
|
|
|
// RemoveParticipant removes a character from the HO participants
|
|
func (ho *HeroicOP) RemoveParticipant(characterID int32) {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
delete(ho.Participants, characterID)
|
|
ho.SaveNeeded = true
|
|
}
|
|
|
|
// IsParticipant checks if a character is participating in this HO
|
|
func (ho *HeroicOP) IsParticipant(characterID int32) bool {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
return ho.Participants[characterID]
|
|
}
|
|
|
|
// GetParticipants returns a slice of participant character IDs
|
|
func (ho *HeroicOP) GetParticipants() []int32 {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
participants := make([]int32, 0, len(ho.Participants))
|
|
for characterID := range ho.Participants {
|
|
participants = append(participants, characterID)
|
|
}
|
|
|
|
return participants
|
|
}
|
|
|
|
// Copy creates a deep copy of the HO instance
|
|
func (ho *HeroicOP) Copy() *HeroicOP {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
newHO := &HeroicOP{
|
|
ID: ho.ID,
|
|
EncounterID: ho.EncounterID,
|
|
StarterID: ho.StarterID,
|
|
WheelID: ho.WheelID,
|
|
State: ho.State,
|
|
StartTime: ho.StartTime,
|
|
WheelStartTime: ho.WheelStartTime,
|
|
TimeRemaining: ho.TimeRemaining,
|
|
TotalTime: ho.TotalTime,
|
|
Complete: ho.Complete,
|
|
Countered: ho.Countered, // Arrays are copied by value
|
|
ShiftUsed: ho.ShiftUsed,
|
|
StarterProgress: ho.StarterProgress,
|
|
CompletedBy: ho.CompletedBy,
|
|
SpellName: ho.SpellName,
|
|
SpellDescription: ho.SpellDescription,
|
|
Participants: make(map[int32]bool, len(ho.Participants)),
|
|
CurrentStarters: make([]int32, len(ho.CurrentStarters)),
|
|
db: ho.db,
|
|
isNew: true, // Copy is always new unless explicitly saved
|
|
SaveNeeded: false,
|
|
}
|
|
|
|
// Deep copy participants map
|
|
for characterID, participating := range ho.Participants {
|
|
newHO.Participants[characterID] = participating
|
|
}
|
|
|
|
// Deep copy current starters slice
|
|
copy(newHO.CurrentStarters, ho.CurrentStarters)
|
|
|
|
return newHO
|
|
}
|
|
|
|
// StartStarterChain initiates the starter chain phase
|
|
func (ho *HeroicOP) StartStarterChain(availableStarters []int32) {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
ho.State = HOStateStarterChain
|
|
ho.CurrentStarters = make([]int32, len(availableStarters))
|
|
copy(ho.CurrentStarters, availableStarters)
|
|
ho.StarterProgress = 0
|
|
ho.StartTime = time.Now()
|
|
ho.SaveNeeded = true
|
|
}
|
|
|
|
// ProcessStarterAbility processes an ability during starter chain phase
|
|
func (ho *HeroicOP) ProcessStarterAbility(abilityIcon int16, masterList *MasterList) bool {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if ho.State != HOStateStarterChain {
|
|
return false
|
|
}
|
|
|
|
// Filter out starters that don't match this ability at current position
|
|
newStarters := make([]int32, 0)
|
|
|
|
for _, starterID := range ho.CurrentStarters {
|
|
starter := masterList.GetStarter(starterID)
|
|
if starter != nil && starter.MatchesAbility(int(ho.StarterProgress), abilityIcon) {
|
|
// Check if this completes the starter
|
|
if starter.IsComplete(int(ho.StarterProgress)) {
|
|
// Starter completed, transition to wheel phase
|
|
ho.StarterID = starterID
|
|
ho.SaveNeeded = true
|
|
return true
|
|
}
|
|
newStarters = append(newStarters, starterID)
|
|
}
|
|
}
|
|
|
|
ho.CurrentStarters = newStarters
|
|
ho.StarterProgress++
|
|
ho.SaveNeeded = true
|
|
|
|
// If no starters remain, HO fails
|
|
return len(ho.CurrentStarters) > 0
|
|
}
|
|
|
|
// StartWheelPhase initiates the wheel phase
|
|
func (ho *HeroicOP) StartWheelPhase(wheel *HeroicOPWheel, timerSeconds int32) {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
ho.State = HOStateWheelPhase
|
|
ho.WheelID = wheel.ID
|
|
ho.WheelStartTime = time.Now()
|
|
ho.TotalTime = timerSeconds * 1000 // Convert to milliseconds
|
|
ho.TimeRemaining = ho.TotalTime
|
|
ho.SpellName = wheel.Name
|
|
ho.SpellDescription = wheel.Description
|
|
|
|
// Clear countered array
|
|
for i := range ho.Countered {
|
|
ho.Countered[i] = 0
|
|
}
|
|
|
|
ho.SaveNeeded = true
|
|
}
|
|
|
|
// ProcessWheelAbility processes an ability during wheel phase
|
|
func (ho *HeroicOP) ProcessWheelAbility(abilityIcon int16, characterID int32, wheel *HeroicOPWheel) bool {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if ho.State != HOStateWheelPhase {
|
|
return false
|
|
}
|
|
|
|
// Check for shift attempt
|
|
if ho.ShiftUsed == ShiftNotUsed && wheel.CanShift(abilityIcon) {
|
|
// Allow shift only if no progress made (unordered) or at start (ordered)
|
|
canShift := false
|
|
if wheel.IsOrdered() {
|
|
// For ordered, can shift only if no abilities completed
|
|
canShift = true
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if ho.Countered[i] != 0 {
|
|
canShift = false
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// For unordered, can shift only if no abilities completed
|
|
canShift = true
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if ho.Countered[i] != 0 {
|
|
canShift = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if canShift {
|
|
ho.ShiftUsed = ShiftUsed
|
|
ho.SaveNeeded = true
|
|
return true // Caller should handle wheel shifting
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check if ability can be used
|
|
if !wheel.CanUseAbility(abilityIcon, ho.Countered) {
|
|
return false
|
|
}
|
|
|
|
// Find matching ability position and mark as countered
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if ho.Countered[i] == 0 && wheel.GetAbility(i) == abilityIcon {
|
|
ho.Countered[i] = 1
|
|
ho.AddParticipant(characterID)
|
|
ho.SaveNeeded = true
|
|
|
|
// Check if wheel is complete
|
|
complete := true
|
|
for j := 0; j < MaxAbilities; j++ {
|
|
if wheel.GetAbility(j) != AbilityIconNone && ho.Countered[j] == 0 {
|
|
complete = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if complete {
|
|
ho.Complete = HOComplete
|
|
ho.State = HOStateComplete
|
|
ho.CompletedBy = characterID
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// UpdateTimer updates the remaining time for the HO
|
|
func (ho *HeroicOP) UpdateTimer(deltaMS int32) bool {
|
|
ho.mu.Lock()
|
|
defer ho.mu.Unlock()
|
|
|
|
if ho.State != HOStateWheelPhase {
|
|
return true // Timer not active
|
|
}
|
|
|
|
ho.TimeRemaining -= deltaMS
|
|
|
|
if ho.TimeRemaining <= 0 {
|
|
ho.TimeRemaining = 0
|
|
ho.State = HOStateFailed
|
|
ho.SaveNeeded = true
|
|
return false // Timer expired
|
|
}
|
|
|
|
ho.SaveNeeded = true
|
|
return true
|
|
}
|
|
|
|
// IsComplete checks if the HO is successfully completed
|
|
func (ho *HeroicOP) IsComplete() bool {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
return ho.Complete == HOComplete && ho.State == HOStateComplete
|
|
}
|
|
|
|
// IsFailed checks if the HO has failed
|
|
func (ho *HeroicOP) IsFailed() bool {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
return ho.State == HOStateFailed
|
|
}
|
|
|
|
// IsActive checks if the HO is currently active (in progress)
|
|
func (ho *HeroicOP) IsActive() bool {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
return ho.State == HOStateStarterChain || ho.State == HOStateWheelPhase
|
|
}
|
|
|
|
// GetProgress returns the completion percentage (0.0 - 1.0)
|
|
func (ho *HeroicOP) GetProgress() float32 {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
if ho.State != HOStateWheelPhase {
|
|
return 0.0
|
|
}
|
|
|
|
completed := 0
|
|
total := 0
|
|
|
|
for i := 0; i < MaxAbilities; i++ {
|
|
if ho.Countered[i] != 0 {
|
|
completed++
|
|
}
|
|
if ho.Countered[i] != 0 || ho.Countered[i] == 0 { // All positions count
|
|
total++
|
|
}
|
|
}
|
|
|
|
if total == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
return float32(completed) / float32(total)
|
|
}
|
|
|
|
// GetPacketData returns data formatted for client packets
|
|
func (ho *HeroicOP) GetPacketData(wheel *HeroicOPWheel) *PacketData {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
data := &PacketData{
|
|
SpellName: ho.SpellName,
|
|
SpellDescription: ho.SpellDescription,
|
|
TimeRemaining: ho.TimeRemaining,
|
|
TotalTime: ho.TotalTime,
|
|
Complete: ho.Complete,
|
|
State: ho.State,
|
|
CanShift: false,
|
|
ShiftIcon: 0,
|
|
}
|
|
|
|
if wheel != nil {
|
|
data.Abilities = wheel.Abilities
|
|
data.CanShift = ho.ShiftUsed == ShiftNotUsed && wheel.HasShift()
|
|
data.ShiftIcon = wheel.ShiftIcon
|
|
}
|
|
|
|
data.Countered = ho.Countered
|
|
|
|
return data
|
|
}
|
|
|
|
// Validate checks if the HO instance is in a valid state
|
|
func (ho *HeroicOP) Validate() error {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
if ho.ID <= 0 {
|
|
return fmt.Errorf("invalid HO instance ID: %d", ho.ID)
|
|
}
|
|
|
|
if ho.EncounterID <= 0 {
|
|
return fmt.Errorf("invalid encounter ID: %d", ho.EncounterID)
|
|
}
|
|
|
|
if ho.State < HOStateInactive || ho.State > HOStateFailed {
|
|
return fmt.Errorf("invalid HO state: %d", ho.State)
|
|
}
|
|
|
|
if ho.State == HOStateWheelPhase {
|
|
if ho.WheelID <= 0 {
|
|
return fmt.Errorf("wheel phase requires valid wheel ID")
|
|
}
|
|
|
|
if ho.TotalTime <= 0 {
|
|
return fmt.Errorf("wheel phase requires valid timer")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetElapsedTime returns the elapsed time since HO started
|
|
func (ho *HeroicOP) GetElapsedTime() time.Duration {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
return time.Since(ho.StartTime)
|
|
}
|
|
|
|
// GetWheelElapsedTime returns the elapsed time since wheel phase started
|
|
func (ho *HeroicOP) GetWheelElapsedTime() time.Duration {
|
|
ho.mu.RLock()
|
|
defer ho.mu.RUnlock()
|
|
|
|
if ho.State != HOStateWheelPhase {
|
|
return 0
|
|
}
|
|
|
|
return time.Since(ho.WheelStartTime)
|
|
} |