133 lines
5.0 KiB
Lua
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
|