Game-file footprint unchanged: still one line in main.lua.
Three-level navigation as specified in D2: Ctrl+PageUp/Down cycles categories,
PageUp/Down cycles kinds, Shift+PageUp/Down cycles instances of that kind, Home
announces where it is.
- categories.lua: flag-first classification into Creatures, Items, Alchemy,
Interactables, Exits and Terrain features, reusing the game's own 'exit' tag
and excluding the six nameless mechanical templates.
- tracker.lua: scan, cache, navigation, announcements.
Caching keys on level name, player position and turn: position because
everything sorts by distance to the player, turn because creatures move and
containers empty. Scans run on keypress, never per frame.
FoV is respected throughout -- creatures only when currently visible, tiles when
visible or remembered -- so the tracker never becomes a cheat.
Home reports path length alongside the offset, via the game's own
calculatePathFromPlayer. Straight-line distance is misleading indoors, where a
chest three tiles north through a wall can be twenty steps away.
Corrects D4 in docs/05-decisions.md. I had claimed tile.tryInteract cleanly
separates 'the player can do something here' from scenery. It does not: 'wall'
carries a flavour-only tryInteractFunction ('You touch the wall. It's cold and
rough.'), so every wall segment was promoted into Interactables -- 13 in the
starting house, burying the chest and cauldron. Structural tiles
(blocksMovement and blocksVision) are now excluded before the tryInteract test;
containers and furniture block movement but not sight, so they survive.
12 kinds/19 instances instead of 13/32.
13/13 testbed checks, history unit tests passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
174 lines
5.3 KiB
Lua
174 lines
5.3 KiB
Lua
-- categories.lua — classifies map contents into tracker categories.
|
|
--
|
|
-- The game's own `templatesByTag` defines exactly one tag ("exit"), so the
|
|
-- tracker needs its own scheme. Per docs/05-decisions.md D4 this is deliberately
|
|
-- FLAG-FIRST rather than a hand-listed set of template IDs:
|
|
--
|
|
-- * flags are authoritative -- modules/tile.lua:234 defines
|
|
-- `tile.tryInteract = (loot or tryInteractFunction) and ...`, which is
|
|
-- exactly "the player can do something here" versus scenery;
|
|
-- * new content in a future game version falls through to a sensible category
|
|
-- instead of vanishing from the tracker.
|
|
--
|
|
-- The only hand-maintained parts are the exclusion list and the exit tag reuse.
|
|
|
|
local categories = {}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Exclusions
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- The six templates with no name, no description and no parent: invisible
|
|
-- mechanics that must never be tracked. (See docs/05-decisions.md D4.)
|
|
local excludedTemplates = {
|
|
blank = true,
|
|
throw_blocker = true,
|
|
movement_blocker = true,
|
|
fire_detector = true,
|
|
wall_features_position_dummy = true,
|
|
ladder_down_hidden = true,
|
|
}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Category list (order is the Ctrl+PageUp/PageDown cycle order)
|
|
--------------------------------------------------------------------------------
|
|
|
|
categories.list = {
|
|
{key = "creatures", name = "Creatures"},
|
|
{key = "items", name = "Items"},
|
|
{key = "alchemy", name = "Alchemy"},
|
|
{key = "interactables", name = "Interactables"},
|
|
{key = "exits", name = "Exits"},
|
|
{key = "terrain", name = "Terrain features"},
|
|
}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Helpers
|
|
--------------------------------------------------------------------------------
|
|
|
|
local exitTemplates
|
|
|
|
local function getExitTemplates()
|
|
if exitTemplates then
|
|
return exitTemplates
|
|
end
|
|
|
|
local ok, templatesByTag = pcall(require, "modules.tile.templatesByTag")
|
|
exitTemplates = (ok and templatesByTag and templatesByTag.exit) or {}
|
|
return exitTemplates
|
|
end
|
|
|
|
local itemTemplates
|
|
|
|
local function getItemTemplates()
|
|
if itemTemplates then
|
|
return itemTemplates
|
|
end
|
|
|
|
local ok, templates = pcall(require, "modules.item.templates")
|
|
itemTemplates = (ok and templates) or {}
|
|
return itemTemplates
|
|
end
|
|
|
|
-- Item category 3 is "Ingredients" (modules/item/categories.lua).
|
|
local INGREDIENT_CATEGORY = 3
|
|
|
|
local function lootContainsIngredient(loot)
|
|
if type(loot) ~= "table" then
|
|
return false
|
|
end
|
|
|
|
local templates = getItemTemplates()
|
|
|
|
for _, entry in ipairs(loot) do
|
|
local templateId = type(entry) == "table" and entry.templateId
|
|
local template = templateId and templates[templateId]
|
|
if template and template.category == INGREDIENT_CATEGORY then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function isLootable(tile)
|
|
return tile.loot and tile.loot[1] and not tile.unlootable
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Classification
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Returns a category key, or nil if the tile should not be tracked.
|
|
function categories.classifyTile(tile)
|
|
if not tile or tile.isHidden or not tile.templateId then
|
|
return nil
|
|
end
|
|
|
|
if excludedTemplates[tile.templateId] then
|
|
return nil
|
|
end
|
|
|
|
if getExitTemplates()[tile.templateId] then
|
|
return "exits"
|
|
end
|
|
|
|
if isLootable(tile) then
|
|
return lootContainsIngredient(tile.loot) and "alchemy" or "items"
|
|
end
|
|
|
|
-- Structural tiles are never tracked, even though many of them report
|
|
-- tryInteract.
|
|
--
|
|
-- `wall` carries a flavour-only tryInteractFunction ("You touch the wall.
|
|
-- It's cold and rough."), so the flag-first rule promoted every wall segment
|
|
-- into Interactables -- 13 of them in the starting house alone, burying the
|
|
-- chest and the cauldron. tryInteract means "something happens if you press
|
|
-- interact", NOT "this is worth walking to".
|
|
--
|
|
-- blocksMovement AND blocksVision identifies the structural shell (walls,
|
|
-- solid rock). Containers and furniture block movement but not sight, so they
|
|
-- survive this filter. Walls are covered by the wall tones and the
|
|
-- surroundings scan anyway.
|
|
if tile.blocksMovement and tile.blocksVision then
|
|
return nil
|
|
end
|
|
|
|
if tile.tryInteract then
|
|
return "interactables"
|
|
end
|
|
|
|
-- Terrain is intentionally narrow: hazards and water only. Including every
|
|
-- wall would bury the useful entries under hundreds of identical ones -- and
|
|
-- walls are already covered by the wall tones and the surroundings scan.
|
|
if tile.isThreatening then
|
|
return "terrain"
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- Water is a property of the level's water map rather than of a tile, so it is
|
|
-- classified separately during the scan.
|
|
categories.WATER_CATEGORY = "terrain"
|
|
|
|
function categories.tileName(tile)
|
|
return tile.name or tile.templateId or "something"
|
|
end
|
|
|
|
function categories.actorName(actor)
|
|
return actor.nameObserved or actor.name or actor.templateId or "someone"
|
|
end
|
|
|
|
-- Kind key: groups instances of the same thing together. Template id is stabler
|
|
-- than the display name (two templates can share a name).
|
|
function categories.tileKind(tile)
|
|
return tile.templateId
|
|
end
|
|
|
|
function categories.actorKind(actor)
|
|
return actor.templateIdOnExamination or actor.templateId
|
|
end
|
|
|
|
return categories
|