diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 37865394e..b8a94f59c 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1548,6 +1548,51 @@ Units module Returns a table of the cutoffs used by the above stress level functions. +Action Timer API +~~~~~~~~~~~~~~~~ + +This is an API to allow manipulation of unit action timers, to speed them up or slow them down. +All functions in this API have overflow/underflow protection when modifying action timers (the value will cap out). +Actions with a timer of 0 (or less) will not be modified as they are completed (or invalid in the case of negatives). +Timers will be capped to go no lower than 1. +``affectedActionType`` parameters are integers from the DF enum ``unit_action_type``. E.g. ``df.unit_action_type.Move``. +``affectedActionTypeGroup`` parameters are integers from the (custom) DF enum ``unit_action_type_group``. They are as follows: + + * ``All`` (does not include unknown unit action types) + * ``Movement`` + * ``MovementFeet`` (for walking speed, such as with pegasus boots from the `modding-guide`) + * ``Combat`` (includes bloodsucking) + * ``Work`` + +API functions: + +* ``subtractActionTimers(unit, amount, affectedActionType)`` + + Subtract ``amount`` (32-bit integer) from the timers of any actions the unit is performing of ``affectedActionType`` + (usually one or zero actions in normal gameplay). + +* ``subtractGroupActionTimers(unit, amount, affectedActionTypeGroup)`` + + Subtract ``amount`` (32-bit integer) from the timers of any actions the unit is performing that match the ``affectedActionTypeGroup`` category. + +* ``multiplyActionTimers(unit, amount, affectedActionType)`` + + Multiply the timers of any actions of ``affectedActionType`` the unit is performing by ``amount`` (float) + (usually one or zero actions in normal gameplay). + +* ``multiplyGroupActionTimers(unit, amount, affectedActionTypeGroup)`` + + Multiply the timers of any actions that match the ``affectedActionTypeGroup`` category the unit is performing by ``amount`` (float). + +* ``setActionTimers(unit, amount, affectedActionType)`` + + Set the timers of any action the unit is performing of ``affectedActionType`` to ``amount`` (32-bit integer) + (usually one or zero actions in normal gameplay). + +* ``setGroupActionTimers(unit, amount, affectedActionTypeGroup)`` + + Set the timers of any action the unit is performing that match the ``affectedActionTypeGroup`` category to ``amount`` (32-bit integer). + Items module ------------ diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index da33a5a9d..e602e632d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -316,7 +316,7 @@ item in the raws:: [METAL] [LEATHER] [HARD] - [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token + [PEGASUS_BOOTS_MOD_FOOT_MOVEMENT_TIMER_REDUCTION_PER_TICK:2] custom raw token (you don't have to comment the custom token every time, but it does clarify what it is) @@ -327,8 +327,7 @@ Then, let's make a ``repeat-util`` callback for once a tick:: repeatUtil.scheduleEvery(modId, 1, "ticks", function() Let's iterate over every active unit, and for every unit, iterate over their -worn items to calculate how much we are going to take from their movement -timer:: +worn items to calculate how much we are going to take from their on-foot movement timers:: for _, unit in ipairs(df.global.world.units.active) do local amount = 0 @@ -336,15 +335,16 @@ timer:: if entry.mode == df.unit_inventory_item.T_mode.Worn then local reduction = customRawTokens.getToken( entry.item, - 'PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK') + 'PEGASUS_BOOTS_MOD_FOOT_MOVEMENT_TIMER_REDUCTION_PER_TICK') amount = amount + (tonumber(reduction) or 0) end end + -- Subtract amount from on-foot movement timers if not on ground + if not unit.flags1.on_ground then + dfhack.units.subtractActionTimers(unit, amount, df.unit_action_type_group.MovementFeet) + end end - -- Subtract amount from movement timer if currently moving - dfhack.units.addMoveTimer(-amount) - The structure of a full mod --------------------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 36d132124..ef0b5c3dd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1779,6 +1779,12 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getMainSocialEvent), WRAPM(Units, getStressCategory), WRAPM(Units, getStressCategoryRaw), + WRAPM(Units, subtractActionTimers), + WRAPM(Units, subtractGroupActionTimers), + WRAPM(Units, multiplyActionTimers), + WRAPM(Units, multiplyGroupActionTimers), + WRAPM(Units, setActionTimers), + WRAPM(Units, setGroupActionTimers), { NULL, NULL } }; diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index be630b802..effa7cd1a 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -38,6 +38,8 @@ distribution. #include "df/misc_trait_type.h" #include "df/physical_attribute_type.h" #include "df/unit.h" +#include "df/unit_action.h" +#include "df/unit_action_type_group.h" namespace df { @@ -224,5 +226,12 @@ DFHACK_EXPORT extern const std::vector stress_cutoffs; DFHACK_EXPORT int getStressCategory(df::unit *unit); DFHACK_EXPORT int getStressCategoryRaw(int32_t stress_level); +DFHACK_EXPORT void subtractActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type affectedActionType); +DFHACK_EXPORT void subtractGroupActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type_group affectedActionTypeGroup); +DFHACK_EXPORT void multiplyActionTimers(color_ostream &out, df::unit *unit, float amount, df::unit_action_type affectedActionType); +DFHACK_EXPORT void multiplyGroupActionTimers(color_ostream &out, df::unit *unit, float amount, df::unit_action_type_group affectedActionTypeGroup); +DFHACK_EXPORT void setActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type affectedActionType); +DFHACK_EXPORT void setGroupActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type_group affectedActionTypeGroup); + } } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 777c3239b..20badb1b5 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -32,6 +32,7 @@ distribution. #include #include #include +#include using namespace std; #include "VersionInfo.h" @@ -80,6 +81,8 @@ using namespace std; #include "df/unit_soul.h" #include "df/unit_wound.h" #include "df/world.h" +#include "df/unit_action.h" +#include "df/unit_action_type_group.h" using namespace DFHack; using namespace df::enums; @@ -1949,3 +1952,166 @@ int Units::getStressCategoryRaw(int32_t stress_level) } return level; } + +int32_t *getActionTimerPointer(df::unit_action *action) { + switch (action->type) + { + case unit_action_type::None: + break; + case unit_action_type::Move: + return &action->data.move.timer; + case unit_action_type::Attack: + if (action->data.attack.timer1 != 0) { + // Wind-up timer is still active, work on it + return &action->data.attack.timer1; + } else { + // Wind-up timer is finished, work on recovery timer + return &action->data.attack.timer2; + } + case unit_action_type::HoldTerrain: + return &action->data.holdterrain.timer; + case unit_action_type::Climb: + return &action->data.climb.timer; + case unit_action_type::Job: + return &action->data.job.timer; + // could also patch the unit->job.current_job->completion_timer + case unit_action_type::Talk: + return &action->data.talk.timer; + case unit_action_type::Unsteady: + return &action->data.unsteady.timer; + case unit_action_type::Dodge: + return &action->data.dodge.timer; + case unit_action_type::Recover: + return &action->data.recover.timer; + case unit_action_type::StandUp: + return &action->data.standup.timer; + case unit_action_type::LieDown: + return &action->data.liedown.timer; + case unit_action_type::Job2: + return &action->data.job2.timer; + // could also patch the unit->job.current_job->completion_timer + case unit_action_type::PushObject: + return &action->data.pushobject.timer; + case unit_action_type::SuckBlood: + return &action->data.suckblood.timer; + case unit_action_type::Jump: + case unit_action_type::ReleaseTerrain: + case unit_action_type::Parry: + case unit_action_type::Block: + case unit_action_type::HoldItem: + case unit_action_type::ReleaseItem: + case unit_action_type::Unk20: + case unit_action_type::Unk21: + case unit_action_type::Unk22: + case unit_action_type::Unk23: + break; + } + return nullptr; +} + +void mutateActionTimerCore(df::unit_action *action, std::function mutator) { + int32_t *timer = getActionTimerPointer(action); + if (timer != nullptr && *timer > 0) { + double value = *timer; + value = mutator(value); + if (value > INT32_MAX) { + value = INT32_MAX; + } else if (value < INT32_MIN) { + value = INT32_MIN; + } + *timer = value; + } +} + +void Units::subtractActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type affectedActionType) +{ + CHECK_NULL_POINTER(unit); + for (auto action : unit->actions) { + if (affectedActionType != action->type) continue; + mutateActionTimerCore(action, [=](double timerValue){return max(timerValue - amount, 1.0);}); + } +} + +void Units::subtractGroupActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type_group affectedActionTypeGroup) +{ + CHECK_NULL_POINTER(unit); + for (auto action : unit->actions) { + auto list = ENUM_ATTR(unit_action_type, group, action->type); + for (size_t i = 0; i < list.size; i++) { + if (list.items[i] == affectedActionTypeGroup) { + mutateActionTimerCore(action, [=](double timerValue){return max(timerValue - amount, 1.0);}); + break; + } + } + } +} + +bool validateMultiplyActionTimerAmount(color_ostream &out, float amount) { + if (amount < 0) { + out.printerr("Can't multiply action timer(s) by negative amount.\n"); + return false; + } + return true; +} + +void Units::multiplyActionTimers(color_ostream &out, df::unit *unit, float amount, df::unit_action_type affectedActionType) +{ + CHECK_NULL_POINTER(unit); + if (!validateMultiplyActionTimerAmount(out, amount)) + return; + for (auto action : unit->actions) { + if (affectedActionType != action->type) continue; + mutateActionTimerCore(action, [=](double timerValue){return max(timerValue * amount, 1.0);}); + } +} + +void Units::multiplyGroupActionTimers(color_ostream &out, df::unit *unit, float amount, df::unit_action_type_group affectedActionTypeGroup) +{ + CHECK_NULL_POINTER(unit); + if (!validateMultiplyActionTimerAmount(out, amount)) + return; + for (auto action : unit->actions) { + auto list = ENUM_ATTR(unit_action_type, group, action->type); + for (size_t i = 0; i < list.size; i++) { + if (list.items[i] == affectedActionTypeGroup) { + mutateActionTimerCore(action, [=](double timerValue){return max(timerValue * amount, 1.0);}); + break; + } + } + } +} + +bool validateSetActionTimerAmount(color_ostream &out, int32_t amount) { + if (amount <= 0) { + out.printerr("Can't set action timer(s) to non-positive amount.\n"); + return false; + } + return true; +} + +void Units::setActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type affectedActionType) +{ + CHECK_NULL_POINTER(unit); + if (!validateSetActionTimerAmount(out, amount)) + return; + for (auto action : unit->actions) { + if (affectedActionType != action->type) continue; + mutateActionTimerCore(action, [=](double timerValue){return amount;}); + } +} + +void Units::setGroupActionTimers(color_ostream &out, df::unit *unit, int32_t amount, df::unit_action_type_group affectedActionTypeGroup) +{ + CHECK_NULL_POINTER(unit); + if (!validateSetActionTimerAmount(out, amount)) + return; + for (auto action : unit->actions) { + auto list = ENUM_ATTR(unit_action_type, group, action->type); + for (size_t i = 0; i < list.size; i++) { + if (list.items[i] == affectedActionTypeGroup) { + mutateActionTimerCore(action, [=](double timerValue){return amount;}); + break; + } + } + } +} diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 787f85c5d..fe0b90be5 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -12,6 +12,7 @@ #include "df/unit_relationship_type.h" #include "df/units_other_id.h" #include "df/world.h" +#include "df/unit_action_type_group.h" using std::string; using std::vector; @@ -82,74 +83,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_fastdwarf) { - for (size_t i = 0; i < unit->actions.size(); i++) - { - df::unit_action *action = unit->actions[i]; - switch (action->type) - { - case unit_action_type::None: - break; - case unit_action_type::Move: - action->data.move.timer = 1; - break; - case unit_action_type::Attack: - // Attacks are executed when timer1 reaches zero, which will be - // on the following tick. - if (action->data.attack.timer1 > 1) - action->data.attack.timer1 = 1; - // Attack actions are completed, and new ones generated, when - // timer2 reaches zero. - if (action->data.attack.timer2 > 1) - action->data.attack.timer2 = 1; - break; - case unit_action_type::HoldTerrain: - action->data.holdterrain.timer = 1; - break; - case unit_action_type::Climb: - action->data.climb.timer = 1; - break; - case unit_action_type::Job: - action->data.job.timer = 1; - // could also patch the unit->job.current_job->completion_timer - break; - case unit_action_type::Talk: - action->data.talk.timer = 1; - break; - case unit_action_type::Unsteady: - action->data.unsteady.timer = 1; - break; - case unit_action_type::Dodge: - action->data.dodge.timer = 1; - break; - case unit_action_type::Recover: - action->data.recover.timer = 1; - break; - case unit_action_type::StandUp: - action->data.standup.timer = 1; - break; - case unit_action_type::LieDown: - action->data.liedown.timer = 1; - break; - case unit_action_type::Job2: - action->data.job2.timer = 1; - // could also patch the unit->job.current_job->completion_timer - break; - case unit_action_type::PushObject: - action->data.pushobject.timer = 1; - break; - case unit_action_type::SuckBlood: - action->data.suckblood.timer = 1; - break; - case unit_action_type::Jump: - case unit_action_type::ReleaseTerrain: - case unit_action_type::Parry: - case unit_action_type::Block: - case unit_action_type::HoldItem: - case unit_action_type::ReleaseItem: - default: - break; - } - } + Units::setGroupActionTimers(out, unit, 1, df::unit_action_type_group::All); } } return CR_OK;