Add functions reverse-engineered from ambushing unit code (#1992)

* Add functions reverse-engineered from ambushing unit code

* Fix whitespace

* Fix debug_showambush check

* Remove getOuterContainerRef from Lua API

Don't think this works properly without allocating a new specific_ref. More trouble that it's worth.

* Fixed tile visibility check

* I don't think gamemode or gametype are ever NULL

* Minor tweaks to documentation

* Reimplement getOuterContainerRef for Lua; fix some comments

* Update Units.cpp and changelog

* Update Units.cpp
* Update changelog.txt
develop
Ryan Williams 2022-03-13 17:19:35 -07:00 committed by GitHub
parent 8a4ddacff3
commit 88b403ec7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 0 deletions

@ -1200,6 +1200,12 @@ Units module
Sets the unit's nickname properly. 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)`` * ``dfhack.units.getVisibleName(unit)``
Returns the language_name object visible in game, accounting for false identities. 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 The unit is an alive sane citizen of the fortress; wraps the
same checks the game uses to decide game-over by extinction. 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)`` * ``dfhack.units.isVisible(unit)``
The unit is visible on the map. 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])`` * ``dfhack.units.getAge(unit[,true_age])``
Returns the age of the unit in years as a floating-point value. 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*. 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)`` * ``dfhack.items.getContainedItems(item)``
Returns a list of items contained in this one. Returns a list of items contained in this one.

@ -50,10 +50,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Documentation ## Documentation
- Add more examples to the plugin skeleton files so they are more informative for a newbie - 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 ## API
- Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef``
## Lua ## Lua
- Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)``
# 0.47.05-r4 # 0.47.05-r4

@ -98,6 +98,9 @@ distribution.
#include "df/enabler.h" #include "df/enabler.h"
#include "df/feature_init.h" #include "df/feature_init.h"
#include "df/plant.h" #include "df/plant.h"
#include "df/specific_ref.h"
#include "df/specific_ref_type.h"
#include "df/vermin.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -1594,7 +1597,9 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isSane), WRAPM(Units, isSane),
WRAPM(Units, isDwarf), WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen), WRAPM(Units, isCitizen),
WRAPM(Units, isFortControlled),
WRAPM(Units, isVisible), WRAPM(Units, isVisible),
WRAPM(Units, isHidden),
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getKillCount), WRAPM(Units, getKillCount),
WRAPM(Units, getNominalSkill), WRAPM(Units, getNominalSkill),
@ -1663,6 +1668,31 @@ static int units_getPosition(lua_State *state)
return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1))); return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1)));
} }
static int units_getOuterContainerRef(lua_State *state)
{
auto ref = Units::getOuterContainerRef(Lua::CheckDFObject<df::unit>(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) static int units_getNoblePositions(lua_State *state)
{ {
std::vector<Units::NoblePosition> np; std::vector<Units::NoblePosition> np;
@ -1715,6 +1745,7 @@ static int units_getStressCutoffs(lua_State *L)
static const luaL_Reg dfhack_units_funcs[] = { static const luaL_Reg dfhack_units_funcs[] = {
{ "getPosition", units_getPosition }, { "getPosition", units_getPosition },
{ "getOuterContainerRef", units_getOuterContainerRef },
{ "getNoblePositions", units_getNoblePositions }, { "getNoblePositions", units_getNoblePositions },
{ "getUnitsInBox", units_getUnitsInBox }, { "getUnitsInBox", units_getUnitsInBox },
{ "getStressCutoffs", units_getStressCutoffs }, { "getStressCutoffs", units_getStressCutoffs },
@ -1804,6 +1835,31 @@ static int items_getPosition(lua_State *state)
return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1))); return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1)));
} }
static int items_getOuterContainerRef(lua_State *state)
{
auto ref = Items::getOuterContainerRef(Lua::CheckDFObject<df::item>(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) static int items_getContainedItems(lua_State *state)
{ {
std::vector<df::item*> pvec; std::vector<df::item*> pvec;
@ -1826,6 +1882,7 @@ static int items_moveToBuilding(lua_State *state)
static const luaL_Reg dfhack_items_funcs[] = { static const luaL_Reg dfhack_items_funcs[] = {
{ "getPosition", items_getPosition }, { "getPosition", items_getPosition },
{ "getOuterContainerRef", items_getOuterContainerRef },
{ "getContainedItems", items_getContainedItems }, { "getContainedItems", items_getContainedItems },
{ "moveToBuilding", items_moveToBuilding }, { "moveToBuilding", items_moveToBuilding },
{ NULL, NULL } { NULL, NULL }

@ -150,6 +150,9 @@ DFHACK_EXPORT bool setOwner(df::item *item, df::unit *unit);
/// which item is it contained in? /// which item is it contained in?
DFHACK_EXPORT df::item *getContainer(df::item *item); 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? /// which items does it contain?
DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items); DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items);

@ -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::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type);
DFHACK_EXPORT df::item *getContainer(df::unit *unit); 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 void setNickname(df::unit *unit, std::string nick);
DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); 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 isAlive(df::unit *unit);
DFHACK_EXPORT bool isSane(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit);
DFHACK_EXPORT bool isCitizen(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 isDwarf(df::unit *unit);
DFHACK_EXPORT bool isWar(df::unit* unit); DFHACK_EXPORT bool isWar(df::unit* unit);
DFHACK_EXPORT bool isHunter(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 isOwnGroup(df::unit* unit);
DFHACK_EXPORT bool isOwnRace(df::unit* unit); DFHACK_EXPORT bool isOwnRace(df::unit* unit);
DFHACK_EXPORT bool isVisible(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 getRaceNameById(int32_t race_id);
DFHACK_EXPORT std::string getRaceName(df::unit* unit); DFHACK_EXPORT std::string getRaceName(df::unit* unit);

@ -590,6 +590,47 @@ df::item *Items::getContainer(df::item * item)
return ref ? ref->getItem() : NULL; 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<df::item*> *items) void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);

@ -86,6 +86,7 @@ using namespace df::enums;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
using df::global::gamemode; using df::global::gamemode;
using df::global::gametype;
int32_t Units::getNumUnits() 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); 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) static df::identity *getFigureIdentity(df::historical_figure *figure)
{ {
if (figure && figure->info && figure->info->reputation) if (figure && figure->info && figure->info->reputation)
@ -503,6 +524,40 @@ bool Units::isCitizen(df::unit *unit)
return isOwnGroup(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) bool Units::isDwarf(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
@ -584,6 +639,42 @@ bool Units::isVisible(df::unit* unit)
return Maps::isTileVisible(unit->pos); 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 // get race name by id or unit pointer
string Units::getRaceNameById(int32_t id) string Units::getRaceNameById(int32_t id)
{ {