diff --git a/NEWS b/NEWS index f0bb73189..660d61b1c 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ DFHack Future Multiple contexts can now be specified when adding keybindings Keybindings can now use F10-F12 and 0-9 Plugin system is no longer restricted to plugins that exist on startup + dfhack.init file locations significantly generalized Lua Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() diff --git a/Readme.rst b/Readme.rst index cfcf1609a..e9ec9f4fd 100644 --- a/Readme.rst +++ b/Readme.rst @@ -180,16 +180,21 @@ tracker on github, or visit the #dfhack IRC channel on freenode. ============= The init file ============= -If your DF folder contains a file named ``dfhack.init``, its contents will be -run every time you start DF. This allows keybindings and other settings to -persist across runs. An example file is provided as ``dfhack.init-example`` - -you can tweak it and rename to ``dfhack.init`` if you want to use this -functionality. If only the example init file is found, will be used and a -warning will be shown. - -When a savegame is loaded, an ``onLoad.init`` file in its raw folder is run, -as a save-portable alternative to ``dfhack.init``. It is recommended that -modders use this to improve mobility of save games and compatibility of mods. +DFHack allows users to automatically run commonly-used DFHack commands when DF is first loaded, when a game is loaded, and when a game is unloaded. +Init scripts function the same way they would if the user manually typed in their contents, but are much more convenient. +If your DF folder contains at least one file with a name following the format ``dfhack*.init`` where ``*`` is a placeholder for any string (including the empty string), then all such files are executed in alphabetical order as init scripts when DF is first loaded. +If your DF folder does not contain any such files, then DFHack will execute ``dfhack.init-example`` as an example of useful commands to be run automatically. +If you want DFHack to do nothing on its own, then create an empty ``dfhack.init`` file in the main DF directory, or delete ``dfhack.init-example``. +The file ``dfhack.init-example`` is included as an example for users to follow if they need DFHack command executed automatically. +We highly recommend modifying or deleting ``dfhack.init-example`` as its settings will not be optimal for all players. + +In order to facilitate savegave portability, mod merging, and general organization of init files, DFHack supports multiple init files both in the main DF directory and save-specific init files in the save folders. +DFHack looks for init files in three places. +It will look for them in the main DF directory, and in ``data/save/_____/raw`` and ``data/save/_____/raw/objects`` where ``_____`` is the name of the current savegame. +When a game is loaded, DFHack looks for files of the form ``onLoad*.init``, where ``*`` can be any string, including the empty string. +When a game is unloaded, DFHack looks for files of the form ``onUnload*.init``. +Again, these files may be in any of the above three places. +All matching init files will be sorted and executed in alphebetical order. Setting keybindings =================== diff --git a/library/Core.cpp b/library/Core.cpp index c5834875e..0b2ae7874 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -32,6 +32,9 @@ distribution. #include #include #include +#include +#include +#include using namespace std; #include "Error.h" @@ -85,6 +88,7 @@ using df::global::world; // FIXME: A lot of code in one file, all doing different things... there's something fishy about it. static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL); +size_t loadScriptFiles(Core* core, color_ostream& out, const vector& prefix, const std::string& folder); struct Core::Cond { @@ -1209,8 +1213,10 @@ static void run_dfhack_init(color_ostream &out, Core *core) out.printerr("Key globals are missing, skipping loading dfhack.init.\n"); return; } - - if (!core->loadScriptFile(out, "dfhack.init", true)) + + std::vector prefixes(1, "dfhack"); + size_t count = loadScriptFiles(core, out, prefixes, "."); + if (!count) { core->runCommand(out, "gui/no-dfhack-init"); core->loadScriptFile(out, "dfhack.init-example", true); @@ -1829,7 +1835,82 @@ void Core::onUpdate(color_ostream &out) Lua::Core::onUpdate(out); } +void getFilesWithPrefixAndSuffix(const std::string& folder, const std::string& prefix, const std::string& suffix, std::vector& result) { + //DFHACK_EXPORT int listdir (std::string dir, std::vector &files); + std::vector files; + DFHack::Filesystem::listdir(folder, files); + for ( size_t a = 0; a < files.size(); a++ ) { + if ( prefix.length() > files[a].length() ) + continue; + if ( suffix.length() > files[a].length() ) + continue; + if ( files[a].compare(0, prefix.length(), prefix) != 0 ) + continue; + if ( files[a].compare(files[a].length()-suffix.length(), suffix.length(), suffix) != 0 ) + continue; + result.push_back(files[a]); + } + return; +} + +size_t loadScriptFiles(Core* core, color_ostream& out, const vector& prefix, const std::string& folder) { + vector scriptFiles; + for ( size_t a = 0; a < prefix.size(); a++ ) { + getFilesWithPrefixAndSuffix(folder, prefix[a], ".init", scriptFiles); + } + std::sort(scriptFiles.begin(), scriptFiles.end()); + size_t result = 0; + for ( size_t a = 0; a < scriptFiles.size(); a++ ) { + result++; + core->loadScriptFile(out, folder + scriptFiles[a], true); + } + return result; +} + +namespace DFHack { + namespace X { + typedef state_change_event Key; + typedef vector Val; + typedef pair Entry; + typedef vector EntryVector; + typedef map InitVariationTable; + + EntryVector computeInitVariationTable(void* none, ...) { + va_list list; + va_start(list,none); + EntryVector result; + while(true) { + Key key = va_arg(list,Key); + if ( key < 0 ) + break; + Val val; + while(true) { + string v = va_arg(list,string); + if ( v.empty() ) + break; + val.push_back(v); + } + result.push_back(Entry(key,val)); + } + va_end(list); + return result; + } + + InitVariationTable getTable(const EntryVector& vec) { + return InitVariationTable(vec.begin(),vec.end()); + } + } +} + void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event event) { + static const X::InitVariationTable table = X::getTable(X::computeInitVariationTable(0, + SC_WORLD_LOADED, (string)"onLoad", (string)"onLoadWorld", (string)"onWorldLoaded", (string)"", + SC_WORLD_UNLOADED, (string)"onUnload", (string)"onUnloadWorld", (string)"onWorldUnloaded", (string)"", + SC_MAP_LOADED, (string)"onMapLoad", (string)"onLoadMap", (string)"", + SC_MAP_UNLOADED, (string)"onMapUnload", (string)"onUnloadMap", (string)"", + (X::Key)(-1) + )); + if (!df::global::world) return; //TODO: use different separators for windows @@ -1839,27 +1920,13 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve static const std::string separator = "/"; #endif std::string rawFolder = "data" + separator + "save" + separator + (df::global::world->cur_savegame.save_dir) + separator + "raw" + separator; - switch(event) { - case SC_WORLD_LOADED: - loadScriptFile(out, "onLoadWorld.init", true); - loadScriptFile(out, rawFolder + "onLoadWorld.init", true); - loadScriptFile(out, rawFolder + "onLoad.init", true); - break; - case SC_WORLD_UNLOADED: - loadScriptFile(out, "onUnloadWorld.init", true); - loadScriptFile(out, rawFolder + "onUnloadWorld.init", true); - loadScriptFile(out, rawFolder + "onUnload.init", true); - break; - case SC_MAP_LOADED: - loadScriptFile(out, "onLoadMap.init", true); - loadScriptFile(out, rawFolder + "onLoadMap.init", true); - break; - case SC_MAP_UNLOADED: - loadScriptFile(out, "onUnloadMap.init", true); - loadScriptFile(out, rawFolder + "onUnloadMap.init", true); - break; - default: - break; + + auto i = table.find(event); + if ( i != table.end() ) { + const std::vector& set = i->second; + loadScriptFiles(this, out, set, "." ); + loadScriptFiles(this, out, set, rawFolder); + loadScriptFiles(this, out, set, rawFolder + "objects" + separator); } for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it)