Research:Combat: Difference between revisions
(→Blocking with a shield: Implemented) |
|||
Line 157: | Line 157: | ||
|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| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified}}}} | |{{StatusCol|green|Verified}}}} | ||
''On enemy hit'' | ''On enemy hit'' | ||
Line 201: | 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 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. | ||
===Bodypart hit chance=== | ===Bodypart hit chance=== |
Revision as of 14:53, 20 July 2014
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 | |
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 | not started yet |
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 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.
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 | |
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 | not started yet |
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 | |
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, epsilon not needed? |
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 |
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