diff --git a/LUA_API.rst b/LUA_API.rst index e68a87a66..050714c72 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -209,7 +209,7 @@ Implemented features: * ``ref:insert(index,item)`` Inserts a new item at the specified index. To add at the end, - use ``#ref`` as index. + use ``#ref``, or just ``'#'`` as index. * ``ref:erase(index)`` @@ -428,6 +428,11 @@ Currently it defines the following features: If the thread owns the interactive console, shows a prompt and returns the entered string. Otherwise returns *nil, error*. + Depending on the context, this function may actually yield the + running coroutine and let the C++ code release the core suspend + lock. Using an explicit ``dfhack.with_suspend`` will prevent + this, forcing the function to block on input with lock held. + * ``dfhack.interpreter([prompt[,env[,history_filename]]])`` Starts an interactive lua interpreter, using the specified prompt @@ -449,6 +454,10 @@ Currently it defines the following features: Just like pcall, but also prints the error using printerr before returning. Intended as a convenience function. +* ``dfhack.saferesume(coroutine[,args...])`` + + Compares to coroutine.resume like dfhack.safecall vs pcall. + * ``dfhack.with_suspend(f[,args...])`` Calls ``f`` with arguments after grabbing the DF core suspend lock. @@ -813,3 +822,37 @@ Core context specific functions: * ``dfhack.is_core_context`` Boolean value; *true* in the core context. + +* ``dfhack.onStateChange.foo = function(code)`` + + Event. Receives the same codes as plugin_onstatechange in C++. + + +Event type +---------- + +An event is just a lua table with a predefined metatable that +contains a __call metamethod. When it is invoked, it loops +through the table with next and calls all contained values. +This is intended as an extensible way to add listeners. + +This type itself is available in any context, but only the +core context has the actual events defined by C++ code. + +Features: + +* ``dfhack.event.new()`` + + Creates a new instance of an event. + +* ``event[key] = function`` + + Sets the function as one of the listeners. + + **NOTE**: The ``df.NULL`` key is reserved for the use by + the C++ owner of the event, and has some special semantics. + +* ``event(args...)`` + + Invokes all listeners contained in the event in an arbitrary + order using ``dfhack.safecall``. diff --git a/Lua API.html b/Lua API.html index 38a375d86..cbf35f0eb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -344,7 +344,10 @@ ul.auto-toc {
  • Maps module
  • -
  • Core interpreter context
  • +
  • Core interpreter context +
  • @@ -513,7 +516,7 @@ possible.

  • ref:insert(index,item)

    Inserts a new item at the specified index. To add at the end, -use #ref as index.

    +use #ref, or just '#' as index.

  • ref:erase(index)

    Removes the element at the given valid index.

    @@ -705,6 +708,10 @@ works with DFHack output infrastructure.

  • dfhack.lineedit([prompt[,history_filename]])

    If the thread owns the interactive console, shows a prompt and returns the entered string. Otherwise returns nil, error.

    +

    Depending on the context, this function may actually yield the +running coroutine and let the C++ code release the core suspend +lock. Using an explicit dfhack.with_suspend will prevent +this, forcing the function to block on input with lock held.

  • dfhack.interpreter([prompt[,env[,history_filename]]])

    Starts an interactive lua interpreter, using the specified prompt @@ -722,6 +729,9 @@ in C++, and dfhack.safecall.

    Just like pcall, but also prints the error using printerr before returning. Intended as a convenience function.

  • +
  • dfhack.saferesume(coroutine[,args...])

    +

    Compares to coroutine.resume like dfhack.safecall vs pcall.

    +
  • dfhack.with_suspend(f[,args...])

    Calls f with arguments after grabbing the DF core suspend lock. Suspending is necessary for accessing a consistent state of DF memory.

    @@ -1021,9 +1031,36 @@ only context that can receive events from DF and plugins.

  • dfhack.is_core_context

    Boolean value; true in the core context.

  • +
  • dfhack.onStateChange.foo = function(code)

    +

    Event. Receives the same codes as plugin_onstatechange in C++.

    +
  • + +
    +

    Event type

    +

    An event is just a lua table with a predefined metatable that +contains a __call metamethod. When it is invoked, it loops +through the table with next and calls all contained values. +This is intended as an extensible way to add listeners.

    +

    This type itself is available in any context, but only the +core context has the actual events defined by C++ code.

    +

    Features:

    +
    + diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index be4fa864e..469caa65f 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1098,6 +1098,18 @@ int DFHack::Lua::NewEvent(lua_State *state) return 1; } +static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun) +{ + for (int i = 0; i < num_args; i++) + lua_pushvalue(L, argbase+i); + + if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) + { + report_error(L); + lua_pop(L, 1); + } +} + static void dfhack_event_invoke(lua_State *L, int base, bool from_c) { int event = base+1; @@ -1108,23 +1120,31 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) lua_insert(L, errorfun); int argbase = base+3; - lua_pushnil(L); - // stack: |base| event errorfun (args) key cb (args) + // stack: |base| event errorfun (args) - while (lua_next(L, event)) + if (!from_c) { - if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1)) - continue; + // Invoke the NULL key first + lua_rawgetp(L, event, NULL); - for (int i = 0; i < num_args; i++) - lua_pushvalue(L, argbase+i); + if (lua_isnil(L, -1)) + lua_pop(L, 1); + else + do_invoke_event(L, argbase, num_args, errorfun); + } - if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) - { - report_error(L); + lua_pushnil(L); + + // stack: |base| event errorfun (args) key || cb (args) + + while (lua_next(L, event)) + { + // Skip the NULL key in the main loop + if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2)) lua_pop(L, 1); - } + else + do_invoke_event(L, argbase, num_args, errorfun); } lua_settop(L, base); @@ -1285,13 +1305,29 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) return state; } +void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { + if (!State) return; + + Lua::Push(State, code); + Lua::InvokeEvent(out, State, (void*)onStateChange, 1); +} + void DFHack::Lua::Core::Init(color_ostream &out) { if (State) return; State = luaL_newstate(); + Lua::Open(out, State); + + // Register events + lua_getglobal(State, "dfhack"); + + MakeEvent(State, (void*)onStateChange); + lua_setfield(State, -2, "onStateChange"); + + lua_pop(State, 1); } void DFHack::Lua::Core::Reset(color_ostream &out, const char *where) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index c5ffe0f6b..2cfe8a84e 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -733,8 +733,18 @@ static int lookup_container_field(lua_State *state, int field, const char *mode * Index verification: number and in range. */ static int check_container_index(lua_State *state, int len, - int fidx, int iidx, const char *mode) + int fidx, int iidx, const char *mode, + bool is_insert = false) { + if (is_insert && len >= 0) + { + if (lua_type(state, iidx) == LUA_TSTRING + && strcmp(lua_tostring(state, iidx), "#") == 0) + return len; + + len++; + } + if (!lua_isnumber(state, iidx)) field_error(state, fidx, "invalid index", mode); @@ -867,8 +877,7 @@ static int method_container_insert(lua_State *state) auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN); - if (len >= 0) len++; - int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call"); + int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true); if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3)) field_error(state, UPVAL_METHOD_NAME, "not supported", "call"); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 1821e18d7..837c3e2d5 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -641,6 +641,8 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event) { all_plugins[i]->on_state_change(out, event); } + + Lua::Core::onStateChange(out, event); } // FIXME: doesn't check name collisions! diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 54aa6dcc7..b2d440a6b 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -278,6 +278,9 @@ namespace DFHack {namespace Lua { void Init(color_ostream &out); void Reset(color_ostream &out, const char *where); + // Events signalled by the core + void onStateChange(color_ostream &out, int code); + template inline void Push(T &arg) { Lua::Push(State, arg); } template inline void Push(const T &arg) { Lua::Push(State, arg); } template inline void PushVector(const T &arg) { Lua::PushVector(State, arg); } diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index a51d90747..7f98bf422 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -67,13 +67,13 @@ namespace DFHack enum state_change_event { - SC_WORLD_LOADED, - SC_WORLD_UNLOADED, - SC_MAP_LOADED, - SC_MAP_UNLOADED, - SC_VIEWSCREEN_CHANGED, - SC_CORE_INITIALIZED, - SC_BEGIN_UNLOAD + SC_WORLD_LOADED = 0, + SC_WORLD_UNLOADED = 1, + SC_MAP_LOADED = 2, + SC_MAP_UNLOADED = 3, + SC_VIEWSCREEN_CHANGED = 4, + SC_CORE_INITIALIZED = 5, + SC_BEGIN_UNLOAD = 6 }; struct DFHACK_EXPORT CommandReg { const char *name; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 4be58661b..c7e2669c5 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13 COLOR_YELLOW = 14 COLOR_WHITE = 15 +-- Events + +if dfhack.is_core_context then + SC_WORLD_LOADED = 0 + SC_WORLD_UNLOADED = 1 + SC_MAP_LOADED = 2 + SC_MAP_UNLOADED = 3 + SC_VIEWSCREEN_CHANGED = 4 + SC_CORE_INITIALIZED = 5 +end + -- Error handling safecall = dfhack.safecall diff --git a/library/xml b/library/xml index a54516705..e8036d3f1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a545167050fee9eedd5e319b518d961f933154a3 +Subproject commit e8036d3f13c6be0141899baae90f605ad11d5385 diff --git a/plugins/Brushes.h b/plugins/Brushes.h index 97ddc44c0..a525d279c 100644 --- a/plugins/Brushes.h +++ b/plugins/Brushes.h @@ -196,6 +196,12 @@ private: Core *c_; }; -std::ostream &operator<<(std::ostream &stream, const Brush& brush) { +inline std::ostream &operator<<(std::ostream &stream, const Brush& brush) { stream << brush.str(); + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const Brush* brush) { + stream << brush->str(); + return stream; } diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 3f4a3b6fe..4e041f180 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -382,12 +382,15 @@ command_result df_bprobe (color_ostream &out, vector & parameters) out.print(", subtype %i", building.subtype); break; } - if(building.origin->isRoom()) + if(building.origin->is_room) //isRoom()) out << ", is room"; else out << ", not a room"; + if(building.origin->getBuildStage()!=building.origin->getMaxBuildStage()) + out << ", in construction"; out.print("\n"); + } return CR_OK; } diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 7302aea60..1871d0f73 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -150,7 +150,7 @@ static command_result rename(color_ostream &out, vector ¶meters) if (parameters.size() != 2) return CR_WRONG_USAGE; - df::unit *unit = Gui::getSelectedUnit(out); + df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) return CR_WRONG_USAGE; @@ -161,7 +161,7 @@ static command_result rename(color_ostream &out, vector ¶meters) if (parameters.size() != 2) return CR_WRONG_USAGE; - df::unit *unit = Gui::getSelectedUnit(out); + df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) return CR_WRONG_USAGE; diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 41627a852..546f9c2c5 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -325,7 +325,7 @@ void printState(color_ostream &out) { out << "Filter: " << filter << std::endl << "Paint: " << paint << std::endl - << "Brush: " << brush->str() << std::endl; + << "Brush: " << brush << std::endl; } //zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build. @@ -559,6 +559,7 @@ bool processTileType(color_ostream & out, TileType &paint, std::vector ¶meters) if (cmd == "clear-missing") { - df::unit *unit = getSelectedUnit(out); + df::unit *unit = getSelectedUnit(out, true); if (!unit) return CR_FAILURE; @@ -157,7 +157,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "clear-ghostly") { - df::unit *unit = getSelectedUnit(out); + df::unit *unit = getSelectedUnit(out, true); if (!unit) return CR_FAILURE; @@ -176,13 +176,9 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "fixmigrant") { - df::unit *unit = getSelectedUnit(out); - + df::unit *unit = getSelectedUnit(out, true); if (!unit) - { - out << "No unit selected!" << endl; return CR_FAILURE; - } if(unit->race != df::global::ui->race_id) { @@ -213,12 +209,10 @@ static command_result tweak(color_ostream &out, vector ¶meters) { // force a unit into your fort, regardless of civ or race // allows to "steal" caravan guards etc - df::unit *unit = getSelectedUnit(out); + df::unit *unit = getSelectedUnit(out, true); if (!unit) - { - out << "No unit selected!" << endl; return CR_FAILURE; - } + if (unit->flags2.bits.resident) unit->flags2.bits.resident = 0; if(unit->flags1.bits.merchant) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 968fda72f..ee6abf327 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -897,6 +897,10 @@ int32_t findCageAtCursor() building->z == cursor->z)) continue; + // don't set id if cage is not constructed yet + if(building->getBuildStage()!=building->getMaxBuildStage()) + break; + if(isCage(building)) { foundID = building->id; @@ -1065,10 +1069,10 @@ bool isInBuiltCageRoom(df::unit* unit) { df::building* building = world->buildings.all[b]; - // !!! for whatever reason isRoom() returns true if a cage is not a room - // !!! and false if it was defined as a room/zoo ingame - // !!! (seems not general behaviour, activity zones return false, for example) - if(building->isRoom()) + // !!! building->isRoom() returns true if the building can be made a room but currently isn't + // !!! except for coffins/tombs which always return false + // !!! using the bool is_room however gives the correct state/value + if(!building->is_room) continue; if(building->getType() == building_type::Cage) @@ -1122,6 +1126,10 @@ df::building * getBuiltCageAtPos(df::coord pos) && building->y1 == pos.y && building->z == pos.z ) { + // don't set pointer if not constructed yet + if(building->getBuildStage()!=building->getMaxBuildStage()) + break; + cage = building; break; } @@ -1594,11 +1602,6 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) else out << "not active"; - //if(building->isRoom()) - // out <<", room"; - //else - // out << ", not a room"; - if(civ->zone_flags.bits.pen_pasture) out << ", pen/pasture"; else if (civ->zone_flags.bits.pit_pond) @@ -1655,16 +1658,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose) << " z:" << building->z << endl; - //if(building->isRoom()) - // out <<", bldg room"; - //else - // out << ", bldg not a room"; - df::building_cagest * cage = (df::building_cagest*) building; - //if(cage->isRoom()) - // out <<", cage is room"; - //else - // out << ", cage is not a room"; int32_t creaturecount = cage->assigned_creature.size(); out << "Creatures in this cage: " << creaturecount << endl; @@ -2277,8 +2271,8 @@ command_result df_zone (color_ostream &out, vector & parameters) // find building under cursor if (!all && !(building->x1 <= cursor->x && cursor->x <= building->x2 && - building->y1 <= cursor->y && cursor->y <= building->y2 && - building->z == cursor->z)) + building->y1 <= cursor->y && cursor->y <= building->y2 && + building->z == cursor->z)) continue; zoneInfo(out, building, verbose); @@ -2465,12 +2459,9 @@ command_result df_zone (color_ostream &out, vector & parameters) else { // must have unit selected - df::unit *unit = getSelectedUnit(out); + df::unit *unit = getSelectedUnit(out, true); if (!unit) - { - out << "No unit selected." << endl; return CR_WRONG_USAGE; - } if(unit_info) { @@ -2503,18 +2494,16 @@ command_result df_zone (color_ostream &out, vector & parameters) if(building_unassign) { // must have unit selected - df::unit *unit = getSelectedUnit(out); + df::unit *unit = getSelectedUnit(out, true); if (!unit) - { - out << "No unit selected." << endl; return CR_WRONG_USAGE; - } // remove assignment reference from unit and old zone if(unassignUnitFromBuilding(unit)) out << "Unit unassigned." << endl; else out << "Unit is not assigned to an activity zone!" << endl; + return CR_OK; }