feat: add Quests.generate, Quests.describe and Ranged.ammoIndex

This commit is contained in:
dualfroz committed 2026-09-07 18:59:25 +02:00
1 parent 54569d9e04
commit 10488b48e8
2 files changed
+163

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
-- 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