Research:Trading and Services: Difference between revisions
(Migrated Trading and Services from Formulae page.) |
No edit summary |
||
(19 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
{{Template:Research Navbox}} | |||
==Trading and services== | ==Trading and services== | ||
Line 39: | Line 42: | ||
{{Formula | {{Formula | ||
|On choosing/offering an item to buy/sell | |On choosing/offering an item to buy/sell | ||
| | |Determining item/stack base prices and adjusted prices. | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified}}}} | |{{StatusCol|green|Verified}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
Line 56: | Line 59: | ||
{{Formula | {{Formula | ||
|On | |On finalizing a transaction. | ||
|Includes haggling mechanic. | |Includes haggling mechanic. Uses common formula [[Research:Common_Terms#fatigueTerm|fatigueTerm]]. | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified}}}} | |{{StatusCol|green|Verified}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
Line 69: | Line 72: | ||
b = abs(playerOffer) | b = abs(playerOffer) | ||
if buying: d = int(100 * (a - b) / a) | if buying: d = int(100 * (a - b) / a) | ||
if selling: d = int(100 * (b - a) / | if selling: d = int(100 * (b - a) / b) | ||
clampedDisposition = clamp int(npcDisposition) to [0..100] | clampedDisposition = clamp int(npcDisposition) to [0..100] | ||
dispositionTerm = fDispositionMod * (clampedDisposition - 50) | |||
pcTerm = (dispositionTerm + pcMercantile + 0.1 * pcLuck + 0.2 * pcPersonality) * pcFatigueTerm | |||
npcTerm = (npcMercantile + 0.1 * npcLuck + 0.2 * npcPersonality) * npcFatigueTerm | npcTerm = (npcMercantile + 0.1 * npcLuck + 0.2 * npcPersonality) * npcFatigueTerm | ||
x = fBargainOfferMulti * d + fBargainOfferBase | x = fBargainOfferMulti * d + fBargainOfferBase | ||
Line 81: | Line 85: | ||
adjust npc temporary disposition by iBarterSuccessDisposition or iBarterFailDisposition | adjust npc temporary disposition by iBarterSuccessDisposition or iBarterFailDisposition | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===Merchant repair=== | ===Merchant repair=== | ||
Line 87: | Line 90: | ||
|On opening the repair service window | |On opening the repair service window | ||
| | | | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified}}}} | |{{StatusCol|green|Verified}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
Line 97: | Line 100: | ||
cost = barterOffer(npc, x, buying) | cost = barterOffer(npc, x, buying) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===Trainers=== | ===Trainers=== | ||
Line 103: | Line 105: | ||
|On purchasing training | |On purchasing training | ||
| | | | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified, but buggy. See notes for solution.}}}} | |{{StatusCol|green|Verified, but buggy. See notes for solution.}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
Line 109: | Line 111: | ||
</syntaxhighlight> | </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. | 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=== | ===Spell merchant=== | ||
Line 115: | Line 116: | ||
|On purchasing a spell | |On purchasing a spell | ||
| | | | ||
|{{StatusCol|green|Implemented}} | |||
|{{StatusCol|green|Verified}}}} | |||
<syntaxhighlight lang="python"> | |||
cost of purchasing existing spell = barterOffer(npc, spell.magickaCost * fSpellValueMult, buying) | |||
</syntaxhighlight> | |||
{{Formula | |||
|On purchasing from a spellmaking service | |||
| | | | ||
|{{StatusCol|orange| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|orange|Needs testing}}}} | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
y = 0 | |||
for each effect in spell: | |||
x = 0.5 * (max(1, effect.magnitudeMin) + max(1, effect.magnitudeMax)) | |||
x *= 0.1 * effect.magicEffect.baseMagickaCost | |||
x *= 1 + effect.duration | |||
x += 0.05 * max(1, effect.area) * effect.magicEffect.baseMagickaCost | |||
y += x * fEffectCostMult | |||
y = max(1, y) | |||
if effect.rangeType & CAST_TARGET: y *= 1.5 | |||
cost of spellmaking = barterOffer(npc, | magickaCost = int(y) | ||
cost of spellmaking = barterOffer(npc, magickaCost * fSpellMakingValueMult, buying) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
===Enchanting merchant=== | ===Enchanting merchant=== | ||
Line 128: | Line 147: | ||
|On purchasing an enchantment | |On purchasing an enchantment | ||
| | | | ||
| | |{{StatusCol|orange|Changes since last implementation}} | ||
|{{StatusCol|orange| | |{{StatusCol|orange|Requires verification}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
cost of enchanting service = barterOffer(npc, | y = 0 | ||
z = [] | |||
enchantPoints = 0 | |||
for each effect in enchantment: | |||
if enchantment is constant effect: | |||
if effect.magnitudeMin > 1 or effect.magnitudeMax > 1 or effect.radius > 1: | |||
effect.duration = int(fEnchantmentConstantDurationMult) | |||
else: | |||
effect.duration = 100 | |||
x = 0.5 * (max(1, effect.magnitudeMin) + max(1, effect.magnitudeMax)) | |||
x *= 0.1 * effect.magicEffect.baseMagickaCost | |||
x *= effect.duration # note difference from spellmaking | |||
x += 0.05 * max(1, spell.radius) * effect.magicEffect.baseMagickaCost | |||
y += x * fEffectCostMult | |||
y = max(1, y) | |||
if effect.rangeType & CAST_TARGET: y *= 1.5 | |||
enchantPoints += int(y) | |||
z[effect.order] = int(pcEnchantSkill - y * fEnchantmentChanceMult) | |||
# note enchantPoints not used for cost | |||
cost of enchanting service = barterOffer(npc, y * fEnchantmentValueMult, buying) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
====Comments==== | |||
Multiple enchantments stack in a expensive manner, as both variables y and enchantPoints are accumulators. Strangely, the price is based on the equivalent to the spellmaking accumulator rather than the enchantment points used. Constant effect enchantments override the duration in an over-complicated manner. As fEnchantmentConstantDurationMult is 100 by default, it would be much cleaner to use that for duration in both cases. | |||
Line 142: | Line 188: | ||
|On travelling via silt strider, boat or similar travel service AI | |On travelling via silt strider, boat or similar travel service AI | ||
| | | | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|orange|Requires independent testing}}}} | |{{StatusCol|orange|Requires independent testing}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
Line 149: | Line 195: | ||
time = int(dist / fTravelTimeMult) | time = int(dist / fTravelTimeMult) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
====Guild guide==== | ====Guild guide==== | ||
Line 155: | Line 200: | ||
|On travelling via Mages' Guild teleport service | |On travelling via Mages' Guild teleport service | ||
| | | | ||
| | |{{StatusCol|green|Implemented}} | ||
|{{StatusCol|green|Verified}}}} | |{{StatusCol|green|Verified}}}} | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
cost = barterOffer(npc, fMagesGuildTravel, buying) | cost = barterOffer(npc, fMagesGuildTravel, buying) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
====Comments==== | |||
NPCs currently following the player and currently in range when the player selects the Travel option will come with. Travel costs are actually scaled (multiplied) according to the number of passengers taken, however, there seems to be a bug where the first follower is ignored (or free): the price will only increase if the player has more than one follower. So if the player has 4 followers in range, he will pay quadruple the price he would pay alone (or with a single follower), rather than 5x the price (for 5 passengers total). | |||
The same likely applies to creatures that are following, but that needs confirmation. | |||
{{Template:Research Navbox}} |
Latest revision as of 05:21, 6 July 2017
Trading and services
Common mechanics
Barter function
All bartering and services use a common function to evaluate costs. Uses common formula fatigueTerm for each side of a transaction.
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)
Bartering
Actions affected | On choosing/offering an item to buy/sell |
Description | Determining item/stack base prices and adjusted prices. |
Implementation status | Implemented |
Analysis status | Verified |
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)
Actions affected | On finalizing a transaction. |
Description | Includes haggling mechanic. Uses common formula fatigueTerm. |
Implementation status | Implemented |
Analysis status | Verified |
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) / b)
clampedDisposition = clamp int(npcDisposition) to [0..100]
dispositionTerm = fDispositionMod * (clampedDisposition - 50)
pcTerm = (dispositionTerm + 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
Merchant repair
Actions affected | On opening the repair service window |
Description | |
Implementation status | Implemented |
Analysis status | Verified |
p = max(1, basePrice)
r = max(1, int(maxDurability / p))
x = int((maxDurability - durability) / r)
x = int(fRepairMult * x)
cost = barterOffer(npc, x, buying)
Trainers
Actions affected | On purchasing training |
Description | |
Implementation status | Implemented |
Analysis status | Verified, but buggy. See notes for solution. |
cost of training a skill = barterOffer(npc, pcSkill * iTrainingMod, buying)
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
Actions affected | On purchasing a spell |
Description | |
Implementation status | Implemented |
Analysis status | Verified |
cost of purchasing existing spell = barterOffer(npc, spell.magickaCost * fSpellValueMult, buying)
Actions affected | On purchasing from a spellmaking service |
Description | |
Implementation status | Implemented |
Analysis status | Needs testing |
y = 0
for each effect in spell:
x = 0.5 * (max(1, effect.magnitudeMin) + max(1, effect.magnitudeMax))
x *= 0.1 * effect.magicEffect.baseMagickaCost
x *= 1 + effect.duration
x += 0.05 * max(1, effect.area) * effect.magicEffect.baseMagickaCost
y += x * fEffectCostMult
y = max(1, y)
if effect.rangeType & CAST_TARGET: y *= 1.5
magickaCost = int(y)
cost of spellmaking = barterOffer(npc, magickaCost * fSpellMakingValueMult, buying)
Enchanting merchant
Actions affected | On purchasing an enchantment |
Description | |
Implementation status | Changes since last implementation |
Analysis status | Requires verification |
y = 0
z = []
enchantPoints = 0
for each effect in enchantment:
if enchantment is constant effect:
if effect.magnitudeMin > 1 or effect.magnitudeMax > 1 or effect.radius > 1:
effect.duration = int(fEnchantmentConstantDurationMult)
else:
effect.duration = 100
x = 0.5 * (max(1, effect.magnitudeMin) + max(1, effect.magnitudeMax))
x *= 0.1 * effect.magicEffect.baseMagickaCost
x *= effect.duration # note difference from spellmaking
x += 0.05 * max(1, spell.radius) * effect.magicEffect.baseMagickaCost
y += x * fEffectCostMult
y = max(1, y)
if effect.rangeType & CAST_TARGET: y *= 1.5
enchantPoints += int(y)
z[effect.order] = int(pcEnchantSkill - y * fEnchantmentChanceMult)
# note enchantPoints not used for cost
cost of enchanting service = barterOffer(npc, y * fEnchantmentValueMult, buying)
Comments
Multiple enchantments stack in a expensive manner, as both variables y and enchantPoints are accumulators. Strangely, the price is based on the equivalent to the spellmaking accumulator rather than the enchantment points used. Constant effect enchantments override the duration in an over-complicated manner. As fEnchantmentConstantDurationMult is 100 by default, it would be much cleaner to use that for duration in both cases.
Travel costs
Physical travel
Actions affected | On travelling via silt strider, boat or similar travel service AI |
Description | |
Implementation status | Implemented |
Analysis status | Requires independent testing |
dist = distance from player to destination
cost = barterOffer(npc, int(dist / fTravelMult), buying)
time = int(dist / fTravelTimeMult)
Guild guide
Actions affected | On travelling via Mages' Guild teleport service |
Description | |
Implementation status | Implemented |
Analysis status | Verified |
cost = barterOffer(npc, fMagesGuildTravel, buying)
Comments
NPCs currently following the player and currently in range when the player selects the Travel option will come with. Travel costs are actually scaled (multiplied) according to the number of passengers taken, however, there seems to be a bug where the first follower is ignored (or free): the price will only increase if the player has more than one follower. So if the player has 4 followers in range, he will pay quadruple the price he would pay alone (or with a single follower), rather than 5x the price (for 5 passengers total). The same likely applies to creatures that are following, but that needs confirmation.