458 lines
13 KiB
Go
458 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"
|
|
}
|
|
} |