From 87b1db03edbc040cd9d06b750ce1f7fc0f42748b Mon Sep 17 00:00:00 2001 From: unboundlopez Date: Wed, 11 Jun 2025 21:19:39 -0500 Subject: [PATCH 1/4] Add zprospectanalyzer script and documentation --- docs/zprospectanalyzer.rst | 43 ++++++++++ zprospectanalyzer.lua | 160 +++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 docs/zprospectanalyzer.rst create mode 100644 zprospectanalyzer.lua diff --git a/docs/zprospectanalyzer.rst b/docs/zprospectanalyzer.rst new file mode 100644 index 000000000..a71b21dde --- /dev/null +++ b/docs/zprospectanalyzer.rst @@ -0,0 +1,43 @@ +zprospectanalyzer +================= + +A DFHack CLI utility and Lua module for scanning and reporting material occurrences +and elevation ranges using the built-in `prospect` command. It provides an easy-to-use +interface to list material counts and elevation ranges, with sorting and preset lists. + +Features +-------- + +- **Output Parsing**: Runs `prospect all` or `prospect all --show
` and parses the text output. +- **Section Filtering**: Filter materials by specific sections like layers, ores, gems, or globally. +- **Presets**: Built-in "blocks" preset for common stone materials. +- **Missing Material Handling**: Lists missing materials at the end with `` marker. + +Usage +----- + +Load the script in DFHack and run: + +.. code-block:: bash + + zprospectanalyzer [section] [material1] [material2] ... + +Examples: + +- **Default** (blocks preset): + + .. code-block:: bash + + zprospectanalyzer + +- **Filter by section**: + + .. code-block:: bash + + zprospectanalyzer layer_materials chert granite + +- **Global search**: + + .. code-block:: bash + + zprospectanalyzer jet ruby tetrahedrite \ No newline at end of file diff --git a/zprospectanalyzer.lua b/zprospectanalyzer.lua new file mode 100644 index 000000000..caf180511 --- /dev/null +++ b/zprospectanalyzer.lua @@ -0,0 +1,160 @@ +-- scripts/zprospectanalyzer.lua + +local zprospectanalyzer = {} + +--- Scans the world using the `prospect` command and returns a table of materials. +-- @return table data[section][material_key] = { count=number, minElev=number?, maxElev=number? } +function zprospectanalyzer.scanProspect(section) + local filterSection = section and section:lower():gsub("%s+","_") or "all" + local showMap = { + base_materials = "base", + liquids = "liquids", + layer_materials = "layers", + features = "features", + ores = "ores", + gems = "gems", + other_vein_stone = "veins", + shrubs = "shrubs", + wood_in_trees = "trees", + } + local cmd = { "prospect", "all" } + if filterSection ~= "all" and showMap[filterSection] then + table.insert(cmd, "--show") + table.insert(cmd, showMap[filterSection]) + end + local output, status = dfhack.run_command_silent(table.unpack(cmd)) + if status ~= CR_OK then + error(("prospect failed (code %d): %s"):format(status, tostring(output))) + end + local data = {} + local current + for line in output:gmatch("([^\n]+)") do + local header = line:match("^%s*([%a%s_]+)%s*:%s*$") + if header then + current = header:lower():gsub("%s+","_") + data[current] = {} + elseif current then + local name, count, elev = line:match( + "^%s*([%u_]+)%s*:%s*(%d+)%s+Elev:?%s*([-%d%.]+)" + ) + if not name then + name, count = line:match("^%s*([%u_]+)%s*:%s*(%d+)") + end + if name and count then + local key = name:lower() + local entry = { count = tonumber(count) } + if elev then + local minE, maxE = elev:match("([-%d]+)%.%.([-%d]+)") + entry.minElev = tonumber(minE) + entry.maxElev = tonumber(maxE) + end + data[current][key] = entry + end + end + end + return data +end + +--- Sorts an array of material entries by quantity then elevation. +-- @return sorted list +function zprospectanalyzer.sortMaterials(list) + table.sort(list, function(a, b) + if a.entry.count ~= b.entry.count then + return a.entry.count > b.entry.count + end + local ae = a.entry.maxElev or a.entry.minElev or 0 + local be = b.entry.maxElev or b.entry.minElev or 0 + return ae > be + end) + return list +end + +--- Prints material entries in aligned columns. +function zprospectanalyzer.printMaterials(list) + for _, item in ipairs(list) do + print(string.format( + " %-15s : %7d Elev:%3s..%-3s", + item.key:upper(), + item.entry.count, + item.entry.minElev or "?", + item.entry.maxElev or "?" + )) + end +end + +--- Main entry point for CLI. +-- Supports "blocks" preset or custom section/material arguments. +-- Not-found entries are printed last. +-- blocks preset includes stones that are worth 3 pts. +function zprospectanalyzer.main(...) + local args = { ... } + if #args == 0 then args = { "blocks" } end + local presets = { + blocks = { + "Alabaster", "Alunite", "Andesite", "Anhydrite", "Basalt", + "Bauxite", "Bismuthinite", "Borax", "Brimstone", "Chert", + "Chromite", "Cinnabar", "Claystone", "Cobaltite", "Conglomerate", + "Cryolite", "Dacite", "Diorite", "Gabbro", "Gneiss", + "Granite", "Graphite", "Gypsum", "Hornblende", "Ilmenite", + "Jet", "Kaolinite", "Kimberlite", "Marcasite", "Mica", + "Microcline", "Olivine", "Orpiment", "Orthoclase", "Periclase", + "Petrified_wood", "Phyllite", "Pitchblende", "Puddingstone", + "Pyrolusite", "Quartzite", "Realgar", "Rhyolite", "Rock_salt", + "Rutile", "Saltpeter", "Sandstone", "Satinspar", "Schist", + "Selenite", "Serpentine", "Shale", "Siltstone", "Slate", + "Stibnite", "Sylvite", "Talc", + } + } + local first = args[1]:lower():gsub("%s+","_") + local materials = {} + local section + if presets[first] then + materials = presets[first] + else + local validSections = { + base_materials=true, liquids=true, layer_materials=true, + features=true, ores=true, gems=true, + other_vein_stone=true, shrubs=true, wood_in_trees=true + } + local startIndex = 1 + if validSections[first] then section = first; startIndex = 2 end + for i = startIndex, #args do materials[#materials+1] = args[i] end + end + local data = zprospectanalyzer.scanProspect(section) + local foundEntries = {} + local missingEntries = {} + for _, mat in ipairs(materials) do + local key = mat:lower():gsub("%s+","_") + local entryFound = false + if section then + local e = (data[section] or {})[key] + if e then + foundEntries[#foundEntries+1] = { key = key, entry = e } + entryFound = true + end + else + for _, items in pairs(data) do + local e = items[key] + if e then + foundEntries[#foundEntries+1] = { key = key, entry = e } + entryFound = true + end + end + end + if not entryFound then + missingEntries[#missingEntries+1] = mat + end + end + zprospectanalyzer.printMaterials(zprospectanalyzer.sortMaterials(foundEntries)) + for _, mat in ipairs(missingEntries) do + print(string.format( + " %-15s : %7s ", + mat:upper(), "-" + )) + end +end + +-- Execute main when run as script +zprospectanalyzer.main(...) + +return zprospectanalyzer From 7b3ad3149b3e2a111e8dfc64924c8ba10d2c839f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 02:21:35 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/zprospectanalyzer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zprospectanalyzer.rst b/docs/zprospectanalyzer.rst index a71b21dde..2027454a6 100644 --- a/docs/zprospectanalyzer.rst +++ b/docs/zprospectanalyzer.rst @@ -40,4 +40,4 @@ Examples: .. code-block:: bash - zprospectanalyzer jet ruby tetrahedrite \ No newline at end of file + zprospectanalyzer jet ruby tetrahedrite From 9c45db2ed54ba686db35d1ad05d792e86b828f4d Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:56:01 -0500 Subject: [PATCH 3/4] Update zprospectanalyzer.lua --- zprospectanalyzer.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/zprospectanalyzer.lua b/zprospectanalyzer.lua index caf180511..53faaef80 100644 --- a/zprospectanalyzer.lua +++ b/zprospectanalyzer.lua @@ -28,6 +28,7 @@ function zprospectanalyzer.scanProspect(section) end local data = {} local current + -- split output into lines without interpreting escape sequences for line in output:gmatch("([^\n]+)") do local header = line:match("^%s*([%a%s_]+)%s*:%s*$") if header then @@ -55,25 +56,27 @@ function zprospectanalyzer.scanProspect(section) return data end ---- Sorts an array of material entries by quantity then elevation. +--- Sorts an array of material entries by min elevation then quantity. -- @return sorted list function zprospectanalyzer.sortMaterials(list) table.sort(list, function(a, b) - if a.entry.count ~= b.entry.count then - return a.entry.count > b.entry.count + local am = a.entry.minElev or 0 + local bm = b.entry.minElev or 0 + if am ~= bm then + return am > bm end - local ae = a.entry.maxElev or a.entry.minElev or 0 - local be = b.entry.maxElev or b.entry.minElev or 0 - return ae > be + return a.entry.count > b.entry.count end) return list end ---- Prints material entries in aligned columns. +--- Prints material entries in aligned columns with a header. function zprospectanalyzer.printMaterials(list) + -- Header line + print(string.format(" %-15s %8s %10s %10s", "Material", "Quantity", "Min Elev", "Max Elev")) for _, item in ipairs(list) do print(string.format( - " %-15s : %7d Elev:%3s..%-3s", + " %-15s %8d %10s %10s", item.key:upper(), item.entry.count, item.entry.minElev or "?", From 11a7cc192d4740b65f878a5d063728cf92b0a07a Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:38:28 -0500 Subject: [PATCH 4/4] Update zprospectanalyzer.rst --- docs/zprospectanalyzer.rst | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/docs/zprospectanalyzer.rst b/docs/zprospectanalyzer.rst index 2027454a6..0dba07296 100644 --- a/docs/zprospectanalyzer.rst +++ b/docs/zprospectanalyzer.rst @@ -1,43 +1,26 @@ zprospectanalyzer ================= -A DFHack CLI utility and Lua module for scanning and reporting material occurrences -and elevation ranges using the built-in `prospect` command. It provides an easy-to-use -interface to list material counts and elevation ranges, with sorting and preset lists. +**Goal**: Filter and print stones that are worth **3 points**. Features -------- -- **Output Parsing**: Runs `prospect all` or `prospect all --show
` and parses the text output. -- **Section Filtering**: Filter materials by specific sections like layers, ores, gems, or globally. -- **Presets**: Built-in "blocks" preset for common stone materials. -- **Missing Material Handling**: Lists missing materials at the end with `` marker. +- **Output Parsing**: Automatically runs ``prospect all`` and parses the text output. +- **Section Filtering**: Optionally filters materials by specific sections like ores or gems. +- **Presets**: Running ``zprospectanalyzer`` **without parameters** will only run the preset of **3-point stones**. +- **Missing Materials Reporting**: Displays ```` next to any requested material that doesn't appear in the output. Usage ----- -Load the script in DFHack and run: - .. code-block:: bash - zprospectanalyzer [section] [material1] [material2] ... - -Examples: - -- **Default** (blocks preset): - - .. code-block:: bash - - zprospectanalyzer + zprospectanalyzer [material1] [material2] ... -- **Filter by section**: +Example +------- - .. code-block:: bash - - zprospectanalyzer layer_materials chert granite - -- **Global search**: - - .. code-block:: bash +.. code-block:: bash - zprospectanalyzer jet ruby tetrahedrite + zprospectanalyzer claystone granite ruby tetrahedrite