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.
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
------------

@ -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
---------------------------

@ -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 }
};

@ -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<int32_t> 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);
}
}

@ -32,6 +32,7 @@ distribution.
#include <cstring>
#include <algorithm>
#include <numeric>
#include <functional>
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<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/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;