Finish the effective skill computation function, and move to core.

develop
Alexander Gavrilov 2012-09-09 12:27:40 +04:00
parent 94b729579e
commit a36fe25e72
7 changed files with 209 additions and 83 deletions

@ -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*.

@ -1106,6 +1106,18 @@ a lua list containing them.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNemesis(unit)</tt></p>
<p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isCrazed(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isOpposedToLife(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.hasExtravision(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isBloodsucker(unit)</tt></p>
<p>Simple checks of caste attributes that can be modified by curses.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getMiscTrait(unit, type[, create])</tt></p>
<p>Finds (or creates if requested) a misc trait object with the given id.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p>
<p>The unit is completely dead and passive, or a ghost.</p>
</li>
@ -1126,6 +1138,9 @@ same checks the game uses to decide game-over by extinction.</p>
<p>Returns the age of the unit in years as a floating-point value.
If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p>
<p>Returns a list of tables describing noble position assignments, or <em>nil</em>.
Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p>

@ -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 <lua.h>
#include <lauxlib.h>
@ -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),

@ -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;

@ -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<df::job_skill,int16_t> 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)

@ -1 +1 @@
Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36
Subproject commit db765a65b17099dbec115812b40a19b46ad59431

@ -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<df::job_skill,int16_t> 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;