386 lines
10 KiB
Go
386 lines
10 KiB
Go
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)
|
|
} |