diff --git a/LICENSE b/LICENSE index 5ea59addf..96ab022d9 100644 --- a/LICENSE +++ b/LICENSE @@ -113,3 +113,27 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------- + +See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +Copyright (c) 2008-2010 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/LUA_API.rst b/LUA_API.rst index bf6c81ec0..71cb6030f 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -456,6 +456,31 @@ Currently it defines the following features: to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock. +* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])`` + + Invokes ``fn`` with ``args``, and after it returns or throws an + error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from + ``fn`` are propagated, and errors are re-thrown. + + The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``, + and the ``always`` boolean specifies if cleanup should be called in any case, + or only in case of an error. + +* ``dfhack.with_finalize(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``. + Implemented using ``call_with_finalizer(0,true,...)``. + +* ``dfhack.with_onerror(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error. + Implemented using ``call_with_finalizer(0,false,...)``. + +* ``dfhack.with_temp_object(obj,fn[,args...])`` + + Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``. + + Persistent configuration storage ================================ @@ -497,3 +522,148 @@ Since the data is hidden in data structures owned by the DF world, and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. + +Material info lookup +==================== + +A material info record has fields: + +* ``type``, ``index``, ``material`` + + DF material code pair, and a reference to the material object. + +* ``mode`` + + One of ``'builtin'``, ``'inorganic'``, ``'plant'``, ``'creature'``. + +* ``inorganic``, ``plant``, ``creature`` + + If the material is of the matching type, contains a reference to the raw object. + +* ``figure`` + + For a specific creature material contains a ref to the historical figure. + +Functions: + +* ``dfhack.matinfo.decode(type,index)`` + + Looks up material info for the given number pair; if not found, returs *nil*. + +* ``....decode(matinfo)``, ``....decode(item)``, ``....decode(obj)`` + + Uses ``matinfo.type``/``matinfo.index``, item getter vmethods, + or ``obj.mat_type``/``obj.mat_index`` to get the code pair. + +* ``dfhack.matinfo.find(token[,token...])`` + + Looks up material by a token string, or a pre-split string token sequence. + +* ``dfhack.matinfo.getToken(...)``, ``info:getToken()`` + + Applies ``decode`` and constructs a string token. + +* ``info:toString([temperature[,named]])`` + + Returns the human-readable name at the given temperature. + +* ``info:getCraftClass()`` + + Returns the classification used for craft skills. + +* ``info:matches(obj)`` + + Checks if the material matches job_material_category or job_item. + Accept dfhack_material_category auto-assign table. + +C++ function wrappers +===================== + +Thin wrappers around C++ functions, similar to the ones for virtual methods. + +* ``dfhack.TranslateName(name,in_english,only_last_name)`` + + Convert a language_name or only the last name part to string. + +Gui module +---------- + +* ``dfhack.gui.getSelectedWorkshopJob(silent)`` + + When a job is selected in *'q'* mode, returns the job, else + prints error unless silent and returns *nil*. + +* ``dfhack.gui.getSelectedJob(silent)`` + + Returns the job selected in a workshop or unit/jobs screen. + +* ``dfhack.gui.getSelectedUnit(silent)`` + + Returns the unit selected via *'v'*, *'k'*, unit/jobs, or + a full-screen item view of a cage or suchlike. + +* ``dfhack.gui.getSelectedItem(silent)`` + + Returns the item selected via *'v'* ->inventory, *'k'*, *'t'*, or + a full-screen item view of a container. Note that in the + last case, the highlighted *contained item* is returned, not + the container itself. + +* ``dfhack.gui.showAnnouncement(text,color,is_bright)`` + + Adds a regular announcement with given text, color, and brightness. + The is_bright boolean actually seems to invert the brightness. + +* ``dfhack.gui.showPopupAnnouncement(text,color,is_bright)`` + + Pops up a titan-style modal announcement window. + +Job module +---------- + +* ``dfhack.job.cloneJobStruct(job)`` + + Creates a deep copy of the given job. + +* ``dfhack.job.printJobDetails(job)`` + + Prints info about the job. + +* ``dfhack.job.getJobHolder(job)`` + + Returns the building holding the job. + +* ``dfhack.job.is_equal(job1,job2)`` + + Compares important fields in the job and nested item structures. + +* ``dfhack.job.is_item_equal(job_item1,job_item2)`` + + Compares important fields in the job item structures. + +Units module +------------ + +* ``dfhack.units.setNickname(unit,nick)`` + + Sets the unit's nickname properly. + +* ``dfhack.units.getVisibleName(unit)`` + + Returns the language_name object visible in game, accounting for false identities. + +* ``dfhack.units.getNemesis(unit)`` + + Returns the nemesis record of the unit if it has one, or *nil*. + +* ``dfhack.units.isDead(unit)`` + + The unit is completely dead and passive. + +* ``dfhack.units.isAlive(unit)`` + + The unit isn't dead or undead. + +* ``dfhack.units.isSane(unit)`` + + The unit is capable of rational action, i.e. not dead, insane or zombie. diff --git a/Lua API.html b/Lua API.html index 25b6f06d6..569940f56 100644 --- a/Lua API.html +++ b/Lua API.html @@ -335,6 +335,13 @@ ul.auto-toc {
  • DFHack utilities
  • @@ -717,6 +724,25 @@ the lock. It is safe to nest suspends.

    to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock.

    +
  • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

    +

    Invokes fn with args, and after it returns or throws an +error calls cleanup_fn with cleanup_args. Any return values from +fn are propagated, and errors are re-thrown.

    +

    The num_cleanup_args integer specifies the number of cleanup_args, +and the always boolean specifies if cleanup should be called in any case, +or only in case of an error.

    +
  • +
  • dfhack.with_finalize(cleanup_fn,fn[,args...])

    +

    Calls fn with arguments, then finalizes with cleanup_fn. +Implemented using call_with_finalizer(0,true,...).

    +
  • +
  • dfhack.with_onerror(cleanup_fn,fn[,args...])

    +

    Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. +Implemented using call_with_finalizer(0,false,...).

    +
  • +
  • dfhack.with_temp_object(obj,fn[,args...])

    +

    Calls fn(obj,args...), then finalizes with obj:delete().

    +
  • Persistent configuration storage

    @@ -753,6 +779,131 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    +
    +

    Material info lookup

    +

    A material info record has fields:

    + +

    Functions:

    + +
    +
    +

    C++ function wrappers

    +

    Thin wrappers around C++ functions, similar to the ones for virtual methods.

    + +
    +

    Gui module

    +
      +
    • dfhack.gui.getSelectedWorkshopJob(silent)

      +

      When a job is selected in 'q' mode, returns the job, else +prints error unless silent and returns nil.

      +
    • +
    • dfhack.gui.getSelectedJob(silent)

      +

      Returns the job selected in a workshop or unit/jobs screen.

      +
    • +
    • dfhack.gui.getSelectedUnit(silent)

      +

      Returns the unit selected via 'v', 'k', unit/jobs, or +a full-screen item view of a cage or suchlike.

      +
    • +
    • dfhack.gui.getSelectedItem(silent)

      +

      Returns the item selected via 'v' ->inventory, 'k', 't', or +a full-screen item view of a container. Note that in the +last case, the highlighted contained item is returned, not +the container itself.

      +
    • +
    • dfhack.gui.showAnnouncement(text,color,is_bright)

      +

      Adds a regular announcement with given text, color, and brightness. +The is_bright boolean actually seems to invert the brightness.

      +
    • +
    • dfhack.gui.showPopupAnnouncement(text,color,is_bright)

      +

      Pops up a titan-style modal announcement window.

      +
    • +
    +
    +
    +

    Job module

    +
      +
    • dfhack.job.cloneJobStruct(job)

      +

      Creates a deep copy of the given job.

      +
    • +
    • dfhack.job.printJobDetails(job)

      +

      Prints info about the job.

      +
    • +
    • dfhack.job.getJobHolder(job)

      +

      Returns the building holding the job.

      +
    • +
    • dfhack.job.is_equal(job1,job2)

      +

      Compares important fields in the job and nested item structures.

      +
    • +
    • dfhack.job.is_item_equal(job_item1,job_item2)

      +

      Compares important fields in the job item structures.

      +
    • +
    +
    +
    +

    Units module

    +
      +
    • dfhack.units.setNickname(unit,nick)

      +

      Sets the unit's nickname properly.

      +
    • +
    • dfhack.units.getVisibleName(unit)

      +

      Returns the language_name object visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.getNemesis(unit)

      +

      Returns the nemesis record of the unit if it has one, or nil.

      +
    • +
    • dfhack.units.isDead(unit)

      +

      The unit is completely dead and passive.

      +
    • +
    • dfhack.units.isAlive(unit)

      +

      The unit isn't dead or undead.

      +
    • +
    • dfhack.units.isSane(unit)

      +

      The unit is capable of rational action, i.e. not dead, insane or zombie.

      +
    • +
    +
    +
    diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index d0410850c..b4ea751e7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -59,6 +59,7 @@ DataDefs.cpp LuaWrapper.cpp LuaTypes.cpp LuaTools.cpp +LuaApi.cpp DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp new file mode 100644 index 000000000..5e55460d7 --- /dev/null +++ b/library/LuaApi.cpp @@ -0,0 +1,581 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "tinythread.h" +// must be last due to MS stupidity +#include "DataDefs.h" +#include "DataIdentity.h" +#include "DataFuncs.h" + +#include "modules/World.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" +#include "modules/Materials.h" + +#include "LuaWrapper.h" +#include "LuaTools.h" + +#include "MiscUtils.h" + +#include "df/job.h" +#include "df/job_item.h" +#include "df/building.h" +#include "df/unit.h" +#include "df/item.h" +#include "df/material.h" +#include "df/nemesis_record.h" +#include "df/historical_figure.h" +#include "df/plant_raw.h" +#include "df/creature_raw.h" +#include "df/inorganic_raw.h" +#include "df/dfhack_material_category.h" +#include "df/job_material_category.h" + +#include +#include +#include + +using namespace DFHack; +using namespace DFHack::LuaWrapper; + +/************************************************** + * Per-world persistent configuration storage API * + **************************************************/ + +static PersistentDataItem persistent_by_struct(lua_State *state, int idx) +{ + lua_getfield(state, idx, "entry_id"); + int id = lua_tointeger(state, -1); + lua_pop(state, 1); + + PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + + if (ref.isValid()) + { + lua_getfield(state, idx, "key"); + const char *str = lua_tostring(state, -1); + if (!str || str != ref.key()) + luaL_argerror(state, idx, "inconsistent id and key"); + lua_pop(state, 1); + } + + return ref; +} + +static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) +{ + if (!ref.isValid()) + { + lua_pushnil(state); + lua_pushstring(state, "entry not found"); + return 2; + } + + if (create) + lua_createtable(state, 0, 4); + + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, ref.entry_id()); + lua_setfield(state, -2, "entry_id"); + lua_pushstring(state, ref.key().c_str()); + lua_setfield(state, -2, "key"); + lua_pushstring(state, ref.val().c_str()); + lua_setfield(state, -2, "value"); + + lua_createtable(state, PersistentDataItem::NumInts, 0); + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_pushinteger(state, ref.ival(i)); + lua_rawseti(state, -2, i+1); + } + lua_setfield(state, -2, "ints"); + + return 1; +} + +static PersistentDataItem get_persistent(lua_State *state) +{ + luaL_checkany(state, 1); + + if (lua_istable(state, 1)) + { + if (!lua_getmetatable(state, 1) || + !lua_rawequal(state, -1, lua_upvalueindex(1))) + luaL_argerror(state, 1, "invalid table type"); + + lua_settop(state, 1); + + return persistent_by_struct(state, 1); + } + else + { + const char *str = luaL_checkstring(state, 1); + + return Core::getInstance().getWorld()->GetPersistentData(str); + } +} + +static int dfhack_persistent_get(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + return read_persistent(state, ref, !lua_istable(state, 1)); +} + +static int dfhack_persistent_delete(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + + lua_pushboolean(state, ok); + return 1; +} + +static int dfhack_persistent_get_all(lua_State *state) +{ + CoreSuspender suspend; + + const char *str = luaL_checkstring(state, 1); + bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); + + std::vector data; + Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + + if (data.empty()) + { + lua_pushnil(state); + } + else + { + lua_createtable(state, data.size(), 0); + for (size_t i = 0; i < data.size(); ++i) + { + read_persistent(state, data[i], true); + lua_rawseti(state, -2, i+1); + } + } + + return 1; +} + +static int dfhack_persistent_save(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 2); + luaL_checktype(state, 1, LUA_TTABLE); + bool add = lua_toboolean(state, 2); + + lua_getfield(state, 1, "key"); + const char *str = lua_tostring(state, -1); + if (!str) + luaL_argerror(state, 1, "no key field"); + + lua_settop(state, 1); + + // Retrieve existing or create a new entry + PersistentDataItem ref; + bool added = false; + + if (add) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + added = true; + } + else if (lua_getmetatable(state, 1)) + { + if (!lua_rawequal(state, -1, lua_upvalueindex(1))) + return luaL_argerror(state, 1, "invalid table type"); + lua_pop(state, 1); + + ref = persistent_by_struct(state, 1); + } + else + { + ref = Core::getInstance().getWorld()->GetPersistentData(str); + } + + // Auto-add if not found + if (!ref.isValid()) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + if (!ref.isValid()) + luaL_error(state, "cannot create persistent entry"); + added = true; + } + + // Copy data from lua to C++ memory + lua_getfield(state, 1, "value"); + if (const char *str = lua_tostring(state, -1)) + ref.val() = str; + lua_pop(state, 1); + + lua_getfield(state, 1, "ints"); + if (lua_istable(state, -1)) + { + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_rawgeti(state, -1, i+1); + if (lua_isnumber(state, -1)) + ref.ival(i) = lua_tointeger(state, -1); + lua_pop(state, 1); + } + } + lua_pop(state, 1); + + // Reinitialize lua from C++ and return + read_persistent(state, ref, false); + lua_pushboolean(state, added); + return 2; +} + +static const luaL_Reg dfhack_persistent_funcs[] = { + { "get", dfhack_persistent_get }, + { "delete", dfhack_persistent_delete }, + { "get_all", dfhack_persistent_get_all }, + { "save", dfhack_persistent_save }, + { NULL, NULL } +}; + +static void OpenPersistent(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "persistent"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_persistent_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + +/************************ + * Material info lookup * + ************************/ + +static void push_matinfo(lua_State *state, MaterialInfo &info) +{ + if (!info.isValid()) + { + lua_pushnil(state); + return; + } + + lua_newtable(state); + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, info.type); + lua_setfield(state, -2, "type"); + lua_pushinteger(state, info.index); + lua_setfield(state, -2, "index"); + +#define SETOBJ(name) { \ + Lua::PushDFObject(state, info.name); \ + lua_setfield(state, -2, #name); \ +} + SETOBJ(material); + if (info.plant) SETOBJ(plant); + if (info.creature) SETOBJ(creature); + if (info.inorganic) SETOBJ(inorganic); + if (info.figure) SETOBJ(figure); +#undef SETOBJ + + if (info.mode != MaterialInfo::Builtin) + { + lua_pushinteger(state, info.subtype); + lua_setfield(state, -2, "subtype"); + } + + const char *id = "builtin"; + switch (info.mode) + { + case MaterialInfo::Plant: id = "plant"; break; + case MaterialInfo::Creature: id = "creature"; break; + case MaterialInfo::Inorganic: id = "inorganic"; break; + } + + lua_pushstring(state, id); + lua_setfield(state, -2, "mode"); +} + +static int dfhack_matinfo_find(lua_State *state) +{ + MaterialInfo info; + int argc = lua_gettop(state); + + if (argc == 1) + info.find(luaL_checkstring(state, 1)); + else + { + std::vector tokens; + + for (int i = 1; i < argc; i++) + tokens.push_back(luaL_checkstring(state, i)); + + info.find(tokens); + } + + push_matinfo(state, info); + return 1; +} + +static bool decode_matinfo(lua_State *state, MaterialInfo *info, bool numpair = false) +{ + int curtop = lua_gettop(state); + + luaL_checkany(state, 1); + + if (!lua_isnumber(state, 1)) + { + if (lua_isnil(state, 1)) + return false; + + if (lua_getmetatable(state, 1)) + { + if (lua_rawequal(state, -1, lua_upvalueindex(1))) + { + lua_getfield(state, 1, "type"); + lua_getfield(state, 1, "index"); + goto int_pair; + } + + lua_pop(state, 1); + } + + if (lua_isuserdata(state, 1)) + { + if (auto item = Lua::GetDFObject(state, 1)) + return info->decode(item); + if (auto mvec = Lua::GetDFObject(state, 1)) + return info->decode(*mvec, luaL_checkint(state, 2)); + } + + lua_getfield(state, 1, "mat_type"); + lua_getfield(state, 1, "mat_index"); + goto int_pair; + } + else + { + if (!numpair) + luaL_argerror(state, 1, "material info object expected"); + + if (curtop < 2) + lua_settop(state, 2); + } + +int_pair: + { + int ok; + int type = lua_tointegerx(state, -2, &ok); + if (!ok) + luaL_argerror(state, 1, "material id is not a number"); + int index = lua_tointegerx(state, -1, &ok); + if (!ok) + index = -1; + + lua_settop(state, curtop); + + return info->decode(type, index); + } +} + +static int dfhack_matinfo_decode(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + push_matinfo(state, info); + return 1; +} + +static int dfhack_matinfo_getToken(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + auto str = info.getToken(); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_toString(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info); + + lua_settop(state, 3); + auto str = info.toString(luaL_optint(state, 2, 10015), lua_toboolean(state, 3)); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_getCraftClass(lua_State *state) +{ + MaterialInfo info; + if (decode_matinfo(state, &info, true)) + lua_pushinteger(state, info.getCraftClass()); + else + lua_pushnil(state); + return 1; +} + +static int dfhack_matinfo_matches(lua_State *state) +{ + MaterialInfo info; + if (!decode_matinfo(state, &info)) + luaL_argerror(state, 1, "material info object expected"); + + luaL_checkany(state, 2); + + if (lua_isuserdata(state, 2)) + { + if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else + luaL_argerror(state, 2, "material category object expected"); + } + else if (lua_istable(state, 2)) + { + df::dfhack_material_category tmp; + if (!Lua::AssignDFObject(*Lua::GetOutput(state), state, &tmp, 2, false)) + lua_error(state); + lua_pushboolean(state, info.matches(tmp)); + } + else + luaL_argerror(state, 2, "material category object expected"); + + return 1; +} + +static const luaL_Reg dfhack_matinfo_funcs[] = { + { "find", dfhack_matinfo_find }, + { "decode", dfhack_matinfo_decode }, + { "getToken", dfhack_matinfo_getToken }, + { "toString", dfhack_matinfo_toString }, + { "getCraftClass", dfhack_matinfo_getCraftClass }, + { "matches", dfhack_matinfo_matches }, + { NULL, NULL } +}; + +static void OpenMatinfo(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "matinfo"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_matinfo_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + +/************************ + * Wrappers for C++ API * + ************************/ + +static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) +{ + luaL_getsubtable(state, lua_gettop(state), mname); + LuaWrapper::SetFunctionWrappers(state, reg); + lua_pop(state, 1); +} + +#define WRAPM(module, function) { #function, df::wrap_function(&module::function) } +#define WRAP(function) { #function, df::wrap_function(&function) } +#define WRAPN(name, function) { #name, df::wrap_function(&function) } + +static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAPM(Translation, TranslateName), + { NULL, NULL } +}; + +static const LuaWrapper::FunctionReg dfhack_gui_module[] = { + WRAPM(Gui, getSelectedWorkshopJob), + WRAPM(Gui, getSelectedJob), + WRAPM(Gui, getSelectedUnit), + WRAPM(Gui, getSelectedItem), + WRAPM(Gui, showAnnouncement), + WRAPM(Gui, showPopupAnnouncement), + { NULL, NULL } +}; + +static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } +static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } + +static const LuaWrapper::FunctionReg dfhack_job_module[] = { + WRAP(cloneJobStruct), + WRAP(printJobDetails), + WRAP(getJobHolder), + WRAPN(is_equal, jobEqual), + WRAPN(is_item_equal, jobItemEqual), + { NULL, NULL } +}; + +static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, setNickname), + WRAPM(Units, getVisibleName), + WRAPM(Units, getNemesis), + WRAPM(Units, isDead), + WRAPM(Units, isAlive), + WRAPM(Units, isSane), + { NULL, NULL } +}; + +/************************ + * Main Open function * + ************************/ + +void OpenDFHackApi(lua_State *state) +{ + OpenPersistent(state); + OpenMatinfo(state); + + LuaWrapper::SetFunctionWrappers(state, dfhack_module); + OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "units", dfhack_units_module); +} diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5a50d0dd2..471ffd550 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -35,14 +35,25 @@ distribution. // must be last due to MS stupidity #include "DataDefs.h" #include "DataIdentity.h" +#include "DataFuncs.h" #include "modules/World.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "LuaWrapper.h" #include "LuaTools.h" #include "MiscUtils.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/building.h" +#include "df/unit.h" +#include "df/item.h" + #include #include #include @@ -70,6 +81,13 @@ color_ostream *DFHack::Lua::GetOutput(lua_State *L) return rv; } +df::cur_lua_ostream_argument::cur_lua_ostream_argument(lua_State *state) +{ + out = DFHack::Lua::GetOutput(state); + if (!out) + LuaWrapper::field_error(state, UPVAL_METHOD_NAME, "no output stream", "invoke"); +} + static void set_dfhack_output(lua_State *L, color_ostream *p) { lua_pushlightuserdata(L, p); @@ -208,10 +226,14 @@ static int lua_dfhack_lineedit(lua_State *S) static int DFHACK_EXCEPTION_META_TOKEN = 0; -static void error_tostring(lua_State *L) +static void error_tostring(lua_State *L, bool keep_old = false) { lua_getglobal(L, "tostring"); - lua_pushvalue(L, -2); + if (keep_old) + lua_pushvalue(L, -2); + else + lua_swap(L); + bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK; const char *msg = lua_tostring(L, -1); @@ -230,8 +252,7 @@ static void error_tostring(lua_State *L) static void report_error(lua_State *L, color_ostream *out = NULL) { - lua_dup(L); - error_tostring(L); + error_tostring(L, true); const char *msg = lua_tostring(L, -1); assert(msg); @@ -272,7 +293,7 @@ static bool convert_to_exception(lua_State *L) lua_setfield(L, base, "message"); else { - error_tostring(L); + error_tostring(L, true); lua_setfield(L, base, "message"); lua_setfield(L, base, "object"); } @@ -328,24 +349,51 @@ static int dfhack_exception_tostring(lua_State *L) if (!lua_isstring(L, -1)) lua_pop(L, 2); + lua_pushstring(L, "\ncaused by:\n"); + lua_getfield(L, 1, "cause"); + if (lua_isnil(L, -1)) + lua_pop(L, 2); + else + error_tostring(L); + lua_concat(L, lua_gettop(L) - base); return 1; } -static int finish_dfhack_safecall (lua_State *L, bool success) +static void push_simple_error(lua_State *L, const char *str) +{ + lua_pushstring(L, str); + + if (lua_checkstack(L, 5)) + convert_to_exception(L); + + if (lua_checkstack(L, LUA_MINSTACK)) + { + luaL_traceback(L, L, NULL, 1); + lua_setfield(L, -2, "stacktrace"); + } +} + +static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) { - if (!lua_checkstack(L, 2)) + if (!lua_checkstack(L, space)) { - lua_settop(L, 0); /* create space for return values */ + lua_settop(L, base-1); /* create space for return values */ lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow in dfhack.safecall()"); - success = false; + push_simple_error(L, "stack overflow"); + return false; } else { lua_pushboolean(L, success); - lua_replace(L, 1); /* put first result in first slot */ + lua_replace(L, base); /* put first result in first slot */ + return true; } +} + +static int finish_dfhack_safecall (lua_State *L, bool success) +{ + success = do_finish_pcall(L, success); if (!success) report_error(L); @@ -581,264 +629,142 @@ static int lua_dfhack_interpreter(lua_State *state) return 1; } -static int lua_dfhack_with_suspend(lua_State *L) +static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) { - int rv = lua_getctx(L, NULL); + bool ok = lua_pcall(L, nargs, 0, errorfun) == LUA_OK; - // Non-resume entry point: - if (rv == LUA_OK) + if (!ok) { - int nargs = lua_gettop(L); - - luaL_checktype(L, 1, LUA_TFUNCTION); - - Core::getInstance().Suspend(); - - lua_pushcfunction(L, dfhack_onerror); - lua_insert(L, 1); + // If finalization failed, attach the previous error + if (lua_istable(L, -1) && !success) + { + lua_swap(L); + lua_setfield(L, -2, "cause"); + } - rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); + success = false; } - // Return, resume, or error entry point: - lua_remove(L, 1); - - Core::getInstance().Resume(); - - if (rv != LUA_OK && rv != LUA_YIELD) - lua_error(L); - - return lua_gettop(L); + return success; } -static const luaL_Reg dfhack_funcs[] = { - { "print", lua_dfhack_print }, - { "println", lua_dfhack_println }, - { "printerr", lua_dfhack_printerr }, - { "color", lua_dfhack_color }, - { "is_interactive", lua_dfhack_is_interactive }, - { "lineedit", lua_dfhack_lineedit }, - { "interpreter", lua_dfhack_interpreter }, - { "safecall", lua_dfhack_safecall }, - { "onerror", dfhack_onerror }, - { "with_suspend", lua_dfhack_with_suspend }, - { NULL, NULL } -}; - -/* - * Per-world persistent configuration storage. - */ - -static PersistentDataItem persistent_by_struct(lua_State *state, int idx) +static int finish_dfhack_cleanup (lua_State *L, bool success) { - lua_getfield(state, idx, "entry_id"); - int id = lua_tointeger(state, -1); - lua_pop(state, 1); - - PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + int nargs = lua_tointeger(L, 1); + bool always = lua_toboolean(L, 2); + int rvbase = 4+nargs; - if (ref.isValid()) - { - lua_getfield(state, idx, "key"); - const char *str = lua_tostring(state, -1); - if (!str || str != ref.key()) - luaL_argerror(state, idx, "inconsistent id and key"); - lua_pop(state, 1); - } + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [rvals/error...] - return ref; -} + int numret = lua_gettop(L) - rvbase; -static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) -{ - if (!ref.isValid()) + if (!success || always) { - lua_pushnil(state); - lua_pushstring(state, "entry not found"); - return 2; - } - - if (create) - lua_createtable(state, 0, 4); - - lua_pushvalue(state, lua_upvalueindex(1)); - lua_setmetatable(state, -2); - - lua_pushinteger(state, ref.entry_id()); - lua_setfield(state, -2, "entry_id"); - lua_pushstring(state, ref.key().c_str()); - lua_setfield(state, -2, "key"); - lua_pushstring(state, ref.val().c_str()); - lua_setfield(state, -2, "value"); + if (numret > 0) + { + if (numret == 1) + { + // Inject the only result instead of pulling cleanup args + lua_insert(L, 4); + } + else if (!lua_checkstack(L, nargs+1)) + { + success = false; + lua_settop(L, rvbase); + push_simple_error(L, "stack overflow"); + lua_insert(L, 4); + } + else + { + for (int i = 0; i <= nargs; i++) + lua_pushvalue(L, 4+i); + } + } - lua_createtable(state, PersistentDataItem::NumInts, 0); - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_pushinteger(state, ref.ival(i)); - lua_rawseti(state, -2, i+1); + success = do_invoke_cleanup(L, nargs, 3, success); } - lua_setfield(state, -2, "ints"); - - return 1; -} -static PersistentDataItem get_persistent(lua_State *state) -{ - luaL_checkany(state, 1); - - if (lua_istable(state, 1)) - { - if (!lua_getmetatable(state, 1) || - !lua_rawequal(state, -1, lua_upvalueindex(1))) - luaL_argerror(state, 1, "invalid table type"); - - lua_settop(state, 1); - - return persistent_by_struct(state, 1); - } - else - { - const char *str = luaL_checkstring(state, 1); + if (!success) + lua_error(L); - return Core::getInstance().getWorld()->GetPersistentData(str); - } + return numret; } -static int dfhack_persistent_get(lua_State *state) +static int dfhack_cleanup_cont (lua_State *L) { - CoreSuspender suspend; - - auto ref = get_persistent(state); - - return read_persistent(state, ref, !lua_istable(state, 1)); + int status = lua_getctx(L, NULL); + return finish_dfhack_cleanup(L, (status == LUA_YIELD)); } -static int dfhack_persistent_delete(lua_State *state) +static int dfhack_call_with_finalizer (lua_State *L) { - CoreSuspender suspend; + int nargs = luaL_checkint(L, 1); + if (nargs < 0) + luaL_argerror(L, 1, "invalid cleanup argument count"); + luaL_checktype(L, 3, LUA_TFUNCTION); - auto ref = get_persistent(state); + // Inject errorfun + lua_pushcfunction(L, dfhack_onerror); + lua_insert(L, 3); - bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + int rvbase = 4+nargs; // rvbase+1 points to the function argument - lua_pushboolean(state, ok); - return 1; -} + if (lua_gettop(L) < rvbase) + luaL_error(L, "not enough arguments even to invoke cleanup"); -static int dfhack_persistent_get_all(lua_State *state) -{ - CoreSuspender suspend; - - const char *str = luaL_checkstring(state, 1); - bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] - std::vector data; - Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + // Not enough stack to call and post-cleanup, or nothing to call? + bool no_args = lua_gettop(L) == rvbase; - if (data.empty()) - { - lua_pushnil(state); - } - else + if (!lua_checkstack(L, nargs+2) || no_args) { - lua_createtable(state, data.size(), 0); - for (size_t i = 0; i < data.size(); ++i) - { - read_persistent(state, data[i], true); - lua_rawseti(state, -2, i+1); - } - } - - return 1; -} - -static int dfhack_persistent_save(lua_State *state) -{ - CoreSuspender suspend; - - lua_settop(state, 2); - luaL_checktype(state, 1, LUA_TTABLE); - bool add = lua_toboolean(state, 2); - - lua_getfield(state, 1, "key"); - const char *str = lua_tostring(state, -1); - if (!str) - luaL_argerror(state, 1, "no key field"); - - lua_settop(state, 1); - - PersistentDataItem ref; - bool added = false; + push_simple_error(L, no_args ? "fn argument expected" : "stack overflow"); + lua_insert(L, 4); - if (add) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - added = true; + // stack: ... [errorfun] [error] [cleanup fun] [cleanup args...] + do_invoke_cleanup(L, nargs, 3, false); + lua_error(L); } - else if (lua_getmetatable(state, 1)) - { - if (!lua_rawequal(state, -1, lua_upvalueindex(1))) - return luaL_argerror(state, 1, "invalid table type"); - lua_pop(state, 1); - ref = persistent_by_struct(state, 1); - } - else - { - ref = Core::getInstance().getWorld()->GetPersistentData(str); - } + // Actually invoke - if (!ref.isValid()) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - if (!ref.isValid()) - luaL_error(state, "cannot create persistent entry"); - added = true; - } + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); + return finish_dfhack_cleanup(L, (status == LUA_OK)); +} - lua_getfield(state, 1, "value"); - if (const char *str = lua_tostring(state, -1)) - ref.val() = str; - lua_pop(state, 1); +static int lua_dfhack_with_suspend(lua_State *L) +{ + int nargs = lua_gettop(L); + luaL_checktype(L, 1, LUA_TFUNCTION); - lua_getfield(state, 1, "ints"); - if (lua_istable(state, -1)) - { - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_rawgeti(state, -1, i+1); - if (lua_isnumber(state, -1)) - ref.ival(i) = lua_tointeger(state, -1); - lua_pop(state, 1); - } - } - lua_pop(state, 1); + CoreSuspender suspend; + lua_call(L, nargs-1, LUA_MULTRET); - read_persistent(state, ref, false); - lua_pushboolean(state, added); - return 2; + return lua_gettop(L); } -static const luaL_Reg dfhack_persistent_funcs[] = { - { "get", dfhack_persistent_get }, - { "delete", dfhack_persistent_delete }, - { "get_all", dfhack_persistent_get_all }, - { "save", dfhack_persistent_save }, +static const luaL_Reg dfhack_funcs[] = { + { "print", lua_dfhack_print }, + { "println", lua_dfhack_println }, + { "printerr", lua_dfhack_printerr }, + { "color", lua_dfhack_color }, + { "is_interactive", lua_dfhack_is_interactive }, + { "lineedit", lua_dfhack_lineedit }, + { "interpreter", lua_dfhack_interpreter }, + { "safecall", lua_dfhack_safecall }, + { "onerror", dfhack_onerror }, + { "call_with_finalizer", dfhack_call_with_finalizer }, + { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; -static void OpenPersistent(lua_State *state) -{ - luaL_getsubtable(state, lua_gettop(state), "persistent"); - - lua_dup(state); - luaL_setfuncs(state, dfhack_persistent_funcs, 1); +/************************ + * Main Open function * + ************************/ - lua_dup(state); - lua_setfield(state, -2, "__index"); - - lua_pop(state, 1); -} +void OpenDFHackApi(lua_State *state); lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { @@ -866,7 +792,7 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); - OpenPersistent(state); + OpenDFHackApi(state); lua_setglobal(state, "dfhack"); diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 6d5961f8d..4213499eb 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -30,6 +30,7 @@ distribution. #include "MemAccess.h" #include "Core.h" +#include "Error.h" #include "VersionInfo.h" #include "tinythread.h" // must be last due to MS stupidity @@ -1022,7 +1023,20 @@ static int meta_call_function(lua_State *state) auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); - id->invoke(state, 1); + + try { + id->invoke(state, 1); + } + catch (Error::NullPointer &e) { + const char *vn = e.varname(); + std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?"); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + catch (std::exception &e) { + std::string tmp = stl_sprintf("C++ exception: %s", e.what()); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + return 1; } @@ -1033,7 +1047,10 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, const char *name, function_identity_base *fun) { lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); - lua_pushvalue(state, meta_idx); + if (meta_idx) + lua_pushvalue(state, meta_idx); + else + lua_pushlightuserdata(state, NULL); // can't be a metatable lua_pushfstring(state, "%s()", name); lua_pushlightuserdata(state, fun); lua_pushcclosure(state, meta_call_function, 4); @@ -1041,6 +1058,17 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, lua_setfield(state, field_idx, name); } +/** + * Wrap functions and add them to the table on the top of the stack. + */ +void LuaWrapper::SetFunctionWrappers(lua_State *state, const FunctionReg *reg) +{ + int base = lua_gettop(state); + + for (; reg && reg->name; ++reg) + AddMethodWrapper(state, 0, base, reg->name, reg->identity); +} + /** * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. */ diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 818e6b9e1..1c3b61c15 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -53,7 +53,10 @@ static luaL_Reg no_functions[] = { { NULL, NULL } }; */ void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) { - lua_getfield(state, UPVAL_METATABLE, "__metatable"); + if (lua_islightuserdata(state, UPVAL_METATABLE)) + lua_pushstring(state, "(global)"); + else + lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); const char *fname = index ? lua_tostring(state, index) : "*"; luaL_error(state, "Cannot %s field %s.%s: %s.", @@ -696,6 +699,9 @@ static int meta_assign(lua_State *state) { type_identity *id = get_object_identity(state, 1, "df.assign()", false); + if (lua_getmetatable(state, 2)) + luaL_error(state, "cannot use lua tables with metatable in df.assign()"); + int base = lua_gettop(state); // x:assign{ assign = foo } => x:assign(foo) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 8247cd002..b9ff35cfc 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -25,6 +25,7 @@ distribution. #include "Internal.h" #include "Export.h" #include "MiscUtils.h" +#include "Error.h" #ifndef LINUX_BUILD #include @@ -37,6 +38,11 @@ distribution. #include #include +#include + +const char *DFHack::Error::NullPointer::what() const throw() { + return "NULL pointer access"; +} std::string stl_sprintf(const char *fmt, ...) { va_list lst; @@ -149,4 +155,162 @@ uint64_t GetTimeMs64() return ret; } -#endif \ No newline at end of file +#endif + +/* Character decoding */ + +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const uint8_t utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +decode(uint32_t* state, uint32_t* codep, uint8_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +/* Character encoding */ + +static inline int encode(uint8_t *out, uint16_t c) { + if (c <= 0x7F) + { + out[0] = c; + return 1; + } + else if (c <= 0x7FF) + { + out[0] = (0xC0 | (c >> 6)); + out[1] = (0x80 | (c & 0x3F)); + return 2; + } + else /*if (c <= 0xFFFF)*/ + { + out[0] = (0xE0 | (c >> 12)); + out[1] = (0x80 | ((c >> 6) & 0x3F)); + out[2] = (0x80 | (c & 0x3F)); + return 3; + } +} + +/* CP437 */ + +static uint16_t character_table[256] = { + 0, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, // + 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, + 0x25BA, 0x25C4, 0x2195, 0x203C, 0xB6, 0xA7, 0x25AC, 0x21A8, // + 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x2302, + 0xC7, 0xFC, 0xE9, 0xE2, 0xE4, 0xE0, 0xE5, 0xE7, // + 0xEA, 0xEB, 0xE8, 0xEF, 0xEE, 0xEC, 0xC4, 0xC5, + 0xC9, 0xE6, 0xC6, 0xF4, 0xF6, 0xF2, 0xFB, 0xF9, // + 0xFF, 0xD6, 0xDC, 0xA2, 0xA3, 0xA5, 0x20A7, 0x192, + 0xE1, 0xED, 0xF3, 0xFA, 0xF1, 0xD1, 0xAA, 0xBA, // + 0xBF, 0x2310, 0xAC, 0xBD, 0xBC, 0xA1, 0xAB, 0xBB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x3B1, 0xDF, 0x393, 0x3C0, 0x3A3, 0x3C3, 0xB5, 0x3C4, // + 0x3A6, 0x398, 0x3A9, 0x3B4, 0x221E, 0x3C6, 0x3B5, 0x2229, + 0x2261, 0xB1, 0x2265, 0x2264, 0x2320, 0x2321, 0xF7, 0x2248, // + 0xB0, 0x2219, 0xB7, 0x221A, 0x207F, 0xB2, 0x25A0, 0xA0 +}; + +std::string DF2UTF(const std::string &in) +{ + std::string out; + out.reserve(in.size()); + + uint8_t buf[4]; + for (size_t i = 0; i < in.size(); i++) + { + int cnt = encode(buf, character_table[(uint8_t)in[i]]); + out.append(&buf[0], &buf[cnt]); + } + + return out; +} + +std::string UTF2DF(const std::string &in) +{ + // Unicode to normal lookup table + static std::map ctable; + + if (ctable.empty()) + { + for (uint16_t i = 0; i < 256; i++) + if (character_table[i] != i) + ctable[character_table[i]] = char(i); + } + + // Actual conversion loop + size_t size = in.size(); + std::string out(size, char(0)); + + uint32_t codepoint = 0; + uint32_t state = UTF8_ACCEPT, prev = UTF8_ACCEPT; + uint32_t pos = 0; + + for (unsigned i = 0; i < size; prev = state, i++) { + switch (decode(&state, &codepoint, uint8_t(in[i]))) { + case UTF8_ACCEPT: + if (codepoint < 256 && character_table[codepoint] == codepoint) { + out[pos++] = char(codepoint); + } else { + char v = ctable[codepoint]; + out[pos++] = v ? v : '?'; + } + break; + + case UTF8_REJECT: + out[pos++] = '?'; + if (prev != UTF8_ACCEPT) --i; + state = UTF8_ACCEPT; + break; + } + } + + if (pos != size) + out.resize(pos); + return out; +} diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 229e5a70c..00344d6a2 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -221,30 +221,30 @@ void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, void DFHack::describeName(NameInfo *info, df::language_name *name) { if (!name->first_name.empty()) - info->set_first_name(name->first_name); + info->set_first_name(DF2UTF(name->first_name)); if (!name->nickname.empty()) - info->set_nickname(name->nickname); + info->set_nickname(DF2UTF(name->nickname)); if (name->language >= 0) info->set_language_id(name->language); std::string lname = Translation::TranslateName(name, false, true); if (!lname.empty()) - info->set_last_name(lname); + info->set_last_name(DF2UTF(lname)); lname = Translation::TranslateName(name, true, true); if (!lname.empty()) - info->set_english_name(lname); + info->set_english_name(DF2UTF(lname)); } void DFHack::describeNameTriple(NameTriple *info, const std::string &name, const std::string &plural, const std::string &adj) { - info->set_normal(name); + info->set_normal(DF2UTF(name)); if (!plural.empty() && plural != name) - info->set_plural(plural); + info->set_plural(DF2UTF(plural)); if (!adj.empty() && adj != name) - info->set_adjective(adj); + info->set_adjective(DF2UTF(adj)); } void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, @@ -256,7 +256,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, info->set_pos_y(unit->pos.y); info->set_pos_z(unit->pos.z); - auto name = Units::GetVisibleName(unit); + auto name = Units::getVisibleName(unit); if (name->has_name) describeName(info->mutable_name(), name); diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 822dcd45d..6cd7d42f4 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,6 +32,10 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" +#ifndef BUILD_DFHACK_LIB +#error Due to export issues this header is internal to the main library. +#endif + namespace df { // A very simple and stupid implementation of some stuff from boost template struct is_same_type { static const bool value = false; }; @@ -46,6 +50,13 @@ namespace df { template::type,void>::value> struct function_wrapper {}; + class cur_lua_ostream_argument { + DFHack::color_ostream *out; + public: + cur_lua_ostream_argument(lua_State *state); + operator DFHack::color_ostream& () { return *out; } + }; + /* * Since templates can't match variable arg count, * a separate specialization is needed for every @@ -63,10 +74,15 @@ namespace df { CT *self = (CT*)DFHack::LuaWrapper::get_object_addr(state, base++, UPVAL_METHOD_NAME, "invoke"); #define LOAD_ARG(type) \ type v##type; df::identity_traits::get()->lua_write(state, UPVAL_METHOD_NAME, &v##type, base++); +#define OSTREAM_ARG DFHack::color_ostream& +#define LOAD_OSTREAM(name) \ + cur_lua_ostream_argument name(state); -#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ +#define INSTANTIATE_RETURN_TYPE(FArgs) \ template struct return_type { typedef RT type; }; \ - template struct return_type { typedef RT type; }; \ + template struct return_type { typedef RT type; }; + +#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ template struct function_wrapper { \ static const bool is_method = false; \ static const int num_args = Count; \ @@ -92,24 +108,35 @@ namespace df { #define FW_TARGSC #define FW_TARGS +INSTANTIATE_RETURN_TYPE(()) INSTANTIATE_WRAPPERS(0, (), (), ;) +INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);) #undef FW_TARGS #undef FW_TARGSC #define FW_TARGSC FW_TARGS, #define FW_TARGS class A1 +INSTANTIATE_RETURN_TYPE((A1)) INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);) +INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);) #undef FW_TARGS #define FW_TARGS class A1, class A2 +INSTANTIATE_RETURN_TYPE((A1,A2)) INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);) +INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3 +INSTANTIATE_RETURN_TYPE((A1,A2,A3)) INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) +INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) #undef FW_TARGS @@ -120,6 +147,8 @@ INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), #undef INVOKE_RV #undef LOAD_CLASS #undef LOAD_ARG +#undef OSTREAM_ARG +#undef LOAD_OSTREAM template class function_identity : public function_identity_base { diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index c83b2092a..bab87e1bb 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,6 +160,8 @@ namespace DFHack }; } +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB namespace df { using DFHack::function_identity_base; @@ -575,4 +577,4 @@ namespace df return &identity; } } - +#endif diff --git a/library/include/Error.h b/library/include/Error.h index 159a84713..0fbc6ba78 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -39,6 +39,18 @@ namespace DFHack * the whole array of DFHack exceptions from the rest */ class DFHACK_EXPORT All : public std::exception{}; + + class DFHACK_EXPORT NullPointer : public All { + const char *varname_; + public: + NullPointer(const char *varname_ = NULL) : varname_(varname_) {} + const char *varname() const { return varname_; } + virtual const char *what() const throw(); + }; + +#define CHECK_NULL_POINTER(var) \ + { if (var == NULL) throw DFHack::Error::NullPointer(#var); } + class DFHACK_EXPORT AllSymbols : public All{}; // Syntax errors and whatnot, the xml can't be read class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 72aa0e12e..479ca0d67 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -221,6 +221,16 @@ namespace DFHack { namespace LuaWrapper { */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); + struct FunctionReg { + const char *name; + function_identity_base *identity; + }; + + /** + * Wrap functions and add them to the table on the top of the stack. + */ + void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); + void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); void AttachDFGlobals(lua_State *state); diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index e5ecb25f4..c2a153eb1 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -279,3 +279,7 @@ DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); + +// Conversion between CP437 and UTF-8 +DFHACK_EXPORT std::string UTF2DF(const std::string &in); +DFHACK_EXPORT std::string DF2UTF(const std::string &in); diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index f3c526d76..3d70dc9fa 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -114,6 +114,7 @@ namespace DFHack } bool find(const std::string &token); + bool find(const std::vector &tokens); bool findBuiltin(const std::string &token); bool findInorganic(const std::string &token); diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index 19a3ed9a6..6d74431e3 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -50,6 +50,8 @@ DFHACK_EXPORT bool IsValid (); DFHACK_EXPORT bool readName(t_name & name, df::language_name * address); DFHACK_EXPORT bool copyName(df::language_name * address, df::language_name * target); +DFHACK_EXPORT void setNickname(df::language_name *name, std::string nick); + // translate a name using the loaded dictionaries DFHACK_EXPORT std::string TranslateName (const df::language_name * name, bool inEnglish = true, bool onlyLastPart = false); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 0fd75bf1f..838d1d596 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -33,6 +33,11 @@ distribution. #include "DataDefs.h" #include "df/unit.h" +namespace df +{ + struct nemesis_record; +} + /** * \defgroup grp_units Unit module parts * @ingroup grp_modules @@ -193,7 +198,10 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); -DFHACK_EXPORT df::language_name *GetVisibleName(df::unit *unit); +DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); +DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); + +DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 9ed6a3ed9..e7b17ce41 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -139,8 +139,16 @@ namespace DFHack PersistentDataItem AddPersistentData(const std::string &key); PersistentDataItem GetPersistentData(const std::string &key); PersistentDataItem GetPersistentData(int entry_id); + // Calls GetPersistentData(key); if not found, adds and sets added to true. + // The result can still be not isValid() e.g. if the world is not loaded. + PersistentDataItem GetPersistentData(const std::string &key, bool *added); + // Lists all items with the given key. + // If prefix is true, search for keys starting with key+"/". + // GetPersistentData(&vec,"",true) returns all items. + // Items have alphabetic order by key; same key ordering is undefined. void GetPersistentData(std::vector *vec, const std::string &key, bool prefix = false); + // Deletes the item; returns true if success. bool DeletePersistentData(const PersistentDataItem &item); void ClearPersistentCache(); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 67652ff15..b37183cb6 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,12 +1,51 @@ -- Common startup file for all dfhack plugins with lua support -- The global dfhack table is already created by C++ init code. +-- Console color constants + +COLOR_RESET = -1 +COLOR_BLACK = 0 +COLOR_BLUE = 1 +COLOR_GREEN = 2 +COLOR_CYAN = 3 +COLOR_RED = 4 +COLOR_MAGENTA = 5 +COLOR_BROWN = 6 +COLOR_GREY = 7 +COLOR_DARKGREY = 8 +COLOR_LIGHTBLUE = 9 +COLOR_LIGHTGREEN = 10 +COLOR_LIGHTCYAN = 11 +COLOR_LIGHTRED = 12 +COLOR_LIGHTMAGENTA = 13 +COLOR_YELLOW = 14 +COLOR_WHITE = 15 + +-- Error handling + safecall = dfhack.safecall function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +function dfhack.with_finalize(...) + return dfhack.call_with_finalizer(0,true,...) +end +function dfhack.with_onerror(...) + return dfhack.call_with_finalizer(0,false,...) +end + +local function call_delete(obj) + if obj then obj:delete() end +end + +function dfhack.with_temp_object(obj,fn,...) + return dfhack.call_with_finalizer(1,true,call_delete,obj,fn,obj,...) +end + +-- Module loading + function mkmodule(module,env) local pkg = package.loaded[module] if pkg == nil then @@ -31,10 +70,13 @@ function reload(module) dofile(path) end +-- Misc functions + function printall(table) - if table == nil then return end - for k,v in pairs(table) do - print(k," = "..tostring(v)) + if type(table) == 'table' or df.isvalid(table) == 'ref' then + for k,v in pairs(table) do + print(string.format("%-23s\t = %s",tostring(k),tostring(v))) + end end end @@ -43,5 +85,9 @@ function dfhack.persistent:__tostring() ..self.value.."\":"..table.concat(self.ints,",")..">" end +function dfhack.matinfo:__tostring() + return "" +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index aae2a90b9..592f27c0b 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -46,6 +46,7 @@ using namespace DFHack; #include "df/global_objects.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_itemst.h" @@ -284,6 +285,9 @@ static df::unit *getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top)) return vector_get(screen->units[screen->page], screen->cursor_pos[screen->page]); + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_dungeon_monsterstatusst, top)) + return screen->unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 2c9a5d1d0..679ecc0e7 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -32,6 +32,7 @@ distribution. using namespace std; #include "Core.h" +#include "Error.h" #include "PluginManager.h" #include "MiscUtils.h" @@ -54,6 +55,8 @@ using namespace df::enums; df::job *DFHack::cloneJobStruct(df::job *job) { + CHECK_NULL_POINTER(job); + df::job *pnew = new df::job(*job); // Clean out transient fields @@ -105,6 +108,9 @@ void DFHack::deleteJobStruct(df::job *job) bool DFHack::operator== (const df::job_item &a, const df::job_item &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(item_type) && CMP(item_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(flags1.whole) && CMP(quantity) && CMP(vector_id) && @@ -125,6 +131,9 @@ bool DFHack::operator== (const df::job_item &a, const df::job_item &b) bool DFHack::operator== (const df::job &a, const df::job &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(job_type) && CMP(unk2) && CMP(mat_type) && CMP(mat_index) && CMP(item_subtype) && CMP(item_category.whole) && @@ -141,6 +150,8 @@ bool DFHack::operator== (const df::job &a, const df::job &b) static void print_job_item_details(color_ostream &out, df::job *job, unsigned idx, df::job_item *item) { + CHECK_NULL_POINTER(item); + ItemTypeInfo info(item); out << " Input Item " << (idx+1) << ": " << info.toString(); @@ -175,6 +186,8 @@ static void print_job_item_details(color_ostream &out, df::job *job, unsigned id void DFHack::printJobDetails(color_ostream &out, df::job *job) { + CHECK_NULL_POINTER(job); + out.color(job->flags.bits.suspend ? Console::COLOR_DARKGREY : Console::COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) @@ -216,6 +229,8 @@ void DFHack::printJobDetails(color_ostream &out, df::job *job) df::building *DFHack::getJobHolder(df::job *job) { + CHECK_NULL_POINTER(job); + for (size_t i = 0; i < job->references.size(); i++) { VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 8f5739c7e..0172e24f8 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -164,6 +164,13 @@ bool MaterialInfo::find(const std::string &token) { std::vector items; split_string(&items, token, ":"); + return find(items); +} + +bool MaterialInfo::find(const std::vector &items) +{ + if (items.empty()) + return false; if (items[0] == "INORGANIC" && items.size() > 1) return findInorganic(vector_get(items,1)); @@ -204,8 +211,11 @@ bool MaterialInfo::findBuiltin(const std::string &token) df::world_raws &raws = world->raws; for (int i = 1; i < NUM_BUILTIN; i++) - if (raws.mat_table.builtin[i]->id == token) + { + auto obj = raws.mat_table.builtin[i]; + if (obj && obj->id == token) return decode(i, -1); + } return decode(-1); } diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 7b5fa654c..6b055a4ac 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -35,6 +35,7 @@ using namespace std; #include "Types.h" #include "ModuleFactory.h" #include "Core.h" +#include "Error.h" using namespace DFHack; using namespace df::enums; @@ -91,8 +92,25 @@ void addNameWord (string &out, const string &word) out.append(upper); } +void Translation::setNickname(df::language_name *name, std::string nick) +{ + CHECK_NULL_POINTER(name); + + if (!name->has_name) + { + *name = df::language_name(); + + name->language = 0; + name->has_name = true; + } + + name->nickname = nick; +} + string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) { + CHECK_NULL_POINTER(name); + string out; string word; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index f6259bd3b..e210a9028 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -48,6 +48,8 @@ using namespace std; #include "df/world.h" #include "df/ui.h" #include "df/unit_inventory_item.h" +#include "df/unit_soul.h" +#include "df/nemesis_record.h" #include "df/historical_entity.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" @@ -527,8 +529,51 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } -df::language_name *Units::GetVisibleName(df::unit *unit) +void Units::setNickname(df::unit *unit, std::string nick) { + CHECK_NULL_POINTER(unit); + + // There are >=3 copies of the name, and the one + // in the unit is not the authoritative one. + // This is the reason why military units often + // lose nicknames set from Dwarf Therapist. + Translation::setNickname(&unit->name, nick); + + if (unit->status.current_soul) + Translation::setNickname(&unit->status.current_soul->name, nick); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + if (figure) + { + Translation::setNickname(&figure->name, nick); + + // v0.34.01: added the vampire's assumed identity + if (figure->info && figure->info->reputation) + { + auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + + if (identity) + { + auto id_hfig = df::historical_figure::find(identity->histfig_id); + + if (id_hfig) + { + // Even DF doesn't do this bit, because it's apparently + // only used for demons masquerading as gods, so you + // can't ever change their nickname in-game. + Translation::setNickname(&id_hfig->name, nick); + } + else + Translation::setNickname(&identity->name, nick); + } + } + } +} + +df::language_name *Units::getVisibleName(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); if (figure) @@ -553,13 +598,33 @@ df::language_name *Units::GetVisibleName(df::unit *unit) return &unit->name; } +df::nemesis_record *Units::getNemesis(df::unit *unit) +{ + if (!unit) + return NULL; + + for (unsigned i = 0; i < unit->refs.size(); i++) + { + df::nemesis_record *rv = unit->refs[i]->getNemesis(); + if (rv && rv->unit == unit) + return rv; + } + + return NULL; +} + + bool DFHack::Units::isDead(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return unit->flags1.bits.dead; } bool DFHack::Units::isAlive(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return !unit->flags1.bits.dead && !unit->flags3.bits.ghostly && !unit->curse.add_tags1.bits.NOT_LIVING; @@ -567,23 +632,22 @@ bool DFHack::Units::isAlive(df::unit *unit) bool DFHack::Units::isSane(df::unit *unit) { + CHECK_NULL_POINTER(unit); + if (unit->flags1.bits.dead || unit->flags3.bits.ghostly || unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.CRAZED) return false; - if (unit->flags1.bits.has_mood) + switch (unit->mood) { - switch (unit->mood) - { - case mood_type::Melancholy: - case mood_type::Raving: - case mood_type::Berserk: - return false; - default: - break; - } + case mood_type::Melancholy: + case mood_type::Raving: + case mood_type::Berserk: + return false; + default: + break; } return true; diff --git a/library/modules/World.cpp b/library/modules/World.cpp index d570abaec..6f6a69a59 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -254,6 +254,8 @@ bool World::BuildPersistentCache() d->persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); } + + return true; } PersistentDataItem World::AddPersistentData(const std::string &key) @@ -300,6 +302,21 @@ PersistentDataItem World::GetPersistentData(int entry_id) return PersistentDataItem(); } +PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) +{ + *added = false; + + PersistentDataItem rv = GetPersistentData(key); + + if (!rv.isValid()) + { + *added = true; + rv = AddPersistentData(key); + } + + return rv; +} + void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix) { if (!BuildPersistentCache()) diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 4fc1b4e5d..ffb428fd8 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -8,6 +8,8 @@ #include "modules/Materials.h" #include "modules/Maps.h" #include "modules/Items.h" +#include "modules/Gui.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/world.h" @@ -159,31 +161,6 @@ static bool bodyswap_hotkey(df::viewscreen *top) !!virtual_cast(top); } -df::unit *getCurUnit() -{ - auto top = Core::getTopViewscreen(); - - if (VIRTUAL_CAST_VAR(ms, df::viewscreen_dungeon_monsterstatusst, top)) - return ms->unit; - - return NULL; -} - -df::nemesis_record *getNemesis(df::unit *unit) -{ - if (!unit) - return NULL; - - for (unsigned i = 0; i < unit->refs.size(); i++) - { - df::nemesis_record *rv = unit->refs[i]->getNemesis(); - if (rv && rv->unit == unit) - return rv; - } - - return NULL; -} - bool bodySwap(color_ostream &out, df::unit *player) { if (!player) @@ -219,7 +196,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) if (restore_swap) { df::unit *ctl = world->units.other[0][0]; - auto ctl_nemesis = getNemesis(ctl); + auto ctl_nemesis = Units::getNemesis(ctl); if (ctl_nemesis != real_nemesis) { @@ -672,8 +649,8 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par return CR_FAILURE; // Get the unit to swap to - auto new_unit = getCurUnit(); - auto new_nemesis = getNemesis(new_unit); + auto new_unit = Gui::getSelectedUnit(out, true); + auto new_nemesis = Units::getNemesis(new_unit); if (!new_nemesis) { diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 8dacf62a9..7302aea60 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -4,6 +4,8 @@ #include "PluginManager.h" #include "modules/Gui.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/ui.h" @@ -20,6 +22,8 @@ #include "RemoteServer.h" #include "rename.pb.h" +#include "MiscUtils.h" + #include using std::vector; @@ -57,19 +61,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static void set_nickname(df::language_name *name, std::string nick) -{ - if (!name->has_name) - { - *name = df::language_name(); - - name->language = 0; - name->has_name = true; - } - - name->nickname = nick; -} - static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -82,45 +73,6 @@ static df::squad *getSquadByIndex(unsigned idx) return df::squad::find(entity->squads[idx]); } -void setUnitNickname(df::unit *unit, const std::string &nick) -{ - // There are >=3 copies of the name, and the one - // in the unit is not the authoritative one. - // This is the reason why military units often - // lose nicknames set from Dwarf Therapist. - set_nickname(&unit->name, nick); - - if (unit->status.current_soul) - set_nickname(&unit->status.current_soul->name, nick); - - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) - { - set_nickname(&figure->name, nick); - - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); - - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - set_nickname(&id_hfig->name, nick); - } - else - set_nickname(&identity->name, nick); - } - } - } -} - static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in) { df::squad *squad = df::squad::find(in->squad_id()); @@ -128,9 +80,9 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in return CR_NOT_FOUND; if (in->has_nickname()) - set_nickname(&squad->name, in->nickname()); + Translation::setNickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) - squad->alias = in->alias(); + squad->alias = UTF2DF(in->alias()); return CR_OK; } @@ -142,9 +94,9 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_NOT_FOUND; if (in->has_nickname()) - setUnitNickname(unit, in->nickname()); + Units::setNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) - unit->custom_profession = in->profession(); + unit->custom_profession = UTF2DF(in->profession()); return CR_OK; } @@ -202,7 +154,7 @@ static command_result rename(color_ostream &out, vector ¶meters) if (!unit) return CR_WRONG_USAGE; - setUnitNickname(unit, parameters[1]); + Units::setNickname(unit, parameters[1]); } else if (cmd == "unit-profession") {