Research:Combat

From OpenMW Wiki
Jump to navigation Jump to search

{{#switch:|subgroup|child=|none=|#default=

}}


Physical combat[edit]

Attack swing[edit]

Actions affected On melee swing and on projectile launch
Description
Implementation status Implemented
Analysis status Verified

Swing is the amount of time the weapon is held back for attack. The damage range of a weapon is directly related to how long the attack key is held. An instant click will result in minimum stated damage, while waiting until the attack wind-up animation is fully complete will result in maximum stated damage.

For the player: <syntaxhighlight lang="python"> attackSwing = attack key time held / full attack wind-up animation length </syntaxhighlight>

For NPCs and creatures: <syntaxhighlight lang="python"> attackSwing = min(1, 0.1 + 0.01 * (roll 100)) </syntaxhighlight>


Damage[edit]

Actions affected On melee swing and on projectile launch
Description Potential damage output from an attack.
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> attackType is one of {chop, slash, thrust}, or equivalent for creatures

if attack is from a creature (non-biped):

   damageMin = creature.attack[attackType].damageMin
   damageMax = creature.attack[attackType].damageMax

elif attack is from a melee or ranged weapon:

   damageMin = weapon.attack[attackType].damageMin
   damageMax = weapon.attack[attackType].damageMax
   # note for thrown weapons: weapon and ammo are the same object which match, causing 2x base damage
   if weapon.isMarksman and ammo that matches the weapon type is available:
       damageMin += ammo.damageMin
       damageMax += ammo.damageMax

elif attack is hand-to-hand:

   if actor.isWerewolf:
       claw = script global float "WerewolfClawMult"
       damageMin = int(fMinHandToHandMult * actor.handToHandSkill * claw)
       damageMax = int(fMaxHandToHandMult * actor.handToHandSkill * claw)
   else:
       damageMin = int(fMinHandToHandMult * actor.handToHandSkill)
       damageMax = int(fMaxHandToHandMult * actor.handToHandSkill)

damageMax = max(damageMin, damageMax) rawDamage = damageMin + actor.attackSwing * (damageMax - damageMin)

if attack is from a melee or ranged weapon: # see comments

   rawDamage *= 0.5 + 0.01 * actor.strength
   rawDamage *= weapon.condition / weapon.maxCondition

</syntaxhighlight>

Comments[edit]

Weapon strength scaling does not use existing scaling GMSTs, and hand-to-hand does not exhibit any strength scaling. MCP uses fDamageStrengthBase + 0.1 * fDamageStrengthMult * actor.strength for weapon scaling.

Hit Chance[edit]

Actions affected On melee and ranged contact
Description Check applies to melee and projectile hits. isAware is the awareness check from NPC AI Behaviour.
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> hitTest :: (isProjectile, attacker, defender) -> hit

fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue) where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0

  1. the attacker may switch weapons while a projectile is in flight

skill = isProjectile ? attacker.marksmanSkill : attacker.skill[attacker.weapon.relatedSkill] attackTerm = (skill + 0.2 * attacker.agility + 0.1 * attacker.luck) * atttacker.fatigueTerm attackTerm += attacker.attackBonus - attacker.blind

defenseTerm = 0

if defender.fatigue >= 0:

   unaware = (not defender.inCombat) and attacker is player and (not defender.isAware(player)) # see comments
   if not (defender.isKnockedDown or defender.isParalyzed or unaware):
       defenseTerm = (0.2 * defender.agility + 0.1 * defender.luck) * defender.fatigueTerm
       defenseTerm += min(100, defender.sanctuary)
   defenseTerm += min(100, fCombatInvisoMult * defender.chameleon)
   defenseTerm += min(100, fCombatInvisoMult * defender.invisibility)

x = round(attackTerm - defenseTerm)

if x > 0 and roll 100 < x: return hit else: return miss </syntaxhighlight>

Comments[edit]

The unaware check is only calculated correctly for projectile attacks. Due to either a design decision or bug, it doesn't apply to melee attacks, but it seems likely to be an design bug. It is recommended to implement it for all cases.

Hit reactions[edit]

Actions affected After a successful hit, before standard damage or blocking is applied
Description Reactionary effects of a hit; effects are applied in order.
Implementation status Implemented
Analysis status Includes confirmed mechanics, but there may be further undocumented effects.

Weapon enchantment[edit]

<syntaxhighlight lang="python"> if exists weapon.enchantment and weapon.enchantment is cast on strike:

   apply weapon.enchantment

</syntaxhighlight>

Elemental shield[edit]

Elemental shields are similar to a thorns effect, causing damage to the attacker. <syntaxhighlight lang="python"> saveTerm = attacker.destruction + 0.2 * attacker.willpower + 0.1 * attacker.luck saveTerm *= 1.25 * attacker.normalisedFatigue

for each elementalShield in defender.activeEffects:

   x = max(0, saveTerm - roll float 100)
   x = min(100, x + attacker.elementalResist[elementalShield.element])
   x = fElementalShieldMult * elementalShield.magnitude * (1 - 0.01 * x)
   attacker takes x damage

</syntaxhighlight>

Elemental shields do not stack, each effect instance is rolled separately.

Disease transfer[edit]

<syntaxhighlight lang="python"> if defender is not player: return

for each disease in attacker.activeSpells:

   if any of the disease.effects is corprus:
       resist = 1 - 0.01 * defender.resistCorprus
   elif spell.castType == disease:
       resist = 1 - 0.01 * defender.resistDisease
   elif spell.castType == blight:
       resist = 1 - 0.01 * defender.resistBlight
   else:
       continue
   if player already has the disease:
       continue
       
   x = int(fDiseaseXferChance * 100 * resist)
   if roll 10000 < x:
       defender acquires disease
       display message sMagicContractDisease with disease name

</syntaxhighlight>


Blocking with a shield[edit]

Actions affected On melee contact
Description With a shield equipped, blocking has a chance to negate all damage. Uses attacker's attackSwing.
Implementation status Implemented
Analysis status Verified

On enemy hit <syntaxhighlight lang="python"> if player is knocked down, knocked out, paralyzed, hitstunned, in cast stance or is casting: no block

theta = angle from player to enemy, negative is enemy left of centreline, positive is enemy right of centreline

if theta < fCombatBlockLeftAngle: no block if theta > fCombatBlockRightAngle: no block

  1. Note that the above tests are inaccurate, as each comparison is calculated using a dot product
  2. which is converted to an angle improperly. The effective value of fCombatBlock*Angle ends up
  3. slightly different than specified.

blockTerm = pcBlockSkill + 0.2 * pcAgility + 0.1 * pcLuck swingTerm = attackSwing * fSwingBlockMult + fSwingBlockBase

fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue) where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0 Note fatigueTerm is normally 1.25 at full fatigue.

playerTerm = blockTerm * swingTerm if player is not moving forward at any speed: playerTerm = playerTerm * 1.25 (note this is not fBlockStillBonus) playerTerm = playerTerm * fatigueTerm

if npc is a creature: npcSkill = creature combat stat otherwise: npcSkill = npc skill with wielded weapon

npcTerm = npcSkill + 0.2 * npcAgility + 0.1 * npcLuck npcFatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue) using NPC normalisedFatigue npcTerm = npcTerm * npcFatigueTerm

x = int(playerTerm - npcTerm) if x < iBlockMinChance: x = iBlockMinChance if x > iBlockMaxChance: x = iBlockMaxChance roll 100, block if roll < x

if a hit is blocked, the shield durability is reduced by incoming damage, no damage mitigation is applied </syntaxhighlight>

Comments[edit]

The enemySwing variable is effectively how much the enemy has charged their attack. It seems to be uniform random and is not based on weapon damage. The other thing of note is the playerTerm bonus. The fBlockStillBonus GMST is not used in vanilla Morrowind (the multiplier is hard-coded), but is used in OpenMW. Unexpectedly, this bonus is still given if the player is moving backwards or strafing, only moving forward will negate it.

Bodypart hit chance[edit]

Actions affected On melee or ranged hit
Description Damage is directed towards a single random body part. Occurs once a hit is confirmed.
Implementation status Implemented
Analysis status Verified
Chest/Cuirass 30%
Head/Helm 10%
Legs/Greaves 10%
Feet/Boots 10%
L Shoulder/L Pauldron 10%
R Shoulder/R Pauldron 10%
Shield arm/Shield 10%
L Hand/L Gauntlet 5%
R Hand/R Gauntlet 5%

Note that you still take unarmored class hits to the shield arm if you don't have a shield equipped. This includes when the actor is holding a 2-handed weapon.


Physical contact[edit]

Actions affected On melee or ranged hit, before mitigation
Description Damage is directed towards a single random body part. Occurs once a hit is confirmed.
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> damage = rawDamage unaware = (not defender.inCombat) and attacker is player and (not defender.isAware(player))

if defender is knocked down or knocked out:

   damage *= fCombatKODamageMult

if unaware and attack is not projectile damage:

   damage *= fCombatCriticalStrikeMult
   display message sTargetCriticalStrike

if unaware:

   use critical strike hit audio

if attack is hand-to-hand:

   if attacker.isWerewolf or defender is knocked down, knocked out or paralyzed:
       damage *= fHandtoHandHealthPer
       damage is applied to health as normal
   else:
       damage is applied to fatigue, ignoring all mitigation and difficulty scaling

if attack uses a weapon:

   if weapon is (not enchanted and does not ignore normal resistance):
       damage *= (1 - 0.01 * actor.resistNormalWeapons)
       if actor.resistNormalWeapons == 100:
           display sMagicTargetResistsWeapons
   if actor is a werewolf:
       if (projectile and projectile.isSilver) or (melee and weapon.isSilver):
           damage *= fWerewolfSilverWeaponDamageMult

</syntaxhighlight>

Comments[edit]

Note that projectile attacks cannot crit, but they do make the critical hit sound, as the sound code is separated.

Armor mitigation[edit]

Actions affected On melee or ranged hit
Description Occurs once a hit is confirmed.
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> Physical damage applied to a target actor

if damage < 0.001: skip rest of mitigation, set damage to 0

unmitigatedDamage = damage

x = damage / (damage + actor.effectiveArmorRating) damage *= max(fCombatArmorMinMult, x) z = int(unmitigatedDamage - damage) if damage < 1: damage = 1

armour = weighted selection from actor armour slots if armour is equipped there, armour loses z condition if actor is player: player exercises relevant armour skill for armour </syntaxhighlight>

Comments[edit]

It appears that the volume of the armour hit sound is proportional to the enemy attackSwing.

Knockdowns[edit]

Actions affected On melee or ranged hit
Description Occurs after damage is taken.
Implementation status implemented
Analysis status Verified

<syntaxhighlight lang="python"> damage = incoming damage before armour reduction (unmitigatedDamage) agilityTerm = agility * fKnockDownMult knockdownTerm = agility * iKnockDownOddsMult * 0.01 + iKnockDownOddsBase

roll 100, knockdown occurs if agilityTerm <= damage and knockdownTerm <= roll </syntaxhighlight>


Hitstun[edit]

Actions affected On physical contact
Description Occurs after damage is taken.
Implementation status not started
Analysis status Verified, but there may be further interactions

<syntaxhighlight lang="python"> if actor is creature and its animation is attacking, casting, or lockpicking/probing: no stun

  1. players or NPCs always take hitstun

apply hitstun if not already in hitstun </syntaxhighlight>

Comments[edit]

Hitstun is the actor state, with accompanying animations (Hit1-5 or SwimHit1-3) and movement restriction that occurs when the actor is hit. Hitstun prevents movement during its animation, while still allowing turning, crouching and jumping. It also prevents initiating new attacks or casting, but allows attacks (including releasing the attack button to strike) or casting to complete.

Weapon wear[edit]

Actions affected On weapon contact, both on hits and on missed hit roll
Description Weapon condition loss from combat
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> if attack missed: rawDamage = 0 x = max(1, fWeaponDamageMult * rawDamage) weapon loses x condition </syntaxhighlight>

Comments[edit]

This occurs on the same conditions that cause the weapon "miss" sound, as well as hits. Uses rawDamage, not mitigated damage. Should be evaluated after the attack is complete, as it may break the weapon.

Armor rating and Armor class[edit]

Determining armor class[edit]

Actions affected On evaluating AR
Description
Implementation status Implemented
Analysis status Verified

Item armor class: <syntaxhighlight lang="python"> epsilon = 5e-4 referenceWeight = iHelmWeight, iPauldronWeight, iCuirassWeight, iGauntletWeight, iGreavesWeight, iBootsWeight or iShieldWeight depending on slot

if itemWeight == 0: return NONE if itemWeight <= referenceWeight * fLightMaxMod + epsilon: return LIGHT if itemWeight <= referenceWeight * fMedMaxMod + epsilon: return MEDIUM else: return HEAVY </syntaxhighlight>

Armor rating[edit]

Actions affected When evaluating effective armor rating
Description
Implementation status Implemented, bug fixed
Analysis status Verified

For NPCs or the player: <syntaxhighlight lang="python"> armorSkill is either npc lightArmorSkill, mediumArmorSkill or heavyArmorSkill dependent on item class

if the actor has no armor at all: effectiveRating = 0 # obvious bug

if there is armor in a slot:

   if itemWeight == 0: rating = armorBaseRating
   else: rating = armorBaseRating * armorSkill / iBaseArmorSkill

otherwise unarmored skill is used instead:

   rating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill)

effectiveRating is a weighted combination of slot ratings: effectiveRating = Cuirass * 0.3 + (Shield + Helm + Greaves + Boots + LPauldron + RPauldron) * 0.1

 + (LGauntlet + RGauntlet) * 0.05 + Shield spell effect magnitude

</syntaxhighlight>

For creatures: <syntaxhighlight lang="python"> effectiveRating = 0 # also a bug

should be:

effectiveRating = Shield spell effect magnitude </syntaxhighlight>

Comments[edit]

Without fixes, if an actor has no armour equipped at all the game would return an armour rating of 0, ignoring any unarmored skill. Similarly, creatures would always have zero rating, even ones that have active shield spells. Note that elemental shield spells do not contribute towards armor.

Difficulty multiplier[edit]

Actions affected On application of health damage after all other mechanics.
Description Difficulty scaling of physical combat damage (including elemental shield damage). Fatigue damage or spell effects do not scale.
Implementation status Implemented
Analysis status Verified

<syntaxhighlight lang="python"> difficulty : int [-100..100] difficultyTerm = 0.01 * difficulty

if defender is player:

   if difficultyTerm > 0:
       x = fDifficultyMult * difficultyTerm
   else:
       x = difficultyTerm / fDifficultyMult

elif attacker is player:

   if difficultyTerm > 0:
       x = -difficultyTerm / fDifficultyMult
   else:
       x = fDifficultyMult * -difficultyTerm

else:

   x = 0

damage *= 1 + x </syntaxhighlight>



{{#switch:|subgroup|child=|none=|#default=

}}