package combat import ( "fmt" "math/rand" "time" ) // DamageSpawn applies damage to a spawn/entity func (cm *CombatManager) DamageSpawn(attacker Entity, victim Entity, attackType int8, damageType int8, lowDamage, highDamage int32, spellName string, critMod int8, isTick bool, noCalcs bool, ignoreAttacker bool, takePower bool, spell Spell) bool { if victim == nil { return false } // Check if victim is invulnerable or dead if victim.GetHP() <= 0 { return false } // Calculate final damage var finalDamage int32 if noCalcs { // Use damage as-is if highDamage > lowDamage { finalDamage = lowDamage + rand.Int31n(highDamage-lowDamage+1) } else { finalDamage = lowDamage } } else { damageInfo := cm.CalculateDamage(attacker, victim, attackType, damageType, lowDamage, highDamage) finalDamage = damageInfo.FinalDamage } // Apply damage currentHP := victim.GetHP() newHP := currentHP - finalDamage if newHP < 0 { newHP = 0 } victim.SetHP(newHP) // Log combat event if cm.database != nil { event := &CombatEvent{ Timestamp: time.Now(), AttackerID: attacker.GetID(), DefenderID: victim.GetID(), DamageType: damageType, HitType: HitTypeHit, DamageAmount: finalDamage, WeaponType: 0, SpellID: 0, ZoneID: victim.GetZoneID(), X: victim.GetX(), Y: victim.GetY(), Z: victim.GetZ(), } if spell != nil { event.SpellID = spell.GetID() } cm.database.LogCombatEvent(event) } // Check if victim died if newHP <= 0 { cm.KillSpawn(attacker, victim, attackType, damageType, 0) } // Check for spell interruption if finalDamage > 0 && !isTick { cm.CheckInterruptSpell(attacker, victim) } return finalDamage > 0 } // CalculateDamage calculates damage with all modifiers applied func (cm *CombatManager) CalculateDamage(attacker Entity, victim Entity, attackType int8, damageType int8, lowDamage, highDamage int32) *DamageInfo { damage := &DamageInfo{ BaseDamage: lowDamage, DamageType: damageType, EffectiveLevelAttacker: int16(attacker.GetLevel()), EffectiveLevelDefender: int16(victim.GetLevel()), } // Calculate base damage with variance if highDamage > lowDamage { damage.ScaledDamage = lowDamage + rand.Int31n(highDamage-lowDamage+1) } else { damage.ScaledDamage = lowDamage } // Apply stat bonuses based on attack type if attackType == AttackTypePrimary || attackType == AttackTypeSecondary { // Melee attacks use STR for damage bonus strBonus := float32(attacker.GetStat(StatSTR)) * 0.1 damage.ScaledDamage = int32(float32(damage.ScaledDamage) * (1.0 + strBonus/100.0)) } else if attackType == AttackTypeSpell { // Spell attacks use INT for damage bonus intBonus := float32(attacker.GetStat(StatINT)) * 0.1 damage.ScaledDamage = int32(float32(damage.ScaledDamage) * (1.0 + intBonus/100.0)) } // Calculate mitigation mitigation := cm.CalculateMitigation(attackType, damageType, damage.EffectiveLevelAttacker, false) damage.Mitigation = mitigation // Apply mitigation damage.FinalDamage = int32(float32(damage.ScaledDamage) * (1.0 - mitigation/100.0)) // Ensure minimum damage if damage.FinalDamage < 1 { damage.FinalDamage = 1 } return damage } // CalculateWeaponDamage calculates damage for weapon-based attacks func (cm *CombatManager) CalculateWeaponDamage(attacker Entity, victim Entity, weapon Item, hitType int8) *DamageInfo { var lowDamage, highDamage int32 if weapon != nil { lowDamage = weapon.GetMinDamage() highDamage = weapon.GetMaxDamage() } else { // Unarmed combat - use base fist damage lowDamage = int32(attacker.GetLevel()) / 5 highDamage = int32(attacker.GetLevel()) / 3 } damageType := int8(DamageTypeCrushing) // Default for unarmed if weapon != nil { damageType = weapon.GetDamageType() } damage := cm.CalculateDamage(attacker, victim, AttackTypePrimary, damageType, lowDamage, highDamage) // Apply critical hit modifier if hitType == HitTypeCritical { critMultiplier := 1.5 + (float32(attacker.GetStat(StatSTR)) * 0.001) damage.FinalDamage = int32(float32(damage.FinalDamage) * critMultiplier) damage.CriticalMod = critMultiplier } else if hitType == HitTypeGlancing { // Glancing blows do reduced damage damage.FinalDamage = int32(float32(damage.FinalDamage) * 0.75) } return damage } // CalculateRangedDamage calculates damage for ranged attacks func (cm *CombatManager) CalculateRangedDamage(attacker Entity, victim Entity, weapon Item, ammo Item, hitType int8) *DamageInfo { if weapon == nil { return &DamageInfo{FinalDamage: 0} } lowDamage := weapon.GetMinDamage() highDamage := weapon.GetMaxDamage() // Add ammo damage if present if ammo != nil { lowDamage += ammo.GetMinDamage() highDamage += ammo.GetMaxDamage() } damageType := int8(weapon.GetDamageType()) damage := cm.CalculateDamage(attacker, victim, AttackTypeRanged, damageType, lowDamage, highDamage) // Apply AGI bonus for ranged attacks agiBonus := float32(attacker.GetStat(StatAGI)) * 0.05 damage.FinalDamage = int32(float32(damage.FinalDamage) * (1.0 + agiBonus/100.0)) // Apply critical hit modifier if hitType == HitTypeCritical { critMultiplier := 1.75 + (float32(attacker.GetStat(StatAGI)) * 0.001) damage.FinalDamage = int32(float32(damage.FinalDamage) * critMultiplier) damage.CriticalMod = critMultiplier } return damage } // CalculateSpellDamage calculates damage for spell attacks func (cm *CombatManager) CalculateSpellDamage(caster Entity, victim Entity, spell Spell, lowDamage, highDamage int32) *DamageInfo { damageType := int8(spell.GetDamageType()) damage := cm.CalculateDamage(caster, victim, AttackTypeSpell, damageType, lowDamage, highDamage) // Apply spell-specific bonuses intBonus := float32(caster.GetStat(StatINT)) * 0.15 wisBonus := float32(caster.GetStat(StatWIS)) * 0.1 totalBonus := intBonus + wisBonus damage.FinalDamage = int32(float32(damage.FinalDamage) * (1.0 + totalBonus/100.0)) return damage } // CalculateMitigation calculates damage mitigation func (cm *CombatManager) CalculateMitigation(attackType int8, damageType int8, effectiveLevelAttacker int16, forPVP bool) float32 { // This is a simplified mitigation calculation // EQ2's actual system is much more complex with multiple mitigation types var baseMitigation float32 = 0.0 // Different mitigation for different damage types switch damageType { case DamageTypeSlashing, DamageTypePiercing, DamageTypeCrushing: baseMitigation = 10.0 // Physical mitigation case DamageTypeFire, DamageTypeCold, DamageTypeHeat: baseMitigation = 5.0 // Elemental mitigation case DamageTypePoison, DamageTypeDisease: baseMitigation = 8.0 // Noxious mitigation case DamageTypeMagic, DamageTypeMental, DamageTypeDivine, DamageTypeArcane: baseMitigation = 3.0 // Arcane mitigation } // Cap mitigation if baseMitigation > cm.config.MaxMitigationPercent { baseMitigation = cm.config.MaxMitigationPercent } return baseMitigation } // SpellHeal performs spell healing func (cm *CombatManager) SpellHeal(caster Entity, target Entity, distance float32, spell Spell, healType string, lowHeal, highHeal int32, critMod int8, noCalcs bool, customSpellName string) *HealInfo { healInfo := &HealInfo{ BaseHealing: lowHeal, } // Check range if distance > spell.GetRange() { return healInfo // No healing if out of range } // Calculate healing amount var finalHealing int32 if noCalcs { if highHeal > lowHeal { finalHealing = lowHeal + rand.Int31n(highHeal-lowHeal+1) } else { finalHealing = lowHeal } } else { // Apply healing bonuses wisBonus := float32(caster.GetStat(StatWIS)) * 0.2 healingMultiplier := 1.0 + wisBonus/100.0 baseHeal := lowHeal if highHeal > lowHeal { baseHeal = lowHeal + rand.Int31n(highHeal-lowHeal+1) } finalHealing = int32(float32(baseHeal) * healingMultiplier) } // Check for critical heal if critMod > 0 { critChance := float32(critMod) + (float32(caster.GetStat(StatWIS)) * 0.1) if rand.Float32()*100.0 < critChance { finalHealing = int32(float32(finalHealing) * (1.0 + float32(critMod)/100.0)) } } healInfo.FinalHealing = finalHealing // Apply healing currentHP := target.GetHP() maxHP := target.GetTotalHP() newHP := currentHP + finalHealing if newHP > maxHP { healInfo.Overheal = newHP - maxHP newHP = maxHP } target.SetHP(newHP) // Update statistics cm.statsMutex.Lock() cm.totalHealing += int64(finalHealing) cm.statsMutex.Unlock() return healInfo } // KillSpawn handles entity death func (cm *CombatManager) KillSpawn(killer Entity, victim Entity, attackType int8, damageType int8, killBlowType int16) { if victim == nil { return } // Set victim as dead victim.SetHP(0) victim.SetAlive(false) // Clear any active combat sessions involving this entity cm.StopCombat(victim.GetID()) // Clear hate lists targeting this entity cm.ClearHateTarget(victim.GetID()) // TODO: Handle experience, loot, etc. // This would involve integration with other systems fmt.Printf("Entity %s (ID: %d) killed by %s (ID: %d)\n", victim.GetName(), victim.GetID(), killer.GetName(), killer.GetID()) } // HandleDeathExperienceDebt handles experience debt on death func (cm *CombatManager) HandleDeathExperienceDebt(victim Entity, killer Entity) { if !victim.IsPlayer() { return } // TODO: Implement experience debt system // This would integrate with the experience system fmt.Printf("Player %s died - applying experience debt\n", victim.GetName()) } // GetDamageTypeResistancePercentage gets resistance percentage for a damage type func (cm *CombatManager) GetDamageTypeResistancePercentage(victim Entity, damageType int8) float32 { var resistanceStat int32 switch damageType { case DamageTypeFire, DamageTypeHeat: resistanceStat = StatVS_Heat case DamageTypeCold: resistanceStat = StatVS_Cold case DamageTypeMagic: resistanceStat = StatVS_Magic case DamageTypeMental: resistanceStat = StatVS_Mental case DamageTypeDivine: resistanceStat = StatVS_Divine case DamageTypeDisease: resistanceStat = StatVS_Disease case DamageTypePoison: resistanceStat = StatVS_Poison case DamageTypeElemental: resistanceStat = StatVS_Elemental case DamageTypeArcane: resistanceStat = StatVS_Arcane case DamageTypeNoxious: resistanceStat = StatVS_Noxious default: return 0.0 } resistance := victim.GetStat(resistanceStat) // Convert resistance value to percentage (simplified formula) return float32(resistance) * 0.1 }