Research:Combat

From OpenMW Wiki
Jump to navigation Jump to search


Physical combat

Attack swing

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:

attackSwing = attack key time held / full attack wind-up animation length

For NPCs and creatures:

attackSwing = min(1, 0.1 + 0.01 * (roll 100))


Damage

Actions affected On melee swing and on projectile launch
Description Potential damage output from an attack.
Implementation status Implemented
Analysis status Verified
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

Comments

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

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
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

# 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

Comments

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

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

if exists weapon.enchantment and weapon.enchantment is cast on strike:
    apply weapon.enchantment

Elemental shield

Elemental shields are similar to a thorns effect, causing damage to the attacker.

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

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

Disease transfer

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


Blocking with a shield

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

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

#Note that the above tests are inaccurate, as each comparison is calculated using a dot product
#which is converted to an angle improperly. The effective value of fCombatBlock*Angle ends up
#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

Comments

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

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

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
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

Comments

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

Armor mitigation

Actions affected On melee or ranged hit
Description Occurs once a hit is confirmed.
Implementation status Implemented
Analysis status Verified
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

Comments

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

Knockdowns

Actions affected On melee or ranged hit
Description Occurs after damage is taken.
Implementation status implemented
Analysis status Verified
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


Hitstun

Actions affected On physical contact
Description Occurs after damage is taken.
Implementation status not started
Analysis status Verified, but there may be further interactions
if actor is creature and its animation is attacking, casting, or lockpicking/probing: no stun
# players or NPCs always take hitstun

apply hitstun if not already in hitstun

Comments

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

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
if attack missed: rawDamage = 0
x = max(1, fWeaponDamageMult * rawDamage)
weapon loses x condition

Comments

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

Determining armor class

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

Item armor class:

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

Armor rating

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

For NPCs or the player:

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

For creatures:

effectiveRating = 0    # also a bug

should be:

effectiveRating = Shield spell effect magnitude

Comments

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

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
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