From beee445f6fac666f768a3e9547d1b0a9a9d0485a Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 9 Nov 2022 11:44:28 -0800 Subject: [PATCH 01/13] Extends Units module Adds unit check functions for - animals - demons - titans - megabeasts - semimegabeasts - night creatures --- docs/changelog.txt | 1 + library/include/modules/Units.h | 6 ++++++ library/modules/Units.cpp | 37 +++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index b90248c91..7836c5e14 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -72,6 +72,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets - ``Lua::Push()``: now handles maps with otherwise supported keys and values +- Units module: added ``isAnimal()``, ``isDemon()``, ``isTitan()``, ``isMegabeast()``, ``isSemiMegabeast()``, and ``isNightCreature()`` - Constructions module: added ``insert()`` to insert constructions into the game's sorted list. - MiscUtils: moved the following string transformation functions from ``uicommon.h``: ``int_to_string``, ``ltrim``, ``rtrim``, and ``trim`` diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 4dfa9f937..c6323f862 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -141,6 +141,7 @@ DFHACK_EXPORT std::string getRaceChildName(df::unit* unit); DFHACK_EXPORT bool isBaby(df::unit* unit); DFHACK_EXPORT bool isChild(df::unit* unit); DFHACK_EXPORT bool isAdult(df::unit* unit); +DFHACK_EXPORT bool isAnimal(df::unit* unit); DFHACK_EXPORT bool isEggLayer(df::unit* unit); DFHACK_EXPORT bool isGrazer(df::unit* unit); DFHACK_EXPORT bool isMilkable(df::unit* unit); @@ -163,6 +164,11 @@ DFHACK_EXPORT bool isActive(df::unit *unit); DFHACK_EXPORT bool isKilled(df::unit *unit); DFHACK_EXPORT bool isGelded(df::unit* unit); DFHACK_EXPORT bool isDomesticated(df::unit* unit); +DFHACK_EXPORT bool isDemon(df::unit* unit); +DFHACK_EXPORT bool isTitan(df::unit* unit); +DFHACK_EXPORT bool isMegabeast(df::unit* unit); +DFHACK_EXPORT bool isSemiMegabeast(df::unit* unit); +DFHACK_EXPORT bool isNightCreature(df::unit* unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getKillCount(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 32b9f28e5..9111b54d4 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -770,6 +770,12 @@ bool Units::isAdult(df::unit* unit) return !isBaby(unit) && !isChild(unit); } +bool Units::isAnimal(df::unit* unit) +{ + CHECK_NULL_POINTER(unit) + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NATURAL_ANIMAL); +} + bool Units::isEggLayer(df::unit* unit) { CHECK_NULL_POINTER(unit); @@ -1824,6 +1830,37 @@ bool Units::isDomesticated(df::unit* unit) return tame; } +bool Units::isDemon(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::DEMON); +} + +bool Units::isTitan(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::TITAN); +} + +bool Units::isMegabeast(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::MEGABEAST); +} + +bool Units::isSemiMegabeast(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::SEMIMEGABEAST); +} + +bool Units::isNightCreature(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NIGHT_CREATURE); +} + + // 50000 and up is level 0, 25000 and up is level 1, etc. const vector Units::stress_cutoffs {50000, 25000, 10000, -10000, -25000, -50000, -100000}; From c0ffcc2f79ee6bff9ddf303c3cfbf20e1aac8a14 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 9 Nov 2022 12:37:41 -0800 Subject: [PATCH 02/13] Updates Units::isDemon() --- library/modules/Units.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 9111b54d4..be8c1ec1e 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1833,7 +1833,9 @@ bool Units::isDomesticated(df::unit* unit) bool Units::isDemon(df::unit* unit) { CHECK_NULL_POINTER(unit); - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::DEMON); + using namespace df::enums::caste_raw_flags; + const auto &cf = unit->enemy.caste_flags; + return cf.is_set(DEMON) || cf.is_set(UNIQUE_DEMON); } bool Units::isTitan(df::unit* unit) From 80824f5b75f0b626afe7ae3180690059afb6da6c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 9 Nov 2022 14:41:45 -0800 Subject: [PATCH 03/13] Extends Units module --- docs/changelog.txt | 16 +++++++++- library/include/modules/Units.h | 10 +++++-- library/modules/Units.cpp | 52 ++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7836c5e14..a5a7f84a2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -72,7 +72,21 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets - ``Lua::Push()``: now handles maps with otherwise supported keys and values -- Units module: added ``isAnimal()``, ``isDemon()``, ``isTitan()``, ``isMegabeast()``, ``isSemiMegabeast()``, and ``isNightCreature()`` +- Units module: added new checks + - ``isAnimal()`` + - ``isVisiting()`` any visiting unit (diplomat, merchant, visitor) + - ``isVisitor()`` ie. not merchants or diplomats + - ``isInvader()`` + - ``isDemon()`` returns true for unique/regular demons + - ``isTitan()`` + - ``isMegabeast()`` + - ``isGreatDanger()`` returns true if unit is a demon, titan, or megabeast + - ``isSemiMegabeast()`` + - ``isNightCreature()`` + - ``isDanger()`` returns true if is a 'GreatDanger', semi-megabeast, night creature, undead, or invader +- Units module: modifies existing checks + - ``isUndead(df::unit* unit)`` => ``isUndead(df::unit* unit, bool ignore_vamps = true)`` isUndead used to always ignore vamps, now it does it by default and includes them when false is passed + - ``isCitizen(df::unit* unit)`` => ``isCitizen(df::unit* unit, bool ignore_sanity = false)`` isCitizen used to always check sanity, now it does it by default and ignores sanity when true is passed - Constructions module: added ``insert()`` to insert constructions into the game's sorted list. - MiscUtils: moved the following string transformation functions from ``uicommon.h``: ``int_to_string``, ``ltrim``, ``rtrim``, and ``trim`` diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index c6323f862..d25ee8d0f 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -115,7 +115,7 @@ DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_t 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 isCitizen(df::unit *unit, bool ignore_sanity = false); DFHACK_EXPORT bool isFortControlled(df::unit *unit); DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT bool isWar(df::unit* unit); @@ -138,6 +138,7 @@ DFHACK_EXPORT std::string getRaceBabyName(df::unit* unit); DFHACK_EXPORT std::string getRaceChildNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceChildName(df::unit* unit); +DFHACK_EXPORT bool isInvader(df::unit* unit); DFHACK_EXPORT bool isBaby(df::unit* unit); DFHACK_EXPORT bool isChild(df::unit* unit); DFHACK_EXPORT bool isAdult(df::unit* unit); @@ -150,15 +151,17 @@ DFHACK_EXPORT bool isTrainableHunting(df::unit* unit); DFHACK_EXPORT bool isTamable(df::unit* unit); DFHACK_EXPORT bool isMale(df::unit* unit); DFHACK_EXPORT bool isFemale(df::unit* unit); +DFHACK_EXPORT bool isVisiting(df::unit* unit); DFHACK_EXPORT bool isMerchant(df::unit* unit); DFHACK_EXPORT bool isDiplomat(df::unit* unit); +DFHACK_EXPORT bool isVisitor(df::unit* unit); DFHACK_EXPORT bool isForest(df::unit* unit); DFHACK_EXPORT bool isMarkedForSlaughter(df::unit* unit); DFHACK_EXPORT bool isTame(df::unit* unit); DFHACK_EXPORT bool isTrained(df::unit* unit); DFHACK_EXPORT bool isGay(df::unit* unit); DFHACK_EXPORT bool isNaked(df::unit* unit); -DFHACK_EXPORT bool isUndead(df::unit* unit); +DFHACK_EXPORT bool isUndead(df::unit* unit, bool ignore_vamps = true); DFHACK_EXPORT bool isGhost(df::unit *unit); DFHACK_EXPORT bool isActive(df::unit *unit); DFHACK_EXPORT bool isKilled(df::unit *unit); @@ -167,8 +170,11 @@ DFHACK_EXPORT bool isDomesticated(df::unit* unit); DFHACK_EXPORT bool isDemon(df::unit* unit); DFHACK_EXPORT bool isTitan(df::unit* unit); DFHACK_EXPORT bool isMegabeast(df::unit* unit); +DFHACK_EXPORT bool isGreatDanger(df::unit* unit); DFHACK_EXPORT bool isSemiMegabeast(df::unit* unit); DFHACK_EXPORT bool isNightCreature(df::unit* unit); +DFHACK_EXPORT bool isDanger(df::unit* unit); + DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getKillCount(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index be8c1ec1e..0bb6235f9 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -499,7 +499,7 @@ bool Units::isSane(df::unit *unit) return true; } -bool Units::isCitizen(df::unit *unit) +bool Units::isCitizen(df::unit *unit, bool ignore_sanity) { CHECK_NULL_POINTER(unit); @@ -519,7 +519,7 @@ bool Units::isCitizen(df::unit *unit) unit->flags2.bits.resident) return false; - if (!isSane(unit)) + if (!ignore_sanity && !isSane(unit)) return false; return isOwnGroup(unit); @@ -752,6 +752,15 @@ string Units::getRaceChildName(df::unit* unit) return getRaceChildNameById(unit->race); } +bool Units::isInvader(df::unit* unit) { + CHECK_NULL_POINTER(unit); + + return !isOwnGroup(unit) && + (unit->flags1.bits.marauder || + unit->flags1.bits.invader_origin || + unit->flags1.bits.active_invader); +} + bool Units::isBaby(df::unit* unit) { CHECK_NULL_POINTER(unit); @@ -1666,6 +1675,15 @@ df::activity_event *Units::getMainSocialEvent(df::unit *unit) return entry->events[entry->events.size() - 1]; } +bool Units::isVisiting(df::unit* unit) { + CHECK_NULL_POINTER(unit); + + return unit->flags1.bits.merchant || + unit->flags1.bits.diplomat || + unit->flags2.bits.visitor || + unit->flags2.bits.visitor_uninvited; +} + bool Units::isMerchant(df::unit* unit) { CHECK_NULL_POINTER(unit); @@ -1680,6 +1698,12 @@ bool Units::isDiplomat(df::unit* unit) return unit->flags1.bits.diplomat == 1; } +bool Units::isVisitor(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->flags2.bits.visitor || unit->flags2.bits.visitor_uninvited; +} + bool Units::isForest(df::unit* unit) { CHECK_NULL_POINTER(unit); @@ -1763,13 +1787,13 @@ bool Units::isNaked(df::unit* unit) return (unit->inventory.empty()); } -bool Units::isUndead(df::unit* unit) +bool Units::isUndead(df::unit* unit, bool ignore_vamps) { CHECK_NULL_POINTER(unit); - // ignore vampires, they should be treated like normal dwarves - return (unit->flags3.bits.ghostly || - ( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING) - && !unit->curse.add_tags1.bits.BLOODSUCKER )); + + const auto &cb = unit->curse.add_tags1.bits; + return unit->flags3.bits.ghostly || + ((cb.OPPOSED_TO_LIFE || cb.NOT_LIVING) && (!ignore_vamps || !cb.BLOODSUCKER)); } bool Units::isGhost(df::unit *unit) @@ -1850,6 +1874,12 @@ bool Units::isMegabeast(df::unit* unit) return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::MEGABEAST); } +bool Units::isGreatDanger(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return isDemon(unit) || isTitan(unit) || isMegabeast(unit); +} + bool Units::isSemiMegabeast(df::unit* unit) { CHECK_NULL_POINTER(unit); @@ -1862,6 +1892,14 @@ bool Units::isNightCreature(df::unit* unit) return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NIGHT_CREATURE); } +bool Units::isDanger(df::unit* unit) { + CHECK_NULL_POINTER(unit); + return isInvader(unit) || + isUndead(unit, false) || + isSemiMegabeast(unit) || + isNightCreature(unit) || + isGreatDanger(unit); +} // 50000 and up is level 0, 25000 and up is level 1, etc. const vector Units::stress_cutoffs {50000, 25000, 10000, -10000, -25000, -50000, -100000}; From 34de030ba9c9c78a10fdeb19e479aff4ba4e6972 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 9 Nov 2022 22:03:39 -0800 Subject: [PATCH 04/13] Units module - Updates `Lua API.rst` - Only adds the most important additions (complicated enough to need explaining) - Adds new functions to LuaApi.cpp - Revises isUndead to accommodate `dfhack.units.isUndead(u)` => `Units::isUndead(u, false)` instead of taking the default value --- docs/Lua API.rst | 29 +++++++++++++++++++++++++++-- library/LuaApi.cpp | 11 +++++++++++ library/include/modules/Units.h | 2 +- library/modules/Units.cpp | 6 +++--- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 5b6a30d93..6b8290d12 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1287,10 +1287,35 @@ Units module The unit is of the correct race of the fortress. -* ``dfhack.units.isCitizen(unit)`` +* ``dfhack.units.isCitizen(unit[,ignore_sanity])`` 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 + (except for the sanity check). + +* ``dfhack.units.isInvader(unit)`` + + The unit is an active invader or marauder. + +* ``dfhack.units.isVisiting(unit)`` + + The unit is either a merchant, diplomat, or plain visitor. + +* ``dfhack.units.isVisitor(unit)`` + + The unit is strictly a visitor. Merchants and diplomats do not count here. + +* ``dfhack.units.isUndead(unit[,include_vamps])`` + + The unit is undead, but not a vampire. + +* ``dfhack.units.isGreatDanger(unit)`` + + The unit is of Great Danger. This include demons, titans, and megabeasts. + +* ``dfhack.units.isDanger(unit)`` + + The unit is dangerous, and probably hostile. This includes Great Dangers, semi-megabeasts, night creatures, undead, and invaders. * ``dfhack.units.isFortControlled(unit)`` diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 9f713244d..f99d65c65 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1615,9 +1615,11 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getRaceBabyNameById), WRAPM(Units, getRaceChildName), WRAPM(Units, getRaceChildNameById), + WRAPM(Units, isInvader), WRAPM(Units, isBaby), WRAPM(Units, isChild), WRAPM(Units, isAdult), + WRAPM(Units, isAnimal), WRAPM(Units, isEggLayer), WRAPM(Units, isGrazer), WRAPM(Units, isMilkable), @@ -1626,8 +1628,10 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isTamable), WRAPM(Units, isMale), WRAPM(Units, isFemale), + WRAPM(Units, isVisiting), WRAPM(Units, isMerchant), WRAPM(Units, isDiplomat), + WRAPM(Units, isVisitor), WRAPM(Units, isForest), WRAPM(Units, isMarkedForSlaughter), WRAPM(Units, isTame), @@ -1640,6 +1644,13 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isKilled), WRAPM(Units, isGelded), WRAPM(Units, isDomesticated), + WRAPM(Units, isDemon), + WRAPM(Units, isTitan), + WRAPM(Units, isMegabeast), + WRAPM(Units, isGreatDanger), + WRAPM(Units, isSemiMegabeast), + WRAPM(Units, isNightCreature), + WRAPM(Units, isDanger), WRAPM(Units, getMainSocialActivity), WRAPM(Units, getMainSocialEvent), WRAPM(Units, getStressCategory), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index d25ee8d0f..84fcf5bfc 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -161,7 +161,7 @@ DFHACK_EXPORT bool isTame(df::unit* unit); DFHACK_EXPORT bool isTrained(df::unit* unit); DFHACK_EXPORT bool isGay(df::unit* unit); DFHACK_EXPORT bool isNaked(df::unit* unit); -DFHACK_EXPORT bool isUndead(df::unit* unit, bool ignore_vamps = true); +DFHACK_EXPORT bool isUndead(df::unit* unit, bool include_vamps = false); DFHACK_EXPORT bool isGhost(df::unit *unit); DFHACK_EXPORT bool isActive(df::unit *unit); DFHACK_EXPORT bool isKilled(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 0bb6235f9..b0aad49fb 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1787,13 +1787,13 @@ bool Units::isNaked(df::unit* unit) return (unit->inventory.empty()); } -bool Units::isUndead(df::unit* unit, bool ignore_vamps) +bool Units::isUndead(df::unit* unit, bool include_vamps) { CHECK_NULL_POINTER(unit); const auto &cb = unit->curse.add_tags1.bits; return unit->flags3.bits.ghostly || - ((cb.OPPOSED_TO_LIFE || cb.NOT_LIVING) && (!ignore_vamps || !cb.BLOODSUCKER)); + ((cb.OPPOSED_TO_LIFE || cb.NOT_LIVING) && (include_vamps || !cb.BLOODSUCKER)); } bool Units::isGhost(df::unit *unit) @@ -1895,7 +1895,7 @@ bool Units::isNightCreature(df::unit* unit) bool Units::isDanger(df::unit* unit) { CHECK_NULL_POINTER(unit); return isInvader(unit) || - isUndead(unit, false) || + isUndead(unit, true) || isSemiMegabeast(unit) || isNightCreature(unit) || isGreatDanger(unit); From b73cef3d78c6bbb93fec234f13c3c55d38d2e3d5 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 12:28:53 -0800 Subject: [PATCH 05/13] Update docs/Lua API.rst Co-authored-by: Myk --- docs/Lua API.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 6b8290d12..607dd424f 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1307,7 +1307,8 @@ Units module * ``dfhack.units.isUndead(unit[,include_vamps])`` - The unit is undead, but not a vampire. + The unit is undead. Pass ``true`` as the optional second parameter to + count vampires as undead. * ``dfhack.units.isGreatDanger(unit)`` From ebd450af0eaebfc1c4fb66f6a0d1b2d43b6e4c31 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 14:42:41 -0800 Subject: [PATCH 06/13] Adds isUnitInBox to Units module --- docs/Lua API.rst | 4 ++++ docs/changelog.txt | 1 + library/LuaApi.cpp | 3 +++ library/include/modules/Units.h | 3 +++ library/modules/Units.cpp | 31 +++++++++++++++++++------------ 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 6b8290d12..33e133162 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1192,6 +1192,10 @@ Units module Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. +* ``dfhack.units.isUnitInBox(unit,x1,y1,z1,x2,y2,z2)`` + + Returns true if the unit is within the specified coordinates. + * ``dfhack.units.getUnitsInBox(x1,y1,z1,x2,y2,z2[,filter])`` Returns a table of all units within the specified coordinates. If the ``filter`` diff --git a/docs/changelog.txt b/docs/changelog.txt index a5a7f84a2..0497f2f64 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -73,6 +73,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets - ``Lua::Push()``: now handles maps with otherwise supported keys and values - Units module: added new checks + - ``isUnitInBox()`` - ``isAnimal()`` - ``isVisiting()`` any visiting unit (diplomat, merchant, visitor) - ``isVisitor()`` ie. not merchants or diplomats diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f99d65c65..b5a1cd754 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1559,6 +1559,7 @@ static const luaL_Reg dfhack_job_funcs[] = { /***** Units module *****/ static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, isUnitInBox), WRAPM(Units, teleport), WRAPM(Units, getGeneralRef), WRAPM(Units, getSpecificRef), @@ -1716,6 +1717,8 @@ static int units_getUnitsInBox(lua_State *state) { luaL_checktype(state, 7, LUA_TFUNCTION); units.erase(std::remove_if(units.begin(), units.end(), [&state](df::unit *unit) -> bool { + // todo: merging this filter into the base function would be welcomed by plugins + // (it would also be faster, and less obfuscated than this [ie. erase(remove_if)]) lua_dup(state); // copy function Lua::PushDFObject(state, unit); lua_call(state, 1, 1); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 84fcf5bfc..c001b6a5a 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -73,6 +73,9 @@ static const int MAX_COLORS = 15; // found. Call repeatedly do get all units in a specified box (uses tile coords) DFHACK_EXPORT int32_t getNumUnits(); DFHACK_EXPORT df::unit *getUnit(const int32_t index); +DFHACK_EXPORT bool isUnitInBox(df::unit* u, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2); DFHACK_EXPORT bool getUnitsInBox(std::vector &units, int16_t x1, int16_t y1, int16_t z1, int16_t x2, int16_t y2, int16_t z2); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index b0aad49fb..5dc41da02 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -98,6 +98,23 @@ df::unit *Units::getUnit (const int32_t index) return vector_get(world->units.all, index); } +bool Units::isUnitInBox(df::unit* u, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2) { + + if (x1 > x2) swap(x1, x2); + if (y1 > y2) swap(y1, y2); + if (z1 > z2) swap(z1, z2); + if (u->pos.x >= x1 && u->pos.x <= x2) { + if (u->pos.y >= y1 && u->pos.y <= y2) { + if (u->pos.z >= z1 && u->pos.z <= z2) { + return true; + } + } + } + return false; +} + // returns index of creature actually read or -1 if no creature can be found bool Units::getUnitsInBox (std::vector &units, int16_t x1, int16_t y1, int16_t z1, @@ -106,22 +123,12 @@ bool Units::getUnitsInBox (std::vector &units, if (!world) return false; - if (x1 > x2) swap(x1, x2); - if (y1 > y2) swap(y1, y2); - if (z1 > z2) swap(z1, z2); - units.clear(); for (df::unit *u : world->units.all) { - if (u->pos.x >= x1 && u->pos.x <= x2) + if (isUnitInBox(u, x1, y1, z1, x2, y2, z2)) { - if (u->pos.y >= y1 && u->pos.y <= y2) - { - if (u->pos.z >= z1 && u->pos.z <= z2) - { - units.push_back(u); - } - } + units.push_back(u); } } return true; From d112649886abfafa799f0f396c1f6ad2f60e331f Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 16:40:20 -0800 Subject: [PATCH 07/13] Organizes and documents Units::is*(unit) functions --- docs/Lua API.rst | 295 +++++-- library/include/modules/Units.h | 132 +-- library/modules/Units.cpp | 1434 ++++++++++++++++--------------- 3 files changed, 1012 insertions(+), 849 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 33e133162..66631f483 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1188,150 +1188,301 @@ Job module Units module ------------ -* ``dfhack.units.getPosition(unit)`` +* ``dfhack.units.isUnitInBox(unit,x1,y1,z1,x2,y2,z2)`` - Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. + The unit is within the specified coordinates. -* ``dfhack.units.isUnitInBox(unit,x1,y1,z1,x2,y2,z2)`` +* ``dfhack.units.isActive(unit)`` - Returns true if the unit is within the specified coordinates. + The unit is active (alive and on the map). -* ``dfhack.units.getUnitsInBox(x1,y1,z1,x2,y2,z2[,filter])`` +* ``dfhack.units.isVisible(unit)`` - Returns a table of all units within the specified coordinates. If the ``filter`` - argument is given, only units where ``filter(unit)`` returns true will be included. - Note that ``pos2xyz()`` cannot currently be used to convert coordinate objects to - the arguments required by this function. + The unit is visible on the map. -* ``dfhack.units.teleport(unit, pos)`` +* ``dfhack.units.isCitizen(unit[,ignore_sanity])`` - Moves the specified unit and any riders to the target coordinates, setting - tile occupancy flags appropriately. Returns true if successful. + The unit is an alive sane citizen of the fortress; wraps the + same checks the game uses to decide game-over by extinction, + with an additional sanity check. You can identify citizens, + regardless of their sanity, by passing ``true`` as the optional + second parameter. -* ``dfhack.units.getGeneralRef(unit, type)`` +* ``dfhack.units.isFortControlled(unit)`` - Searches for a general_ref with the given type. + 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.getSpecificRef(unit, type)`` +* ``dfhack.units.isOwnCiv(unit)`` - Searches for a specific_ref with the given type. + The unit belongs to the player's civilization. -* ``dfhack.units.getContainer(unit)`` +* ``dfhack.units.isOwnGroup(unit)`` - Returns the container (cage) item or *nil*. + The unit belongs to the player's group. -* ``dfhack.units.setNickname(unit,nick)`` +* ``dfhack.units.isOwnRace(unit)`` - Sets the unit's nickname properly. + The unit belongs to the player's race. -* ``dfhack.units.getOuterContainerRef(unit)`` +* ``dfhack.units.isAlive(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. + The unit isn't dead or undead. -* ``dfhack.units.getVisibleName(unit)`` +* ``dfhack.units.isDead(unit)`` - Returns the language_name object visible in game, accounting for false identities. + The unit is completely dead and passive, or a ghost. Equivalent to + ``dfhack.units.isKilled(unit) or dfhack.units.isGhost(unit)``. -* ``dfhack.units.getIdentity(unit)`` +* ``dfhack.units.isKilled(unit)`` - Returns the false identity of the unit if it has one, or *nil*. + The unit has been killed. -* ``dfhack.units.getNemesis(unit)`` +* ``dfhack.units.isSane(unit)`` - Returns the nemesis record of the unit if it has one, or *nil*. + The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf. + +* ``dfhack.units.isCrazed`` + + The unit is berserk and will attack all other creatures except members of its own species + that are also crazed. (can be modified by curses) + +* ``dfhack.units.isGhost(unit)`` + + The unit is a ghost. + +* ``dfhack.units.isHidden(unit)`` + + The unit is hidden to the player, accounting for sneaking. Works for any game mode. * ``dfhack.units.isHidingCurse(unit)`` - Checks if the unit hides improved attributes from its curse. + The unit is hiding a curse. -* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)`` -* ``dfhack.units.getMentalAttrValue(unit, attr_type)`` - Computes the effective attribute value, including curse effect. +* ``dfhack.units.isMale(unit)`` -* ``dfhack.units.isCrazed(unit)`` -* ``dfhack.units.isOpposedToLife(unit)`` -* ``dfhack.units.hasExtravision(unit)`` -* ``dfhack.units.isBloodsucker(unit)`` + The unit is male. - Simple checks of caste attributes that can be modified by curses. +* ``dfhack.units.isFemale(unit)`` -* ``dfhack.units.getMiscTrait(unit, type[, create])`` + The unit is female. - Finds (or creates if requested) a misc trait object with the given id. +* ``dfhack.units.isBaby(unit)`` -* ``dfhack.units.isActive(unit)`` + The unit is a baby. - The unit is active (alive and on the map). +* ``dfhack.units.isChild(unit)`` -* ``dfhack.units.isAlive(unit)`` + The unit is a child. - The unit isn't dead or undead. +* ``dfhack.units.isAdult(unit)`` -* ``dfhack.units.isDead(unit)`` + The unit is an adult. - The unit is completely dead and passive, or a ghost. Equivalent to - ``dfhack.units.isKilled(unit) or dfhack.units.isGhost(unit)``. +* ``dfhack.units.isGay(unit)`` -* ``dfhack.units.isKilled(unit)`` + The unit is gay. - The unit has been killed. +* ``dfhack.units.isNake(unit)`` -* ``dfhack.units.isGhost(unit)`` + The unit is naked. - The unit is a ghost. +* ``dfhack.units.isVisiting(unit)`` -* ``dfhack.units.isSane(unit)`` + The unit is visiting. eg. Merchants, Diplomatics, travelers. + + +* ``dfhack.units.isTrainableHunting(unit)`` + + The unit is trainable for hunting. + +* ``dfhack.units.isTrainableWar(unit)`` + + The unit is trainable for war. + +* ``dfhack.units.isTrained(unit)`` + + The unit is trained. + +* ``dfhack.units.isHunter(unit)`` + + The unit is a trained hunter. + +* ``dfhack.units.isWar(unit)`` + + The unit is trained for war. + +* ``dfhack.units.isTame(unit)`` + + The unit is tame. + +* ``dfhack.units.isTamable(unit)`` + + The unit is tamable. + +* ``dfhack.units.isDomesticated(unit)`` + + The unit is domesticated. + +* ``dfhack.units.isMarkedForSlaughter(unit)`` + + The unit is marked for slaughter. + +* ``dfhack.units.isGelded(unit)`` + + The unit is gelded. + +* ``dfhack.units.isEggLayer(unit)`` + + The unit is an egg layer. + +* ``dfhack.units.isGrazer(unit)`` + + The unit is a grazer. + +* ``dfhack.units.isMilkable(unit)`` + + The unit is milkable. + +* ``dfhack.units.isForest(unit)`` + + The unit is of the forest. + +* ``dfhack.units.isMischievous(unit)`` + + The unit is mischievous. + +* ``dfhack.units.isAvailableForAdoption(unit)`` + + The unit is available for adoption. + + +* ``dfhack.units.isOpposedToLife(unit)`` +* ``dfhack.units.hasExtravision(unit)`` +* ``dfhack.units.isBloodsucker(unit)`` + + Simple checks of caste attributes that can be modified by curses. - The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf. * ``dfhack.units.isDwarf(unit)`` - The unit is of the correct race of the fortress. + The unit is of the correct race for the fortress. -* ``dfhack.units.isCitizen(unit[,ignore_sanity])`` +* ``dfhack.units.isAnimal(unit)`` - The unit is an alive sane citizen of the fortress; wraps the - same checks the game uses to decide game-over by extinction - (except for the sanity check). + The unit is an animal. -* ``dfhack.units.isInvader(unit)`` +* ``dfhack.units.isMerchant(unit)`` - The unit is an active invader or marauder. + The unit is a merchant. -* ``dfhack.units.isVisiting(unit)`` +* ``dfhack.units.isDiplomat(unit)`` - The unit is either a merchant, diplomat, or plain visitor. + The unit is a diplomat. * ``dfhack.units.isVisitor(unit)`` - The unit is strictly a visitor. Merchants and diplomats do not count here. + The unit is a regular visitor with no special purpose (eg. merchant). + +* ``dfhack.units.isInvader(unit)`` + + The unit is an active invader or marauder. * ``dfhack.units.isUndead(unit[,include_vamps])`` The unit is undead, but not a vampire. +* ``dfhack.units.isNightCreature(unit)`` + + The unit is undead, but not a vampire. + +* ``dfhack.units.isSemiMegabeast(unit)`` + + The unit is undead, but not a vampire. + +* ``dfhack.units.isMegabeast(unit)`` + + The unit is a megabeast. + +* ``dfhack.units.isTitan(unit)`` + + The unit is a titan. + +* ``dfhack.units.isDemon(unit)`` + + The unit is a demon. + +* ``dfhack.units.isDanger(unit)`` + + The unit is dangerous, and probably hostile. This includes + Great Dangers (see below), semi-megabeasts, night creatures, + undead, invaders, and crazed units. + * ``dfhack.units.isGreatDanger(unit)`` The unit is of Great Danger. This include demons, titans, and megabeasts. -* ``dfhack.units.isDanger(unit)`` - The unit is dangerous, and probably hostile. This includes Great Dangers, semi-megabeasts, night creatures, undead, and invaders. +* ``dfhack.units.getPosition(unit)`` -* ``dfhack.units.isFortControlled(unit)`` + Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. - 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.getUnitsInBox(x1,y1,z1,x2,y2,z2[,filter])`` -* ``dfhack.units.isVisible(unit)`` + Returns a table of all units within the specified coordinates. If the ``filter`` + argument is given, only units where ``filter(unit)`` returns true will be included. + Note that ``pos2xyz()`` cannot currently be used to convert coordinate objects to + the arguments required by this function. - The unit is visible on the map. +* ``dfhack.units.teleport(unit, pos)`` -* ``dfhack.units.isHidden(unit)`` + Moves the specified unit and any riders to the target coordinates, setting + tile occupancy flags appropriately. Returns true if successful. - The unit is hidden to the player, accounting for sneaking. Works for any game mode. +* ``dfhack.units.getGeneralRef(unit, type)`` + + Searches for a general_ref with the given type. + +* ``dfhack.units.getSpecificRef(unit, type)`` + + Searches for a specific_ref with the given type. + +* ``dfhack.units.getContainer(unit)`` + + Returns the container (cage) item or *nil*. + +* ``dfhack.units.setNickname(unit,nick)`` + + 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. + +* ``dfhack.units.getIdentity(unit)`` + + Returns the false identity of the unit if it has one, or *nil*. + +* ``dfhack.units.getNemesis(unit)`` + + Returns the nemesis record of the unit if it has one, or *nil*. + +* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)`` +* ``dfhack.units.getMentalAttrValue(unit, attr_type)`` + + Computes the effective attribute value, including curse effect. + +* ``dfhack.units.getMiscTrait(unit, type[, create])`` + + Finds (or creates if requested) a misc trait object with the given id. * ``dfhack.units.getAge(unit[,true_age])`` diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index c001b6a5a..aa5edcf8c 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -68,14 +68,78 @@ static const int MAX_COLORS = 15; * The Units module - allows reading all non-vermin units and their properties */ +DFHACK_EXPORT bool isUnitInBox(df::unit* u, +int16_t x1, int16_t y1, int16_t z1, +int16_t x2, int16_t y2, int16_t z2); + +DFHACK_EXPORT bool isActive(df::unit *unit); +DFHACK_EXPORT bool isVisible(df::unit* unit); +DFHACK_EXPORT bool isCitizen(df::unit *unit, bool ignore_sanity = false); +DFHACK_EXPORT bool isFortControlled(df::unit *unit); +DFHACK_EXPORT bool isOwnCiv(df::unit* unit); +DFHACK_EXPORT bool isOwnGroup(df::unit* unit); +DFHACK_EXPORT bool isOwnRace(df::unit* unit); + +DFHACK_EXPORT bool isAlive(df::unit *unit); +DFHACK_EXPORT bool isDead(df::unit *unit); +DFHACK_EXPORT bool isKilled(df::unit *unit); +DFHACK_EXPORT bool isSane(df::unit *unit); +DFHACK_EXPORT bool isCrazed(df::unit *unit); +DFHACK_EXPORT bool isGhost(df::unit *unit); +/// is unit hidden to the player? accounts for ambushing +DFHACK_EXPORT bool isHidden(df::unit *unit); +DFHACK_EXPORT bool isHidingCurse(df::unit *unit); + +DFHACK_EXPORT bool isMale(df::unit* unit); +DFHACK_EXPORT bool isFemale(df::unit* unit); +DFHACK_EXPORT bool isBaby(df::unit* unit); +DFHACK_EXPORT bool isChild(df::unit* unit); +DFHACK_EXPORT bool isAdult(df::unit* unit); +DFHACK_EXPORT bool isGay(df::unit* unit); +DFHACK_EXPORT bool isNaked(df::unit* unit); +DFHACK_EXPORT bool isVisiting(df::unit* unit); + +DFHACK_EXPORT bool isTrainableHunting(df::unit* unit); +DFHACK_EXPORT bool isTrainableWar(df::unit* unit); +DFHACK_EXPORT bool isTrained(df::unit* unit); +DFHACK_EXPORT bool isHunter(df::unit* unit); +DFHACK_EXPORT bool isWar(df::unit* unit); +DFHACK_EXPORT bool isTame(df::unit* unit); +DFHACK_EXPORT bool isTamable(df::unit* unit); +DFHACK_EXPORT bool isDomesticated(df::unit* unit); +DFHACK_EXPORT bool isMarkedForSlaughter(df::unit* unit); +DFHACK_EXPORT bool isGelded(df::unit* unit); +DFHACK_EXPORT bool isEggLayer(df::unit* unit); +DFHACK_EXPORT bool isGrazer(df::unit* unit); +DFHACK_EXPORT bool isMilkable(df::unit* unit); +DFHACK_EXPORT bool isForest(df::unit* unit); +DFHACK_EXPORT bool isMischievous(df::unit *unit); +DFHACK_EXPORT bool isAvailableForAdoption(df::unit* unit); + +DFHACK_EXPORT bool hasExtravision(df::unit *unit); +DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); +DFHACK_EXPORT bool isBloodsucker(df::unit *unit); + +DFHACK_EXPORT bool isDwarf(df::unit *unit); +DFHACK_EXPORT bool isAnimal(df::unit* unit); +DFHACK_EXPORT bool isMerchant(df::unit* unit); +DFHACK_EXPORT bool isDiplomat(df::unit* unit); +DFHACK_EXPORT bool isVisitor(df::unit* unit); +DFHACK_EXPORT bool isInvader(df::unit* unit); +DFHACK_EXPORT bool isUndead(df::unit* unit, bool include_vamps = false); +DFHACK_EXPORT bool isNightCreature(df::unit* unit); +DFHACK_EXPORT bool isSemiMegabeast(df::unit* unit); +DFHACK_EXPORT bool isMegabeast(df::unit* unit); +DFHACK_EXPORT bool isTitan(df::unit* unit); +DFHACK_EXPORT bool isDemon(df::unit* unit); +DFHACK_EXPORT bool isDanger(df::unit* unit); +DFHACK_EXPORT bool isGreatDanger(df::unit* unit); + /* Read Functions */ // Read units in a box, starting with index. Returns -1 if no more units // found. Call repeatedly do get all units in a specified box (uses tile coords) DFHACK_EXPORT int32_t getNumUnits(); DFHACK_EXPORT df::unit *getUnit(const int32_t index); -DFHACK_EXPORT bool isUnitInBox(df::unit* u, - int16_t x1, int16_t y1, int16_t z1, - int16_t x2, int16_t y2, int16_t z2); DFHACK_EXPORT bool getUnitsInBox(std::vector &units, int16_t x1, int16_t y1, int16_t z1, int16_t x2, int16_t y2, int16_t z2); @@ -102,34 +166,11 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); -DFHACK_EXPORT bool isHidingCurse(df::unit *unit); DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr); DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr); DFHACK_EXPORT bool casteFlagSet(int race, int caste, df::caste_raw_flags flag); -DFHACK_EXPORT bool isCrazed(df::unit *unit); -DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); -DFHACK_EXPORT bool hasExtravision(df::unit *unit); -DFHACK_EXPORT bool isBloodsucker(df::unit *unit); -DFHACK_EXPORT bool isMischievous(df::unit *unit); - -DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); - -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, bool ignore_sanity = false); -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); -DFHACK_EXPORT bool isAvailableForAdoption(df::unit* unit); -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 df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); @@ -141,43 +182,6 @@ DFHACK_EXPORT std::string getRaceBabyName(df::unit* unit); DFHACK_EXPORT std::string getRaceChildNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceChildName(df::unit* unit); -DFHACK_EXPORT bool isInvader(df::unit* unit); -DFHACK_EXPORT bool isBaby(df::unit* unit); -DFHACK_EXPORT bool isChild(df::unit* unit); -DFHACK_EXPORT bool isAdult(df::unit* unit); -DFHACK_EXPORT bool isAnimal(df::unit* unit); -DFHACK_EXPORT bool isEggLayer(df::unit* unit); -DFHACK_EXPORT bool isGrazer(df::unit* unit); -DFHACK_EXPORT bool isMilkable(df::unit* unit); -DFHACK_EXPORT bool isTrainableWar(df::unit* unit); -DFHACK_EXPORT bool isTrainableHunting(df::unit* unit); -DFHACK_EXPORT bool isTamable(df::unit* unit); -DFHACK_EXPORT bool isMale(df::unit* unit); -DFHACK_EXPORT bool isFemale(df::unit* unit); -DFHACK_EXPORT bool isVisiting(df::unit* unit); -DFHACK_EXPORT bool isMerchant(df::unit* unit); -DFHACK_EXPORT bool isDiplomat(df::unit* unit); -DFHACK_EXPORT bool isVisitor(df::unit* unit); -DFHACK_EXPORT bool isForest(df::unit* unit); -DFHACK_EXPORT bool isMarkedForSlaughter(df::unit* unit); -DFHACK_EXPORT bool isTame(df::unit* unit); -DFHACK_EXPORT bool isTrained(df::unit* unit); -DFHACK_EXPORT bool isGay(df::unit* unit); -DFHACK_EXPORT bool isNaked(df::unit* unit); -DFHACK_EXPORT bool isUndead(df::unit* unit, bool include_vamps = false); -DFHACK_EXPORT bool isGhost(df::unit *unit); -DFHACK_EXPORT bool isActive(df::unit *unit); -DFHACK_EXPORT bool isKilled(df::unit *unit); -DFHACK_EXPORT bool isGelded(df::unit* unit); -DFHACK_EXPORT bool isDomesticated(df::unit* unit); -DFHACK_EXPORT bool isDemon(df::unit* unit); -DFHACK_EXPORT bool isTitan(df::unit* unit); -DFHACK_EXPORT bool isMegabeast(df::unit* unit); -DFHACK_EXPORT bool isGreatDanger(df::unit* unit); -DFHACK_EXPORT bool isSemiMegabeast(df::unit* unit); -DFHACK_EXPORT bool isNightCreature(df::unit* unit); -DFHACK_EXPORT bool isDanger(df::unit* unit); - DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getKillCount(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 5dc41da02..0395044ce 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -88,19 +88,9 @@ using df::global::ui; using df::global::gamemode; using df::global::gametype; -int32_t Units::getNumUnits() -{ - return world->units.all.size(); -} - -df::unit *Units::getUnit (const int32_t index) -{ - return vector_get(world->units.all, index); -} - bool Units::isUnitInBox(df::unit* u, - int16_t x1, int16_t y1, int16_t z1, - int16_t x2, int16_t y2, int16_t z2) { + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2) { if (x1 > x2) swap(x1, x2); if (y1 > y2) swap(y1, y2); @@ -115,218 +105,215 @@ bool Units::isUnitInBox(df::unit* u, return false; } -// returns index of creature actually read or -1 if no creature can be found -bool Units::getUnitsInBox (std::vector &units, - int16_t x1, int16_t y1, int16_t z1, - int16_t x2, int16_t y2, int16_t z2) +bool Units::isActive(df::unit *unit) { - if (!world) - return false; + CHECK_NULL_POINTER(unit); - units.clear(); - for (df::unit *u : world->units.all) - { - if (isUnitInBox(u, x1, y1, z1, x2, y2, z2)) - { - units.push_back(u); - } - } - return true; + return !unit->flags1.bits.inactive; } -int32_t Units::findIndexById(int32_t creature_id) +bool Units::isVisible(df::unit* unit) { - return df::unit::binsearch_index(world->units.all, creature_id); + CHECK_NULL_POINTER(unit); + return Maps::isTileVisible(unit->pos); } -df::coord Units::getPosition(df::unit *unit) +bool Units::isCitizen(df::unit *unit, bool ignore_sanity) { CHECK_NULL_POINTER(unit); - if (unit->flags1.bits.caged) - { - auto cage = getContainer(unit); - if (cage) - return Items::getPosition(cage); - } + // Copied from the conditions used to decide game over, + // except that the game appears to let melancholy/raving + // dwarves count as citizens. - return unit->pos; -} + 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 || + unit->flags2.bits.visitor || + unit->flags2.bits.visitor_uninvited || + unit->flags2.bits.underworld || + unit->flags2.bits.resident) + return false; -bool Units::teleport(df::unit *unit, df::coord target_pos) -{ - // make sure source and dest map blocks are valid - auto old_occ = Maps::getTileOccupancy(unit->pos); - auto new_occ = Maps::getTileOccupancy(target_pos); - if (!old_occ || !new_occ) + if (!ignore_sanity && !isSane(unit)) return false; - // clear appropriate occupancy flags at old tile - if (unit->flags1.bits.on_ground) - // this is potentially wrong, but the game will recompute this as needed - old_occ->bits.unit_grounded = 0; - else - old_occ->bits.unit = 0; + return isOwnGroup(unit); +} - // if there's already somebody standing at the destination, then force the - // unit to lay down - if (new_occ->bits.unit) - unit->flags1.bits.on_ground = 1; +bool Units::isFortControlled(df::unit *unit) +{ // Reverse-engineered from ambushing unit code + CHECK_NULL_POINTER(unit); - // set appropriate occupancy flags at new tile - if (unit->flags1.bits.on_ground) - new_occ->bits.unit_grounded = 1; - else - new_occ->bits.unit = 1; + if (*gamemode != game_mode::DWARF) + return false; - // move unit to destination - unit->pos = target_pos; - unit->idle_area = target_pos; + if (unit->mood == mood_type::Berserk || + Units::isCrazed(unit) || + Units::isOpposedToLife(unit) || + unit->enemy.undead || + unit->flags3.bits.ghostly) + return false; - // move unit's riders (including babies) to destination - if (unit->flags1.bits.ridden) - { - for (size_t j = 0; j < world->units.other[units_other_id::ANY_RIDER].size(); j++) - { - df::unit *rider = world->units.other[units_other_id::ANY_RIDER][j]; - if (rider->relationship_ids[df::unit_relationship_type::RiderMount] == unit->id) - rider->pos = unit->pos; - } - } + 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; - return true; + 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; } -df::general_ref *Units::getGeneralRef(df::unit *unit, df::general_ref_type type) +// check if creature belongs to the player's civilization +// (don't try to pasture/slaughter random untame animals) +bool Units::isOwnCiv(df::unit* unit) { CHECK_NULL_POINTER(unit); - - return findRef(unit->general_refs, type); + return unit->civ_id == ui->civ_id; } -df::specific_ref *Units::getSpecificRef(df::unit *unit, df::specific_ref_type type) +// check if creature belongs to the player's group +bool Units::isOwnGroup(df::unit* unit) { CHECK_NULL_POINTER(unit); - - return findRef(unit->specific_refs, type); + auto histfig = df::historical_figure::find(unit->hist_figure_id); + if (!histfig) + return false; + for (size_t i = 0; i < histfig->entity_links.size(); i++) + { + auto link = histfig->entity_links[i]; + if (link->entity_id == ui->group_id && link->getType() == df::histfig_entity_link_type::MEMBER) + return true; + } + return false; } -df::item *Units::getContainer(df::unit *unit) +// check if creature belongs to the player's race +// (in combination with check for civ helps to filter out own dwarves) +bool Units::isOwnRace(df::unit* unit) { CHECK_NULL_POINTER(unit); - - return findItemRef(unit->general_refs, general_ref_type::CONTAINED_IN_ITEM); + return unit->race == ui->race_id; } -void Units::getOuterContainerRef(df::specific_ref &spec_ref, df::unit *unit, bool init_ref) + +bool Units::isAlive(df::unit *unit) { 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; + return !unit->flags2.bits.killed && + !unit->flags3.bits.ghostly && + !unit->curse.add_tags1.bits.NOT_LIVING; } -static df::identity *getFigureIdentity(df::historical_figure *figure) +bool Units::isDead(df::unit *unit) { - if (figure && figure->info && figure->info->reputation) - return df::identity::find(figure->info->reputation->cur_identity); + CHECK_NULL_POINTER(unit); - return NULL; + return unit->flags2.bits.killed || + unit->flags3.bits.ghostly; } -df::identity *Units::getIdentity(df::unit *unit) +bool Units::isKilled(df::unit *unit) { CHECK_NULL_POINTER(unit); - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - - return getFigureIdentity(figure); + return unit->flags2.bits.killed; } -void Units::setNickname(df::unit *unit, std::string nick) +bool Units::isSane(df::unit *unit) { 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 (isDead(unit) || + isOpposedToLife(unit) || + unit->enemy.undead) + return false; - if (unit->status.current_soul) - Translation::setNickname(&unit->status.current_soul->name, nick); + if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit)) + return false; - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) + switch (unit->mood) { - Translation::setNickname(&figure->name, nick); - - if (auto identity = getFigureIdentity(figure)) - { - df::historical_figure *id_hfig = NULL; - - switch (identity->type) { - case df::identity_type::None: - case df::identity_type::HidingCurse: - case df::identity_type::FalseIdentity: - case df::identity_type::InfiltrationIdentity: - case df::identity_type::Identity: - break; // We want the nickname to end up in the identity - - case df::identity_type::Impersonating: - case df::identity_type::TrueName: - id_hfig = df::historical_figure::find(identity->histfig_id); - break; - } - - if (id_hfig) - { - Translation::setNickname(&id_hfig->name, nick); - } - else - Translation::setNickname(&identity->name, nick); - } + case mood_type::Melancholy: + case mood_type::Raving: + case mood_type::Berserk: + return false; + default: + break; } + + return true; } -df::language_name *Units::getVisibleName(df::unit *unit) +bool Units::isCrazed(df::unit *unit) { CHECK_NULL_POINTER(unit); + if (unit->flags3.bits.scuttle) + return false; + if (unit->curse.rem_tags1.bits.CRAZED) + return false; + if (unit->curse.add_tags1.bits.CRAZED) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); +} - // as of 0.44.11, identity names take precedence over associated histfig names - if (auto identity = getIdentity(unit)) - return &identity->name; +bool Units::isGhost(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); - return &unit->name; + return unit->flags3.bits.ghostly; } -df::nemesis_record *Units::getNemesis(df::unit *unit) +bool Units::isHidden(df::unit *unit) { - if (!unit) - return NULL; + CHECK_NULL_POINTER(unit); + // Reverse-engineered from ambushing unit code - for (unsigned i = 0; i < unit->general_refs.size(); i++) + if (*df::global::debug_showambush) + return false; + + if (*gamemode == game_mode::ADVENTURE) { - df::nemesis_record *rv = unit->general_refs[i]->getNemesis(); - if (rv && rv->unit == unit) - return rv; + 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; } - return NULL; -} + 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)); +} bool Units::isHidingCurse(df::unit *unit) { @@ -340,254 +327,241 @@ bool Units::isHidingCurse(df::unit *unit) return false; } -int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr) -{ - auto &aobj = unit->body.physical_attrs[attr]; - int value = std::max(0, aobj.value - aobj.soft_demotion); - - if (auto mod = unit->curse.attr_change) - { - int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr]; - - if (isHidingCurse(unit)) - value = std::min(value, mvalue); - else - value = mvalue; - } - return std::max(0, value); +bool Units::isMale(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return unit->sex == 1; } -int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) +bool Units::isFemale(df::unit* unit) { - auto soul = unit->status.current_soul; - if (!soul) return 0; - - auto &aobj = soul->mental_attrs[attr]; - int value = std::max(0, aobj.value - aobj.soft_demotion); - - if (auto mod = unit->curse.attr_change) - { - int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr]; - - if (isHidingCurse(unit)) - value = std::min(value, mvalue); - else - value = mvalue; - } - - return std::max(0, value); + CHECK_NULL_POINTER(unit); + return unit->sex == 0; } -bool Units::casteFlagSet(int race, int caste, df::caste_raw_flags flag) +bool Units::isBaby(df::unit* unit) { - auto creature = df::creature_raw::find(race); - if (!creature) - return false; - - auto craw = vector_get(creature->caste, caste); - if (!craw) - return false; - - return craw->flags.is_set(flag); + CHECK_NULL_POINTER(unit); + return unit->profession == df::profession::BABY; } -bool Units::isCrazed(df::unit *unit) +bool Units::isChild(df::unit* unit) { CHECK_NULL_POINTER(unit); - if (unit->flags3.bits.scuttle) - return false; - if (unit->curse.rem_tags1.bits.CRAZED) - return false; - if (unit->curse.add_tags1.bits.CRAZED) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); + return unit->profession == df::profession::CHILD; } -bool Units::isOpposedToLife(df::unit *unit) +bool Units::isAdult(df::unit* unit) { CHECK_NULL_POINTER(unit); - if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) - return false; - if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE); + return !isBaby(unit) && !isChild(unit); } -bool Units::hasExtravision(df::unit *unit) +bool Units::isGay(df::unit* unit) { CHECK_NULL_POINTER(unit); - if (unit->curse.rem_tags1.bits.EXTRAVISION) + if (!unit->status.current_soul) return false; - if (unit->curse.add_tags1.bits.EXTRAVISION) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); + df::orientation_flags orientation = unit->status.current_soul->orientation_flags; + return (!Units::isFemale(unit) || !(orientation.whole & (orientation.mask_marry_male | orientation.mask_romance_male))) + && (!Units::isMale(unit) || !(orientation.whole & (orientation.mask_marry_female | orientation.mask_romance_female))); } -bool Units::isBloodsucker(df::unit *unit) +bool Units::isNaked(df::unit* unit) { CHECK_NULL_POINTER(unit); - if (unit->curse.rem_tags1.bits.BLOODSUCKER) - return false; - if (unit->curse.add_tags1.bits.BLOODSUCKER) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); + // TODO(kazimuth): is this correct? + return (unit->inventory.empty()); } -bool Units::isMischievous(df::unit *unit) +bool Units::isVisiting(df::unit* unit) { + CHECK_NULL_POINTER(unit); + + return unit->flags1.bits.merchant || + unit->flags1.bits.diplomat || + unit->flags2.bits.visitor || + unit->flags2.bits.visitor_uninvited; +} + + +bool Units::isTrainableHunting(df::unit* unit) { CHECK_NULL_POINTER(unit); - if (unit->curse.rem_tags1.bits.MISCHIEVOUS) - return false; - if (unit->curse.add_tags1.bits.MISCHIEVOUS) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS); + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::TRAINABLE_HUNTING); } -df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) +bool Units::isTrainableWar(df::unit* unit) { CHECK_NULL_POINTER(unit); + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::TRAINABLE_WAR); +} - auto &vec = unit->status.misc_traits; - for (size_t i = 0; i < vec.size(); i++) - if (vec[i]->id == type) - return vec[i]; +bool Units::isTrained(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + // case a: trained for war/hunting (those don't have a training level, strangely) + if(Units::isWar(unit) || Units::isHunter(unit)) + return true; - if (create) + // case b: tamed and trained wild creature, gets a training level + bool trained = false; + switch (unit->training_level) { - auto obj = new df::unit_misc_trait(); - obj->id = type; - vec.push_back(obj); - return obj; + case df::animal_training_level::Trained: + case df::animal_training_level::WellTrained: + case df::animal_training_level::SkilfullyTrained: + case df::animal_training_level::ExpertlyTrained: + case df::animal_training_level::ExceptionallyTrained: + case df::animal_training_level::MasterfullyTrained: + //case df::animal_training_level::Domesticated: + trained = true; + break; + default: + break; } + return trained; +} - return NULL; +// check for profession "hunting creature" +bool Units::isHunter(df::unit* unit) +{ + CHECK_NULL_POINTER(unit) + return unit->profession == df::profession::TRAINED_HUNTER + || unit->profession2 == df::profession::TRAINED_HUNTER; } -bool Units::isDead(df::unit *unit) +// check for profession "war creature" +bool Units::isWar(df::unit* unit) { CHECK_NULL_POINTER(unit); - - return unit->flags2.bits.killed || - unit->flags3.bits.ghostly; + return unit->profession == df::profession::TRAINED_WAR + || unit->profession2 == df::profession::TRAINED_WAR; } -bool Units::isAlive(df::unit *unit) +bool Units::isTame(df::unit* unit) { CHECK_NULL_POINTER(unit); - - return !unit->flags2.bits.killed && - !unit->flags3.bits.ghostly && - !unit->curse.add_tags1.bits.NOT_LIVING; + bool tame = false; + if(unit->flags1.bits.tame) + { + switch (unit->training_level) + { + case df::animal_training_level::SemiWild: //?? + case df::animal_training_level::Trained: + case df::animal_training_level::WellTrained: + case df::animal_training_level::SkilfullyTrained: + case df::animal_training_level::ExpertlyTrained: + case df::animal_training_level::ExceptionallyTrained: + case df::animal_training_level::MasterfullyTrained: + case df::animal_training_level::Domesticated: + tame=true; + break; + case df::animal_training_level::Unk8: //?? + case df::animal_training_level::WildUntamed: + default: + tame=false; + break; + } + } + return tame; } -bool Units::isSane(df::unit *unit) +bool Units::isTamable(df::unit* unit) { CHECK_NULL_POINTER(unit); + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::PET) + || caste->flags.is_set(caste_raw_flags::PET_EXOTIC); +} - if (isDead(unit) || - isOpposedToLife(unit) || - unit->enemy.undead) - return false; - - if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit)) - return false; - - switch (unit->mood) +// check if creature is domesticated +// seems to be the only way to really tell if it's completely safe to autonestbox it (training can revert) +bool Units::isDomesticated(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + bool tame = false; + if(unit->flags1.bits.tame) { - case mood_type::Melancholy: - case mood_type::Raving: - case mood_type::Berserk: - return false; - default: - break; + switch (unit->training_level) + { + case df::animal_training_level::Domesticated: + tame=true; + break; + default: + tame=false; + break; + } } - - return true; + return tame; } -bool Units::isCitizen(df::unit *unit, bool ignore_sanity) +bool Units::isMarkedForSlaughter(df::unit* unit) { CHECK_NULL_POINTER(unit); + return unit->flags2.bits.slaughter == 1; +} - // Copied from the conditions used to decide game over, - // except that the game appears to let melancholy/raving - // dwarves count as citizens. - - 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 || - unit->flags2.bits.visitor || - unit->flags2.bits.visitor_uninvited || - unit->flags2.bits.underworld || - unit->flags2.bits.resident) - return false; - - if (!ignore_sanity && !isSane(unit)) - return false; - - return isOwnGroup(unit); +bool Units::isGelded(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + auto wounds = unit->body.wounds; + for(auto wound = wounds.begin(); wound != wounds.end(); ++wound) + { + auto parts = (*wound)->parts; + for (auto part = parts.begin(); part != parts.end(); ++part) + { + if ((*part)->flags2.bits.gelded) + return true; + } + } + return false; } -bool Units::isFortControlled(df::unit *unit) -{ // Reverse-engineered from ambushing unit code +bool Units::isEggLayer(df::unit* unit) +{ 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; + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::LAYS_EGGS) + || caste->flags.is_set(caste_raw_flags::LAYS_UNUSUAL_EGGS); } -bool Units::isDwarf(df::unit *unit) +bool Units::isGrazer(df::unit* unit) { CHECK_NULL_POINTER(unit); + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::GRAZER); +} - return unit->race == ui->race_id || - unit->enemy.normal_race == ui->race_id; +bool Units::isMilkable(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + df::creature_raw *raw = world->raws.creatures.all[unit->race]; + df::caste_raw *caste = raw->caste.at(unit->caste); + return caste->flags.is_set(caste_raw_flags::MILKABLE); } -// check for profession "war creature" -bool Units::isWar(df::unit* unit) +bool Units::isForest(df::unit* unit) { CHECK_NULL_POINTER(unit); - return unit->profession == df::profession::TRAINED_WAR - || unit->profession2 == df::profession::TRAINED_WAR; + return unit->flags1.bits.forest == 1; } -// check for profession "hunting creature" -bool Units::isHunter(df::unit* unit) +bool Units::isMischievous(df::unit *unit) { - CHECK_NULL_POINTER(unit) - return unit->profession == df::profession::TRAINED_HUNTER - || unit->profession2 == df::profession::TRAINED_HUNTER; + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.MISCHIEVOUS) + return false; + if (unit->curse.add_tags1.bits.MISCHIEVOUS) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS); } // check if unit is marked as available for adoption @@ -609,154 +583,70 @@ bool Units::isAvailableForAdoption(df::unit* unit) return false; } -// check if creature belongs to the player's civilization -// (don't try to pasture/slaughter random untame animals) -bool Units::isOwnCiv(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->civ_id == ui->civ_id; -} -// check if creature belongs to the player's group -bool Units::isOwnGroup(df::unit* unit) +bool Units::hasExtravision(df::unit *unit) { CHECK_NULL_POINTER(unit); - auto histfig = df::historical_figure::find(unit->hist_figure_id); - if (!histfig) + if (unit->curse.rem_tags1.bits.EXTRAVISION) return false; - for (size_t i = 0; i < histfig->entity_links.size(); i++) - { - auto link = histfig->entity_links[i]; - if (link->entity_id == ui->group_id && link->getType() == df::histfig_entity_link_type::MEMBER) - return true; - } - return false; -} - -// check if creature belongs to the player's race -// (in combination with check for civ helps to filter out own dwarves) -bool Units::isOwnRace(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->race == ui->race_id; -} - -bool Units::isVisible(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return Maps::isTileVisible(unit->pos); + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); } -bool Units::isHidden(df::unit *unit) +bool Units::isOpposedToLife(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)) + if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) return false; - else - return !Maps::isTileVisible(Units::getPosition(unit)); + if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE); } -// get race name by id or unit pointer -string Units::getRaceNameById(int32_t id) -{ - df::creature_raw *raw = world->raws.creatures.all[id]; - if (raw) - return raw->creature_id; - return ""; -} -string Units::getRaceName(df::unit* unit) +bool Units::isBloodsucker(df::unit *unit) { CHECK_NULL_POINTER(unit); - return getRaceNameById(unit->race); + if (unit->curse.rem_tags1.bits.BLOODSUCKER) + return false; + if (unit->curse.add_tags1.bits.BLOODSUCKER) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); } -void df_unit_get_physical_description(df::unit* unit, string* out_str) -{ - static auto* const fn = - reinterpret_cast( - Core::getInstance().vinfo->getAddress("unit_get_physical_description")); - if (fn) - fn(unit, out_str); - else - *out_str = ""; -} -string Units::getPhysicalDescription(df::unit* unit) +bool Units::isDwarf(df::unit *unit) { CHECK_NULL_POINTER(unit); - string str; - df_unit_get_physical_description(unit, &str); - return str; + + return unit->race == ui->race_id || + unit->enemy.normal_race == ui->race_id; } -// get plural of race name (used for display in autobutcher UI and for sorting the watchlist) -string Units::getRaceNamePluralById(int32_t id) +bool Units::isAnimal(df::unit* unit) { - df::creature_raw *raw = world->raws.creatures.all[id]; - if (raw) - return raw->name[1]; // second field is plural of race name - return ""; + CHECK_NULL_POINTER(unit) + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NATURAL_ANIMAL); } -string Units::getRaceNamePlural(df::unit* unit) +bool Units::isMerchant(df::unit* unit) { CHECK_NULL_POINTER(unit); - return getRaceNamePluralById(unit->race); -} -string Units::getRaceBabyNameById(int32_t id) -{ - df::creature_raw *raw = world->raws.creatures.all[id]; - if (raw) - return raw->general_baby_name[0]; - return ""; + return unit->flags1.bits.merchant == 1; } -string Units::getRaceBabyName(df::unit* unit) +bool Units::isDiplomat(df::unit* unit) { CHECK_NULL_POINTER(unit); - return getRaceBabyNameById(unit->race); -} -string Units::getRaceChildNameById(int32_t id) -{ - df::creature_raw *raw = world->raws.creatures.all[id]; - if (raw) - return raw->general_child_name[0]; - return ""; + return unit->flags1.bits.diplomat == 1; } -string Units::getRaceChildName(df::unit* unit) +bool Units::isVisitor(df::unit* unit) { CHECK_NULL_POINTER(unit); - return getRaceChildNameById(unit->race); + return unit->flags2.bits.visitor || unit->flags2.bits.visitor_uninvited; } bool Units::isInvader(df::unit* unit) { @@ -768,90 +658,434 @@ bool Units::isInvader(df::unit* unit) { unit->flags1.bits.active_invader); } -bool Units::isBaby(df::unit* unit) +bool Units::isUndead(df::unit* unit, bool include_vamps) { CHECK_NULL_POINTER(unit); - return unit->profession == df::profession::BABY; + + const auto &cb = unit->curse.add_tags1.bits; + return unit->flags3.bits.ghostly || + ((cb.OPPOSED_TO_LIFE || cb.NOT_LIVING) && (include_vamps || !cb.BLOODSUCKER)); } -bool Units::isChild(df::unit* unit) +bool Units::isNightCreature(df::unit* unit) { CHECK_NULL_POINTER(unit); - return unit->profession == df::profession::CHILD; + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NIGHT_CREATURE); } -bool Units::isAdult(df::unit* unit) +bool Units::isSemiMegabeast(df::unit* unit) { CHECK_NULL_POINTER(unit); - return !isBaby(unit) && !isChild(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::SEMIMEGABEAST); } -bool Units::isAnimal(df::unit* unit) +bool Units::isMegabeast(df::unit* unit) { - CHECK_NULL_POINTER(unit) - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NATURAL_ANIMAL); + CHECK_NULL_POINTER(unit); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::MEGABEAST); } -bool Units::isEggLayer(df::unit* unit) +bool Units::isTitan(df::unit* unit) { CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::LAYS_EGGS) - || caste->flags.is_set(caste_raw_flags::LAYS_UNUSUAL_EGGS); + return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::TITAN); } -bool Units::isGrazer(df::unit* unit) +bool Units::isDemon(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + using namespace df::enums::caste_raw_flags; + const auto &cf = unit->enemy.caste_flags; + return cf.is_set(DEMON) || cf.is_set(UNIQUE_DEMON); +} + +bool Units::isDanger(df::unit* unit) { + CHECK_NULL_POINTER(unit); + return isCrazed(unit) || + isInvader(unit) || + isUndead(unit, true) || + isSemiMegabeast(unit) || + isNightCreature(unit) || + isGreatDanger(unit); +} + +bool Units::isGreatDanger(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return isDemon(unit) || isTitan(unit) || isMegabeast(unit); +} + + + +int32_t Units::getNumUnits() +{ + return world->units.all.size(); +} + +df::unit *Units::getUnit (const int32_t index) +{ + return vector_get(world->units.all, index); +} + +// returns index of creature actually read or -1 if no creature can be found +bool Units::getUnitsInBox (std::vector &units, + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2) +{ + if (!world) + return false; + + units.clear(); + for (df::unit *u : world->units.all) + { + if (isUnitInBox(u, x1, y1, z1, x2, y2, z2)) + { + units.push_back(u); + } + } + return true; +} + +int32_t Units::findIndexById(int32_t creature_id) +{ + return df::unit::binsearch_index(world->units.all, creature_id); +} + +df::coord Units::getPosition(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + if (unit->flags1.bits.caged) + { + auto cage = getContainer(unit); + if (cage) + return Items::getPosition(cage); + } + + return unit->pos; +} + +bool Units::teleport(df::unit *unit, df::coord target_pos) +{ + // make sure source and dest map blocks are valid + auto old_occ = Maps::getTileOccupancy(unit->pos); + auto new_occ = Maps::getTileOccupancy(target_pos); + if (!old_occ || !new_occ) + return false; + + // clear appropriate occupancy flags at old tile + if (unit->flags1.bits.on_ground) + // this is potentially wrong, but the game will recompute this as needed + old_occ->bits.unit_grounded = 0; + else + old_occ->bits.unit = 0; + + // if there's already somebody standing at the destination, then force the + // unit to lay down + if (new_occ->bits.unit) + unit->flags1.bits.on_ground = 1; + + // set appropriate occupancy flags at new tile + if (unit->flags1.bits.on_ground) + new_occ->bits.unit_grounded = 1; + else + new_occ->bits.unit = 1; + + // move unit to destination + unit->pos = target_pos; + unit->idle_area = target_pos; + + // move unit's riders (including babies) to destination + if (unit->flags1.bits.ridden) + { + for (size_t j = 0; j < world->units.other[units_other_id::ANY_RIDER].size(); j++) + { + df::unit *rider = world->units.other[units_other_id::ANY_RIDER][j]; + if (rider->relationship_ids[df::unit_relationship_type::RiderMount] == unit->id) + rider->pos = unit->pos; + } + } + + return true; +} + +df::general_ref *Units::getGeneralRef(df::unit *unit, df::general_ref_type type) +{ + CHECK_NULL_POINTER(unit); + + return findRef(unit->general_refs, type); +} + +df::specific_ref *Units::getSpecificRef(df::unit *unit, df::specific_ref_type type) +{ + CHECK_NULL_POINTER(unit); + + return findRef(unit->specific_refs, type); +} + +df::item *Units::getContainer(df::unit *unit) +{ + CHECK_NULL_POINTER(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) + return df::identity::find(figure->info->reputation->cur_identity); + + return NULL; +} + +df::identity *Units::getIdentity(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + + return getFigureIdentity(figure); +} + +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); + + if (auto identity = getFigureIdentity(figure)) + { + df::historical_figure *id_hfig = NULL; + + switch (identity->type) { + case df::identity_type::None: + case df::identity_type::HidingCurse: + case df::identity_type::FalseIdentity: + case df::identity_type::InfiltrationIdentity: + case df::identity_type::Identity: + break; // We want the nickname to end up in the identity + + case df::identity_type::Impersonating: + case df::identity_type::TrueName: + id_hfig = df::historical_figure::find(identity->histfig_id); + break; + } + + if (id_hfig) + { + Translation::setNickname(&id_hfig->name, nick); + } + else + Translation::setNickname(&identity->name, nick); + } + } +} + +df::language_name *Units::getVisibleName(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + // as of 0.44.11, identity names take precedence over associated histfig names + if (auto identity = getIdentity(unit)) + return &identity->name; + + return &unit->name; +} + +df::nemesis_record *Units::getNemesis(df::unit *unit) +{ + if (!unit) + return NULL; + + for (unsigned i = 0; i < unit->general_refs.size(); i++) + { + df::nemesis_record *rv = unit->general_refs[i]->getNemesis(); + if (rv && rv->unit == unit) + return rv; + } + + return NULL; +} + + +int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr) +{ + auto &aobj = unit->body.physical_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return std::max(0, value); +} + +int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) +{ + auto soul = unit->status.current_soul; + if (!soul) return 0; + + auto &aobj = soul->mental_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return std::max(0, value); +} + +bool Units::casteFlagSet(int race, int caste, df::caste_raw_flags flag) +{ + auto creature = df::creature_raw::find(race); + if (!creature) + return false; + + auto craw = vector_get(creature->caste, caste); + if (!craw) + return false; + + return craw->flags.is_set(flag); +} + +df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) +{ + CHECK_NULL_POINTER(unit); + + auto &vec = unit->status.misc_traits; + for (size_t i = 0; i < vec.size(); i++) + if (vec[i]->id == type) + return vec[i]; + + if (create) + { + auto obj = new df::unit_misc_trait(); + obj->id = type; + vec.push_back(obj); + return obj; + } + + return NULL; +} + +// get race name by id or unit pointer +string Units::getRaceNameById(int32_t id) +{ + df::creature_raw *raw = world->raws.creatures.all[id]; + if (raw) + return raw->creature_id; + return ""; +} +string Units::getRaceName(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + return getRaceNameById(unit->race); +} + +void df_unit_get_physical_description(df::unit* unit, string* out_str) +{ + static auto* const fn = + reinterpret_cast( + Core::getInstance().vinfo->getAddress("unit_get_physical_description")); + if (fn) + fn(unit, out_str); + else + *out_str = ""; +} + +string Units::getPhysicalDescription(df::unit* unit) { CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::GRAZER); + string str; + df_unit_get_physical_description(unit, &str); + return str; } -bool Units::isMilkable(df::unit* unit) +// get plural of race name (used for display in autobutcher UI and for sorting the watchlist) +string Units::getRaceNamePluralById(int32_t id) { - CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::MILKABLE); + df::creature_raw *raw = world->raws.creatures.all[id]; + if (raw) + return raw->name[1]; // second field is plural of race name + return ""; } -bool Units::isTrainableWar(df::unit* unit) +string Units::getRaceNamePlural(df::unit* unit) { CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::TRAINABLE_WAR); + return getRaceNamePluralById(unit->race); } -bool Units::isTrainableHunting(df::unit* unit) +string Units::getRaceBabyNameById(int32_t id) { - CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::TRAINABLE_HUNTING); + df::creature_raw *raw = world->raws.creatures.all[id]; + if (raw) + return raw->general_baby_name[0]; + return ""; } -bool Units::isTamable(df::unit* unit) +string Units::getRaceBabyName(df::unit* unit) { CHECK_NULL_POINTER(unit); - df::creature_raw *raw = world->raws.creatures.all[unit->race]; - df::caste_raw *caste = raw->caste.at(unit->caste); - return caste->flags.is_set(caste_raw_flags::PET) - || caste->flags.is_set(caste_raw_flags::PET_EXOTIC); + return getRaceBabyNameById(unit->race); } -bool Units::isMale(df::unit* unit) +string Units::getRaceChildNameById(int32_t id) { - CHECK_NULL_POINTER(unit); - return unit->sex == 1; + df::creature_raw *raw = world->raws.creatures.all[id]; + if (raw) + return raw->general_child_name[0]; + return ""; } -bool Units::isFemale(df::unit* unit) +string Units::getRaceChildName(df::unit* unit) { CHECK_NULL_POINTER(unit); - return unit->sex == 0; + return getRaceChildNameById(unit->race); } @@ -1682,232 +1916,6 @@ df::activity_event *Units::getMainSocialEvent(df::unit *unit) return entry->events[entry->events.size() - 1]; } -bool Units::isVisiting(df::unit* unit) { - CHECK_NULL_POINTER(unit); - - return unit->flags1.bits.merchant || - unit->flags1.bits.diplomat || - unit->flags2.bits.visitor || - unit->flags2.bits.visitor_uninvited; -} - -bool Units::isMerchant(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - - return unit->flags1.bits.merchant == 1; -} - -bool Units::isDiplomat(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - - return unit->flags1.bits.diplomat == 1; -} - -bool Units::isVisitor(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->flags2.bits.visitor || unit->flags2.bits.visitor_uninvited; -} - -bool Units::isForest(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->flags1.bits.forest == 1; -} - -bool Units::isMarkedForSlaughter(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->flags2.bits.slaughter == 1; -} - -bool Units::isTame(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - bool tame = false; - if(unit->flags1.bits.tame) - { - switch (unit->training_level) - { - case df::animal_training_level::SemiWild: //?? - case df::animal_training_level::Trained: - case df::animal_training_level::WellTrained: - case df::animal_training_level::SkilfullyTrained: - case df::animal_training_level::ExpertlyTrained: - case df::animal_training_level::ExceptionallyTrained: - case df::animal_training_level::MasterfullyTrained: - case df::animal_training_level::Domesticated: - tame=true; - break; - case df::animal_training_level::Unk8: //?? - case df::animal_training_level::WildUntamed: - default: - tame=false; - break; - } - } - return tame; -} - -bool Units::isTrained(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - // case a: trained for war/hunting (those don't have a training level, strangely) - if(Units::isWar(unit) || Units::isHunter(unit)) - return true; - - // case b: tamed and trained wild creature, gets a training level - bool trained = false; - switch (unit->training_level) - { - case df::animal_training_level::Trained: - case df::animal_training_level::WellTrained: - case df::animal_training_level::SkilfullyTrained: - case df::animal_training_level::ExpertlyTrained: - case df::animal_training_level::ExceptionallyTrained: - case df::animal_training_level::MasterfullyTrained: - //case df::animal_training_level::Domesticated: - trained = true; - break; - default: - break; - } - return trained; -} - -bool Units::isGay(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - if (!unit->status.current_soul) - return false; - df::orientation_flags orientation = unit->status.current_soul->orientation_flags; - return (!Units::isFemale(unit) || !(orientation.whole & (orientation.mask_marry_male | orientation.mask_romance_male))) - && (!Units::isMale(unit) || !(orientation.whole & (orientation.mask_marry_female | orientation.mask_romance_female))); -} - -bool Units::isNaked(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - // TODO(kazimuth): is this correct? - return (unit->inventory.empty()); -} - -bool Units::isUndead(df::unit* unit, bool include_vamps) -{ - CHECK_NULL_POINTER(unit); - - const auto &cb = unit->curse.add_tags1.bits; - return unit->flags3.bits.ghostly || - ((cb.OPPOSED_TO_LIFE || cb.NOT_LIVING) && (include_vamps || !cb.BLOODSUCKER)); -} - -bool Units::isGhost(df::unit *unit) -{ - CHECK_NULL_POINTER(unit); - - return unit->flags3.bits.ghostly; -} - -bool Units::isActive(df::unit *unit) -{ - CHECK_NULL_POINTER(unit); - - return !unit->flags1.bits.inactive; -} - -bool Units::isKilled(df::unit *unit) -{ - CHECK_NULL_POINTER(unit); - - return unit->flags2.bits.killed; -} - -bool Units::isGelded(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - auto wounds = unit->body.wounds; - for(auto wound = wounds.begin(); wound != wounds.end(); ++wound) - { - auto parts = (*wound)->parts; - for (auto part = parts.begin(); part != parts.end(); ++part) - { - if ((*part)->flags2.bits.gelded) - return true; - } - } - return false; -} - -// check if creature is domesticated -// seems to be the only way to really tell if it's completely safe to autonestbox it (training can revert) -bool Units::isDomesticated(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - bool tame = false; - if(unit->flags1.bits.tame) - { - switch (unit->training_level) - { - case df::animal_training_level::Domesticated: - tame=true; - break; - default: - tame=false; - break; - } - } - return tame; -} - -bool Units::isDemon(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - using namespace df::enums::caste_raw_flags; - const auto &cf = unit->enemy.caste_flags; - return cf.is_set(DEMON) || cf.is_set(UNIQUE_DEMON); -} - -bool Units::isTitan(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::TITAN); -} - -bool Units::isMegabeast(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::MEGABEAST); -} - -bool Units::isGreatDanger(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return isDemon(unit) || isTitan(unit) || isMegabeast(unit); -} - -bool Units::isSemiMegabeast(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::SEMIMEGABEAST); -} - -bool Units::isNightCreature(df::unit* unit) -{ - CHECK_NULL_POINTER(unit); - return unit->enemy.caste_flags.is_set(df::enums::caste_raw_flags::NIGHT_CREATURE); -} - -bool Units::isDanger(df::unit* unit) { - CHECK_NULL_POINTER(unit); - return isInvader(unit) || - isUndead(unit, true) || - isSemiMegabeast(unit) || - isNightCreature(unit) || - isGreatDanger(unit); -} - // 50000 and up is level 0, 25000 and up is level 1, etc. const vector Units::stress_cutoffs {50000, 25000, 10000, -10000, -25000, -50000, -100000}; From 7e1bdb53026d7869471e80566fc79283914346e8 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 17:05:11 -0800 Subject: [PATCH 08/13] Matches Units.h ordering in LuaApi.cpp --- library/LuaApi.cpp | 112 ++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b5a1cd754..ed59786a6 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1560,6 +1560,62 @@ static const luaL_Reg dfhack_job_funcs[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isUnitInBox), + WRAPM(Units, isActive), + WRAPM(Units, isVisible), + WRAPM(Units, isCitizen), + WRAPM(Units, isFortControlled), + WRAPM(Units, isOwnCiv), + WRAPM(Units, isOwnGroup), + WRAPM(Units, isOwnRace), + WRAPM(Units, isAlive), + WRAPM(Units, isDead), + WRAPM(Units, isKilled), + WRAPM(Units, isSane), + WRAPM(Units, isCrazed), + WRAPM(Units, isGhost), + WRAPM(Units, isHidden), + WRAPM(Units, isHidingCurse), + WRAPM(Units, isMale), + WRAPM(Units, isFemale), + WRAPM(Units, isBaby), + WRAPM(Units, isChild), + WRAPM(Units, isAdult), + WRAPM(Units, isGay), + WRAPM(Units, isNaked), + WRAPM(Units, isVisiting), + WRAPM(Units, isTrainableHunting), + WRAPM(Units, isTrainableWar), + WRAPM(Units, isTrained), + WRAPM(Units, isHunter), + WRAPM(Units, isWar), + WRAPM(Units, isTame), + WRAPM(Units, isTamable), + WRAPM(Units, isDomesticated), + WRAPM(Units, isMarkedForSlaughter), + WRAPM(Units, isGelded), + WRAPM(Units, isEggLayer), + WRAPM(Units, isGrazer), + WRAPM(Units, isMilkable), + WRAPM(Units, isForest), + WRAPM(Units, isMischievous), + WRAPM(Units, isAvailableForAdoption), + WRAPM(Units, hasExtravision), + WRAPM(Units, isOpposedToLife), + WRAPM(Units, isBloodsucker), + WRAPM(Units, isDwarf), + WRAPM(Units, isAnimal), + WRAPM(Units, isMerchant), + WRAPM(Units, isDiplomat), + WRAPM(Units, isVisitor), + WRAPM(Units, isInvader), + WRAPM(Units, isUndead), + WRAPM(Units, isNightCreature), + WRAPM(Units, isSemiMegabeast), + WRAPM(Units, isMegabeast), + WRAPM(Units, isTitan), + WRAPM(Units, isDemon), + WRAPM(Units, isDanger), + WRAPM(Units, isGreatDanger), WRAPM(Units, teleport), WRAPM(Units, getGeneralRef), WRAPM(Units, getSpecificRef), @@ -1568,23 +1624,9 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getVisibleName), WRAPM(Units, getIdentity), WRAPM(Units, getNemesis), - WRAPM(Units, isHidingCurse), WRAPM(Units, getPhysicalAttrValue), WRAPM(Units, getMentalAttrValue), - WRAPM(Units, isCrazed), - WRAPM(Units, isOpposedToLife), - WRAPM(Units, hasExtravision), - WRAPM(Units, isBloodsucker), - WRAPM(Units, isMischievous), WRAPM(Units, getMiscTrait), - WRAPM(Units, isDead), - WRAPM(Units, isAlive), - 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), @@ -1602,12 +1644,6 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getGoalName), WRAPM(Units, isGoalAchieved), WRAPM(Units, getSquadName), - WRAPM(Units, isWar), - WRAPM(Units, isHunter), - WRAPM(Units, isAvailableForAdoption), - WRAPM(Units, isOwnCiv), - WRAPM(Units, isOwnGroup), - WRAPM(Units, isOwnRace), WRAPM(Units, getPhysicalDescription), WRAPM(Units, getRaceName), WRAPM(Units, getRaceNamePlural), @@ -1616,42 +1652,6 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getRaceBabyNameById), WRAPM(Units, getRaceChildName), WRAPM(Units, getRaceChildNameById), - WRAPM(Units, isInvader), - WRAPM(Units, isBaby), - WRAPM(Units, isChild), - WRAPM(Units, isAdult), - WRAPM(Units, isAnimal), - WRAPM(Units, isEggLayer), - WRAPM(Units, isGrazer), - WRAPM(Units, isMilkable), - WRAPM(Units, isTrainableWar), - WRAPM(Units, isTrainableHunting), - WRAPM(Units, isTamable), - WRAPM(Units, isMale), - WRAPM(Units, isFemale), - WRAPM(Units, isVisiting), - WRAPM(Units, isMerchant), - WRAPM(Units, isDiplomat), - WRAPM(Units, isVisitor), - WRAPM(Units, isForest), - WRAPM(Units, isMarkedForSlaughter), - WRAPM(Units, isTame), - WRAPM(Units, isTrained), - WRAPM(Units, isGay), - WRAPM(Units, isNaked), - WRAPM(Units, isUndead), - WRAPM(Units, isGhost), - WRAPM(Units, isActive), - WRAPM(Units, isKilled), - WRAPM(Units, isGelded), - WRAPM(Units, isDomesticated), - WRAPM(Units, isDemon), - WRAPM(Units, isTitan), - WRAPM(Units, isMegabeast), - WRAPM(Units, isGreatDanger), - WRAPM(Units, isSemiMegabeast), - WRAPM(Units, isNightCreature), - WRAPM(Units, isDanger), WRAPM(Units, getMainSocialActivity), WRAPM(Units, getMainSocialEvent), WRAPM(Units, getStressCategory), From 441ff8a820912170ab526ededb8f60a8d0f7a773 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 17:55:17 -0800 Subject: [PATCH 09/13] Fixes Lua API.rst mistakes --- docs/Lua API.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 059f28d37..fc750f077 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1397,11 +1397,11 @@ Units module * ``dfhack.units.isNightCreature(unit)`` - The unit is undead, but not a vampire. + The unit is a Night Creature. * ``dfhack.units.isSemiMegabeast(unit)`` - The unit is undead, but not a vampire. + The unit is a semi-megabeast. * ``dfhack.units.isMegabeast(unit)`` From 52ce09804b35ae096c4dd542dd45a75366bfd0a5 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 12 Nov 2022 19:07:16 -0800 Subject: [PATCH 10/13] Apply suggestions from code review --- docs/Lua API.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fc750f077..1af3335df 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1285,7 +1285,7 @@ Units module The unit is gay. -* ``dfhack.units.isNake(unit)`` +* ``dfhack.units.isNaked(unit)`` The unit is naked. From 78021ec672bddf599b6a9567c02f64fbc45ddd74 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 13 Nov 2022 11:35:01 -0800 Subject: [PATCH 11/13] Adds indentation --- library/include/modules/Units.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index aa5edcf8c..9cfe7d3c5 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -69,8 +69,8 @@ static const int MAX_COLORS = 15; */ DFHACK_EXPORT bool isUnitInBox(df::unit* u, -int16_t x1, int16_t y1, int16_t z1, -int16_t x2, int16_t y2, int16_t z2); + int16_t x1, int16_t y1, int16_t z1, + int16_t x2, int16_t y2, int16_t z2); DFHACK_EXPORT bool isActive(df::unit *unit); DFHACK_EXPORT bool isVisible(df::unit* unit); From c164263af9f1ac2a7c142b74f2ce3ec2465ff74b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 13 Nov 2022 11:35:20 -0800 Subject: [PATCH 12/13] Groups sections of functions in lua api docs --- docs/Lua API.rst | 65 +++--------------------------------------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 1af3335df..b2d976ec2 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1262,32 +1262,14 @@ Units module * ``dfhack.units.isMale(unit)`` - - The unit is male. - * ``dfhack.units.isFemale(unit)`` - - The unit is female. - * ``dfhack.units.isBaby(unit)`` - - The unit is a baby. - * ``dfhack.units.isChild(unit)`` - - The unit is a child. - * ``dfhack.units.isAdult(unit)`` - - The unit is an adult. - * ``dfhack.units.isGay(unit)`` - - The unit is gay. - * ``dfhack.units.isNaked(unit)`` - The unit is naked. + Simple unit property checks * ``dfhack.units.isVisiting(unit)`` @@ -1315,36 +1297,15 @@ Units module The unit is trained for war. * ``dfhack.units.isTame(unit)`` - - The unit is tame. - * ``dfhack.units.isTamable(unit)`` - - The unit is tamable. - * ``dfhack.units.isDomesticated(unit)`` - - The unit is domesticated. - * ``dfhack.units.isMarkedForSlaughter(unit)`` - - The unit is marked for slaughter. - * ``dfhack.units.isGelded(unit)`` - - The unit is gelded. - * ``dfhack.units.isEggLayer(unit)`` - - The unit is an egg layer. - * ``dfhack.units.isGrazer(unit)`` - - The unit is a grazer. - * ``dfhack.units.isMilkable(unit)`` - The unit is milkable. + Simple unit property checks. * ``dfhack.units.isForest(unit)`` @@ -1371,16 +1332,10 @@ Units module The unit is of the correct race for the fortress. * ``dfhack.units.isAnimal(unit)`` - - The unit is an animal. - * ``dfhack.units.isMerchant(unit)`` - - The unit is a merchant. - * ``dfhack.units.isDiplomat(unit)`` - The unit is a diplomat. + Simple unit type checks. * ``dfhack.units.isVisitor(unit)`` @@ -1396,24 +1351,12 @@ Units module count vampires as undead. * ``dfhack.units.isNightCreature(unit)`` - - The unit is a Night Creature. - * ``dfhack.units.isSemiMegabeast(unit)`` - - The unit is a semi-megabeast. - * ``dfhack.units.isMegabeast(unit)`` - - The unit is a megabeast. - * ``dfhack.units.isTitan(unit)`` - - The unit is a titan. - * ``dfhack.units.isDemon(unit)`` - The unit is a demon. + Simple enemy type checks. * ``dfhack.units.isDanger(unit)`` From aa5c6515e001afd77df3547b1f7ea6028ff28291 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 13 Nov 2022 11:39:20 -0800 Subject: [PATCH 13/13] Removes rogue indent --- library/include/modules/Units.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 9cfe7d3c5..be630b802 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -170,7 +170,7 @@ DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_ty DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr); DFHACK_EXPORT bool casteFlagSet(int race, int caste, df::caste_raw_flags flag); - DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); +DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit);