diff --git a/.gitignore b/.gitignore index 9ff16b94c..4b533ab20 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ build/Makefile build/CMakeCache.txt build/cmake_install.cmake build/CMakeFiles +build/data build/doc build/lua build/bin diff --git a/CMakeLists.txt b/CMakeLists.txt index de0c858af..44d6a02ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -429,6 +429,7 @@ if(BUILD_PLUGINS) add_subdirectory(plugins) endif() +add_subdirectory(data) add_subdirectory(scripts) find_package(Sphinx QUIET) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 000000000..19acb9261 --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,2 @@ +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/ DESTINATION blueprints) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") diff --git a/data/blueprints/README.txt b/data/blueprints/README.txt new file mode 100644 index 000000000..2096476c5 --- /dev/null +++ b/data/blueprints/README.txt @@ -0,0 +1,14 @@ +This directory is for quickfort blueprints. You can apply them to your fortress +map with the DFHack quickfort plugin. See +https://docs.dfhack.org/en/stable/docs/Scripts.html#quickfort for details. + +You can create blueprints by hand or by using any spreadsheet application, +saving them as .xlsx or .csv files. You can also build your plan "for real" in +Dwarf Fortress, and then export your map using the DFHack blueprint for later +replay in a different fort. See +https://docs.dfhack.org/en/stable/docs/Plugins.html#blueprint for more info. + +DFHack blueprints follow the original Quickfort 2.0 syntax. See +https://github.com/joelpt/quickfort for joelpt's excellent documentation. + +There are many ready-to-use examples of blueprints in the library subfolder. diff --git a/data/blueprints/library/square-dig.csv b/data/blueprints/library/square-dig.csv new file mode 100644 index 000000000..548c5c3f2 --- /dev/null +++ b/data/blueprints/library/square-dig.csv @@ -0,0 +1,4 @@ +#dig start(2;2) This is a library placeholder for use during quickfort development +d,d,d +d, ,d +d,d,d diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt new file mode 100644 index 000000000..b4e19c782 --- /dev/null +++ b/data/quickfort/aliases-common.txt @@ -0,0 +1,135 @@ +# quickfort aliases common baseline configuration file +# +# Defines custom keycode shortcuts for blueprints. Please DO NOT EDIT this file +# directly. Instead, custom aliases should be added to +# dfhack-config/quickfort/aliases.txt. +# +# Syntax: +# aliasname: keystrokes +# +# Special keys: +# {Right}, {Left}, {Up}, {Down}, >, < move the DF cursor +# {/}, {*}, {+}, {-} can be used to navigate some DF menus +# {Enter}, +{Enter}, {ExitMenu} - Enter, Shift+Enter, and Escape, respectively +# {Wait} pauses playback briefly +# +# Special keys can be repeated by adding a number inside the curly braces, for +# example: {Down 5} +# +# Some shorthand: +# & expands to {Enter} +# @ expands to +{Enter} +# ^ expands to {ExitMenu} +# % expands to {Wait} +# +# The aliases in this file were tested in DF 0.47.04 on 2020 Jul 18. + +################################## +# food stockpile adjustments +################################## + +seeds: s{Down}deb{Right}{Down 9}p^ +noseeds: s{Down}dea{Right}{Down 9}f^ +booze: s{Down}deb{Right}{Down 5}p{Down}p^ +food: s{Down}dea{Right}{Down 5}f{Down}f{Down 3}f^ +plants: s{Down}deb{Right}{Down 4}p^ + +################################## +# refuse stockpile adjustments +################################## + +corpses: s{Down 4}deb{Right 2}&{Down 2}&{Left}{Down}p{Down}p^ +bones: s{Down 4}deb{Right}{Down 3}p{Down}p^ +rawhides: s{Down 4}deb{Right 2}{Down}&^ +tannedhides: s{Down 4}deb{Right 2}{Down 53}&^ + +################################## +# stone stockpile adjustments +################################## + +metal: s{Down 5}deb{Right}p^ +nometal: s{Down 5}dea{Right}f^ +bauxite: s{Down 5}deb{Right}{Down 2}{Right}{Down 42}&^ + +# Only use nobauxite on stone piles that you want to accept all "Other Stone" on. +# This alias works by permitting all "Other Stone",then forbidding just bauxite. +# Thus you wouldn't want to use this on a metal-only pile, for example. +nobauxite: s{Down 5}{Right}{Down 2}p{Right}{Down 42}&^ + +################################## +# misc stockpile adjustments +################################## + +# Artifacts-only stockpile, usable on any type of existing pile. +artifacts: sd{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down}d{Down 4}deu{Right}{Up}f{Right}{Up}&{Left 2}{Down 4}e{Right}{Up}f{Right}{Up}&{Left 2}{Down 4}e{Right}{Up}f{Right}{Up}&{Left 2}{Down 4}e{Right}{Up}f{Right}{Up}&{Left 2}{Down}e{Right}{Up}f{Right}{Up}&{Left 2}^ + +# Bans artifacts on any pile (or rather, allows items of any quality except Artifact quality). +# This should be safe to run on any type of pile. +noartifacts: sd{Down 2}{Right}{Up}fp{Right}{Up}&{Down 2}{Left 2}{Down 4}{Right}{Up}fp{Right}{Up}&{Down 2}{Left 2}{Down 4}{Right}{Up}fp{Right}{Up}&{Down 2}{Left 2}{Down 4}{Right}{Up}fp{Right}{Up}&{Down 2}{Left 2}{Down}{Right}{Up}fp{Right}{Up}&{Down 2}{Left 2}^ + +# Set a finished goods stockpile to crappy low-quality trade goods only. +# Position such a stockpile near fort entrances to (hopefully) let thieves steal low quality junk. +junkgoods: s{Down 10}de{Right 2}&{Down 5}&{Down}&{Down}&{Down}&{Down 8}&{Down 2}&{Down}&{Down}&{Down}&{Down}&{Down}&{Left}{Down}f{Right}{Down}&{Down}&{Left}{Down}f{Down 2}f{Right}&{Down}&^ + +################################## +# farm plots +################################## + +# Sets a farm plot to grow the LAST type of seed in the list of available seeds, for all 4 seasons. +# The last seed is used because it's usually Plump helmet spawn, suitable for post-embark. If you +# only have 1 seed type, that'll be grown. +growlastcropall: a{/}&b{/}&c{/}&d{/}& + +# Like growlastcropall but grows the first one in the list instead. +growfirstcropall: a&b&c&d& + +################################## +# mining tracks +################################## + +# The following aliases make it more convenient to build the various types of mine tracks. +# For example, to build a north/south track 'Track (NS)', you would put trackNS in a cell(s). +trackN: CT{Enter} +trackS: CT{+ 1}{Enter} +trackE: CT{+ 2}{Enter} +trackW: CT{+ 3}{Enter} +trackNS: CT{+ 4}{Enter} +trackNE: CT{+ 5}{Enter} +trackNW: CT{+ 6}{Enter} +trackSE: CT{+ 7}{Enter} +trackSW: CT{+ 8}{Enter} +trackEW: CT{+ 9}{Enter} +trackNSE: CT{+ 10}{Enter} +trackNSW: CT{+ 11}{Enter} +trackNEW: CT{+ 12}{Enter} +trackSEW: CT{+ 13}{Enter} +trackNSEW: CT{+ 14}{Enter} +trackrampN: CT{+ 15}{Enter} +trackrampS: CT{+ 15}{+ 1}{Enter} +trackrampE: CT{+ 15}{+ 2}{Enter} +trackrampW: CT{+ 15}{+ 3}{Enter} +trackrampNS: CT{+ 15}{+ 4}{Enter} +trackrampNE: CT{+ 15}{+ 5}{Enter} +trackrampNW: CT{+ 15}{+ 6}{Enter} +trackrampSE: CT{+ 15}{+ 7}{Enter} +trackrampSW: CT{+ 15}{+ 8}{Enter} +trackrampEW: CT{+ 15}{+ 9}{Enter} +trackrampNSE: CT{+ 15}{+ 10}{Enter} +trackrampNSW: CT{+ 15}{+ 11}{Enter} +trackrampNEW: CT{+ 15}{+ 12}{Enter} +trackrampSEW: CT{+ 15}{+ 13}{Enter} +trackrampNSEW: CT{+ 15}{+ 14}{Enter} + +# Aliases for building track rollers; use e.g. rollerHqqq to make a low-speed horizontal roller +rollerH: Mrs +rollerV: Mr +rollerNS: Mr +rollerSN: Mrss +rollerEW: Mrs +rollerWE: Mrsss + +# Aliases for building track stops that dump in each of the four directions +trackstopN: CSd +trackstopS: CSdd +trackstopE: CSddd +trackstopW: CSdddd diff --git a/data/quickfort/materials-common.txt b/data/quickfort/materials-common.txt new file mode 100644 index 000000000..256d5deb8 --- /dev/null +++ b/data/quickfort/materials-common.txt @@ -0,0 +1,8 @@ +# quickfort materials common baseline configuration file +# +# Defines forbidden materials and material preferences for build blueprints. +# Please DO NOT EDIT this file directly. Instead, custom materials preferences +# should be added to dfhack-config/quickfort/materials.txt. +# +# Syntax TBD + diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt new file mode 100644 index 000000000..52cdb2893 --- /dev/null +++ b/dfhack-config/quickfort/aliases.txt @@ -0,0 +1,30 @@ +# quickfort aliases configuration file +# +# Defines custom keycode shortcuts for blueprints. Definitions in this file take +# precedence over any definitions in the baseline aliases configuration file at +# hack/data/quickfort/aliases-common.txt. See that file for aliases that are +# already defined. +# +# This file can be used to simplify repetitive tasks, such as building minecart +# tracks or adjusting a food stockpile to accept seeds only. Making new aliases +# is just a matter of mimicking the keys used to navigate through the menus and +# select options. +# +# Syntax: +# aliasname: keystrokes +# +# Special keys: +# {Right}, {Left}, {Up}, {Down}, >, < move the DF cursor +# {/}, {*}, {+}, {-} can be used to navigate some DF menus +# {Enter}, +{Enter}, {ExitMenu} - Enter, Shift+Enter, and Escape, respectively +# {Wait} pauses playback briefly +# +# Special keys can be repeated by adding a number inside the curly braces, for +# example: {Down 5} +# +# Some shorthand: +# & expands to {Enter} +# @ expands to +{Enter} +# ^ expands to {ExitMenu} +# % expands to {Wait} + diff --git a/dfhack-config/quickfort/materials.txt b/dfhack-config/quickfort/materials.txt new file mode 100644 index 000000000..5938f16fe --- /dev/null +++ b/dfhack-config/quickfort/materials.txt @@ -0,0 +1,9 @@ +# quickfort materials preference configuration file +# +# Defines forbidden materials and material preferences for build blueprints. +# Settings in this file take precedence over any settings in the baseline +# materials configuration file at hack/data/quickfort/materials-common.txt. See +# that file to view the global defaults. +# +# Syntax TBD + diff --git a/dfhack-config/quickfort/quickfort.txt b/dfhack-config/quickfort/quickfort.txt new file mode 100644 index 000000000..683c1da7d --- /dev/null +++ b/dfhack-config/quickfort/quickfort.txt @@ -0,0 +1,20 @@ +# quickfort main configuration file +# +# Set startup defaults for the quickfort script in this file. Settings can be +# dynamically overridden in the active session with the `quickfort set` command. + +# Directory to search for blueprints. Can be set to an absolute or relative +# path. If set to a relative path, resolves to a directory under the DF folder. +blueprints_dir=blueprints + +# Set to "true" or "false". If true, will designate dig blueprints in marker +# mode. If false, only cells with dig codes prefixed with ``m`` will be +# designated in marker mode. +force_marker_mode=false + +# Allows you to manually select building materials for each +# building/construction when running (or creating orders for) build blueprints. +# Materials in selection dialogs are ordered according to preferences in +# materials.txt. If false, will only prompt for materials that have :labels. +# See https://github.com/joelpt/quickfort#manual-material-selection for details. +force_interactive_build=false diff --git a/docs/Lua API.rst b/docs/Lua API.rst index e9392f9c4..de5741861 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2116,8 +2116,10 @@ unless otherwise noted. * ``dfhack.filesystem.listdir(path)`` Lists files/directories in a directory. Returns ``{}`` if ``path`` does not exist. + Set include_prefix to false if you don't want the ``path`` string prepended to the + returned filenames. -* ``dfhack.filesystem.listdir_recursive(path [, depth = 10])`` +* ``dfhack.filesystem.listdir_recursive(path [, depth = 10[, include_prefix = true]])`` Lists all files/directories in a directory and its subdirectories. All directories are listed before their contents. Returns a table with subtables of the format:: diff --git a/docs/changelog.txt b/docs/changelog.txt index 9d03b1f0a..58669e321 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - Added ``Filesystem::mkdir_recursive`` +- Extended ``Filesystem::listdir_recursive`` to optionally make returned filenames relative to the start directory ## Lua - Added a ``ref_target`` field to primitive field references, corresponding to the ``ref-target`` XML attribute diff --git a/library/Core.cpp b/library/Core.cpp index a9fe9a69a..20d9c994b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1737,18 +1737,35 @@ bool Core::Init() virtual_identity::Init(this); // copy over default config files if necessary - std::vector config_files; - std::vector default_config_files; - if (Filesystem::listdir("dfhack-config", config_files) != 0) + std::map config_files; + std::map default_config_files; + if (Filesystem::listdir_recursive("dfhack-config", config_files, 10, false) != 0) con.printerr("Failed to list directory: dfhack-config"); - else if (Filesystem::listdir("dfhack-config/default", default_config_files) != 0) + else if (Filesystem::listdir_recursive("dfhack-config/default", default_config_files, 10, false) != 0) con.printerr("Failed to list directory: dfhack-config/default"); else { + // ensure all config file directories exist before we start copying files for (auto it = default_config_files.begin(); it != default_config_files.end(); ++it) { - std::string filename = *it; - if (std::find(config_files.begin(), config_files.end(), filename) == config_files.end()) + // skip over files + if (!it->second) + continue; + std::string dirname = "dfhack-config/" + it->first; + if (!Filesystem::mkdir_recursive(dirname)) + { + con.printerr("Failed to create config directory: '%s'\n", dirname.c_str()); + } + } + + // copy files from the default tree that don't already exist in the config tree + for (auto it = default_config_files.begin(); it != default_config_files.end(); ++it) + { + // skip over directories + if (it->second) + continue; + std::string filename = it->first; + if (config_files.find(filename) == config_files.end()) { std::string src_file = std::string("dfhack-config/default/") + filename; if (!Filesystem::isfile(src_file)) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b1dc3de0b..c20be4618 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2406,8 +2406,11 @@ static int filesystem_listdir_recursive(lua_State *L) int depth = 10; if (lua_type(L, 2) == LUA_TNUMBER) depth = lua_tounsigned(L, 2); + bool include_prefix = true; + if (lua_type(L, 3) == LUA_TBOOLEAN) + include_prefix = lua_toboolean(L, 3); std::map files; - int err = DFHack::Filesystem::listdir_recursive(dir, files, depth); + int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix); if (err) { lua_pushnil(L); diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index bc0953d27..c9218ba33 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -149,6 +149,7 @@ namespace DFHack { DFHACK_EXPORT bool chdir (std::string path); DFHACK_EXPORT std::string getcwd (); DFHACK_EXPORT bool mkdir (std::string path); + // returns true on success or if directory already exists DFHACK_EXPORT bool mkdir_recursive (std::string path); DFHACK_EXPORT bool rmdir (std::string path); DFHACK_EXPORT bool stat (std::string path, STAT_STRUCT &info); @@ -160,7 +161,9 @@ namespace DFHack { DFHACK_EXPORT int64_t ctime (std::string path); DFHACK_EXPORT int64_t mtime (std::string path); DFHACK_EXPORT int listdir (std::string dir, std::vector &files); + // set include_prefix to false to prevent dir from being prepended to + // paths returned in files DFHACK_EXPORT int listdir_recursive (std::string dir, std::map &files, - int depth = 10, std::string prefix = ""); + int depth = 10, bool include_prefix = true); } } diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index ddc091525..c0d0860ad 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -205,34 +205,46 @@ int Filesystem::listdir (std::string dir, std::vector &files) return 0; } -int Filesystem::listdir_recursive (std::string dir, std::map &files, - int depth /* = 10 */, std::string prefix /* = "" */) +// prefix is the top-level dir where we started recursing +// path is the relative path under the prefix; must be empty or end in a '/' +// files is the output list of files and directories (bool == true for dir) +// depth is the remaining dir depth to recurse into. function returns -1 if +// we haven't finished recursing when we run out of depth. +// include_prefix controls whether the directory where we started recursing is +// included in the filenames returned in files. +static int listdir_recursive_impl (std::string prefix, std::string path, + std::map &files, int depth, bool include_prefix) { - int err; if (depth < 0) return -1; - if (prefix == "") - prefix = dir; - std::vector tmp; - err = listdir(dir, tmp); + std::string prefixed_path = prefix + "/" + path; + std::vector curdir_files; + int err = Filesystem::listdir(prefixed_path, curdir_files); if (err) return err; - for (auto file = tmp.begin(); file != tmp.end(); ++file) + for (auto file = curdir_files.begin(); file != curdir_files.end(); ++file) { if (*file == "." || *file == "..") continue; - std::string rel_path = prefix + "/" + *file; - if (isdir(rel_path)) + std::string prefixed_file = prefixed_path + *file; + std::string path_file = path + *file; + if (Filesystem::isdir(prefixed_file)) { - files.insert(std::pair(rel_path, true)); - err = listdir_recursive(dir + "/" + *file, files, depth - 1, rel_path); + files.insert(std::pair(include_prefix ? prefixed_file : path_file, true)); + err = listdir_recursive_impl(prefix, path_file + "/", files, depth - 1, include_prefix); if (err) return err; } else { - files.insert(std::pair(rel_path, false)); + files.insert(std::pair(include_prefix ? prefixed_file : path_file, false)); } } return 0; } + +int Filesystem::listdir_recursive (std::string dir, std::map &files, + int depth /* = 10 */, bool include_prefix /* = true */) +{ + return listdir_recursive_impl(dir, "", files, depth, include_prefix); +}