summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-05-04 20:59:06 +0400
committerAlexander Gavrilov2012-05-04 20:59:06 +0400
commit7e01b004e9675fec417bee0eead7d3d0702395e6 (patch)
tree5d37f646154b15659e2db4cb5f3a515080aabc87
parentd4d6349f48d01b31f59f94238d6656e3b5d08508 (diff)
downloaddfhack-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.rst20
-rw-r--r--Lua API.html17
-rw-r--r--library/Core.cpp4
-rw-r--r--library/LuaApi.cpp7
-rw-r--r--library/LuaTools.cpp129
-rw-r--r--library/PluginManager.cpp2
-rw-r--r--library/include/LuaTools.h2
-rw-r--r--scripts/quicksave.lua28
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.'