From a36fe25e7249c60094a6347726f961779cf3b98a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 12:27:40 +0400 Subject: [PATCH] Finish the effective skill computation function, and move to core. --- LUA_API.rst | 15 +++ Lua API.html | 15 +++ library/LuaApi.cpp | 7 ++ library/include/modules/Units.h | 12 +++ library/modules/Units.cpp | 159 +++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/devel/siege-engine.cpp | 82 +--------------- 7 files changed, 209 insertions(+), 83 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 4d9170d6e..48d2c19e6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -868,6 +868,17 @@ Units module Returns the nemesis record of the unit if it has one, or *nil*. +* ``dfhack.units.isCrazed(unit)`` +* ``dfhack.units.isOpposedToLife(unit)`` +* ``dfhack.units.hasExtravision(unit)`` +* ``dfhack.units.isBloodsucker(unit)`` + + Simple checks of caste attributes that can be modified by curses. + +* ``dfhack.units.getMiscTrait(unit, type[, create])`` + + Finds (or creates if requested) a misc trait object with the given id. + * ``dfhack.units.isDead(unit)`` The unit is completely dead and passive, or a ghost. @@ -894,6 +905,10 @@ Units module Returns the age of the unit in years as a floating-point value. If ``true_age`` is true, ignores false identities. +* ``dfhack.units.getEffectiveSkill(unit, skill)`` + + Computes the effective rating for the given skill, taking into account exhaustion, pain etc. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/Lua API.html b/Lua API.html index dc9c8d73e..c302c29f7 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1106,6 +1106,18 @@ a lua list containing them.

  • dfhack.units.getNemesis(unit)

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

  • +
  • dfhack.units.isCrazed(unit)

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

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

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

    +

    Simple checks of caste attributes that can be modified by curses.

    +
  • +
  • dfhack.units.getMiscTrait(unit, type[, create])

    +

    Finds (or creates if requested) a misc trait object with the given id.

    +
  • dfhack.units.isDead(unit)

    The unit is completely dead and passive, or a ghost.

  • @@ -1126,6 +1138,9 @@ same checks the game uses to decide game-over by extinction.

    Returns the age of the unit in years as a floating-point value. If true_age is true, ignores false identities.

    +
  • dfhack.units.getEffectiveSkill(unit, skill)

    +

    Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

    +
  • dfhack.units.getNoblePositions(unit)

    Returns a list of tables describing noble position assignments, or nil. Every table has fields entity, assignment and position.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 807cbf539..6caf45575 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -79,6 +79,7 @@ distribution. #include "df/building_civzonest.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/unit_misc_trait.h" #include #include @@ -813,12 +814,18 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getVisibleName), WRAPM(Units, getIdentity), WRAPM(Units, getNemesis), + WRAPM(Units, isCrazed), + WRAPM(Units, isOpposedToLife), + WRAPM(Units, hasExtravision), + WRAPM(Units, isBloodsucker), + WRAPM(Units, getMiscTrait), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), WRAPM(Units, getAge), + WRAPM(Units, getEffectiveSkill), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 9003dc3af..ece151127 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -32,6 +32,8 @@ distribution. #include "modules/Items.h" #include "DataDefs.h" #include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/job_skill.h" namespace df { @@ -41,6 +43,7 @@ namespace df struct historical_entity; struct entity_position_assignment; struct entity_position; + struct unit_misc_trait; } /** @@ -208,6 +211,13 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); +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 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); @@ -216,6 +226,8 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); +DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); + struct NoblePosition { df::historical_entity *entity; df::entity_position_assignment *assignment; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 874dabc3d..6a672b585 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -63,11 +63,15 @@ using namespace std; #include "df/burrow.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "df/game_mode.h" +#include "df/unit_misc_trait.h" +#include "df/unit_skill.h" using namespace DFHack; using namespace df::enums; using df::global::world; using df::global::ui; +using df::global::gamemode; bool Units::isValid() { @@ -626,8 +630,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) return craw->flags.is_set(flag); } -static bool isCrazed(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) @@ -637,13 +642,54 @@ static bool isCrazed(df::unit *unit) return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); } -static bool isOpposedToLife(df::unit *unit) +bool Units::isOpposedToLife(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::CANNOT_UNDEAD); + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE); +} + +bool Units::hasExtravision(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.EXTRAVISION) + return false; + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); +} + +bool Units::isBloodsucker(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); +} + +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; } bool DFHack::Units::isDead(df::unit *unit) @@ -753,6 +799,113 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age) return cur_time - birth_time; } +inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) +{ + if (is_adventure) + { + if (value >= adv1_2) rating >>= 1; + else if (value >= adv3_4) rating = rating*3/4; + else if (value >= adv9_10) rating = rating*9/10; + } + else + { + if (value >= dwarf1_2) rating >>= 1; + else if (value >= dwarf3_4) return rating*3/4; + } +} + +int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + CHECK_NULL_POINTER(unit); + + /* + * This is 100% reverse-engineered from DF code. + */ + + if (!unit->status.current_soul) + return 0; + + // Retrieve skill from unit soul: + + df::enum_field key(skill_id); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + + int rating = 0; + if (skill) + rating = std::max(0, int(skill->rating) - skill->rusty); + + // Apply special states + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) rating >>= 1; + if (unit->counters.winded > 0) rating >>= 1; + if (unit->counters.stunned > 0) rating >>= 1; + if (unit->counters.dizziness > 0) rating >>= 1; + if (unit->counters2.fever > 0) rating >>= 1; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && + !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && + !hasExtravision(unit)) + { + rating >>= 2; + } + if (unit->counters.pain >= 100 && unit->mood == -1) + { + rating >>= 1; + } + if (unit->counters2.exhaustion >= 2000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 4000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 6000) + rating = rating*3/4; + } + } + } + + // Hunger etc timers + + bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE); + + if (!unit->flags3.bits.scuttle && isBloodsucker(unit)) + { + using namespace df::enums::misc_trait_type; + + if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood)) + { + adjust_skill_rating( + rating, is_adventure, trait->value, + 302400, 403200, // dwf 3/4; 1/2 + 1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2 + ); + } + } + + adjust_skill_rating( + rating, is_adventure, unit->counters2.thirst_timer, + 50000, 50000, 115200, 172800, 345600 + ); + adjust_skill_rating( + rating, is_adventure, unit->counters2.hunger_timer, + 75000, 75000, 172800, 1209600, 2592000 + ); + if (is_adventure && unit->counters2.sleepiness_timer >= 846000) + rating >>= 2; + else + adjust_skill_rating( + rating, is_adventure, unit->counters2.sleepiness_timer, + 150000, 150000, 172800, 259200, 345600 + ); + + return rating; +} + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/library/xml b/library/xml index 18e76d8bd..db765a65b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36 +Subproject commit db765a65b17099dbec115812b40a19b46ad59431 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 60035b276..8b5010194 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -501,82 +501,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -static bool 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); -} - -static bool hasExtravision(df::unit *unit) -{ - if (unit->curse.rem_tags1.bits.EXTRAVISION) - return false; - if (unit->curse.add_tags1.bits.EXTRAVISION) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); -} - -int getEffectiveSkill(df::unit *unit, df::job_skill skill_id) -{ - CHECK_NULL_POINTER(unit); - - if (!unit->status.current_soul) - return 0; - - int rating = 0; - - df::enum_field key(skill_id); - auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); - if (skill) - rating = std::max(0, int(skill->rating) - skill->rusty); - - if (unit->counters.soldier_mood == df::unit::T_counters::None) - { - if (unit->counters.nausea > 0) rating >>= 1; - if (unit->counters.winded > 0) rating >>= 1; - if (unit->counters.stunned > 0) rating >>= 1; - if (unit->counters.dizziness > 0) rating >>= 1; - if (unit->counters2.fever > 0) rating >>= 1; - } - - if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) - { - if (unit->counters.pain >= 100 && unit->mood == -1) rating >>= 1; - - if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && - !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && - !hasExtravision(unit)) - { - rating >>= 2; - } - if (unit->counters2.exhaustion >= 2000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 4000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 6000) - rating = rating*3/4; - } - } - } - - // TODO: bloodsucker; advmode - - if (unit->counters2.thirst_timer >= 50000) rating >>= 1; - if (unit->counters2.hunger_timer >= 75000) rating >>= 1; - if (unit->counters2.sleepiness_timer >= 150000) rating >>= 1; - - return rating; -} - static int getAttrValue(const df::unit_attribute &attr) { return std::max(0, attr.value - attr.soft_demotion); @@ -635,7 +559,7 @@ int getSpeedRating(df::unit *unit) speed *= 2; if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) { - int skill = getEffectiveSkill(unit, job_skill::SWIMMING); + int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); if (skill > 1) skill = skill * std::max(6, 21-skill) / 20; } @@ -693,7 +617,7 @@ int getSpeedRating(df::unit *unit) speed += 2000; else if (unit->flags3.bits.on_crutch) { - int skill = getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); speed += 2000 - 100*std::min(20, skill); } @@ -739,7 +663,7 @@ int getSpeedRating(df::unit *unit) if (unit->mood == mood_type::Melancholy) speed += 8000; // Inventory encumberance - int armor_skill = getEffectiveSkill(unit, job_skill::ARMOR); + int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); armor_skill = std::min(15, armor_skill); int inv_weight = 0, inv_weight_fraction = 0;