Research:Combat: Difference between revisions

From OpenMW Wiki
Jump to navigation Jump to search
(Clarifications.)
 
(18 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Template:Research Navbox}}
==Physical combat==
==Physical combat==


Line 25: Line 28:
|On melee swing and on projectile launch
|On melee swing and on projectile launch
|Potential damage output from an attack.
|Potential damage output from an attack.
|
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 60: Line 63:
====Comments====
====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.
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===
===Hit Chance===
Line 66: Line 68:
|On melee and ranged contact
|On melee and ranged contact
|Check applies to melee and projectile hits. isAware is the awareness check from [[Research:NPC_AI_Behaviour#NPC_Awareness_Check|NPC AI Behaviour]].
|Check applies to melee and projectile hits. isAware is the awareness check from [[Research:NPC_AI_Behaviour#NPC_Awareness_Check|NPC AI Behaviour]].
|{{StatusCol|red|not started yet}}
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 99: Line 101:


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.
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===
===Hit reactions===
Line 157: Line 158:
|On melee contact
|On melee contact
|With a shield equipped, blocking has a chance to negate all damage. Uses attacker's [[Research:Combat#Attack_swing|attackSwing]].
|With a shield equipped, blocking has a chance to negate all damage. Uses attacker's [[Research:Combat#Attack_swing|attackSwing]].
|{{StatusCol|orange|Changed since last implementation}}
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
''On enemy hit''
''On enemy hit''
Line 200: Line 201:


====Comments====
====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 at all; the multiplier is hard-coded. Unexpectedly, this bonus is still given if the player is moving backwards or strafing, only moving forward will negate it.
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===
===Bodypart hit chance===
Line 236: Line 236:
|On melee or ranged hit, before mitigation
|On melee or ranged hit, before mitigation
|Damage is directed towards a single random body part. Occurs once a hit is confirmed.
|Damage is directed towards a single random body part. Occurs once a hit is confirmed.
|
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 270: Line 270:
====Comments====
====Comments====
Note that projectile attacks cannot crit, but they do make the critical hit sound, as the sound code is separated.
Note that projectile attacks cannot crit, but they do make the critical hit sound, as the sound code is separated.


===Armor mitigation===
===Armor mitigation===
Line 276: Line 275:
|On melee or ranged hit
|On melee or ranged hit
|Occurs once a hit is confirmed.
|Occurs once a hit is confirmed.
|{{StatusCol|red|not started yet}}
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 297: Line 296:
====Comments====
====Comments====
It appears that the volume of the armour hit sound is proportional to the enemy attackSwing.
It appears that the volume of the armour hit sound is proportional to the enemy attackSwing.


===Knockdowns===
===Knockdowns===
Line 318: Line 316:
|On physical contact
|On physical contact
|Occurs after damage is taken.
|Occurs after damage is taken.
|
|{{StatusCol|red|not started}}
|{{StatusCol|orange|Verified, but there may be further interactions}}}}
|{{StatusCol|orange|Verified, but there may be further interactions}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 329: Line 327:
====Comments====
====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.
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===
===Weapon wear===
{{Formula
{{Formula
|On weapon contact, both on hits and on missed hit roll
|On weapon contact, both on hits and on missed hit roll
|This occurs on the same conditions that cause the weapon "miss" sound, as well as hits. Uses rawDamage, not mitigated damage.
|Weapon condition loss from combat
|{{StatusCol|orange|Changed since last implementation}}
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
Line 344: Line 341:


====Comments====
====Comments====
Should be evaluated after the attack is complete, as it may break the weapon.
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==
==Armor rating and Armor class==
Line 354: Line 349:
|On evaluating AR
|On evaluating AR
|
|
|{{StatusCol|green|implemented, epsilon not needed?}}
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
Item armor class:
Item armor class:
Line 366: Line 361:
else: return HEAVY
else: return HEAVY
</syntaxhighlight>
</syntaxhighlight>


===Armor rating===
===Armor rating===
Line 372: Line 366:
|When evaluating effective armor rating
|When evaluating effective armor rating
|
|
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Implemented, bug fixed}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
For NPCs or the player:
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
armorSkill is either npc lightArmorSkill, mediumArmorSkill or heavyArmorSkill dependent on item class
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 there is armor in a slot:
if itemWeight == 0: rating = armorBaseRating
    if itemWeight == 0: rating = armorBaseRating
else: rating = armorBaseRating * armorSkill / iBaseArmorSkill
    else: rating = armorBaseRating * armorSkill / iBaseArmorSkill


otherwise unarmored skill is used instead:
otherwise unarmored skill is used instead:
rating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill)
    rating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill)


effectiveRating is a weighted combination of slot ratings:
effectiveRating is a weighted combination of slot ratings:
Line 389: Line 386:
</syntaxhighlight>
</syntaxhighlight>


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


Note that elemental shield spells do not contribute towards armor.
should be:


effectiveRating = Shield spell effect magnitude
</syntaxhighlight>


====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==
==Difficulty multiplier==
{{Formula
{{Formula
|Difficulty scaling of combat damage
|On application of health damage after all other mechanics.
|On application of health damage after all other mechanics. Used in combat only - physical and spell damage to health.
|Difficulty scaling of physical combat damage (including elemental shield damage). Fatigue damage or spell effects do not scale.
|
|{{StatusCol|green|Implemented}}
|{{StatusCol|green|Verified}}}}
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
difficulty : int [0..100]
difficulty : int [-100..100]
difficultyTerm = -1 + 0.02 * difficulty
difficultyTerm = 0.01 * difficulty


if actor is player:
if defender is player:
     if difficultyTerm > 0:
     if difficultyTerm > 0:
         x = fDifficultyMult * difficultyTerm
         x = fDifficultyMult * difficultyTerm
     else:
     else:
         x = difficultyTerm / fDifficultyMult
         x = difficultyTerm / fDifficultyMult
else:
elif attacker is player:
     if difficultyTerm > 0:
     if difficultyTerm > 0:
         x = -difficultyTerm / fDifficultyMult
         x = -difficultyTerm / fDifficultyMult
     else:
     else:
         x = fDifficultyMult * -difficultyTerm
         x = fDifficultyMult * -difficultyTerm
else:
    x = 0


damage *= 1 + x
damage *= 1 + x
</syntaxhighlight>
</syntaxhighlight>
{{Template:Research Navbox}}

Latest revision as of 20:27, 8 September 2017


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