feat: add Shop.priceOf and Shop.sellValue
This commit is contained in:
1 parent
2eb903beb5
commit
3fe3f21de4
1 file changed
+115
+115
@@ -0,0 +1,115 @@
|
|||||||
|
-- Gold economy: a price catalog for tradeable goods plus the buy/sell math for
|
||||||
|
-- a shopkeeper's wares. Wares seeded on the floor by the builder carry a
|
||||||
|
-- `price` and `shopId` tag on the ground entry (not the item), so a purchased
|
||||||
|
-- item is indistinguishable from a found one once it reaches the inventory.
|
||||||
|
|
||||||
|
local Items = require("items")
|
||||||
|
local Affixes = require("affixes")
|
||||||
|
local Factions = require("factions")
|
||||||
|
local Mercenaries = require("mercenaries")
|
||||||
|
|
||||||
|
local Shop = {}
|
||||||
|
|
||||||
|
local POTION_PRICE = {
|
||||||
|
heal = 15, poison = 6, strength = 45, haste = 25, clarity = 20, cure = 30,
|
||||||
|
confuse = 8, blind = 8, levitate = 20,
|
||||||
|
}
|
||||||
|
local SCROLL_PRICE = { teleport = 25, identify = 18, mapping = 20, fireball = 35 }
|
||||||
|
local FLAT_PRICE = { [Items.FOOD] = 5, [Items.TOME] = 55, [Items.PICK] = 40 }
|
||||||
|
local AMMO_UNIT_PRICE = 2
|
||||||
|
|
||||||
|
-- Catalog price for any tradeable item. Gear scales with its effective power
|
||||||
|
-- or bonuses so a heavily-affixed drop costs more than a plain one.
|
||||||
|
function Shop.priceOf(item)
|
||||||
|
if item.kind == Items.POTION then
|
||||||
|
return POTION_PRICE[item.effect] or 10
|
||||||
|
elseif item.kind == Items.SCROLL then
|
||||||
|
return SCROLL_PRICE[item.effect] or 10
|
||||||
|
elseif item.kind == Items.WEAPON or item.kind == Items.ARMOR then
|
||||||
|
return 20 + item.power * 15
|
||||||
|
elseif item.kind == Items.BOW then
|
||||||
|
return 25 + item.power * 12
|
||||||
|
elseif item.kind == Items.RING then
|
||||||
|
return 30 + (item.attack + item.defense) * 20
|
||||||
|
elseif item.kind == Items.AMMO then
|
||||||
|
return AMMO_UNIT_PRICE * (item.count or 1)
|
||||||
|
elseif item.kind == Items.CONTRACT then
|
||||||
|
return Mercenaries.byKey[item.hireling].hireCost
|
||||||
|
else
|
||||||
|
return FLAT_PRICE[item.kind] or 10
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Shop.sellValue(item)
|
||||||
|
return math.max(1, Shop.priceOf(item) // 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The price a shopkeeper actually charges once the player's standing with
|
||||||
|
-- the merchant guild (see factions.lua) has had its say: sour relations mark
|
||||||
|
-- goods up, warm ones discount them. `ground.price` (stamped once at
|
||||||
|
-- generation - see builder.lua) stays the neutral catalog price throughout,
|
||||||
|
-- so it keeps meaning "what a stranger pays" even as this moves the amount
|
||||||
|
-- actually charged.
|
||||||
|
function Shop.effectivePrice(game, ground)
|
||||||
|
return math.max(1, math.floor(ground.price * Factions.priceMultiplier(game.player, Factions.MERCHANT)))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Buys a warded ground item if the player can afford its reputation-adjusted
|
||||||
|
-- price: deducts gold and transfers the item into the inventory. Returns the
|
||||||
|
-- gold actually spent, or nil without side effects when the player is short.
|
||||||
|
function Shop.buy(game, ground)
|
||||||
|
if not ground.price then return nil end
|
||||||
|
local price = Shop.effectivePrice(game, ground)
|
||||||
|
if game.player.gold < price then return nil end
|
||||||
|
game.player.gold = game.player.gold - price
|
||||||
|
game.player.inventory[#game.player.inventory + 1] = ground.item
|
||||||
|
return price
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sells an inventory item at half its catalog price (further scaled by
|
||||||
|
-- merchant standing) and removes it.
|
||||||
|
function Shop.sell(game, index)
|
||||||
|
local item = game.player.inventory[index]
|
||||||
|
if not item then return nil end
|
||||||
|
local value = math.max(1, math.floor(Shop.sellValue(item) / Factions.priceMultiplier(game.player, Factions.MERCHANT)))
|
||||||
|
game.player.gold = game.player.gold + value
|
||||||
|
table.remove(game.player.inventory, index)
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local weaponBases = { { name = "rapier", power = 3 }, { name = "war hammer", power = 5 } }
|
||||||
|
local armorBases = { { name = "banded mail", power = 2 } }
|
||||||
|
|
||||||
|
-- Rolls a small selection of wares for a shop room. Weapons and armour may
|
||||||
|
-- still carry an affix, so the shop is a genuine alternative to a lucky drop
|
||||||
|
-- rather than a strictly worse copy of one.
|
||||||
|
function Shop.stock(rng)
|
||||||
|
local wares = {}
|
||||||
|
local slots = rng:range(3, 5)
|
||||||
|
for _ = 1, slots do
|
||||||
|
local roll = rng:range(1, 100)
|
||||||
|
local item
|
||||||
|
if roll <= 30 then
|
||||||
|
local effects = { "heal", "strength", "haste", "clarity", "cure" }
|
||||||
|
item = Items.potionOf(effects[rng:range(1, #effects)])
|
||||||
|
elseif roll <= 45 then
|
||||||
|
local effects = { "identify", "mapping", "fireball" }
|
||||||
|
item = Items.scroll(effects[rng:range(1, #effects)])
|
||||||
|
elseif roll <= 62 then
|
||||||
|
local base = weaponBases[rng:range(1, #weaponBases)]
|
||||||
|
item = Affixes.decorate(Items.weapon(base.name, base.power), Affixes.rollWeapon(rng))
|
||||||
|
elseif roll <= 75 then
|
||||||
|
local base = armorBases[rng:range(1, #armorBases)]
|
||||||
|
item = Affixes.decorate(Items.armor(base.name, base.power), Affixes.rollArmor(rng))
|
||||||
|
elseif roll <= 85 then
|
||||||
|
item = Items.ring("ring of the merchant", rng:range(0, 2), rng:range(0, 2))
|
||||||
|
else
|
||||||
|
local key = Mercenaries.order[rng:range(1, #Mercenaries.order)]
|
||||||
|
item = Items.contract(key, "contract: hire the " .. Mercenaries.byKey[key].name)
|
||||||
|
end
|
||||||
|
wares[#wares + 1] = item
|
||||||
|
end
|
||||||
|
return wares
|
||||||
|
end
|
||||||
|
|
||||||
|
return Shop
|
||||||
Reference in new issue
Block a user