Merge pull request #2374 from wolfboyft/movement-timer-api

Action timer API
develop
Myk 2022-12-02 09:31:13 -08:00 committed by GitHub
commit ed52c4cd6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 75 deletions

@ -1548,6 +1548,51 @@ Units module
Returns a table of the cutoffs used by the above stress level functions. 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 Items module
------------ ------------

@ -316,7 +316,7 @@ item in the raws::
[METAL] [METAL]
[LEATHER] [LEATHER]
[HARD] [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, (you don't have to comment the custom token every time,
but it does clarify what it is) 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() repeatUtil.scheduleEvery(modId, 1, "ticks", function()
Let's iterate over every active unit, and for every unit, iterate over their 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 worn items to calculate how much we are going to take from their on-foot movement timers::
timer::
for _, unit in ipairs(df.global.world.units.active) do for _, unit in ipairs(df.global.world.units.active) do
local amount = 0 local amount = 0
@ -336,15 +335,16 @@ timer::
if entry.mode == df.unit_inventory_item.T_mode.Worn then if entry.mode == df.unit_inventory_item.T_mode.Worn then
local reduction = customRawTokens.getToken( local reduction = customRawTokens.getToken(
entry.item, 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) amount = amount + (tonumber(reduction) or 0)
end end
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 end
-- Subtract amount from movement timer if currently moving
dfhack.units.addMoveTimer(-amount)
The structure of a full mod The structure of a full mod
--------------------------- ---------------------------

@ -1779,6 +1779,12 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getMainSocialEvent), WRAPM(Units, getMainSocialEvent),
WRAPM(Units, getStressCategory), WRAPM(Units, getStressCategory),
WRAPM(Units, getStressCategoryRaw), WRAPM(Units, getStressCategoryRaw),
WRAPM(Units, subtractActionTimers),
WRAPM(Units, subtractGroupActionTimers),
WRAPM(Units, multiplyActionTimers),
WRAPM(Units, multiplyGroupActionTimers),
WRAPM(Units, setActionTimers),
WRAPM(Units, setGroupActionTimers),
{ NULL, NULL } { NULL, NULL }
}; };

@ -38,6 +38,8 @@ distribution.
#include "df/misc_trait_type.h" #include "df/misc_trait_type.h"
#include "df/physical_attribute_type.h" #include "df/physical_attribute_type.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/unit_action.h"
#include "df/unit_action_type_group.h"
namespace df namespace df
{ {
@ -224,5 +226,12 @@ DFHACK_EXPORT extern const std::vector<int32_t> stress_cutoffs;
DFHACK_EXPORT int getStressCategory(df::unit *unit); DFHACK_EXPORT int getStressCategory(df::unit *unit);
DFHACK_EXPORT int getStressCategoryRaw(int32_t stress_level); 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);
} }
} }

@ -32,6 +32,7 @@ distribution.
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <numeric> #include <numeric>
#include <functional>
using namespace std; using namespace std;
#include "VersionInfo.h" #include "VersionInfo.h"
@ -80,6 +81,8 @@ using namespace std;
#include "df/unit_soul.h" #include "df/unit_soul.h"
#include "df/unit_wound.h" #include "df/unit_wound.h"
#include "df/world.h" #include "df/world.h"
#include "df/unit_action.h"
#include "df/unit_action_type_group.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -1949,3 +1952,166 @@ int Units::getStressCategoryRaw(int32_t stress_level)
} }
return 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<double(double)> 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;
}
}
}
}

@ -12,6 +12,7 @@
#include "df/unit_relationship_type.h" #include "df/unit_relationship_type.h"
#include "df/units_other_id.h" #include "df/units_other_id.h"
#include "df/world.h" #include "df/world.h"
#include "df/unit_action_type_group.h"
using std::string; using std::string;
using std::vector; using std::vector;
@ -82,74 +83,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (enable_fastdwarf) if (enable_fastdwarf)
{ {
for (size_t i = 0; i < unit->actions.size(); i++) Units::setGroupActionTimers(out, unit, 1, df::unit_action_type_group::All);
{
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;
}
}
} }
} }
return CR_OK; return CR_OK;