package combat import ( "math" "math/rand" ) // DetermineHit calculates hit result (hit/miss/dodge/parry/block/riposte) func (cm *CombatManager) DetermineHit(attacker Entity, victim Entity, attackType int8, damageType int8, toHitBonus float32, isCasterSpell bool, spell Spell) int8 { // Spell attacks have different hit mechanics if isCasterSpell && spell != nil { return cm.determineSpellHit(attacker, victim, spell) } // Calculate base hit chance baseHitChance := cm.config.BaseHitChance + toHitBonus // Modify hit chance based on level difference levelDiff := float32(attacker.GetLevel() - victim.GetLevel()) hitChance := baseHitChance + (levelDiff * 2.0) // Add attacker's accuracy bonuses if attackType == AttackTypePrimary || attackType == AttackTypeSecondary { // Melee attacks use STR for accuracy accuracyBonus := float32(attacker.GetStat(StatSTR)) * 0.05 hitChance += accuracyBonus } else if attackType == AttackTypeRanged { // Ranged attacks use AGI for accuracy accuracyBonus := float32(attacker.GetStat(StatAGI)) * 0.05 hitChance += accuracyBonus } // Cap hit chance if hitChance > 95.0 { hitChance = 95.0 } else if hitChance < 5.0 { hitChance = 5.0 } // Roll for hit hitRoll := rand.Float32() * 100.0 if hitRoll > hitChance { return HitTypeMiss } // Hit successful - check for defensive actions defenseRoll := rand.Float32() * 100.0 // Check for dodge (AGI based) dodgeChance := cm.calculateDodgeChance(victim) if defenseRoll < dodgeChance { return HitTypeDodge } defenseRoll -= dodgeChance // Check for parry (only for frontal attacks with weapon equipped) if cm.isFrontalAttack(attacker, victim) && cm.hasWeaponEquipped(victim) { parryChance := cm.calculateParryChance(victim) if defenseRoll < parryChance { // Check for riposte riposteChance := parryChance * 0.3 // 30% of parry chance if rand.Float32()*100.0 < riposteChance { return HitTypeRiposte } return HitTypeParry } defenseRoll -= parryChance } // Check for block (only with shield equipped) if cm.hasShieldEquipped(victim) { blockChance := cm.calculateBlockChance(victim) if defenseRoll < blockChance { return HitTypeBlock } defenseRoll -= blockChance } // Check for critical hit critChance := cm.calculateCritChance(attacker, attackType) if rand.Float32()*100.0 < critChance { return HitTypeCritical } // Regular hit return HitTypeHit } // determineSpellHit calculates hit for spell attacks func (cm *CombatManager) determineSpellHit(caster Entity, victim Entity, spell Spell) int8 { // Spells generally always hit unless resisted // Check for spell resistance if cm.config.EnableSpellResist { resistChance := cm.CalculateSpellResistance(caster, victim, spell) if rand.Float32()*100.0 < resistChance { return HitTypeMiss } } // Check for spell critical critChance := cm.GetSpellCritChance(caster) if rand.Float32()*100.0 < critChance { return HitTypeCritical } return HitTypeHit } // calculateDodgeChance calculates dodge chance based on AGI and level func (cm *CombatManager) calculateDodgeChance(entity Entity) float32 { baseDodge := cm.config.BaseDodgeChance agiBonus := float32(entity.GetStat(StatAGI)) * 0.1 // Level scaling levelBonus := float32(entity.GetLevel()) * 0.2 totalDodge := baseDodge + agiBonus + levelBonus // Cap dodge chance if totalDodge > 40.0 { totalDodge = 40.0 } else if totalDodge < 0.0 { totalDodge = 0.0 } return totalDodge } // calculateParryChance calculates parry chance based on weapon skill and STR func (cm *CombatManager) calculateParryChance(entity Entity) float32 { baseParry := cm.config.BaseParryChance strBonus := float32(entity.GetStat(StatSTR)) * 0.08 // Level scaling levelBonus := float32(entity.GetLevel()) * 0.15 totalParry := baseParry + strBonus + levelBonus // Cap parry chance if totalParry > 35.0 { totalParry = 35.0 } else if totalParry < 0.0 { totalParry = 0.0 } return totalParry } // calculateBlockChance calculates block chance based on shield and STA func (cm *CombatManager) calculateBlockChance(entity Entity) float32 { baseBlock := cm.config.BaseBlockChance staBonus := float32(entity.GetStat(StatSTA)) * 0.1 // TODO: Add shield-specific bonuses when item system is integrated totalBlock := baseBlock + staBonus // Cap block chance if totalBlock > 30.0 { totalBlock = 30.0 } else if totalBlock < 0.0 { totalBlock = 0.0 } return totalBlock } // calculateCritChance calculates critical hit chance func (cm *CombatManager) calculateCritChance(attacker Entity, attackType int8) float32 { baseCrit := cm.config.BaseCriticalChance var statBonus float32 switch attackType { case AttackTypePrimary, AttackTypeSecondary: // Melee crits use STR statBonus = float32(attacker.GetStat(StatSTR)) * 0.1 case AttackTypeRanged: // Ranged crits use AGI statBonus = float32(attacker.GetStat(StatAGI)) * 0.1 case AttackTypeSpell: // Spell crits use INT statBonus = float32(attacker.GetStat(StatINT)) * 0.1 default: statBonus = 0.0 } totalCrit := baseCrit + statBonus // Cap critical chance if totalCrit > 50.0 { totalCrit = 50.0 } else if totalCrit < 0.0 { totalCrit = 0.0 } return totalCrit } // isFrontalAttack checks if attack is from the front (for parry eligibility) func (cm *CombatManager) isFrontalAttack(attacker Entity, victim Entity) bool { // Calculate angle between attacker and victim's facing direction dx := attacker.GetX() - victim.GetX() dz := attacker.GetZ() - victim.GetZ() if dx == 0 && dz == 0 { return true // Same position } // Calculate angle to attacker angleToAttacker := math.Atan2(float64(dx), float64(dz)) * 180.0 / math.Pi // Normalize to 0-360 if angleToAttacker < 0 { angleToAttacker += 360.0 } // Get victim's heading (already in degrees 0-360) victimHeading := float64(victim.GetHeading()) // Calculate relative angle relativeAngle := math.Abs(angleToAttacker - victimHeading) if relativeAngle > 180.0 { relativeAngle = 360.0 - relativeAngle } // Front arc is 90 degrees (45 degrees either side of facing direction) return relativeAngle <= 45.0 } // hasWeaponEquipped checks if entity has a weapon equipped for parrying func (cm *CombatManager) hasWeaponEquipped(entity Entity) bool { primaryWeapon := cm.itemManager.GetEquippedItem(entity.GetID(), SlotPrimary) return primaryWeapon != nil } // hasShieldEquipped checks if entity has a shield equipped for blocking func (cm *CombatManager) hasShieldEquipped(entity Entity) bool { secondaryItem := cm.itemManager.GetEquippedItem(entity.GetID(), SlotSecondary) if secondaryItem == nil { return false } // Check if it's a shield (weapon type 11 = shield) return secondaryItem.GetSkillType() == WeaponTypeShield } // GetSkillByWeaponType gets the appropriate skill for a weapon type func (cm *CombatManager) GetSkillByWeaponType(weaponType int8, damageType int8, update bool) int8 { // Map weapon types to skill types // This would integrate with the skills system switch weaponType { case WeaponType1HSlash: return 0 // 1H Slashing skill case WeaponType2HSlash: return 1 // 2H Slashing skill case WeaponType1HPierce: return 2 // 1H Piercing skill case WeaponType1HCrush: return 3 // 1H Crushing skill case WeaponType2HCrush: return 4 // 2H Crushing skill case WeaponType2HPierce: return 5 // 2H Piercing skill case WeaponTypeBow: return 6 // Bow skill case WeaponTypeThrown: return 7 // Thrown skill case WeaponTypeCrossbow: return 8 // Crossbow skill case WeaponTypeWand: return 9 // Wand skill case WeaponTypeFist: return 10 // Fist/Unarmed skill default: return 0 // Default to basic weapon skill } }