diff --git a/Lua API.html b/Lua API.html index f0af74012..7851e1d20 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1326,6 +1326,10 @@ is true, subtracts the rust penalty.

  • 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.computeSlowdownFactor(unit)

    +

    Meandering and floundering in liquid introduces additional slowdown. It is +random, but the function computes and returns the expected mean factor as a float.

    +
  • 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/Lua API.rst b/Lua API.rst index 90e36213f..696284fa5 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1067,6 +1067,11 @@ Units module Computes number of frames * 100 it takes the unit to move in its current state of mind and body. +* ``dfhack.units.computeSlowdownFactor(unit)`` + + Meandering and floundering in liquid introduces additional slowdown. It is + random, but the function computes and returns the expected mean factor as a float. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 622b3f286..e90fa0937 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1391,6 +1391,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getEffectiveSkill), WRAPM(Units, getExperience), WRAPM(Units, computeMovementSpeed), + WRAPM(Units, computeSlowdownFactor), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index c2eb7ca18..b51183d6b 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -241,6 +241,7 @@ DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); +DFHACK_EXPORT float computeSlowdownFactor(df::unit *unit); struct NoblePosition { df::historical_entity *entity; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 39f9dd167..13e23fb39 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -54,6 +54,8 @@ using namespace std; #include "df/unit_soul.h" #include "df/nemesis_record.h" #include "df/historical_entity.h" +#include "df/entity_raw.h" +#include "df/entity_raw_flags.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" #include "df/entity_position.h" @@ -1060,6 +1062,8 @@ int Units::computeMovementSpeed(df::unit *unit) { using namespace df::enums::physical_attribute_type; + CHECK_NULL_POINTER(unit); + /* * Pure reverse-engineered computation of unit _slowness_, * i.e. number of ticks to move * 100. @@ -1264,6 +1268,54 @@ int Units::computeMovementSpeed(df::unit *unit) return std::min(10000, std::max(0, speed)); } +static bool entityRawFlagSet(int civ_id, df::entity_raw_flags flag) +{ + auto entity = df::historical_entity::find(civ_id); + + return entity && entity->entity_raw && entity->entity_raw->flags.is_set(flag); +} + +float Units::computeSlowdownFactor(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + /* + * These slowdowns are actually done by skipping a move if random(x) != 0, so + * it follows the geometric distribution. The mean expected slowdown is x. + */ + + float coeff = 1.0f; + + if (!unit->job.hunt_target && (!gamemode || *gamemode == game_mode::DWARF)) + { + if (!unit->flags1.bits.marauder && + casteFlagSet(unit->race, unit->caste, caste_raw_flags::MEANDERER) && + !(unit->relations.following && isCitizen(unit)) && + linear_index(unit->inventory, &df::unit_inventory_item::mode, + df::unit_inventory_item::Hauled) < 0) + { + coeff *= 4.0f; + } + + if (unit->relations.group_leader_id < 0 && + unit->flags1.bits.active_invader && + !unit->job.current_job && !unit->flags3.bits.no_meandering && + unit->profession != profession::THIEF && unit->profession != profession::MASTER_THIEF && + !entityRawFlagSet(unit->civ_id, entity_raw_flags::ITEM_THIEF)) + { + coeff *= 3.0f; + } + } + + if (unit->flags3.bits.floundering) + { + coeff *= 3.0f; + } + + return coeff; +} + + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 7d2d6415d..d6f4359b1 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -1206,6 +1206,7 @@ struct UnitPath { { float time = unit->counters.job_counter+0.5f; float speed = Units::computeMovementSpeed(unit)/100.0f; + float slowdown = Units::computeSlowdownFactor(unit); if (unit->counters.unconscious > 0) time += unit->counters.unconscious; @@ -1217,9 +1218,14 @@ struct UnitPath { continue; float delay = speed; + + // Diagonal movement if (new_pos.x != pos.x && new_pos.y != pos.y) delay *= 362.0/256.0; + // Meandering slowdown + delay += (slowdown - 1) * speed; + path[time] = pos; pos = new_pos; time += delay + 1;