294 lines
8.2 KiB
Go
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)
|
|
} |