Files
warren/src/morgue.lua
T

133 lines
5.0 KiB
Lua

-- End-of-run obituary. Turns a finished game's own stats, gear and message
-- log into a NetHack-style morgue report plus a single comparable score,
-- reusing exactly the field names Game:metaStats/graveEntry already expose
-- so a future stat only has to land there to show up here too.
local Curses = require("curses")
local Compendium = require("compendium")
local Titles = require("titles")
local Morgue = {}
Morgue.SCORE_WEIGHTS = {
depth = 100, kill = 10, bossKill = 250, quest = 150, artifact = 200,
vault = 120, setPiece = 180, chest = 15, playerLevel = 50, win = 1000,
}
-- A single deterministic number from a Morgue.build report, so two runs
-- (or a run against its own best-so-far) can be ranked without re-reading
-- every field by hand.
function Morgue.score(report)
local w = Morgue.SCORE_WEIGHTS
local total = report.deepestDepth * w.depth + report.kills * w.kill
+ report.bossKills * w.bossKill + report.questsCompleted * w.quest
+ report.artifactsFound * w.artifact + report.vaultsOpened * w.vault
+ report.setPiecesFound * w.setPiece + report.chestsOpened * w.chest
+ report.playerLevel * w.playerLevel + report.gold
if report.won then total = total + w.win end
return total
end
Morgue.EQUIP_SLOTS = { "weapon", "armor", "ring", "bow" }
-- One line per worn slot, empty-handed slots omitted outright rather than
-- padded with "(nothing)" - an obituary should read like a eulogy, not a
-- character sheet.
function Morgue.equipment(player)
local lines = {}
for _, slot in ipairs(Morgue.EQUIP_SLOTS) do
local item = player[slot]
if item then
lines[#lines + 1] = slot .. ": " .. Curses.label(item, item.name)
end
end
return lines
end
-- Every species this compendium has ever seen the player slay, heaviest
-- kill count first, ties broken by catalog order (Compendium.entries' own
-- fixed order) rather than name so the listing never depends on whatever
-- order table.sort happens to leave equal keys in.
function Morgue.killTally(compendium)
local rows = {}
for i, entry in ipairs(Compendium.entries(compendium)) do
if entry.kills > 0 then
rows[#rows + 1] = { key = entry.key, name = entry.name, kills = entry.kills, order = i }
end
end
table.sort(rows, function(a, b)
if a.kills ~= b.kills then return a.kills > b.kills end
return a.order < b.order
end)
return rows
end
-- Gathers everything Morgue.format needs straight off a live or just-ended
-- game, mirroring Game:metaStats' own field names so the two never drift.
-- `meta` is optional (see warren.lua's --meta flag) - a meta-free run's
-- report simply carries no title, exactly like it banks no renown.
function Morgue.build(game, meta)
return {
won = game.won,
alive = game.player:isAlive(),
className = game.player.class or "wanderer",
title = meta and Titles.forRenown(meta.renown) or nil,
playerLevel = game.player.level,
deepestDepth = game.deepestDepth,
turn = game.turn,
gold = game.player.gold,
essence = game.player.essence,
kills = game.stats.kills,
bossKills = game.stats.bossKills,
questsCompleted = game.stats.questsCompleted,
artifactsFound = game.stats.artifactsFound,
vaultsOpened = game.stats.vaultsOpened,
setPiecesFound = game.stats.setPiecesFound,
chestsOpened = game.stats.chestsOpened,
equipment = Morgue.equipment(game.player),
killTally = Morgue.killTally(game.compendium),
finalMessages = game.log:recent(5),
}
end
local function outcomeLine(report)
if report.won then return "escaped the warren with the Amulet" end
if report.alive then return "still drawing breath, for now" end
return "died in the dark"
end
-- Renders `report` (see Morgue.build) as a fixed-shape text obituary:
-- outcome, vitals, gear, kills, final words, score - always in that order
-- so two runs diff cleanly against each other.
function Morgue.format(report)
local lines = {}
local heading = report.alive and (report.won and "Victorious" or "Wandering") or "Late"
if report.title then
lines[#lines + 1] = string.format("%s the %s, the %s", heading, report.className, report.title)
else
lines[#lines + 1] = string.format("%s the %s", heading, report.className)
end
lines[#lines + 1] = outcomeLine(report)
lines[#lines + 1] = string.format("depth %d, turn %d, level %d", report.deepestDepth, report.turn,
report.playerLevel)
lines[#lines + 1] = string.format("gold %d, essence %d", report.gold, report.essence)
if #report.equipment > 0 then
lines[#lines + 1] = "equipment:"
for _, line in ipairs(report.equipment) do lines[#lines + 1] = " " .. line end
end
if #report.killTally > 0 then
lines[#lines + 1] = "kills:"
for _, row in ipairs(report.killTally) do
lines[#lines + 1] = string.format(" %-14s x%d", row.name, row.kills)
end
end
if #report.finalMessages > 0 then
lines[#lines + 1] = "final moments:"
for _, line in ipairs(report.finalMessages) do lines[#lines + 1] = " " .. line end
end
lines[#lines + 1] = string.format("score: %d", Morgue.score(report))
return table.concat(lines, "\n") .. "\n"
end
return Morgue