package combat import ( "fmt" "time" ) // NewCombatManager creates a new combat manager func NewCombatManager( database Database, ruleManager RuleManager, spellManager SpellManager, itemManager ItemManager, zoneManager ZoneManager, ) *CombatManager { config := &CombatConfig{ EnablePVP: ruleManager.GetBool(RuleCategoryPVP, RuleAllowPVP), PVPType: ruleManager.GetInt32(RuleCategoryPVP, RulePVPType), PVPLevelRange: ruleManager.GetInt32(RuleCategoryPVP, RulePVPLevelRange), MaxMitigationPercent: ruleManager.GetFloat32(RuleCategoryCombat, RuleArmorMitigationLimit), BaseHitChance: ruleManager.GetFloat32(RuleCategoryCombat, RuleDefaultHitChance), BaseCriticalChance: DefaultCriticalChance, BaseDodgeChance: DefaultDodgeChance, BaseParryChance: DefaultParryChance, BaseBlockChance: DefaultBlockChance, HateDecayRate: DefaultHateDecayRate, CombatTimeout: 5 * time.Minute, EnableArmorMitigation: true, EnableSpellResist: true, EnableMultiAttack: true, EnableFlurry: true, EnableBerserk: true, EnableRiposte: true, } cm := &CombatManager{ activeSessions: make(map[int32]*CombatSession), hateLists: make(map[int32]*HateList), sessionStats: make(map[int32]*SessionStats), config: config, database: database, ruleManager: ruleManager, spellManager: spellManager, itemManager: itemManager, zoneManager: zoneManager, } // Initialize hate manager cm.hateManager = NewHateManager(config) // Initialize PVP manager cm.pvpManager = NewPVPManager(config, ruleManager) // Initialize weapon timing manager cm.weaponTiming = NewWeaponTimingManager() return cm } // StartCombat initiates combat between two entities func (cm *CombatManager) StartCombat(attacker, defender Entity) error { if attacker == nil || defender == nil { return fmt.Errorf("attacker and defender cannot be nil") } // Check if combat is allowed if !cm.AttackAllowed(attacker, defender, false) { return fmt.Errorf("attack not allowed") } // Create combat session session := NewCombatSession(attacker.GetID(), defender.GetID(), CombatTypeMelee) cm.sessionMutex.Lock() cm.activeSessions[attacker.GetID()] = session cm.sessionMutex.Unlock() // Initialize hate if defender is NPC if defender.IsNPC() { cm.AddHate(defender.GetID(), attacker.GetID(), 1, false) } return nil } // StopCombat ends combat for an entity func (cm *CombatManager) StopCombat(entityID int32) { cm.sessionMutex.Lock() defer cm.sessionMutex.Unlock() if session, exists := cm.activeSessions[entityID]; exists { session.IsActive = false delete(cm.activeSessions, entityID) } } // IsInCombat checks if an entity is in combat func (cm *CombatManager) IsInCombat(entityID int32) bool { cm.sessionMutex.RLock() defer cm.sessionMutex.RUnlock() session, exists := cm.activeSessions[entityID] return exists && session.IsActive } // GetCombatSession returns the combat session for an entity func (cm *CombatManager) GetCombatSession(entityID int32) *CombatSession { cm.sessionMutex.RLock() defer cm.sessionMutex.RUnlock() if session, exists := cm.activeSessions[entityID]; exists { // Return session directly (caller should not modify) return session } return nil } // UpdateConfig refreshes combat configuration from rules func (cm *CombatManager) UpdateConfig() { cm.config.EnablePVP = cm.ruleManager.GetBool(RuleCategoryPVP, RuleAllowPVP) cm.config.PVPType = cm.ruleManager.GetInt32(RuleCategoryPVP, RulePVPType) cm.config.PVPLevelRange = cm.ruleManager.GetInt32(RuleCategoryPVP, RulePVPLevelRange) cm.config.MaxMitigationPercent = cm.ruleManager.GetFloat32(RuleCategoryCombat, RuleArmorMitigationLimit) cm.config.BaseHitChance = cm.ruleManager.GetFloat32(RuleCategoryCombat, RuleDefaultHitChance) } // GetStatistics returns combat system statistics func (cm *CombatManager) GetStatistics() map[string]any { cm.statsMutex.RLock() defer cm.statsMutex.RUnlock() return map[string]any{ "total_attacks": cm.totalAttacks, "total_damage": cm.totalDamage, "total_healing": cm.totalHealing, "active_sessions": len(cm.activeSessions), "active_hate_lists": len(cm.hateLists), "last_update": time.Now(), } } // Process handles periodic combat updates (called by main server loop) func (cm *CombatManager) Process() { currentTime := time.Now() // Process active combat sessions cm.processActiveSessions(currentTime) // Decay hate lists cm.hateManager.ProcessHateDecay(currentTime) // Process weapon timers cm.weaponTiming.ProcessWeaponTimers() // Clean up expired sessions cm.cleanupExpiredSessions(currentTime) } // processActiveSessions updates active combat sessions func (cm *CombatManager) processActiveSessions(currentTime time.Time) { cm.sessionMutex.Lock() defer cm.sessionMutex.Unlock() for sessionID, session := range cm.activeSessions { session.mutex.Lock() // Check for timeout if currentTime.Sub(session.LastActivity) > cm.config.CombatTimeout { session.IsActive = false } session.mutex.Unlock() // Remove inactive sessions if !session.IsActive { delete(cm.activeSessions, sessionID) } } } // cleanupExpiredSessions removes old combat sessions func (cm *CombatManager) cleanupExpiredSessions(currentTime time.Time) { cm.sessionMutex.Lock() defer cm.sessionMutex.Unlock() for sessionID, session := range cm.activeSessions { if currentTime.Sub(session.StartTime) > 24*time.Hour { delete(cm.activeSessions, sessionID) } } } // GetConfig returns the current combat configuration func (cm *CombatManager) GetConfig() *CombatConfig { return cm.config } // Shutdown gracefully shuts down the combat manager func (cm *CombatManager) Shutdown() { // Save any persistent data cm.sessionMutex.Lock() defer cm.sessionMutex.Unlock() for _, session := range cm.activeSessions { session.IsActive = false } // Clear all data cm.activeSessions = make(map[int32]*CombatSession) cm.hateLists = make(map[int32]*HateList) } // AttackAllowed checks if an attack is allowed between two entities func (cm *CombatManager) AttackAllowed(attacker Entity, victim Entity, isRanged bool) bool { if attacker == nil || victim == nil { return false } // Cannot attack self if attacker.GetID() == victim.GetID() { return false } // Cannot attack dead entities if !victim.IsAlive() || victim.GetHP() <= 0 { return false } // Check if attacker is stunned/mezzed if attacker.IsMezzedOrStunned() { return false } // Check PVP rules if attacker.IsPlayer() && victim.IsPlayer() { return cm.isPVPAllowed(attacker, victim) } // Check faction rules if !cm.isFactionAttackAllowed(attacker, victim) { return false } // Check if victim is attackable if victim.GetAttackable() == 0 { return false } return true } // isPVPAllowed checks if PVP is allowed between two players func (cm *CombatManager) isPVPAllowed(attacker Entity, victim Entity) bool { return cm.pvpManager.IsPVPAllowed(attacker, victim) } // isFactionAttackAllowed checks faction rules for attacks func (cm *CombatManager) isFactionAttackAllowed(attacker Entity, victim Entity) bool { // Simplified faction check - in full implementation would check faction standings attackerFaction := attacker.GetFactionID() victimFaction := victim.GetFactionID() // Same faction cannot attack each other (except in PVP) if attackerFaction == victimFaction && attackerFaction > 0 { if attacker.IsPlayer() && victim.IsPlayer() { return cm.isPVPAllowed(attacker, victim) } return false } return true } // AddHate adds hate/threat to an entity's hate list func (cm *CombatManager) AddHate(ownerID int32, targetID int32, hateAmount int32, lockHate bool) { cm.hateManager.AddHate(ownerID, targetID, hateAmount, lockHate) } // ClearHateTarget removes a target from all hate lists func (cm *CombatManager) ClearHateTarget(targetID int32) { cm.hateManager.ClearHateTarget(targetID) } // GetHateList returns the hate list for an entity func (cm *CombatManager) GetHateList(ownerID int32) *HateList { return cm.hateManager.GetHateList(ownerID) } // GetMostHated returns the most hated target for an entity func (cm *CombatManager) GetMostHated(ownerID int32) int32 { return cm.hateManager.GetMostHated(ownerID) }