eq2go/internal/combat/manager.go
2025-08-30 11:51:05 -05:00

294 lines
8.2 KiB
Go

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