summaryrefslogtreecommitdiff
path: root/plugins/devel
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-09-08 21:07:18 +0400
committerAlexander Gavrilov2012-09-08 21:07:18 +0400
commitfb88aad51d9a49f489ed99fb1c33f9212b38c068 (patch)
tree27deb08405072f27282ccf413a00be08a6d318f4 /plugins/devel
parent003c3391d1a1712c79ef3c151441bfc798fee3d0 (diff)
downloaddfhack-fb88aad51d9a49f489ed99fb1c33f9212b38c068.tar.gz
dfhack-fb88aad51d9a49f489ed99fb1c33f9212b38c068.tar.bz2
dfhack-fb88aad51d9a49f489ed99fb1c33f9212b38c068.tar.xz
Reverse-engineer unit speed computation from DF code.
Diffstat (limited to 'plugins/devel')
-rw-r--r--plugins/devel/siege-engine.cpp293
1 files changed, 293 insertions, 0 deletions
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index 86fe3ba5..60035b27 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -7,6 +7,7 @@
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <modules/World.h>
+#include <modules/Units.h>
#include <LuaTools.h>
#include <TileTypes.h>
#include <vector>
@@ -30,6 +31,14 @@
#include "df/flow_info.h"
#include "df/report.h"
#include "df/proj_itemst.h"
+#include "df/unit.h"
+#include "df/unit_soul.h"
+#include "df/unit_skill.h"
+#include "df/physical_attribute_type.h"
+#include "df/creature_raw.h"
+#include "df/caste_raw.h"
+#include "df/caste_raw_flags.h"
+#include "df/assumed_identity.h"
#include "MiscUtils.h"
@@ -489,6 +498,290 @@ 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);
+}
+
+static int getPhysAttrValue(df::unit *unit, df::physical_attribute_type attr, bool hide_curse)
+{
+ int value = getAttrValue(unit->body.physical_attrs[attr]);
+
+ if (auto mod = unit->curse.attr_change)
+ {
+ int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr];
+
+ if (hide_curse)
+ value = std::min(value, mvalue);
+ else
+ value = mvalue;
+ }
+
+ return value;
+}
+
+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 = 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 = 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
+ bool hide_curse = false;
+ if (!unit->job.hunt_target)
+ {
+ auto identity = Units::getIdentity(unit);
+ if (identity && identity->unk_4c == 0)
+ hide_curse = true;
+ }
+
+ int strength_attr = getPhysAttrValue(unit, STRENGTH, hide_curse);
+ int agility_attr = getPhysAttrValue(unit, AGILITY, hide_curse);
+ 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 = 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
*/