diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 0b50695e5..b785bee49 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1200,6 +1200,12 @@ Units module Sets the unit's nickname properly. +* ``dfhack.units.getOuterContainerRef(unit)`` + + Returns a table (in the style of a ``specific_ref`` struct) of the outermost object that contains the unit (or one of the unit itself.) + The ``type`` field contains a ``specific_ref_type`` of ``UNIT``, ``ITEM_GENERAL``, or ``VERMIN_EVENT``. + The ``object`` field contains a pointer to a unit, item, or vermin, respectively. + * ``dfhack.units.getVisibleName(unit)`` Returns the language_name object visible in game, accounting for false identities. @@ -1266,10 +1272,18 @@ Units module The unit is an alive sane citizen of the fortress; wraps the same checks the game uses to decide game-over by extinction. +* ``dfhack.units.isFortControlled(unit)`` + + Similar to ``dfhack.units.isCitizen(unit)``, but is based on checks for units hidden in ambush, and includes tame animals. Returns *false* if not in fort mode. + * ``dfhack.units.isVisible(unit)`` The unit is visible on the map. +* ``dfhack.units.isHidden(unit)`` + + The unit is hidden to the player, accounting for sneaking. Works for any game mode. + * ``dfhack.units.getAge(unit[,true_age])`` Returns the age of the unit in years as a floating-point value. @@ -1407,6 +1421,12 @@ Items module Returns the container item or *nil*. +* ``dfhack.items.getOuterContainerRef(item)`` + + Returns a table (in the style of a ``specific_ref`` struct) of the outermost object that contains the item (or one of the item itself.) + The ``type`` field contains a ``specific_ref_type`` of ``UNIT``, ``ITEM_GENERAL``, or ``VERMIN_EVENT``. + The ``object`` field contains a pointer to a unit, item, or vermin, respectively. + * ``dfhack.items.getContainedItems(item)`` Returns a list of items contained in this one. diff --git a/docs/changelog.txt b/docs/changelog.txt index 9a83dd904..194af2699 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,10 +50,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation - Add more examples to the plugin skeleton files so they are more informative for a newbie +- Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` ## API +- Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` ## Lua +- Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` # 0.47.05-r4 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f6a49c2aa..101b645fd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -98,6 +98,9 @@ distribution. #include "df/enabler.h" #include "df/feature_init.h" #include "df/plant.h" +#include "df/specific_ref.h" +#include "df/specific_ref_type.h" +#include "df/vermin.h" #include #include @@ -1594,7 +1597,9 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), + WRAPM(Units, isFortControlled), WRAPM(Units, isVisible), + WRAPM(Units, isHidden), WRAPM(Units, getAge), WRAPM(Units, getKillCount), WRAPM(Units, getNominalSkill), @@ -1663,6 +1668,31 @@ static int units_getPosition(lua_State *state) return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject(state,1))); } +static int units_getOuterContainerRef(lua_State *state) +{ + auto ref = Units::getOuterContainerRef(Lua::CheckDFObject(state, 1)); + + lua_newtable(state); + Lua::TableInsert(state, "type", ref.type); + + switch (ref.type) + { + case specific_ref_type::UNIT: + Lua::TableInsert(state, "object", ref.data.unit); + break; + case specific_ref_type::ITEM_GENERAL: + Lua::TableInsert(state, "object", (df::item*)ref.data.object); + break; + case specific_ref_type::VERMIN_EVENT: + Lua::TableInsert(state, "object", ref.data.vermin); + break; + default: + Lua::TableInsert(state, "object", NULL); + } + + return 1; +} + static int units_getNoblePositions(lua_State *state) { std::vector np; @@ -1715,6 +1745,7 @@ static int units_getStressCutoffs(lua_State *L) static const luaL_Reg dfhack_units_funcs[] = { { "getPosition", units_getPosition }, + { "getOuterContainerRef", units_getOuterContainerRef }, { "getNoblePositions", units_getNoblePositions }, { "getUnitsInBox", units_getUnitsInBox }, { "getStressCutoffs", units_getStressCutoffs }, @@ -1804,6 +1835,31 @@ static int items_getPosition(lua_State *state) return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject(state,1))); } +static int items_getOuterContainerRef(lua_State *state) +{ + auto ref = Items::getOuterContainerRef(Lua::CheckDFObject(state, 1)); + + lua_newtable(state); + Lua::TableInsert(state, "type", ref.type); + + switch (ref.type) + { + case specific_ref_type::UNIT: + Lua::TableInsert(state, "object", ref.data.unit); + break; + case specific_ref_type::ITEM_GENERAL: + Lua::TableInsert(state, "object", (df::item*)ref.data.object); + break; + case specific_ref_type::VERMIN_EVENT: + Lua::TableInsert(state, "object", ref.data.vermin); + break; + default: + Lua::TableInsert(state, "object", NULL); + } + + return 1; +} + static int items_getContainedItems(lua_State *state) { std::vector pvec; @@ -1826,6 +1882,7 @@ static int items_moveToBuilding(lua_State *state) static const luaL_Reg dfhack_items_funcs[] = { { "getPosition", items_getPosition }, + { "getOuterContainerRef", items_getOuterContainerRef }, { "getContainedItems", items_getContainedItems }, { "moveToBuilding", items_moveToBuilding }, { NULL, NULL } diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 66ca9d1cf..25090cd83 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -150,6 +150,9 @@ DFHACK_EXPORT bool setOwner(df::item *item, df::unit *unit); /// which item is it contained in? DFHACK_EXPORT df::item *getContainer(df::item *item); +/// what is the outermost object it is contained in? Possible ref types: UNIT, ITEM_GENERAL, VERMIN_EVENT +DFHACK_EXPORT void getOuterContainerRef(df::specific_ref &spec_ref, df::item *item, bool init_ref = true); +DFHACK_EXPORT inline df::specific_ref getOuterContainerRef(df::item *item) { df::specific_ref s; getOuterContainerRef(s, item); return s; } /// which items does it contain? DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector *items); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 419a7b0db..4dfa9f937 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -89,6 +89,9 @@ DFHACK_EXPORT df::general_ref *getGeneralRef(df::unit *unit, df::general_ref_typ DFHACK_EXPORT df::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type); DFHACK_EXPORT df::item *getContainer(df::unit *unit); +/// what is the outermost object it is contained in? Possible ref types: UNIT, ITEM_GENERAL, VERMIN_EVENT +DFHACK_EXPORT void getOuterContainerRef(df::specific_ref &spec_ref, df::unit *unit, bool init_ref=true); +DFHACK_EXPORT inline df::specific_ref getOuterContainerRef(df::unit *unit) { df::specific_ref s; getOuterContainerRef(s, unit); return s; } DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); @@ -113,6 +116,7 @@ DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); DFHACK_EXPORT bool isCitizen(df::unit *unit); +DFHACK_EXPORT bool isFortControlled(df::unit *unit); DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT bool isWar(df::unit* unit); DFHACK_EXPORT bool isHunter(df::unit* unit); @@ -121,6 +125,8 @@ DFHACK_EXPORT bool isOwnCiv(df::unit* unit); DFHACK_EXPORT bool isOwnGroup(df::unit* unit); DFHACK_EXPORT bool isOwnRace(df::unit* unit); DFHACK_EXPORT bool isVisible(df::unit* unit); +/// is unit hidden to the player? accounts for ambushing +DFHACK_EXPORT bool isHidden(df::unit *unit); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index d3693aad7..3f24df3d6 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -590,6 +590,47 @@ df::item *Items::getContainer(df::item * item) return ref ? ref->getItem() : NULL; } +void Items::getOuterContainerRef(df::specific_ref &spec_ref, df::item *item, bool init_ref) +{ + CHECK_NULL_POINTER(item); + // Reverse-engineered from ambushing unit code + + if (init_ref) + { + spec_ref.type = specific_ref_type::ITEM_GENERAL; + spec_ref.data.object = item; + } + + if (item->flags.bits.removed || !item->flags.bits.in_inventory) + return; + + for (size_t i = 0; i < item->general_refs.size(); i++) + { + auto g = item->general_refs[i]; + switch (g->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + if (auto item2 = g->getItem()) + return Items::getOuterContainerRef(spec_ref, item2); + break; + case general_ref_type::UNIT_HOLDER: + if (auto unit = g->getUnit()) + return Units::getOuterContainerRef(spec_ref, unit); + break; + default: + break; + } + } + + auto s = findRef(item->specific_refs, specific_ref_type::VERMIN_ESCAPED_PET); + if (s) + { + spec_ref.type = specific_ref_type::VERMIN_EVENT; + spec_ref.data.vermin = s->data.vermin; + } + return; +} + void Items::getContainedItems(df::item *item, std::vector *items) { CHECK_NULL_POINTER(item); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 58cd24d44..87dc61a9d 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -86,6 +86,7 @@ using namespace df::enums; using df::global::world; using df::global::ui; using df::global::gamemode; +using df::global::gametype; int32_t Units::getNumUnits() { @@ -209,6 +210,26 @@ df::item *Units::getContainer(df::unit *unit) return findItemRef(unit->general_refs, general_ref_type::CONTAINED_IN_ITEM); } +void Units::getOuterContainerRef(df::specific_ref &spec_ref, df::unit *unit, bool init_ref) +{ + CHECK_NULL_POINTER(unit); + // Reverse-engineered from ambushing unit code + + if (init_ref) + { + spec_ref.type = specific_ref_type::UNIT; + spec_ref.data.unit = unit; + } + + if (unit->flags1.bits.caged) + { + df::item *cage = getContainer(unit); + if (cage) + return Items::getOuterContainerRef(spec_ref, cage); + } + return; +} + static df::identity *getFigureIdentity(df::historical_figure *figure) { if (figure && figure->info && figure->info->reputation) @@ -503,6 +524,40 @@ bool Units::isCitizen(df::unit *unit) return isOwnGroup(unit); } +bool Units::isFortControlled(df::unit *unit) +{ // Reverse-engineered from ambushing unit code + CHECK_NULL_POINTER(unit); + + if (*gamemode != game_mode::DWARF) + return false; + + if (unit->mood == mood_type::Berserk || + Units::isCrazed(unit) || + Units::isOpposedToLife(unit) || + unit->enemy.undead || + unit->flags3.bits.ghostly) + return false; + + if (unit->flags1.bits.marauder || + unit->flags1.bits.invader_origin || + unit->flags1.bits.active_invader || + unit->flags1.bits.forest || + unit->flags1.bits.merchant || + unit->flags1.bits.diplomat) + return false; + + if (unit->flags1.bits.tame) + return true; + + if (unit->flags2.bits.visitor || + unit->flags2.bits.visitor_uninvited || + unit->flags2.bits.underworld || + unit->flags2.bits.resident) + return false; + + return unit->civ_id != -1 && unit->civ_id == ui->civ_id; +} + bool Units::isDwarf(df::unit *unit) { CHECK_NULL_POINTER(unit); @@ -584,6 +639,42 @@ bool Units::isVisible(df::unit* unit) return Maps::isTileVisible(unit->pos); } +bool Units::isHidden(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + // Reverse-engineered from ambushing unit code + + if (*df::global::debug_showambush) + return false; + + if (*gamemode == game_mode::ADVENTURE) + { + if (unit == world->units.active[0]) + return false; + else if (unit->flags1.bits.hidden_in_ambush) + return true; + } + else + { + if (*gametype == game_type::DWARF_ARENA) + return false; + else if (unit->flags1.bits.hidden_in_ambush && !isFortControlled(unit)) + return true; + } + + if (unit->flags1.bits.caged) + { + auto spec_ref = getOuterContainerRef(unit); + if (spec_ref.type == specific_ref_type::UNIT) + return isHidden(spec_ref.data.unit); + } + + if (*gamemode == game_mode::ADVENTURE || isFortControlled(unit)) + return false; + else + return !Maps::isTileVisible(Units::getPosition(unit)); +} + // get race name by id or unit pointer string Units::getRaceNameById(int32_t id) {