Files
warren/src/shop.lua
T

116 lines
4.7 KiB
Lua

-- 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