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