package combat import ( "sync" "time" ) // WeaponTimingManager manages weapon attack timing and delays type WeaponTimingManager struct { timings map[int32]*WeaponTiming // entityID -> timing timingMutex sync.RWMutex } // NewWeaponTimingManager creates a new weapon timing manager func NewWeaponTimingManager() *WeaponTimingManager { return &WeaponTimingManager{ timings: make(map[int32]*WeaponTiming), } } // GetWeaponTiming gets or creates weapon timing for an entity func (wtm *WeaponTimingManager) GetWeaponTiming(entityID int32) *WeaponTiming { wtm.timingMutex.RLock() timing, exists := wtm.timings[entityID] wtm.timingMutex.RUnlock() if exists { return timing } // Create new timing entry wtm.timingMutex.Lock() defer wtm.timingMutex.Unlock() // Check again in case another goroutine created it if timing, exists = wtm.timings[entityID]; exists { return timing } timing = &WeaponTiming{ PrimaryReady: true, SecondaryReady: true, RangedReady: true, } wtm.timings[entityID] = timing return timing } // IsPrimaryWeaponReady checks if primary weapon is ready to attack func (wtm *WeaponTimingManager) IsPrimaryWeaponReady(entity Entity) bool { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.RLock() defer timing.mutex.RUnlock() currentTime := time.Now().UnixMilli() // Check if enough time has passed since last attack if timing.LastPrimaryTime > 0 { timeSinceLastAttack := currentTime - timing.LastPrimaryTime return timeSinceLastAttack >= timing.PrimaryDelay } return timing.PrimaryReady } // IsSecondaryWeaponReady checks if secondary weapon is ready to attack func (wtm *WeaponTimingManager) IsSecondaryWeaponReady(entity Entity) bool { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.RLock() defer timing.mutex.RUnlock() currentTime := time.Now().UnixMilli() // Check if enough time has passed since last attack if timing.LastSecondaryTime > 0 { timeSinceLastAttack := currentTime - timing.LastSecondaryTime return timeSinceLastAttack >= timing.SecondaryDelay } return timing.SecondaryReady } // IsRangedWeaponReady checks if ranged weapon is ready to attack func (wtm *WeaponTimingManager) IsRangedWeaponReady(entity Entity) bool { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.RLock() defer timing.mutex.RUnlock() currentTime := time.Now().UnixMilli() // Check if enough time has passed since last attack if timing.LastRangedTime > 0 { timeSinceLastAttack := currentTime - timing.LastRangedTime return timeSinceLastAttack >= timing.RangedDelay } return timing.RangedReady } // CalculateWeaponDelay calculates attack delay based on weapon and entity stats func (wtm *WeaponTimingManager) CalculateWeaponDelay(entity Entity, weapon Item, attackType int8) int64 { baseDelay := int64(4000) // 4 seconds default if weapon != nil { // Use weapon speed as base delay weaponSpeed := weapon.GetSpeed() baseDelay = int64(weaponSpeed * 1000) // Convert to milliseconds } // Apply haste modifiers based on stats hasteModifier := wtm.calculateHasteModifier(entity, attackType) finalDelay := int64(float32(baseDelay) / hasteModifier) // Apply minimum delay cap minDelay := int64(1000) // 1 second minimum if finalDelay < minDelay { finalDelay = minDelay } return finalDelay } // calculateHasteModifier calculates haste modifier based on entity stats func (wtm *WeaponTimingManager) calculateHasteModifier(entity Entity, attackType int8) float32 { hasteModifier := float32(1.0) // Different stats affect different attack types switch attackType { case AttackTypePrimary, AttackTypeSecondary: // Melee attacks use DEX for attack speed dexBonus := float32(entity.GetStat(StatDEX)) * 0.002 hasteModifier += dexBonus case AttackTypeRanged: // Ranged attacks use AGI for attack speed agiBonus := float32(entity.GetStat(StatAGI)) * 0.002 hasteModifier += agiBonus case AttackTypeSpell: // Spell casting uses INT for casting speed intBonus := float32(entity.GetStat(StatINT)) * 0.001 hasteModifier += intBonus } // Cap haste modifier if hasteModifier > 3.0 { hasteModifier = 3.0 } else if hasteModifier < 0.5 { hasteModifier = 0.5 } return hasteModifier } // SetPrimaryWeaponTimer sets the primary weapon timer after an attack func (wtm *WeaponTimingManager) SetPrimaryWeaponTimer(entity Entity, weapon Item) { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.Lock() defer timing.mutex.Unlock() currentTime := time.Now().UnixMilli() delay := wtm.CalculateWeaponDelay(entity, weapon, AttackTypePrimary) timing.LastPrimaryTime = currentTime timing.PrimaryDelay = delay timing.PrimaryReady = false } // SetSecondaryWeaponTimer sets the secondary weapon timer after an attack func (wtm *WeaponTimingManager) SetSecondaryWeaponTimer(entity Entity, weapon Item) { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.Lock() defer timing.mutex.Unlock() currentTime := time.Now().UnixMilli() delay := wtm.CalculateWeaponDelay(entity, weapon, AttackTypeSecondary) timing.LastSecondaryTime = currentTime timing.SecondaryDelay = delay timing.SecondaryReady = false } // SetRangedWeaponTimer sets the ranged weapon timer after an attack func (wtm *WeaponTimingManager) SetRangedWeaponTimer(entity Entity, weapon Item) { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.Lock() defer timing.mutex.Unlock() currentTime := time.Now().UnixMilli() delay := wtm.CalculateWeaponDelay(entity, weapon, AttackTypeRanged) timing.LastRangedTime = currentTime timing.RangedDelay = delay timing.RangedReady = false } // ProcessWeaponTimers updates weapon readiness based on elapsed time func (wtm *WeaponTimingManager) ProcessWeaponTimers() { wtm.timingMutex.RLock() defer wtm.timingMutex.RUnlock() currentTime := time.Now().UnixMilli() for _, timing := range wtm.timings { timing.mutex.Lock() // Check primary weapon if !timing.PrimaryReady && timing.LastPrimaryTime > 0 { if currentTime-timing.LastPrimaryTime >= timing.PrimaryDelay { timing.PrimaryReady = true } } // Check secondary weapon if !timing.SecondaryReady && timing.LastSecondaryTime > 0 { if currentTime-timing.LastSecondaryTime >= timing.SecondaryDelay { timing.SecondaryReady = true } } // Check ranged weapon if !timing.RangedReady && timing.LastRangedTime > 0 { if currentTime-timing.LastRangedTime >= timing.RangedDelay { timing.RangedReady = true } } timing.mutex.Unlock() } } // ValidateWeaponAttack validates if a weapon attack is allowed func (wtm *WeaponTimingManager) ValidateWeaponAttack(entity Entity, weapon Item, attackType int8) (bool, string) { // Check weapon readiness switch attackType { case AttackTypePrimary: if !wtm.IsPrimaryWeaponReady(entity) { return false, "Primary weapon not ready" } case AttackTypeSecondary: if !wtm.IsSecondaryWeaponReady(entity) { return false, "Secondary weapon not ready" } // Check if entity can dual wield if !entity.IsDualWield() { return false, "Cannot dual wield" } case AttackTypeRanged: if !wtm.IsRangedWeaponReady(entity) { return false, "Ranged weapon not ready" } } // Validate weapon requirements if weapon != nil { if !wtm.validateWeaponRequirements(entity, weapon) { return false, "Weapon requirements not met" } } return true, "" } // validateWeaponRequirements checks if entity meets weapon requirements func (wtm *WeaponTimingManager) validateWeaponRequirements(entity Entity, weapon Item) bool { // This would integrate with item requirements system // For now, simplified validation // Check if weapon is appropriate for entity level entityLevel := entity.GetLevel() if entityLevel < 1 { return false } // All basic validation passed return true } // GetWeaponCooldownRemaining gets remaining cooldown time for a weapon type func (wtm *WeaponTimingManager) GetWeaponCooldownRemaining(entity Entity, attackType int8) int64 { timing := wtm.GetWeaponTiming(entity.GetID()) timing.mutex.RLock() defer timing.mutex.RUnlock() currentTime := time.Now().UnixMilli() switch attackType { case AttackTypePrimary: if timing.LastPrimaryTime > 0 { elapsed := currentTime - timing.LastPrimaryTime if elapsed < timing.PrimaryDelay { return timing.PrimaryDelay - elapsed } } case AttackTypeSecondary: if timing.LastSecondaryTime > 0 { elapsed := currentTime - timing.LastSecondaryTime if elapsed < timing.SecondaryDelay { return timing.SecondaryDelay - elapsed } } case AttackTypeRanged: if timing.LastRangedTime > 0 { elapsed := currentTime - timing.LastRangedTime if elapsed < timing.RangedDelay { return timing.RangedDelay - elapsed } } } return 0 } // ResetWeaponTimers resets all weapon timers for an entity func (wtm *WeaponTimingManager) ResetWeaponTimers(entityID int32) { wtm.timingMutex.Lock() defer wtm.timingMutex.Unlock() if timing, exists := wtm.timings[entityID]; exists { timing.mutex.Lock() timing.PrimaryReady = true timing.SecondaryReady = true timing.RangedReady = true timing.LastPrimaryTime = 0 timing.LastSecondaryTime = 0 timing.LastRangedTime = 0 timing.mutex.Unlock() } } // RemoveEntity removes timing data for an entity (cleanup) func (wtm *WeaponTimingManager) RemoveEntity(entityID int32) { wtm.timingMutex.Lock() defer wtm.timingMutex.Unlock() delete(wtm.timings, entityID) } // GetWeaponTimingStats returns statistics about weapon timings func (wtm *WeaponTimingManager) GetWeaponTimingStats() map[string]interface{} { wtm.timingMutex.RLock() defer wtm.timingMutex.RUnlock() stats := map[string]interface{}{ "active_timings": len(wtm.timings), "timestamp": time.Now(), } // Count ready weapons primaryReady := 0 secondaryReady := 0 rangedReady := 0 for _, timing := range wtm.timings { timing.mutex.RLock() if timing.PrimaryReady { primaryReady++ } if timing.SecondaryReady { secondaryReady++ } if timing.RangedReady { rangedReady++ } timing.mutex.RUnlock() } stats["primary_ready"] = primaryReady stats["secondary_ready"] = secondaryReady stats["ranged_ready"] = rangedReady return stats } // Shutdown cleans up the weapon timing manager func (wtm *WeaponTimingManager) Shutdown() { wtm.timingMutex.Lock() defer wtm.timingMutex.Unlock() wtm.timings = make(map[int32]*WeaponTiming) }