91 lines
3.4 KiB
Lua
91 lines
3.4 KiB
Lua
-- Bounty boards scattered through the dungeon from depth two on. Each board
|
|
-- is built with exactly one bounty already written on it (a cull, a hoard or
|
|
-- a depth pact), fixed at generation time from the floor's own rng so a given
|
|
-- (worldSeed, depth) always offers the identical bounty. Standing on an
|
|
-- unclaimed board binds the player to its pact for free (no turn spent, same
|
|
-- as learning a skill); a board only ever grants one bounty in its lifetime,
|
|
-- so re-rolling by revisiting an already-claimed board is not possible. A
|
|
-- bound pact resolves itself the instant its condition is met - no trip back
|
|
-- to the board required - paying out gold, xp and essence in one stroke.
|
|
|
|
local Monsters = require("monsters")
|
|
|
|
local Quests = {}
|
|
|
|
Quests.CULL = "cull"
|
|
Quests.HOARD = "hoard"
|
|
Quests.DEPTH = "depth"
|
|
|
|
-- Fixed order so a given rng draw always names the same kind.
|
|
Quests.order = { Quests.CULL, Quests.HOARD, Quests.DEPTH }
|
|
|
|
-- Builds the one bounty a freshly placed board offers on `depth`, clamping any
|
|
-- depth target to `maxDepth` so a pact can never demand the unreachable.
|
|
function Quests.generate(rng, depth, maxDepth)
|
|
local kind = Quests.order[rng:range(1, #Quests.order)]
|
|
local bounty = {
|
|
kind = kind,
|
|
rewardGold = 15 + depth * 8,
|
|
rewardXp = 10 + depth * 4,
|
|
}
|
|
if kind == Quests.CULL then
|
|
local eligible = Monsters.forDepth(depth)
|
|
local spec = eligible[rng:range(1, #eligible)]
|
|
bounty.species = spec.key
|
|
bounty.speciesName = spec.name
|
|
bounty.need = rng:range(2, 4)
|
|
bounty.have = 0
|
|
elseif kind == Quests.HOARD then
|
|
bounty.need = 40 + depth * 20
|
|
else
|
|
bounty.need = math.min(depth + rng:range(2, 4), maxDepth)
|
|
end
|
|
return bounty
|
|
end
|
|
|
|
function Quests.describe(bounty)
|
|
if bounty.kind == Quests.CULL then
|
|
return string.format("cull %d %s (%d/%d)", bounty.need, bounty.speciesName, bounty.have, bounty.need)
|
|
elseif bounty.kind == Quests.HOARD then
|
|
return string.format("amass %d gold", bounty.need)
|
|
end
|
|
return string.format("reach depth %d", bounty.need)
|
|
end
|
|
|
|
-- Binds `player` to `board`'s bounty, provided the player is not already
|
|
-- bound to one and the board has not already been claimed. Returns (true,
|
|
-- line) on success or (false, line) explaining why not.
|
|
function Quests.accept(player, board)
|
|
if player.quest then
|
|
return false, "you already hold a bounty"
|
|
end
|
|
if board.claimed then
|
|
return false, "this board's ledger is already spent"
|
|
end
|
|
board.claimed = true
|
|
player.quest = board.quest
|
|
return true, "you bind yourself to a bounty: " .. Quests.describe(board.quest)
|
|
end
|
|
|
|
-- Registers a kill toward the player's active cull bounty if the species
|
|
-- matches. A no-op for any other kind, an unrelated species, or no bounty.
|
|
function Quests.registerKill(player, species)
|
|
local bounty = player.quest
|
|
if bounty and bounty.kind == Quests.CULL and species == bounty.species and bounty.have < bounty.need then
|
|
bounty.have = bounty.have + 1
|
|
end
|
|
end
|
|
|
|
-- True once `player`'s active bounty (if any) is fulfilled by the current
|
|
-- state: a cull once its tally is met, a hoard once live gold clears its
|
|
-- absolute target, a depth pact once the player has reached it.
|
|
function Quests.resolved(player, depth)
|
|
local bounty = player.quest
|
|
if not bounty then return false end
|
|
if bounty.kind == Quests.CULL then return bounty.have >= bounty.need end
|
|
if bounty.kind == Quests.HOARD then return player.gold >= bounty.need end
|
|
return depth >= bounty.need
|
|
end
|
|
|
|
return Quests
|