459 lines
13 KiB
Go

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"
}
}