Formulae: Difference between revisions

From OpenMW Wiki
Jump to navigation Jump to search
(Redirect to newly organized Research namespace.)
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
==Combat Formulae==
#REDIRECT [[Research]]
 
===Melee===
 
 
====Hit Chance====
{{Formula
|On melee contact
|Check applies to melee hits. Does not apply to projectiles or magic.
|
|{{StatusCol|red|Inaccurate: Fatigue is not accounted for at all; any kind of knockdown or other bonus is unaccounted for; blind, chameleon or other penalties are not accounted for}}}}
<syntaxhighlight lang="python">
hit chance = Attackers weapon skill * 1.25 + Attackers Attack Bonus - Defenders Sanctuary Bonus
  + (Attackers Agility - Defenders Agility) * 0.25 + (Attackers Luck - Defenders Luck) * 0.125
</syntaxhighlight>
 
 
====Blocking with a shield====
{{Formula
|On melee contact
|With a shield equipped, blocking has a chance to negate all damage.
|
|{{StatusCol|green|Verified}}}}
''On enemy hit''
<syntaxhighlight lang="python">
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
enemySwing = random number in range [0.1, 1]
swingTerm = enemySwing * 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>
 
 
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====
{{Formula
|On melee hit
|Damage is directed towards a single random body part. Occurs once a hit is confirmed.
|
|{{StatusCol|green|Verified}}}}
{| class="wikitable"
| 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.
 
 
====Damage====
{{Formula
|On melee hit
|Occurs pre armor mitigation.
|
|{{StatusCol|red|Sources of melee damage and critical hits are unexplained}}}}
<syntaxhighlight lang="python">
if actor is knocked down: damage *= fCombatKODamageMult
if actor is critically hit: damage *= fCombatCriticalStrikeMult
</syntaxhighlight>
 
 
====Armor Mitigation====
{{Formula
|On melee hit
|Occurs once a hit is confirmed.
|
|{{StatusCol|red|Inaccurate: AR is underspecified (vs armor part/unarmored/creature); damage to armor is unspecified; shield spell effects are underspecified}}}}
<syntaxhighlight lang="python">
if damage < 0.001: skip mitigation to avoid divide by zero, set damage to 0
AR = target overall armor rating including shield spells
x = damage / (damage + target armor rating)
damage *= max(fCombatArmorMinMult, x)
</syntaxhighlight>
 
 
====Knockdowns====
{{Formula
|On melee hit
|Occurs after damage is taken.
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
damage = incoming damage before armour reduction
agilityTerm = agility * fKnockDownMult
knockdownTerm = agility * iKnockDownOddsMult * 0.01 + iKnockDownOddsBase
 
roll 100, knockdown occurs if agilityTerm <= damage and knockdownTerm <= roll
</syntaxhighlight>
 
===Magic===
 
 
====Chance of Successful Spell Cast====
{{Formula
|On cast attempt
|
|
|{{StatusCol|orange|Accurate, but requires more work; spell skill depends on dominant school for multi effect spells, but is unspecified; Sound spell effect is not accounted for}}}}
<syntaxhighlight lang="python">
chance = (Spells Skill * 2 + Willpower / 5 + Luck / 10 - SpellCost + CastPenalty) * (CurrentFatigue + MaximumFatigue * 1.5) / (MaximumFatigue * 2)
</syntaxhighlight>
 
 
====Cast cost====
{{Formula
|On cast attempt
|
|
|{{StatusCol|red|Does not account for multiple effect spells, non-duration spells or fixed magnitude spells}}}}
<syntaxhighlight lang="python">
cost = 0.1 + ((min effect value / 20) + (max effect value / 20)) * ((duration - 1) / 2) + (range / 40).
</syntaxhighlight>
x1.5 for ranged spell. Constant effect enchantments use fEnchantmentConstantDurationMult as their duration values (default 100).
 
* Touch/Self
<syntaxhighlight lang="python">
cost = base cost * ((max + min) * duration * 0.025 + area * 0.0125)
</syntaxhighlight>
* Target
<syntaxhighlight lang="python">
cost = base cost * ((max + min) * duration * 0.3125 + area * 0.0125)
</syntaxhighlight>
* Constant effect
<syntaxhighlight lang="python">
cost = base cost * ((max + min) * 2.5 + area * 0.025)
</syntaxhighlight>
The base cost are [http://www.uesp.net/wiki/Morrowind:Spell_Effects here]
 
 
====Magic Damage and Mitigation====
{{Formula
|On magic hit
|
|
|{{StatusCol|red|Ignores willpower resists; which effects magic resistance apply to; reflection and absorption}}}}
<syntaxhighlight lang="python">
Spell Damage = Damage - Damage * ( Resistance - Weakness ) / 100
</syntaxhighlight>
 
===Ranged===
 
==Trading and services==
 
 
===Common mechanics===
 
 
====Barter function====
All bartering and services use a common function to evaluate costs.
 
<syntaxhighlight lang="python">
defining a function barterOffer :: (Actor npc, int basePrice, bool buyingSelling) -> int offerPrice
 
if npc is a creature: return basePrice
 
clampedDisposition = clamp int(npcDisposition) to [0..100]
a = min(pcMercantile, 100)
b = min(0.1 * pcLuck, 10)
c = min(0.2 * pcPersonality, 10)
d = min(npcMercantile, 100)
e = min(0.1 * npcLuck, 10)
f = min(0.2 * npcPersonality, 10)
 
pcTerm = (clampedDisposition - 50 + a + b + c) * pcFatigueTerm
npcTerm = (d + e + f) * npcFatigueTerm
buyTerm = 0.01 * (100 - 0.5 * (pcTerm - npcTerm))
sellTerm = 0.01 * (50 - 0.5 * (npcTerm - pcTerm))
 
if buying: x = buyTerm
if selling: x = min(buyTerm, sellTerm)
 
if x < 1: offerPrice = int(x * basePrice)
if x >= 1: offerPrice = basePrice + int((x - 1) * basePrice)
offerPrice = max(1, offerPrice)
</syntaxhighlight>
 
 
===Bartering===
 
{{Formula
|On choosing/offering an item to buy/sell
|When selecting items to trade.
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
basePrice = price of prototype item of the same kind, enchanted items have prices set at enchant time and saved with the object
stackSize = number of items in selected stack
 
if item is a weapon or armor: x = basePrice * (remainingDurability / maxDurability)
if item is lockpick, probe or repair hammer: x = basePrice * (remainingUses / maxUses)
if item is a filled soul gem: basePrice = soul points contained * empty soul gem price
otherwise: x = basePrice
 
if buying: merchant offer is adjusted by -barterOffer(merchant, x * stackSize, buying)
if selling: merchant offer is adjusted by +barterOffer(merchant, x * stackSize, selling)
</syntaxhighlight>
 
{{Formula
|On bartering
|Includes haggling mechanic.
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
all prices are negative when player is buying, positive when player is selling
 
accept if playerOffer <= merchantOffer (same for buy and sell)
if npc is a creature: reject (no haggle)
 
a = abs(merchantOffer)
b = abs(playerOffer)
if buying: d = int(100 * (a - b) / a)
if selling: d = int(100 * (b - a) / a)
 
clampedDisposition = clamp int(npcDisposition) to [0..100]
pcTerm = (clampedDisposition - 50 + pcMercantile + 0.1 * pcLuck + 0.2 * pcPersonality) * pcFatigueTerm
npcTerm = (npcMercantile + 0.1 * npcLuck + 0.2 * npcPersonality) * npcFatigueTerm
x = fBargainOfferMulti * d + fBargainOfferBase
if buying: x += abs(int(pcTerm - npcTerm))
if selling: x += abs(int(npcTerm - pcTerm))
 
roll 100, if roll <= x then trade is accepted
adjust npc temporary disposition by iBarterSuccessDisposition or iBarterFailDisposition
</syntaxhighlight>
 
 
===Merchant repair===
{{Formula
|On opening the repair service window
|
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
p = max(1, basePrice)
r = max(1, int(maxDurability / p))
 
x = int((maxDurability - durability) / r)
x = int(fRepairMult * x)
cost = barterOffer(x)
</syntaxhighlight>
 
 
===Trainers===
{{Formula
|On purchasing training
|
|
|{{StatusCol|green|Verified, but buggy. See notes for solution.}}}}
<syntaxhighlight lang="python">
cost of training a skill = barterOffer(npc, pcSkill * iTrainingMod, buying)
</syntaxhighlight>
Standard Morrowind uses the current skill value, including fortifies and drains, which allows cheap training exploits with drain spells. A new implementation should use the skill's base value.
 
 
===Spell merchant===
{{Formula
|On purchasing a spell
|
|
|{{StatusCol|orange|Unverified; spell cost calculations are unspecified}}}}
<syntaxhighlight lang="python">
cost of purchasing existing spell = barterOffer(npc, spell cost in magicka * fSpellValueMult, buying)
 
cost of spellmaking = barterOffer(npc, spell cost in magicka * fSpellMakingValueMult, buying)
</syntaxhighlight>
 
 
===Enchanting merchant===
{{Formula
|On purchasing an enchantment
|
|
|{{StatusCol|orange|Unverified; enchantment points are unspecified}}}}
<syntaxhighlight lang="python">
cost of enchanting service = barterOffer(npc, enchantment points * fEnchantmentValueMult, buying)
</syntaxhighlight>
 
 
===Travel costs===
 
 
====Physical travel====
{{Formula
|On travelling via silt strider, boat or similar travel service AI
|
|
|{{StatusCol|orange|Requires testing}}}}
<syntaxhighlight lang="python">
dist = distance from player to destination
cost = barterOffer(npc, int(dist / fTravelMult), buying)
time = int(dist / fTravelTimeMult)
</syntaxhighlight>
 
 
====Guild guide====
{{Formula
|On travelling via Mages' Guild teleport service
|
|
|{{StatusCol|orange|Requires testing}}}}
<syntaxhighlight lang="python">
cost = barterOffer(npc, fMagesGuildTravel * barter modifiers, buying)
</syntaxhighlight>
 
==Item durability, item charge==
 
===Damages===
{{Formula
|On melee hit
|Occurs once a hit is confirmed.
|
|{{StatusCol|orange|Mostly accurate but underspecified}}}}
With every successful hit a weapon takes fWeaponDamageMult: (default 0.1) percentage of damage rounded down. Minimum damage to weapon = 1 With weapon health going down, its damage is also going down, thus in turn slowing the rate of damaging weapon. Armor takes an amount of damage that it stopped. Only armor part that took a hit takes any damage.
 
===Charge===
 
 
 
==Armor Rating and Armor Class==
 
===Determining armor class===
{{Formula
|On evaluating AR
|
|
|{{StatusCol|green|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===
{{Formula
|When evaluating effective armor rating
|
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
armorSkill is either npc lightArmorSkill, mediumArmorSkill or heavyArmorSkill dependent on item class
 
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)
 
totalRating is a weighted combination of slot ratings:
totalRating = Cuirass * 0.3 + (Shield + Helm + Greaves + Boots + LPauldron + RPauldron) * 0.1
  + (LGauntlet + RGauntlet) * 0.05 + Shield spell effect magnitude
</syntaxhighlight>
 
 
Note that elemental shield spells do not contribute towards armor.
 
==Persuasion Formulae==
{{Formula
|On persuade dialogue action
|Persuasion options in the NPC dialogue menu.
|
|{{StatusCol|green|Verified}}}}
 
===Shared terms===
<syntaxhighlight lang="python">
persTerm = personality / fPersonalityMod
luckTerm = luck / fLuckMod
repTerm = reputation * fReputationMod
levelTerm = level * fLevelMod
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.
</syntaxhighlight>
Using player stats:
<syntaxhighlight lang="python">
playerRating1 = (repTerm + luckTerm + persTerm + speechcraft) * fatigueTerm
playerRating2 = playerRating1 + levelTerm
playerRating3 = (mercantile + luckTerm + persTerm) * fatigueTerm
</syntaxhighlight>
Using NPC stats (note differences):
<syntaxhighlight lang="python">
npcRating1 = (repTerm + luckTerm + persTerm + speechcraft) * fatigueTerm
npcRating2 = (levelTerm + repTerm + luckTerm + persTerm + npcSpeechcraft) * fatigueTerm
npcRating3 = (mercantile + repTerm + luckTerm + persTerm) * fatigueTerm
</syntaxhighlight>
Summary:
<syntaxhighlight lang="python">
d = 1 - 0.02 * abs(npcDisposition - 50)
target1 = d * (playerRating1 - npcRating1 + 50)
target2 = d * (playerRating2 - npcRating2 + 50)
target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod
where bribeMod is fBribe10Mod, fBribe100Mod or fBribe1000Mod
</syntaxhighlight>
 
 
===Admire===
<syntaxhighlight lang="python">
target1 = max(iPerMinChance, target1)
roll 100, win if roll <= target1
c = int(fPerDieRollMult * (target1 - roll))
x = max(iPerMinChange, c) on success, c on fail
</syntaxhighlight>
 
===Intimidate===
<syntaxhighlight lang="python">
target2 = max(iPerMinChance, target2)
roll 100, win if roll <= target2
 
if roll != target2:
    r = int(target2 - roll)
else:
    r = 1
       
if roll <= target2:
    s = int(r * fPerDieRollMult * fPerTempMult)
    flee = max(iPerMinChange, s)
    fight = min(-iPerMinChange, -s)
 
c = -abs(int(r * fPerDieRollMult))
if success:
    if abs(c) < iPerMinChange:
        x = 0, y = -iPerMinChange
    else:
        x = -int(c * fPerTempMult), y = c
else fail:
    x = int(c * fPerTempMult), y = c
</syntaxhighlight>
 
===Taunt===
<syntaxhighlight lang="python">
target1 = max(iPerMinChance, target1)
roll 100, win if roll <= target1
 
c = abs(int(target1 - roll))
 
if roll <= target1:
    s = c * fPerDieRollMult * fPerTempMult
    flee = min(-iPerMinChange, int(-s))
    fight = max(iPerMinChange, int(s))
x = int(-c * fPerDieRollMult)
if success and abs(x) < iPerMinChange:
    x = -iPerMinChange
</syntaxhighlight>
 
===Bribe===
<syntaxhighlight lang="python">
target3 = max(iPerMinChance, target3)
roll 100, win if roll <= target3
c = int((target3 - roll) * fPerDieRollMult)
x = max(iPerMinChange, c) on success, c on fail
</syntaxhighlight>
 
 
 
===Disposition===
 
For all persuasion actions there is a temporary and a permanent disposition change. The temporary one applies to the disposition meter you see in the dialogue window.
The permanent one is applied when you say goodbye to the NPC; the NPC's disposition is reset to the disposition they had when you initiated the conversation, then the permanent disposition change is applied. You can see these values in the console by using ToggleDialogStats before persuading.
 
For all methods:
<syntaxhighlight lang="python">
temporary disposition change = int(x * fPerTempMult)
</syntaxhighlight>
 
except for Intimidate:
<syntaxhighlight lang="python">
change = x
</syntaxhighlight>
 
This may attempt to change actual disposition below/above 0/100. Disposition changes are clamped so as not to go past the caps, and the actual amount the disposition moved is used in the next function.
<syntaxhighlight lang="python">
permanent disposition change = int(cappedDispositionchange / fPerTempMult)
</syntaxhighlight>
 
except for Intimidate
<syntaxhighlight lang="python">
change = -int(cappedDispositionchange/ fPerTempMult) on success
y on fail
</syntaxhighlight>
 
There may also be modifications to the NPC's flee and fight ratings. The flee and fight variables hold the amount those ratings are changed. They are also capped at 0 and 100.
 
===Comments===
 
The function is long and highly redundant, much of the same formulas are repeatedly calculated many times for no reason.
It's just another poorly coded part of Morrowind. There is at least one bug with Intimidate where you can see if the calculated
change is under iPerMinChange, it fails to set x correctly (it should have the value y does). This is responsible for the disposition meter not moving on some Intimidate Success results.
 
 
 
==NPC Behavior==
===NPC Awareness Check===
{{Formula
|NPC AI
|
|
|{{StatusCol|green|Verified}}}}
 
This check runs every 5 seconds for each NPC. It occurs whether you are sneaking or not, but isn't the same as the combat distance check.
 
====Player side====
<syntaxhighlight lang="python">
if sneaking:
    sneakTerm = fSneakSkillMult * sneak + 0.2 * agility + 0.1 * luck + bootWeight * fSneakBootMult
else:
    sneakTerm = 0
 
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
distTerm = fSneakDistBase + fSneakDistMult*dist
x = sneakTerm * distTerm * fatigueTerm + chameleon (+ 100 if invisible)
</syntaxhighlight>
 
====NPC side====
<syntaxhighlight lang="python">
npcTerm = npcSneak + 0.2 * npcAgility + 0.1 * npcLuck - npcBlind
npcFatigueTerm = fFatigueBase - fFatigueMult * (1 - normalisedFatigue)
using NPC normalisedFatigue
 
if PC is behind NPC (180 degrees):
    y = npcTerm * npcFatigueTerm * fSneakNoViewMult
else:
    y = npcTerm * npcFatigueTerm * fSneakViewMult
</syntaxhighlight>
 
====Final check====
<syntaxhighlight lang="python">
target = x - y
roll 100, win if roll < target
</syntaxhighlight>
 
====Comments====
 
Appears straightforward and bug-free. NPCs can take up to five seconds to notice you even if you are not sneaking. This function precedes the combat distance check. I have not identified if there is a line of sight check occuring before or after.
 
===Combat Behavior===
 
====Combat AI====
 
====What Calm Does====
 
====What Demoralize Does====
 
====What Frenzy Does====
 
====What Rally Does====
 
 
 
==Pickpocketing==
{{Formula
|On pickpocket (activate while sneaking)
|
|
|{{StatusCol|orange|Mechanic is broken and shouldn't be used as it is, but verified}}}}
Pickpocketing is a multi-stage process. Not all items in the NPC's inventory are available, depending on the initial rolls. There are checks on a steal attempt, and when the window is closed.
 
 
===On initiating===
<syntaxhighlight lang="python">
for each item stack:
    roll 100, stack is visible if roll <= pcSneak
</syntaxhighlight>
 
 
===On picking an item===
<syntaxhighlight lang="python">
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.
 
#checks the whole stack no matter how many you try to take
#note: filled soulgems have the value of an empty soulgem due to a missing calculation
stackValue = itemValue * itemsInStack
valueTerm = 10 * fPickPocketMod * stackValue
 
x = (0.2 * pcAgility + 0.1 * pcLuck + pcSneak) * fatigueTerm
y = (valueTerm + npcSneak + 0.2 * npcAgilityTerm + 0.1 * npcLuckTerm) * npcFatigueTerm
t = x - y + x  # yes, that is what it does
 
if t < pcSneak / iPickMinChance:
    roll 100, win if roll <= int(pcSneak / iPickMinChance)
else:
    t = min(iPickMaxChance, t)
    roll 100, win if roll <= int(t)
</syntaxhighlight>
 
 
===On closing the pickpocket window===
 
Same calculation as taking an item, but with valueTerm = 0
 
 
===Comments===
 
The stealing process is highly broken for most items; any item or stack of items worth over 100 septims has such a negative result that it is picked at minimum chance, and this is at maximum all stats. A player with stats around 50 is picking at minimum for anything valuable. The available items window is not reset after every successful steal, only when you close the window and retry the pickpocket.
 
 
 
==Factions==
 
===Favored Attributes===
 
===Favored Skills===
 
===Ranks===
 
===Faction vs. Faction Modifiers===
 
===Effect on Disposition===
 
===Effect on Behavior===
 
 
 
==PC's levelling==
 
How to achieve new level and what happens then.
 
 
 
===PC Level Progress===
{{Formula
|On skill level-up
|
|
|{{StatusCol|orange|Usable, doesn't cover how all of the counters wrap on level up}}}}
When player receive point in the skill:
 
<syntaxhighlight lang="python">
int total # global counter of skill increases
int attribCounter[8] # counter of attribute bonuses
if skill in Major:
    total += iLevelUpMajorMult
    attribCounter [skill->basicAttribute] += iLevelUpMajorMultAttribute
 
if skill in Minor:
    total += iLevelUpMinorMult
    attribCounter [skill->basicAttribute] += iLevelUpMinorMultAttribute
if skill in Misc:
    attribCounter [skill->basicAttribute] += iLevelUpMinorMultAttriubte # note: game setting name has a typo
if total >= iLevelUpTotal:
    level up
</syntaxhighlight>
 
 
 
=== Levelling up ===
{{Formula
|On resting when a level up is available
|
|
|{{StatusCol|orange|Requires documentation of level-up conditions and GMSTs}}}}
On level-up PC get 3 points to redistribute, bonus will be this:
 
<syntaxhighlight lang="python">
if attribCounter != 0
    bonus = iLevelUp$$Mult
else
    bonus = 1
</syntaxhighlight>
 
where $$ is value of attribute counter
 
 
 
===Skill increases===
{{Formula
|On exercising a skill
|
|
|{{StatusCol|green|Accurate, but doesn't cover how progress wraps on level up and multiple level ups per action}}}}
 
Based on research on leveling of the Alchemy skill (ref: http://openmw.org/forum/viewtopic.php?f=2&t=853)
 
<code>level_progress = 1 / ((level + 1) * (1/skill_gain_factor) * skill_type_GMST * specialisation_bonus)</code>
 
'''level_progress''' = the progress factor through a level (from 0 to 1).
 
'''level''' = current level of the skill.
 
'''skill_gain_factor''' = skill gain factor(s) that come from the skill records.
* For alchemy only "Potion creation" (default value: 2.00) is used.
* For other skills, please add alphabetically.
 
'''skill_type_GMST''' = value of the GMST corresponding skill type:
* Major skill: GMST fMajorSkillBonus (default: 0.75)
* Minor skill: GMST fMinorSkillBonus (default: 1.00)
* Misc skill: GMST fMiscSkillBonus (default: 1.25)
 
'''specialisation_bonus''' = value of fSpecialSkillBonus (default: 0.80) when the Player has the same specialization as the skill or 1.00 if not.
 
===== On using player->setSkill =====
: Experiments on Alchemy showed that using player->setAlchemy did change the alchemy level, but not the skill progress. Therefore, when lowering the  alchemy level, the skill progress will be recalculated for that level en give a higher progress. When increasing the alchemy level, the reverse will happen, resulting in a lower skill progress.
: To reset the progress, level first to the next level and then use player->setSkill to the target level.
 
 
 
==PC's/NPC's dynamic stats==
 
{{Formula
|Recalculated when base attributes are modified
|Health points, magicka, fatigue, and disposition
|
|{{StatusCol|orange|Ignores all magicka multiplier GMSTs, ignores differences between PC and NPC calculations}}}}
 
 
 
===HP===
 
Initial HP
<syntaxhighlight lang="python">
health = int(0.5 * (baseStrength + baseEndurance))
</syntaxhighlight>
 
At every level-up
<syntaxhighlight lang="python">
occurs after attributes have been increased by the level-up
bonusHP = fLevelUpHealthEndMult * baseEndurance
</syntaxhighlight>
 
 
===Magicka points===
Every time intelligence or magicka multiplier is modified
<syntaxhighlight lang="python">
M = total magic bonus from race, item, sign (Maximum Magicka Multiplier and Stunted Magicka effects) and magicka GMSTs
maxMagicka = M * intelligence
currentMagicka is rescaled to preserve % left
</syntaxhighlight>
 
 
===Fatigue===
Every time a base stat is modified
<syntaxhighlight lang="python">
maxFatigue = strength + willpower + agility + endurance
currentFatigue is rescaled to preserve % left
</syntaxhighlight>
 
 
===Disposition===
{{Formula
|All disposition using functions
|Recalculated every time effective disposition is required. Setting disposition assumes you are specifying the effective disposition, so the base disposition is adjust by the delta between the current and desired effective disposition.
|
|{{StatusCol|green|Verified}}}}
<syntaxhighlight lang="python">
x = baseDisposition (from npc reference)
if pc and npc are the same race: x += fDispRaceMod
x += fDispPersonalityMult * (pcPersonality - fDispPersonalityBase)
 
if pc and npc are the same faction and pc is not expelled:
    reaction = faction reaction for same faction
    rank = rank in shared faction, range [0..9]
else if npc has a faction:
    reaction = min(faction reactions for all pc factions) (most hated faction) (0 if no faction)
    rank = 0
else:
    reaction = 0, rank = 0
 
x += (fDispFactionRankMult * rank + fDispFactionRankBase) * fDispFactionMod * reaction
x -= fDispCrimeMod * pcBounty
if pc has a disease: x += fDispDiseaseMod
if pc has weapon drawn: x += fDispWeaponDrawn
 
effective disposition = int(x), normally clamped to [0..100] when used
</syntaxhighlight>
 
==NPC Auto-calculate stats==
{{Formula
|On creating an NPC
|NPCs' auto-calculated stats. Affected by race, class, faction and rank.
|
|{{StatusCol|green|Verified}}}}
 
 
===Attributes===
 
<syntaxhighlight lang="python">
for each attribute:
base = race base attribute (+ 10 if a class primary attribute)
 
k = 0
for each skill with this governing attribute:
    if skill is class major: k += 1
    if skill is class minor: k += 0.5
    if skill is miscellaneous: k += 0.2
 
final attribute = base + k * (level - 1)
round attribute to nearest, half to nearest even (standard IEEE 754 rounding mode)
</syntaxhighlight>
 
 
===Health===
<syntaxhighlight lang="python">
health = 0.5 * (strength + endurance) + 5 * (level - 1)
</syntaxhighlight>
 
 
===Skills===
<syntaxhighlight lang="python">
for each skill:
 
    if skill is class major: base = 30, k = 1
    if skill is class minor: base = 15, k = 1
    if skill is miscellaneous: base = 5, k = 0.1
    if skill is in class specialization: base += 5, k += 0.5
    if skill has race bonus: base += racebonus
 
    final skill = base + k * (level - 1)
    round skill to nearest, half to nearest even (standard IEEE 754 rounding mode)
</syntaxhighlight>
 
 
===Reputation===
<syntaxhighlight lang="python">
if not in a faction:
    reputation = 0
else:
    reputation = iAutoRepFacMod * rank + iAutoRepLevMod * (level - 1)
    where the entry level rank in the faction means rank = 1
</syntaxhighlight>
 
 
 
===Spells===
 
==Skills==
 
===Enchanting===
{{Formula
|On PC enchant attempt / recharge attempt
|
|
|{{StatusCol|red|Self-enchanting formula ignores fatigue and some GMSTs, recharge is verified}}}}
 
====Self-enchanting====
for a constant effect item:
<syntaxhighlight lang="python">
chance = Enchant + 0.125 * Intelligence + 0.25 * Luck - 5 * [enchantment points]
note: last term in above formula is solved for default GMSTs, full version is:
7.5/(fEnchantmentChanceMult*fEnchantmentConstantChanceMult) * [enchantment points]
</syntaxhighlight>
 
 
====Enchanted item recharge====
<syntaxhighlight lang="python">
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
 
enchantTerm = enchant skill
 
luckTerm = 0.1 * luck
if luckTerm < 1 or luckTerm > 10: luckTerm = 1
 
intelligenceTerm = 0.2 * intelligence
if intelligenceTerm > 20: intelligenceTerm = 20
if intelligenceTerm < 1: intelligenceTerm = 1
 
x = (enchantTerm + intelligenceTerm + luckTerm) * fatigueTerm
roll 100, success if roll < x
on success restore charge: soulgem charge * (roll / x)
</syntaxhighlight>
 
 
=====Comments=====
Recharging for most characters has a good chance of wasting a soul gem, as the enchant skill is the dominant term used for success. You would require enchant skill of over 65 with average stats to have a 100% success rate. The amount restored is a uniform random percentage of the soul gem, except if you have over a 100% success rate, in which case you will never get the full charge range out of a gem. The missing range increases as your skill does, but the lost charge is no more than 25% at the natural stat limit. Finally, note the strange luck term capping behaviour.
 
 
 
===Armorer===
{{Formula
|On using a repair item
|
|
|{{StatusCol|orange|Verified, but original contains bugs}}}}
<syntaxhighlight lang="python">
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
 
x = (0.1 * pcStrength + 0.1 * pcLuck + armorerSkill) / fatigueTerm
roll 100, if roll <= x then repair continues
 
y = int(fRepairAmountMult * hammerQuality * roll)
y = max(1, y)
repair item by y points
</syntaxhighlight>
 
 
=====Comments=====
Bug in original game: Being more tired makes it easier to repair an item. The game should have multiplied by fatigueTerm instead of dividing by it.
 
 
 
===Alchemy===
{{Formula
|On potion creation
|
|
|{{StatusCol|red|Fatigue is not accounted for; all alchemy GMSTs are not accounted for}}}}
 
'''Creating potions'''
 
Positive attributes:
 
<syntaxhighlight lang="python">
duration = [(Alchemy + [(Intelligence + Luck) / 10]) / Cost] * quality_of_the_mortar
power = duration / 3
</syntaxhighlight>
 
''The cost is related to basic cost of effect.''
 
Negative attributes:
 
* Alembic reduces the time of negative effects:
<syntaxhighlight lang="python">
duration_with_alembic = duration_without_alembic / ( 1 + quality_of_alembic)
</syntaxhighlight>
 
* Power of negative effects:
<syntaxhighlight lang="python">
power = duration_with_alembic / 3
</syntaxhighlight>
 
 
The image of a created potion is picked at random among the available.
 
It stays the same until one of the following things occur:
 
1. Change of quality or amount of any/all apparatus.
 
2. Adding or removing ingredients.
 
3. Any skill gain.
 
4. Loading a save.
 
 
'''Eating ingredients'''
 
Simply eating ingredients raw will improve your alchemy skill. (Though at 1/4 the rate of mixing potions.)
 
 
Eating has a chance to apply the first effect of an ingredient onto the player.
 
<syntaxhighlight lang="python">
Duration = (magnitude * 2) + 1
</syntaxhighlight>
 
Magnitude is a random value between 1 and max_magnitude.
 
<syntaxhighlight lang="python">
Max_magnitude = floor(0.152 * Alch + 0.0158 * Luck + 0.0324 * Int)
</syntaxhighlight>
 
 
The chance to fail is based on alchemy, intelligence and luck. The exact formula is to be determined.
 
 
 
===Security===
{{Formula
|On lockpicking and probing
|Affects doors and containers.
|
|{{StatusCol|green|Verified}}}}
 
====On picking a lock====
<syntaxhighlight lang="python">
x = 0.2 * pcAgility + 0.1 * pcLuck + securitySkill
x *= pickQuality * fatigueTerm
x += fPickLockMult * lockStrength
 
if x <= 0: fail and report impossible
roll 100, if roll <= x then open lock else report failure
</syntaxhighlight>
 
 
====On probing a trap====
<syntaxhighlight lang="python">
x = 0.2 * pcAgility + 0.1 * pcLuck + securitySkill
x += fTrapCostMult * trapSpellPoints
x *= probeQuality * fatigueTerm
 
if x <= 0: fail and report impossible
roll 100, if roll <= x then untrap else report failure
</syntaxhighlight>
 
===Acrobatics===
{{Formula
|On jumping and landing
|Acrobatics
|
|{{StatusCol|orange|Initial velocity verified; requires testing in combination with physics system}}}}
 
====On jumping====
<syntaxhighlight lang="python">
encumbranceTerm = fJumpEncumbranceBase + fJumpEncumbranceMultiplier*(1 - normalisedEncumbrance)
where normalisedEncumbrance is a function of encumbrance. empty bar -> 0.0, full bar -> 1.0
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
 
if acrobaticsSkill <= 50:
    a = acrobaticsSkill, b = 0
else:
    a = 50, b = acrobaticsSkill - 50
 
x = fJumpAcrobaticsBase + pow(a / 15.0, fJumpAcroMultiplier)
x += 3 * b * fJumpAcroMultiplier
x += jumpSpellBonus * 64
x *= encumbranceTerm
if actor is running: x *= fJumpRunMultiplier
x *= fatigueTerm
x -= gravityAcceleration [-627.2 exactly]
x /= 3
 
if actor is standing still:
    set kinematic velocity to {0, 0, x}
 
if actor is moving:
    groundVelocity = normalize({actorVelocity.x, actorVelocity.y})
    set kinematic velocity to 0.707 * x * {groundVelocity.x, groundVelocity.y, 1.0}
 
decrease fatigue by fFatigueJumpBase + (1 - normalisedEncumbrance) * fFatigueJumpMult
</syntaxhighlight>
 
 
====On landing====
<syntaxhighlight lang="python">
fallingDist = distance from peak height
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
 
if fallingDist <= fFallDamageDistanceMin: soft landing; skip the rest of the function
 
x = fallingDist - fFallDamageDistanceMin
x -= 1.5 * acrobaticsSkill + jumpSpellBonus
x = max(0, x)
 
a = fFallAcroBase + fFallAcroMult * (100 - acrobaticsSkill)
x = fFallDistanceBase + fFallDistanceMult * x
x *= a
 
if x > 0: damage health by x * (1 - 0.25 * fatigueTerm)
 
if acrobaticsSkill * fatigueTerm < x: actor falls over
 
if actor is not incapacitated: acrobatics skill exercised (fall damage)
</syntaxhighlight>
 
 
====Comments====
Note that initial actor velocity is taken into account. Animation-driven kinematics mean the jump direction can be offset from the player facing if there is root bone movement. Agility does not appear to be involved in this calculation.
 
==Sources==
 
* [http://forums.bethsoft.com/index.php?/topic/1097214-gameplay-mechanics-analysis/ bethsoft forum]
* [http://www.uesp.net/wiki/Morrowind:Combat Uesp's wiki: Combat section]

Latest revision as of 18:29, 3 September 2012

Redirect to: