Merge pull request #689 from expwnent/develop

Generalize dfhack.init so it looks for more files in more places, for onload, onunload, etc.
develop
expwnent 2015-09-21 17:33:52 -04:00
commit 2c9b268184
3 changed files with 106 additions and 33 deletions

@ -11,6 +11,7 @@ DFHack Future
Multiple contexts can now be specified when adding keybindings Multiple contexts can now be specified when adding keybindings
Keybindings can now use F10-F12 and 0-9 Keybindings can now use F10-F12 and 0-9
Plugin system is no longer restricted to plugins that exist on startup Plugin system is no longer restricted to plugins that exist on startup
dfhack.init file locations significantly generalized
Lua Lua
Scripts can be enabled with the built-in enable/disable commands Scripts can be enabled with the built-in enable/disable commands
A new function, reqscript(), is available as a safer alternative to script_environment() A new function, reqscript(), is available as a safer alternative to script_environment()

@ -180,16 +180,21 @@ tracker on github, or visit the #dfhack IRC channel on freenode.
============= =============
The init file The init file
============= =============
If your DF folder contains a file named ``dfhack.init``, its contents will be 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.
run every time you start DF. This allows keybindings and other settings to Init scripts function the same way they would if the user manually typed in their contents, but are much more convenient.
persist across runs. An example file is provided as ``dfhack.init-example`` - 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.
you can tweak it and rename to ``dfhack.init`` if you want to use this 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.
functionality. If only the example init file is found, will be used and a 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``.
warning will be shown. 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.
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 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.
modders use this to improve mobility of save games and compatibility of mods. 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 Setting keybindings
=================== ===================

@ -32,6 +32,9 @@ distribution.
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <sstream> #include <sstream>
#include <forward_list>
#include <type_traits>
#include <cstdarg>
using namespace std; using namespace std;
#include "Error.h" #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. // 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); static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL);
size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>& prefix, const std::string& folder);
struct Core::Cond 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"); out.printerr("Key globals are missing, skipping loading dfhack.init.\n");
return; return;
} }
if (!core->loadScriptFile(out, "dfhack.init", true)) std::vector<std::string> prefixes(1, "dfhack");
size_t count = loadScriptFiles(core, out, prefixes, ".");
if (!count)
{ {
core->runCommand(out, "gui/no-dfhack-init"); core->runCommand(out, "gui/no-dfhack-init");
core->loadScriptFile(out, "dfhack.init-example", true); core->loadScriptFile(out, "dfhack.init-example", true);
@ -1829,7 +1835,82 @@ void Core::onUpdate(color_ostream &out)
Lua::Core::onUpdate(out); Lua::Core::onUpdate(out);
} }
void getFilesWithPrefixAndSuffix(const std::string& folder, const std::string& prefix, const std::string& suffix, std::vector<std::string>& result) {
//DFHACK_EXPORT int listdir (std::string dir, std::vector<std::string> &files);
std::vector<std::string> 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<std::string>& prefix, const std::string& folder) {
vector<std::string> 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<string> Val;
typedef pair<Key,Val> Entry;
typedef vector<Entry> EntryVector;
typedef map<Key,Val> 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) { 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) if (!df::global::world)
return; return;
//TODO: use different separators for windows //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 = "/"; static const std::string separator = "/";
#endif #endif
std::string rawFolder = "data" + separator + "save" + separator + (df::global::world->cur_savegame.save_dir) + separator + "raw" + separator; std::string rawFolder = "data" + separator + "save" + separator + (df::global::world->cur_savegame.save_dir) + separator + "raw" + separator;
switch(event) {
case SC_WORLD_LOADED: auto i = table.find(event);
loadScriptFile(out, "onLoadWorld.init", true); if ( i != table.end() ) {
loadScriptFile(out, rawFolder + "onLoadWorld.init", true); const std::vector<std::string>& set = i->second;
loadScriptFile(out, rawFolder + "onLoad.init", true); loadScriptFiles(this, out, set, "." );
break; loadScriptFiles(this, out, set, rawFolder);
case SC_WORLD_UNLOADED: loadScriptFiles(this, out, set, rawFolder + "objects" + separator);
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;
} }
for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it)