Research:Player Craft Skills

From OpenMW Wiki
Jump to navigation Jump to search


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)