package heroic_ops import ( "encoding/binary" "fmt" "math" ) // HeroicOPPacketBuilder handles building packets for heroic opportunity client communication type HeroicOPPacketBuilder struct { clientVersion int } // NewHeroicOPPacketBuilder creates a new packet builder func NewHeroicOPPacketBuilder(clientVersion int) *HeroicOPPacketBuilder { return &HeroicOPPacketBuilder{ clientVersion: clientVersion, } } // BuildHOStartPacket builds the initial HO start packet func (hpb *HeroicOPPacketBuilder) BuildHOStartPacket(ho *HeroicOP) ([]byte, error) { if ho == nil { return nil, fmt.Errorf("heroic opportunity is nil") } // Start with base packet structure packet := make([]byte, 0, 256) // Packet header (simplified - real implementation would use proper packet structure) // This is a placeholder implementation packet = append(packet, 0x01) // HO Start packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // Encounter ID (4 bytes) encounterBytes := make([]byte, 4) binary.LittleEndian.PutUint32(encounterBytes, uint32(ho.EncounterID)) packet = append(packet, encounterBytes...) // State (1 byte) packet = append(packet, byte(ho.State)) // Starter ID (4 bytes) starterBytes := make([]byte, 4) binary.LittleEndian.PutUint32(starterBytes, uint32(ho.StarterID)) packet = append(packet, starterBytes...) return packet, nil } // BuildHOUpdatePacket builds an HO update packet for wheel phase func (hpb *HeroicOPPacketBuilder) BuildHOUpdatePacket(ho *HeroicOP) ([]byte, error) { if ho == nil { return nil, fmt.Errorf("heroic opportunity is nil") } // Build packet based on HO state packet := make([]byte, 0, 512) // Packet header packet = append(packet, 0x02) // HO Update packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // State (1 byte) packet = append(packet, byte(ho.State)) if ho.State == HOStateWheelPhase { // Wheel ID (4 bytes) wheelBytes := make([]byte, 4) binary.LittleEndian.PutUint32(wheelBytes, uint32(ho.WheelID)) packet = append(packet, wheelBytes...) // Time remaining (4 bytes) timeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(timeBytes, uint32(ho.TimeRemaining)) packet = append(packet, timeBytes...) // Total time (4 bytes) totalTimeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(totalTimeBytes, uint32(ho.TotalTime)) packet = append(packet, totalTimeBytes...) // Countered array (6 bytes) for i := 0; i < MaxAbilities; i++ { packet = append(packet, byte(ho.Countered[i])) } // Complete flag (1 byte) packet = append(packet, byte(ho.Complete)) // Shift used flag (1 byte) packet = append(packet, byte(ho.ShiftUsed)) // Spell name length and data spellNameBytes := []byte(ho.SpellName) packet = append(packet, byte(len(spellNameBytes))) packet = append(packet, spellNameBytes...) // Spell description length and data spellDescBytes := []byte(ho.SpellDescription) descLen := make([]byte, 2) binary.LittleEndian.PutUint16(descLen, uint16(len(spellDescBytes))) packet = append(packet, descLen...) packet = append(packet, spellDescBytes...) } return packet, nil } // BuildHOCompletePacket builds completion packet func (hpb *HeroicOPPacketBuilder) BuildHOCompletePacket(ho *HeroicOP, success bool) ([]byte, error) { if ho == nil { return nil, fmt.Errorf("heroic opportunity is nil") } packet := make([]byte, 0, 256) // Packet header packet = append(packet, 0x03) // HO Complete packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // Success flag (1 byte) if success { packet = append(packet, 0x01) } else { packet = append(packet, 0x00) } // Completed by character ID (4 bytes) completedByBytes := make([]byte, 4) binary.LittleEndian.PutUint32(completedByBytes, uint32(ho.CompletedBy)) packet = append(packet, completedByBytes...) if success { // Spell ID if successful (4 bytes) spellBytes := make([]byte, 4) // Note: In real implementation, get spell ID from wheel binary.LittleEndian.PutUint32(spellBytes, 0) // Placeholder packet = append(packet, spellBytes...) } return packet, nil } // BuildHOTimerPacket builds timer update packet func (hpb *HeroicOPPacketBuilder) BuildHOTimerPacket(timeRemaining, totalTime int32) ([]byte, error) { packet := make([]byte, 0, 16) // Packet header packet = append(packet, 0x04) // HO Timer packet type // Time remaining (4 bytes) timeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(timeBytes, uint32(timeRemaining)) packet = append(packet, timeBytes...) // Total time (4 bytes) totalTimeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(totalTimeBytes, uint32(totalTime)) packet = append(packet, totalTimeBytes...) return packet, nil } // BuildHOWheelPacket builds wheel-specific packet with abilities func (hpb *HeroicOPPacketBuilder) BuildHOWheelPacket(ho *HeroicOP, wheel *HeroicOPWheel) ([]byte, error) { if ho == nil || wheel == nil { return nil, fmt.Errorf("heroic opportunity or wheel is nil") } packet := make([]byte, 0, 512) // Packet header packet = append(packet, 0x05) // HO Wheel packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // Wheel ID (4 bytes) wheelBytes := make([]byte, 4) binary.LittleEndian.PutUint32(wheelBytes, uint32(wheel.ID)) packet = append(packet, wheelBytes...) // Order type (1 byte) packet = append(packet, byte(wheel.Order)) // Shift icon (2 bytes) shiftBytes := make([]byte, 2) binary.LittleEndian.PutUint16(shiftBytes, uint16(wheel.ShiftIcon)) packet = append(packet, shiftBytes...) // Abilities (12 bytes - 2 bytes per ability) for i := 0; i < MaxAbilities; i++ { abilityBytes := make([]byte, 2) binary.LittleEndian.PutUint16(abilityBytes, uint16(wheel.Abilities[i])) packet = append(packet, abilityBytes...) } // Countered status (6 bytes) for i := 0; i < MaxAbilities; i++ { packet = append(packet, byte(ho.Countered[i])) } // Timer information timeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(timeBytes, uint32(ho.TimeRemaining)) packet = append(packet, timeBytes...) totalTimeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(totalTimeBytes, uint32(ho.TotalTime)) packet = append(packet, totalTimeBytes...) // Spell information spellBytes := make([]byte, 4) binary.LittleEndian.PutUint32(spellBytes, uint32(wheel.SpellID)) packet = append(packet, spellBytes...) // Spell name length and data spellNameBytes := []byte(wheel.Name) packet = append(packet, byte(len(spellNameBytes))) packet = append(packet, spellNameBytes...) // Spell description length and data spellDescBytes := []byte(wheel.Description) descLen := make([]byte, 2) binary.LittleEndian.PutUint16(descLen, uint16(len(spellDescBytes))) packet = append(packet, descLen...) packet = append(packet, spellDescBytes...) return packet, nil } // BuildHOProgressPacket builds progress update packet func (hpb *HeroicOPPacketBuilder) BuildHOProgressPacket(ho *HeroicOP, progressPercent float32) ([]byte, error) { if ho == nil { return nil, fmt.Errorf("heroic opportunity is nil") } packet := make([]byte, 0, 32) // Packet header packet = append(packet, 0x06) // HO Progress packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // Progress percentage as float (4 bytes) progressBits := math.Float32bits(progressPercent) progressBytes := make([]byte, 4) binary.LittleEndian.PutUint32(progressBytes, progressBits) packet = append(packet, progressBytes...) // Current completion count (1 byte) completed := int8(0) for i := 0; i < MaxAbilities; i++ { if ho.Countered[i] != 0 { completed++ } } packet = append(packet, byte(completed)) return packet, nil } // BuildHOErrorPacket builds error notification packet func (hpb *HeroicOPPacketBuilder) BuildHOErrorPacket(instanceID int64, errorCode int, errorMessage string) ([]byte, error) { packet := make([]byte, 0, 256) // Packet header packet = append(packet, 0x07) // HO Error packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(instanceID)) packet = append(packet, idBytes...) // Error code (2 bytes) errorBytes := make([]byte, 2) binary.LittleEndian.PutUint16(errorBytes, uint16(errorCode)) packet = append(packet, errorBytes...) // Error message length and data messageBytes := []byte(errorMessage) packet = append(packet, byte(len(messageBytes))) packet = append(packet, messageBytes...) return packet, nil } // BuildHOShiftPacket builds wheel shift notification packet func (hpb *HeroicOPPacketBuilder) BuildHOShiftPacket(ho *HeroicOP, oldWheelID, newWheelID int32) ([]byte, error) { if ho == nil { return nil, fmt.Errorf("heroic opportunity is nil") } packet := make([]byte, 0, 32) // Packet header packet = append(packet, 0x08) // HO Shift packet type // HO Instance ID (8 bytes) idBytes := make([]byte, 8) binary.LittleEndian.PutUint64(idBytes, uint64(ho.ID)) packet = append(packet, idBytes...) // Old wheel ID (4 bytes) oldWheelBytes := make([]byte, 4) binary.LittleEndian.PutUint32(oldWheelBytes, uint32(oldWheelID)) packet = append(packet, oldWheelBytes...) // New wheel ID (4 bytes) newWheelBytes := make([]byte, 4) binary.LittleEndian.PutUint32(newWheelBytes, uint32(newWheelID)) packet = append(packet, newWheelBytes...) return packet, nil } // PacketData conversion methods // ToPacketData converts HO and wheel to packet data structure func (hpb *HeroicOPPacketBuilder) ToPacketData(ho *HeroicOP, wheel *HeroicOPWheel) *PacketData { data := &PacketData{ TimeRemaining: ho.TimeRemaining, TotalTime: ho.TotalTime, Complete: ho.Complete, State: ho.State, Countered: ho.Countered, } if wheel != nil { data.SpellName = wheel.Name data.SpellDescription = wheel.Description data.Abilities = wheel.Abilities data.CanShift = ho.ShiftUsed == ShiftNotUsed && wheel.HasShift() data.ShiftIcon = wheel.ShiftIcon } else { data.SpellName = ho.SpellName data.SpellDescription = ho.SpellDescription // Abilities will be zero-initialized } return data } // Helper methods for packet validation // ValidatePacketSize checks if packet size is within acceptable limits func (hpb *HeroicOPPacketBuilder) ValidatePacketSize(packet []byte) error { const maxPacketSize = 1024 // 1KB limit for HO packets if len(packet) > maxPacketSize { return fmt.Errorf("packet size %d exceeds maximum %d", len(packet), maxPacketSize) } return nil } // GetPacketTypeDescription returns human-readable packet type description func (hpb *HeroicOPPacketBuilder) GetPacketTypeDescription(packetType byte) string { switch packetType { case 0x01: return "HO Start" case 0x02: return "HO Update" case 0x03: return "HO Complete" case 0x04: return "HO Timer" case 0x05: return "HO Wheel" case 0x06: return "HO Progress" case 0x07: return "HO Error" case 0x08: return "HO Shift" default: return "Unknown" } } // Client version specific methods // IsVersionSupported checks if client version supports specific features func (hpb *HeroicOPPacketBuilder) IsVersionSupported(feature string) bool { // Version-specific feature support switch feature { case "wheel_shifting": return hpb.clientVersion >= 546 // Example version requirement case "progress_updates": return hpb.clientVersion >= 564 case "extended_timers": return hpb.clientVersion >= 572 default: return true // Basic features supported in all versions } } // GetVersionSpecificPacketSize returns packet size limits for client version func (hpb *HeroicOPPacketBuilder) GetVersionSpecificPacketSize() int { if hpb.clientVersion >= 564 { return 1024 // Newer clients support larger packets } return 512 // Older clients have smaller limits } // Error codes for HO system const ( HOErrorNone = iota HOErrorInvalidState HOErrorTimerExpired HOErrorAbilityNotAllowed HOErrorShiftAlreadyUsed HOErrorPlayerNotInEncounter HOErrorEncounterEnded HOErrorSystemDisabled ) // GetErrorMessage returns human-readable error message for error code func GetErrorMessage(errorCode int) string { switch errorCode { case HOErrorNone: return "No error" case HOErrorInvalidState: return "Heroic opportunity is in an invalid state" case HOErrorTimerExpired: return "Heroic opportunity timer has expired" case HOErrorAbilityNotAllowed: return "This ability cannot be used for the current heroic opportunity" case HOErrorShiftAlreadyUsed: return "Wheel shift has already been used" case HOErrorPlayerNotInEncounter: return "Player is not in the encounter" case HOErrorEncounterEnded: return "Encounter has ended" case HOErrorSystemDisabled: return "Heroic opportunity system is disabled" default: return "Unknown error" } }