dfhack.job.getWorker(job)
Returns the unit performing the job.
+dfhack.job.setJobCooldown(building,worker,timeout)
+Prevent the worker from taking jobs at the specified workshop for the specified time.
+This doesn't decrease the timeout in any circumstances.
+
+dfhack.job.removeWorker(job,timeout)
+Removes the worker from the specified workshop job, and sets the cooldown.
+Returns true on success.
+
dfhack.job.checkBuildingsNow()
Instructs the game to check buildings for jobs next frame and assign workers.
diff --git a/Lua API.rst b/Lua API.rst
index 696284fa5..37ff003ed 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -938,6 +938,16 @@ Job module
Returns the unit performing the job.
+* ``dfhack.job.setJobCooldown(building,worker,timeout)``
+
+ Prevent the worker from taking jobs at the specified workshop for the specified time.
+ This doesn't decrease the timeout in any circumstances.
+
+* ``dfhack.job.removeWorker(job,timeout)``
+
+ Removes the worker from the specified workshop job, and sets the cooldown.
+ Returns *true* on success.
+
* ``dfhack.job.checkBuildingsNow()``
Instructs the game to check buildings for jobs next frame and assign workers.
diff --git a/NEWS b/NEWS
index d017e172c..6e66a6017 100644
--- a/NEWS
+++ b/NEWS
@@ -18,10 +18,15 @@ DFHack future
Misc improvements:
- digfort: improved csv parsing, add start() comment handling
- exterminate: allow specifying a caste (exterminate gob:male)
- - siege-engine: engine quality and distance to target now affect accuracy;
- firing the siege engine produces a combat report.
- createitem: in adventure mode it now defaults to the controlled unit as maker.
+ Siege engine plugin:
+ - engine quality and distance to target now affect accuracy
+ - firing the siege engine at a target produces a combat report
+ - improved movement speed computation for meandering units
+ - operators in Prepare To Fire mode are released from duty once
+ hungry/thirsty if there is a free replacement
+
DFHack v0.34.11-r4
New commands:
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index e90fa0937..8c76a36f7 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1331,6 +1331,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder),
WRAPM(Job,getWorker),
+ WRAPM(Job,setJobCooldown),
+ WRAPM(Job,removeWorker),
WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow),
WRAPM(Job,isSuitableItem),
diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h
index a7b107b18..b13d9c5a9 100644
--- a/library/include/modules/Job.h
+++ b/library/include/modules/Job.h
@@ -63,6 +63,9 @@ namespace DFHack
DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job);
+ DFHACK_EXPORT void setJobCooldown(df::building *workshop, df::unit *worker, int cooldown = 100);
+ DFHACK_EXPORT bool removeWorker(df::job *job, int cooldown = 100);
+
// Instruct the game to check and assign workers
DFHACK_EXPORT void checkBuildingsNow();
DFHACK_EXPORT void checkDesignationsNow();
diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp
index 270cffa8a..a86a82d8a 100644
--- a/library/modules/Job.cpp
+++ b/library/modules/Job.cpp
@@ -44,6 +44,8 @@ using namespace std;
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
+#include "df/unit.h"
+#include "df/building.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_list_link.h"
@@ -286,6 +288,63 @@ df::unit *DFHack::Job::getWorker(df::job *job)
return NULL;
}
+void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int cooldown)
+{
+ CHECK_NULL_POINTER(workshop);
+ CHECK_NULL_POINTER(worker);
+
+ if (cooldown <= 0)
+ return;
+
+ int idx = linear_index(workshop->job_claim_suppress, &df::building::T_job_claim_suppress::unit, worker);
+
+ if (idx < 0)
+ {
+ auto obj = new df::building::T_job_claim_suppress;
+ obj->unit = worker;
+ obj->timer = cooldown;
+ workshop->job_claim_suppress.push_back(obj);
+ }
+ else
+ {
+ auto obj = workshop->job_claim_suppress[idx];
+ obj->timer = std::max(obj->timer, cooldown);
+ }
+}
+
+bool DFHack::Job::removeWorker(df::job *job, int cooldown)
+{
+ CHECK_NULL_POINTER(job);
+
+ if (job->flags.bits.special)
+ return false;
+
+ auto holder = getHolder(job);
+ if (!holder || linear_index(holder->jobs,job) < 0)
+ return false;
+
+ for (size_t i = 0; i < job->general_refs.size(); i++)
+ {
+ VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]);
+ if (!ref)
+ continue;
+
+ auto worker = ref->getUnit();
+ if (!worker || worker->job.current_job != job)
+ return false;
+
+ setJobCooldown(holder, worker, cooldown);
+
+ vector_erase_at(job->general_refs, i);
+ worker->job.current_job = NULL;
+ delete ref;
+
+ return true;
+ }
+
+ return false;
+}
+
void DFHack::Job::checkBuildingsNow()
{
if (df::global::process_jobs)
diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp
index d6f4359b1..340968f60 100644
--- a/plugins/siege-engine.cpp
+++ b/plugins/siege-engine.cpp
@@ -55,6 +55,7 @@
#include "df/strain_type.h"
#include "df/material.h"
#include "df/flow_type.h"
+#include "df/invasion_info.h"
#include "MiscUtils.h"
@@ -1459,6 +1460,55 @@ static int computeNearbyWeight(lua_State *L)
return 0;
}
+static bool isTired(df::unit *worker)
+{
+ return worker->counters2.exhaustion >= 1000 ||
+ worker->counters2.thirst_timer >= 25000 ||
+ worker->counters2.hunger_timer >= 50000 ||
+ worker->counters2.sleepiness_timer >= 57600;
+}
+
+static void releaseTiredWorker(EngineInfo *engine, df::job *job, df::unit *worker)
+{
+ // If not in siege
+ auto &sieges = ui->invasions.list;
+
+ for (size_t i = 0; i < sieges.size(); i++)
+ if (sieges[i]->flags.bits.active)
+ return;
+
+ // And there is a free replacement
+ auto &others = world->units.active;
+
+ for (size_t i = 0; i < others.size(); i++)
+ {
+ auto unit = others[i];
+
+ if (unit == worker ||
+ unit->job.current_job || !unit->status.labors[unit_labor::SIEGEOPERATE] ||
+ !Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::OnBreak) ||
+ isTired(unit) || !Maps::canWalkBetween(job->pos, unit->pos))
+ continue;
+
+ int skill2 = Units::getEffectiveSkill(unit, job_skill::SIEGEOPERATE);
+
+ if (skill2 >= engine->profile.min_level && skill2 <= engine->profile.max_level)
+ {
+ // Remove the worker and request a recheck
+ if (Job::removeWorker(job))
+ {
+ color_ostream_proxy out(Core::getInstance().getConsole());
+ out.print("Released tired operator %d from siege engine.\n", worker->id);
+
+ if (df::global::process_jobs)
+ *df::global::process_jobs = true;
+ }
+
+ return;
+ }
+ }
+}
+
/*
* Projectile hook
*/
@@ -1796,6 +1846,7 @@ struct building_hook : df::building_siegeenginest {
{
auto job = jobs[0];
bool save_op = false;
+ bool load_op = false;
switch (job->job_type)
{
@@ -1830,12 +1881,18 @@ struct building_hook : df::building_siegeenginest {
// fallthrough
case job_type::LoadBallista:
+ load_op = true;
+
case job_type::FireCatapult:
case job_type::FireBallista:
if (auto worker = Job::getWorker(job))
{
engine->operator_id = worker->id;
engine->operator_frame = world->frame_counter;
+
+ if (action == PrepareToFire && !load_op &&
+ (world->frame_counter%100) == 0 && isTired(worker))
+ releaseTiredWorker(engine, job, worker);
}
break;