Research:Player Craft Skills
Enchanting
Self-enchanting
Actions affected | On PC enchant attempt |
Description | |
Implementation status | Changes since last implementation |
Analysis status | Verified, contains bugs |
Original behaviour
This uses the variable z from Enchanting service.
for each effect in the enchantment:
x = z[effect.order] - int(-0.2 * pcIntelligence) + int(0.1 * pcLuck)
x = int(x * fatigueTerm)
if enchantment is constant effect:
x = int(x * fEnchantmentConstantChanceMult)
roll 100, success if roll < x, else fail and destroy gem
Suggested implementation
z = enchantSkill - y * fEnchantmentChanceMult
x = (z + 0.2 * pcIntelligence + 0.1 * pcLuck) * fatigueTerm
if enchantment is constant effect:
x *= fEnchantmentConstantChanceMult
x = int(x)
roll 100, success if roll < x, else fail and destroy gem
Comments
There is a serious bug where failure is checked for every effect instead of once for the whole attempt, while still using the cumulative cost. This makes multiple effect self-enchants near impossible, but doesn't affect single effect self-enchanting. There is also an excessive use of rounding and a double negation, which seem to indicate this function had a rushed implementation.
A better implementation should not have the effect loop, and may omit the excessive integer conversions. One implementation is suggested here.
Enchanted item recharge
Actions affected | On soulgem use |
Description | Recharging with a filled soulgem. Uses common term fatigueTerm. |
Implementation status | Implemented |
Analysis status | Verified |
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 = (pcEnchant + intelligenceTerm + luckTerm) * fatigueTerm
roll 100, success if roll < x
on success restore charge: soulgem charge * (roll / x)
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
Actions affected | On using a repair item |
Description | |
Implementation status | Implemented, bug corrected |
Analysis status | Verified, but original contains bugs |
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
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
Ingredients
Actions affected | On viewing ingredient tooltip |
Description | Effect visibility. |
Implementation status | Implemented |
Analysis status | Verified |
Effects are not immediately apparent without alchemy skill. Ingredient effects are hidden, appearing as '?' unless the player has sufficient alchemy skill.
First effect visible: pcAlchemy >= fWortChanceValue
Second effect visible: pcAlchemy >= 2 * fWortChanceValue
Third effect visible: pcAlchemy >= 3 * fWortChanceValue
Fourth effect visible: pcAlchemy >= 4 * fWortChanceValue
Potions can still be made with the hidden effects, and the resulting effects appear normally in the potion result window.
Potions
Actions affected | On potion creation |
Description | Uses magic effect flags. |
Implementation status | implemented |
Analysis status | Requires regression testing |
consume ingredients on both success and failure
x = pcAlchemy + 0.1 * pcIntelligence + 0.1 * pcLuck
roll 100 vs x, succeed if roll <= x
x *= mortarQuality * fPotionStrengthMult
for each ingredient:
for each effect in ingredient:
if effect.id == -1: continue
magicEffect = lookupEffect(effect.id)
check magicEffect for match with effects in all other ingredients:
effect.id should match
if magicEffect.flags & TARGET_SKILL: effect.targetSkill should match
if magicEffect.flags & TARGET_ATTR: effect.targetAttribute should match
if effect is matched:
if magicEffect is not in effectList, add it
if effectList is empty: return with failure
for each magicEffect in effectList:
if not magicEffect.flags & NO_MAGNITUDE:
if not magicEffect.flags & NO_DURATION:
magnitude = (x / fPotionT1MagMult) / magicEffect.baseMagickaCost
duration = (x / fPotionT1DurMult) / magicEffect.baseMagickaCost
if not magicEffect.flags & NEGATIVE:
if retort and calcinator: (magnitude, duration) += 2*retort + calcinator
if only retort: (magnitude, duration) += retort
if only calcinator: (magnitude, duration) += calcincator
else:
if alembic and calcinator: (magnitude, duration) /= 2*alembic + 3*calcinator
if only alembic: (magnitude, duration) /= 1 + alembic
if only calcinator: (magnitude, duration) += calcinator
else:
magnitude = (x / fPotionT1MagMult) / magicEffect.baseMagickaCost
duration = 1
if not magicEffect.flags & NEGATIVE:
if retort and calcinator: magnitude += 2/3 * (retort + calcinator) + 0.5
if only retort: magnitude *= retort + 0.5
if only calcinator: magnitude *= calcincator + 0.5
else:
if alembic and calcinator: magnitude /= 2*alembic + 3*calcinator
if only alembic: magnitude /= 1 + alembic
if only calcinator: magnitude *= calcinator + 0.5
else:
if not magicEffect.flags & NO_DURATION:
magnitude = 1
duration = (x / fPotionT1DurMult) / magicEffect.baseMagickaCost
if not magicEffect.flags & NEGATIVE:
if retort and calcinator: duration += 2/3 * (retort + calcinator) + 0.5
if only retort: duration *= retort + 0.5
if only calcinator: duration *= calcincator + 0.5
else:
if alembic and calcinator: duration /= 2*alembic + 3*calcinator
if only alembic: duration /= 1 + alembic
if only calcinator: duration *= calcinator + 0.5
else:
magnitude = 1
duration = 1
magnitude = floor(magnitude + 0.5)
duration = floor(duration + 0.5);
if magnitude > 0 and duration > 0: add potionEffect(magicEffect, magnitude, duration) else nullify effect
if all effects are nullified: return with failure
price = int(iAlchemyMod * x)
weight = sum of ingredient weights / total ingredients added
visual = roll { 'Bargain', 'Cheap', 'Exclusive', 'Fresh', 'Quality', 'Standard' }
pick model and icon { 'm/misc_potion_{visual}_01.nif', 'm/tx_potion_{visual}_01.tga' }
exercise alchemy skill (potion creation)
Comments
It is notable how effective a calcinator becomes when combined with a retort or alembic. One of the few mechanics unaffected by fatigue. Each code path is slightly different; the most common (effects with magnitude and duration) acts differently enough with only a calcinator that it may be considered a bug. GMSTs fPotionMinUsefulDuration, fPotionT4BaseStrengthMult, fPotionT4EquipStrengthMult are unused.
Wortcraft
Actions affected | On eating a raw ingredient |
Description | Uses magic effect flags. Uses common term fatigueTerm. |
Implementation status | implemented |
Analysis status | Verified |
magicEffect is the first effect of ingredient eaten
x = (pcAlchemy + 0.2 * pcIntelligence + 0.1 * pcLuck) * fatigueTerm
roll 100, succeed if roll <= x
y = roll / min(x, 100)
y *= 0.25 * x
if not magicEffect.flags & NO_DURATION:
duration = int(y)
else:
duration = 1
if not magicEffect.flags & NO_MAGNITUDE:
if not magicEffect.flags & NO_DURATION:
magnitude = int((0.05 * y) / (0.1 * magicEffect.baseMagickaCost));
else:
magnitude = int(y / (0.1 * baseCost))
magnitude = max(1, magnitude)
else:
magnitude = 1
apply effect(magicEffect, magnitude, duration) to player
exercise alchemy skill (ingredient use)