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
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()

@ -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
===================

@ -32,6 +32,9 @@ distribution.
#include <cstring>
#include <iterator>
#include <sstream>
#include <forward_list>
#include <type_traits>
#include <cstdarg>
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<std::string>& 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<std::string> 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<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) {
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<std::string>& 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)