diff options
| author | Alexander Gavrilov | 2012-05-04 20:59:06 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-05-04 20:59:06 +0400 |
| commit | 7e01b004e9675fec417bee0eead7d3d0702395e6 (patch) | |
| tree | 5d37f646154b15659e2db4cb5f3a515080aabc87 | |
| parent | d4d6349f48d01b31f59f94238d6656e3b5d08508 (diff) | |
| download | dfhack-7e01b004e9675fec417bee0eead7d3d0702395e6.tar.gz dfhack-7e01b004e9675fec417bee0eead7d3d0702395e6.tar.bz2 dfhack-7e01b004e9675fec417bee0eead7d3d0702395e6.tar.xz | |
Implement timeouts in the core lua context, and quicksave script.
| -rw-r--r-- | LUA_API.rst | 20 | ||||
| -rw-r--r-- | Lua API.html | 17 | ||||
| -rw-r--r-- | library/Core.cpp | 4 | ||||
| -rw-r--r-- | library/LuaApi.cpp | 7 | ||||
| -rw-r--r-- | library/LuaTools.cpp | 129 | ||||
| -rw-r--r-- | library/PluginManager.cpp | 2 | ||||
| -rw-r--r-- | library/include/LuaTools.h | 2 | ||||
| -rw-r--r-- | scripts/quicksave.lua | 28 |
8 files changed, 207 insertions, 2 deletions
diff --git a/LUA_API.rst b/LUA_API.rst index d9488d60..93866437 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -598,6 +598,14 @@ One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments can be omitted. +* ``dfhack.isWorldLoaded()`` + + Checks if the world is loaded. + +* ``dfhack.isMapLoaded()`` + + Checks if the world and map are loaded. + * ``dfhack.TranslateName(name[,in_english,only_last_name])`` Convert a language_name or only the last name part to string. @@ -950,6 +958,18 @@ Core context specific functions: Boolean value; *true* in the core context. +* ``dfhack.timeout(time,mode,callback)`` + + Arranges for the callback to be called once the specified + period of time passes. The ``mode`` argument specifies the + unit of time used, and may be one of ``'frames'`` (raw FPS), + ``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``, + ``'years'`` (in-game time). All timers other than + ``'frames'`` are cancelled when the world is unloaded, + and cannot be queued until it is loaded again. + Returns the timer id, or *nil* if unsuccessful due to + world being unloaded. + * ``dfhack.onStateChange.foo = function(code)`` Event. Receives the same codes as plugin_onstatechange in C++. diff --git a/Lua API.html b/Lua API.html index 40706d14..b96d1215 100644 --- a/Lua API.html +++ b/Lua API.html @@ -855,6 +855,12 @@ One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments can be omitted.</p> <ul> +<li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p> +<p>Checks if the world is loaded.</p> +</li> +<li><p class="first"><tt class="docutils literal">dfhack.isMapLoaded()</tt></p> +<p>Checks if the world and map are loaded.</p> +</li> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.TranslateName(name[,in_english,only_last_name])</span></tt></p> <p>Convert a language_name or only the last name part to string.</p> </li> @@ -1151,6 +1157,17 @@ only context that can receive events from DF and plugins.</p> <li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p> <p>Boolean value; <em>true</em> in the core context.</p> </li> +<li><p class="first"><tt class="docutils literal">dfhack.timeout(time,mode,callback)</tt></p> +<p>Arranges for the callback to be called once the specified +period of time passes. The <tt class="docutils literal">mode</tt> argument specifies the +unit of time used, and may be one of <tt class="docutils literal">'frames'</tt> (raw FPS), +<tt class="docutils literal">'ticks'</tt> (unpaused FPS), <tt class="docutils literal">'days'</tt>, <tt class="docutils literal">'months'</tt>, +<tt class="docutils literal">'years'</tt> (in-game time). All timers other than +<tt class="docutils literal">'frames'</tt> are cancelled when the world is unloaded, +and cannot be queued until it is loaded again. +Returns the timer id, or <em>nil</em> if unsuccessful due to +world being unloaded.</p> +</li> <li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p> <p>Event. Receives the same codes as plugin_onstatechange in C++.</p> </li> diff --git a/library/Core.cpp b/library/Core.cpp index 60e38059..d7e4435c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -355,6 +355,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve { string help = getLuaHelp(filename); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); + return CR_OK; } con.printerr("Unknown command: %s\n", parts[0].c_str()); } @@ -1077,6 +1078,9 @@ int Core::Update() // notify all the plugins that a game tick is finished plug_mgr->OnUpdate(out); + // process timers in lua + Lua::Core::onUpdate(out); + // Release the fake suspend lock { lock_guard<mutex> lock(d->AccessMutex); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 828e54cb..ea723df6 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -570,9 +570,14 @@ static void OpenModule(lua_State *state, const char *mname, #define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) } -/***** Translation module *****/ +/***** DFHack module *****/ + +static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); } +static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); } static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAP(isWorldLoaded), + WRAP(isMapLoaded), WRAPM(Translation, TranslateName), { NULL, NULL } }; diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 17ad0abd..10ff51ba 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -53,6 +53,7 @@ distribution. #include "df/building.h" #include "df/unit.h" #include "df/item.h" +#include "df/world.h" #include <lua.h> #include <lauxlib.h> @@ -1412,13 +1413,135 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) return state; } +static int next_timeout_id = 0; +static int frame_idx = 0; +static std::multimap<int,int> frame_timers; +static std::multimap<int,int> tick_timers; + +int DFHACK_TIMEOUTS_TOKEN = 0; + +static const char *const timeout_modes[] = { + "frames", "ticks", "days", "months", "years", NULL +}; + +int dfhack_timeout(lua_State *L) +{ + using df::global::world; + using df::global::enabler; + + lua_Number time = luaL_checknumber(L, 1); + int mode = luaL_checkoption(L, 2, NULL, timeout_modes); + luaL_checktype(L, 3, LUA_TFUNCTION); + lua_settop(L, 3); + + if (mode > 0 && !Core::getInstance().isWorldLoaded()) + { + lua_pushnil(L); + return 1; + } + + switch (mode) + { + case 2: + time *= 1200; + break; + case 3: + time *= 33600; + break; + case 4: + time *= 403200; + break; + default:; + } + + int id = next_timeout_id++; + int delta = time; + + if (delta <= 0) + luaL_error(L, "Invalid timeout: %d", delta); + + if (mode) + tick_timers.insert(std::pair<int,int>(world->frame_counter+delta, id)); + else + frame_timers.insert(std::pair<int,int>(frame_idx+delta, id)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + lua_swap(L); + lua_rawseti(L, -2, id); + + lua_pushinteger(L, id); + return 1; +} + +static void cancel_tick_timers() +{ + using Lua::Core::State; + + Lua::StackUnwinder frame(State); + lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + + for (auto it = tick_timers.begin(); it != tick_timers.end(); ++it) + { + lua_pushnil(State); + lua_rawseti(State, frame[1], it->second); + } + + tick_timers.clear(); +} + void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { if (!State) return; + switch (code) + { + case SC_MAP_UNLOADED: + case SC_WORLD_UNLOADED: + cancel_tick_timers(); + break; + + default:; + } + Lua::Push(State, code); Lua::InvokeEvent(out, State, (void*)onStateChange, 1); } +void DFHack::Lua::Core::onUpdate(color_ostream &out) +{ + using df::global::world; + + Lua::StackUnwinder frame(State); + lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + + frame_idx++; + + while (!frame_timers.empty() && + frame_timers.begin()->first <= frame_idx) + { + int id = frame_timers.begin()->second; + frame_timers.erase(frame_timers.begin()); + + lua_rawgeti(State, frame[1], id); + lua_pushnil(State); + lua_rawseti(State, frame[1], id); + + Lua::SafeCall(out, State, 0, 0); + } + + while (!tick_timers.empty() && + tick_timers.begin()->first <= world->frame_counter) + { + int id = tick_timers.begin()->second; + tick_timers.erase(tick_timers.begin()); + + lua_rawgeti(State, frame[1], id); + lua_pushnil(State); + lua_rawseti(State, frame[1], id); + + Lua::SafeCall(out, State, 0, 0); + } +} + void DFHack::Lua::Core::Init(color_ostream &out) { if (State) @@ -1428,12 +1551,18 @@ void DFHack::Lua::Core::Init(color_ostream &out) Lua::Open(out, State); + lua_newtable(State); + lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + // Register events lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); MakeEvent(State, (void*)onStateChange); lua_setfield(State, -2, "onStateChange"); + lua_pushcfunction(State, dfhack_timeout); + lua_setfield(State, -2, "timeout"); + lua_pop(State, 1); } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 69b8bfe0..b8619d50 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -591,7 +591,7 @@ command_result PluginManager::InvokeCommand(color_ostream &out, const std::strin bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top) { Plugin *plugin = getPluginByCommand(command); - return plugin ? plugin->can_invoke_hotkey(command, top) : false; + return plugin ? plugin->can_invoke_hotkey(command, top) : true; } void PluginManager::OnUpdate(color_ostream &out) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 2ffc5c58..669d4d38 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -333,6 +333,8 @@ namespace DFHack {namespace Lua { // Events signalled by the core void onStateChange(color_ostream &out, int code); + // Signals timers + void onUpdate(color_ostream &out); template<class T> inline void Push(T &arg) { Lua::Push(State, arg); } template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); } diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua new file mode 100644 index 00000000..c54cc730 --- /dev/null +++ b/scripts/quicksave.lua @@ -0,0 +1,28 @@ +-- Makes the game immediately save the state. + +if not dfhack.isMapLoaded() then + dfhack.printerr("World and map aren't loaded.") + return +end + +local ui_main = df.global.ui.main +local flags4 = df.global.d_init.flags4 + +local function restore_autobackup() + if ui_main.autosave_request and dfhack.isMapLoaded() then + dfhack.timeout(10, 'frames', restore_autobackup) + else + flags4.AUTOBACKUP = true + end +end + +-- Request auto-save +ui_main.autosave_request = true + +-- And since it will overwrite the backup, disable it temporarily +if flags4.AUTOBACKUP then + flags4.AUTOBACKUP = false + restore_autobackup() +end + +print 'The game should save the state now.' |
