Clean up the movement speed calculation function and move into the core.

develop
Alexander Gavrilov 2012-09-09 17:04:58 +04:00
parent ec3d489bda
commit 9679b7729c
7 changed files with 289 additions and 181 deletions

@ -918,6 +918,10 @@ Units module
Computes the effective rating for the given skill, taking into account exhaustion, pain etc. Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
* ``dfhack.units.computeMovementSpeed(unit)``
Computes number of frames * 100 it takes the unit to move in its current state of mind and body.
* ``dfhack.units.getNoblePositions(unit)`` * ``dfhack.units.getNoblePositions(unit)``
Returns a list of tables describing noble position assignments, or *nil*. Returns a list of tables describing noble position assignments, or *nil*.

@ -1149,6 +1149,9 @@ If <tt class="docutils literal">true_age</tt> is true, ignores false identities.
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p> <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> <p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p> <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>. <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> Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p>

@ -818,6 +818,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isOpposedToLife), WRAPM(Units, isOpposedToLife),
WRAPM(Units, hasExtravision), WRAPM(Units, hasExtravision),
WRAPM(Units, isBloodsucker), WRAPM(Units, isBloodsucker),
WRAPM(Units, isMischievous),
WRAPM(Units, getMiscTrait), WRAPM(Units, getMiscTrait),
WRAPM(Units, isDead), WRAPM(Units, isDead),
WRAPM(Units, isAlive), WRAPM(Units, isAlive),
@ -826,6 +827,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isCitizen), WRAPM(Units, isCitizen),
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor), WRAPM(Units, getProfessionColor),

@ -221,6 +221,7 @@ DFHACK_EXPORT bool isCrazed(df::unit *unit);
DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit);
DFHACK_EXPORT bool hasExtravision(df::unit *unit); DFHACK_EXPORT bool hasExtravision(df::unit *unit);
DFHACK_EXPORT bool isBloodsucker(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 df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false);
@ -233,6 +234,7 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {
df::historical_entity *entity; df::historical_entity *entity;

@ -637,7 +637,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr
if (auto mod = unit->curse.attr_change) if (auto mod = unit->curse.attr_change)
{ {
int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr];
if (isHidingCurse(unit)) if (isHidingCurse(unit))
value = std::min(value, mvalue); value = std::min(value, mvalue);
@ -645,7 +645,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr
value = mvalue; value = mvalue;
} }
return value; return std::max(0, value);
} }
int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
@ -658,7 +658,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
if (auto mod = unit->curse.attr_change) if (auto mod = unit->curse.attr_change)
{ {
int mvalue = (value * mod->ment_att_perc[attr]) + mod->ment_att_add[attr]; int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr];
if (isHidingCurse(unit)) if (isHidingCurse(unit))
value = std::min(value, mvalue); value = std::min(value, mvalue);
@ -666,7 +666,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
value = mvalue; value = mvalue;
} }
return value; return std::max(0, value);
} }
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
@ -724,6 +724,16 @@ bool Units::isBloodsucker(df::unit *unit)
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER);
} }
bool Units::isMischievous(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::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
@ -851,7 +861,7 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age)
return cur_time - birth_time; 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) inline void 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 (is_adventure)
{ {
@ -862,7 +872,7 @@ inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dw
else else
{ {
if (value >= dwarf1_2) rating >>= 1; if (value >= dwarf1_2) rating >>= 1;
else if (value >= dwarf3_4) return rating*3/4; else if (value >= dwarf3_4) rating = rating*3/4;
} }
} }
@ -958,6 +968,264 @@ int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
return rating; return rating;
} }
inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200)
{
if (is_adventure)
{
if (value >= adv200) rating += 200;
else if (value >= adv100) rating += 100;
else if (value >= adv75) rating += 75;
else if (value >= adv50) rating += 50;
}
else
{
if (value >= dwarf200) rating += 200;
else if (value >= dwarf100) rating += 100;
}
}
static int calcInventoryWeight(df::unit *unit)
{
int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR);
int armor_mul = 15 - std::min(15, armor_skill);
int inv_weight = 0, inv_weight_fraction = 0;
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i]->item;
if (!item->flags.bits.weight_computed)
continue;
int wval = item->weight;
int wfval = item->weight_fraction;
auto mode = unit->inventory[i]->mode;
if ((mode == df::unit_inventory_item::Worn ||
mode == df::unit_inventory_item::WrappedAround) &&
item->isArmor() && armor_skill > 1)
{
wval = wval * armor_mul / 16;
wfval = wfval * armor_mul / 16;
}
inv_weight += wval;
inv_weight_fraction += wfval;
}
return inv_weight*100 + inv_weight_fraction/10000;
}
int Units::computeMovementSpeed(df::unit *unit)
{
using namespace df::enums::physical_attribute_type;
/*
* Pure reverse-engineered computation of unit _slowness_,
* i.e. number of ticks to move * 100.
*/
// Base speed
auto creature = df::creature_raw::find(unit->race);
if (!creature)
return 0;
auto craw = vector_get(creature->caste, unit->caste);
if (!craw)
return 0;
int speed = craw->misc.speed;
if (unit->flags3.bits.ghostly)
return speed;
// Curse multiplier
if (unit->curse.speed_mul_percent != 100)
{
speed *= 100;
if (unit->curse.speed_mul_percent != 0)
speed /= unit->curse.speed_mul_percent;
}
speed += unit->curse.speed_add;
// Swimming
auto cur_liquid = unit->status2.liquid_type.bits.liquid_type;
bool in_magma = (cur_liquid == tile_liquid::Magma);
if (unit->flags2.bits.swimming)
{
speed = craw->misc.swim_speed;
if (in_magma)
speed *= 2;
if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
// Originally a switch:
if (skill > 1)
speed = speed * std::max(6, 21-skill) / 20;
}
}
else
{
int delta = 150*unit->status2.liquid_depth;
if (in_magma)
delta *= 2;
speed += delta;
}
// General counters and flags
if (unit->profession == profession::BABY)
speed += 3000;
if (unit->flags3.bits.unk15)
speed /= 20;
if (unit->counters2.exhaustion >= 2000)
{
speed += 200;
if (unit->counters2.exhaustion >= 4000)
{
speed += 200;
if (unit->counters2.exhaustion >= 6000)
speed += 200;
}
}
if (unit->flags2.bits.gutted) speed += 2000;
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) speed += 1000;
if (unit->counters.winded > 0) speed += 1000;
if (unit->counters.stunned > 0) speed += 1000;
if (unit->counters.dizziness > 0) speed += 1000;
if (unit->counters2.fever > 0) speed += 1000;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (unit->counters.pain >= 100 && unit->mood == -1)
speed += 1000;
}
// Hunger etc timers
bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit))
{
using namespace df::enums::misc_trait_type;
if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood))
{
adjust_speed_rating(
speed, is_adventure, trait->value,
302400, 403200, // dwf 100; 200
1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200
);
}
}
adjust_speed_rating(
speed, is_adventure, unit->counters2.thirst_timer,
50000, 0x7fffffff, 172800, 172800, 172800, 345600
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.hunger_timer,
75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.sleepiness_timer,
57600, 150000, 172800, 259200, 345600, 864000
);
// Activity state
if (unit->relations.draggee_id != -1) speed += 1000;
if (unit->flags1.bits.on_ground)
speed += 2000;
else if (unit->flags3.bits.on_crutch)
{
int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK);
speed += 2000 - 100*std::min(20, skill);
}
if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK);
speed += 2000 - 100*std::min(20, skill);
}
if (unsigned(unit->counters2.paralysis-1) <= 98)
speed += unit->counters2.paralysis*10;
if (unsigned(unit->counters.webbed-1) <= 8)
speed += unit->counters.webbed*100;
// Muscle weight vs vascular tissue (?)
auto &attr_tissue = unit->body.physical_attr_tissues;
int muscle = attr_tissue[STRENGTH];
int blood = attr_tissue[AGILITY];
speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood)));
// Attributes
int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH);
int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY);
int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr));
speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600;
// Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
{
// WTF
int as = unit->status2.able_stand;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;
}
// Mood
if (unit->mood == mood_type::Melancholy) speed += 8000;
// Inventory encumberance
int total_weight = calcInventoryWeight(unit);
int free_weight = std::max(1, muscle/10 + strength_attr*3);
if (free_weight < total_weight)
{
int delta = (total_weight - free_weight)/10 + 1;
if (!is_adventure)
delta = std::min(5000, delta);
speed += delta;
}
// skipped: unknown loop on inventory items that amounts to 0 change
if (is_adventure)
{
auto player = vector_get(world->units.active, 0);
if (player && player->id == unit->relations.group_leader_id)
speed = std::min(speed, computeMovementSpeed(player));
}
return std::min(10000, std::max(0, speed));
}
static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b)
{ {
if (a.position->precedence < b.position->precedence) if (a.position->precedence < b.position->precedence)

@ -1 +1 @@
Subproject commit db765a65b17099dbec115812b40a19b46ad59431 Subproject commit d55f1cf43dd71d3abee724bfa88a0a401b4ccaa3

@ -39,6 +39,8 @@
#include "df/caste_raw.h" #include "df/caste_raw.h"
#include "df/caste_raw_flags.h" #include "df/caste_raw_flags.h"
#include "df/assumed_identity.h" #include "df/assumed_identity.h"
#include "df/game_mode.h"
#include "df/unit_misc_trait.h"
#include "MiscUtils.h" #include "MiscUtils.h"
@ -48,6 +50,7 @@ using std::stack;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::gamemode;
using df::global::gps; using df::global::gps;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
@ -501,180 +504,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::
* Unit tracking * Unit tracking
*/ */
int getSpeedRating(df::unit *unit)
{
using namespace df::enums::physical_attribute_type;
// Base speed
auto creature = df::creature_raw::find(unit->race);
if (!creature)
return 0;
auto craw = vector_get(creature->caste, unit->caste);
if (!craw)
return 0;
int speed = craw->misc.speed;
if (unit->flags3.bits.ghostly)
return speed;
// Curse multiplier
if (unit->curse.speed_mul_percent != 100)
{
speed *= 100;
if (unit->curse.speed_mul_percent != 0)
speed /= unit->curse.speed_mul_percent;
}
speed += unit->curse.speed_add;
// Swimming
if (unit->flags2.bits.swimming)
{
speed = craw->misc.swim_speed;
if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma)
speed *= 2;
if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
if (skill > 1)
skill = skill * std::max(6, 21-skill) / 20;
}
}
else
{
if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Water)
speed += 150*unit->status2.liquid_depth;
else if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma)
speed += 300*unit->status2.liquid_depth;
}
// General counters
if (unit->profession == profession::BABY)
speed += 3000;
if (unit->counters2.exhaustion >= 2000)
{
speed += 200;
if (unit->counters2.exhaustion >= 4000)
{
speed += 200;
if (unit->counters2.exhaustion >= 6000)
speed += 200;
}
}
if (unit->flags2.bits.gutted) speed += 2000;
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) speed += 1000;
if (unit->counters.winded > 0) speed += 1000;
if (unit->counters.stunned > 0) speed += 1000;
if (unit->counters.dizziness > 0) speed += 1000;
if (unit->counters2.fever > 0) speed += 1000;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (unit->counters.pain >= 100 && unit->mood < 0)
speed += 1000;
}
// TODO: bloodsucker
if (unit->counters2.thirst_timer >= 50000) speed += 100;
if (unit->counters2.hunger_timer >= 75000) speed += 100;
if (unit->counters2.sleepiness_timer >= 150000) speed += 200;
else if (unit->counters2.sleepiness_timer >= 57600) speed += 100;
if (unit->relations.draggee_id != -1) speed += 1000;
if (unit->flags1.bits.on_ground)
speed += 2000;
else if (unit->flags3.bits.on_crutch)
{
int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK);
speed += 2000 - 100*std::min(20, skill);
}
// TODO: hidden_in_ambush
if (unsigned(unit->counters2.paralysis-1) <= 98)
speed += unit->counters2.paralysis*10;
if (unsigned(unit->counters.webbed-1) <= 8)
speed += unit->counters.webbed*100;
// Muscle weight vs agility
auto &attr_unk3 = unit->body.physical_attr_unk3;
int strength = attr_unk3[STRENGTH];
int agility = attr_unk3[AGILITY];
speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility)));
// Attributes
int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH);
int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY);
int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr));
speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ??
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
{
// WTF
int as = unit->status2.able_stand;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;
}
if (unit->mood == mood_type::Melancholy) speed += 8000;
// Inventory encumberance
int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR);
armor_skill = std::min(15, armor_skill);
int inv_weight = 0, inv_weight_fraction = 0;
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i]->item;
if (!item->flags.bits.weight_computed)
continue;
int wval = item->weight;
int wfval = item->weight_fraction;
auto mode = unit->inventory[i]->mode;
if ((mode == df::unit_inventory_item::Worn ||
mode == df::unit_inventory_item::WrappedAround) &&
item->isArmor() && armor_skill > 1)
{
wval = wval * (15 - armor_skill) / 16;
wfval = wfval * (15 - armor_skill) / 16;
}
inv_weight += wval;
inv_weight_fraction += wfval;
}
int total_weight = inv_weight*100 + inv_weight_fraction/10000;
int free_weight = std::max(1, attr_unk3[STRENGTH]/10 + strength_attr*3);
if (free_weight < total_weight)
{
int delta = (total_weight - free_weight)/10;
delta = std::min(5000, delta); // dwarfmode only
speed += delta;
}
// skipped: unknown loop on inventory items that amounts to 0 change
return std::min(10000, std::max(0, speed));
}
/* /*
* Projectile hook * Projectile hook
*/ */