diff --git a/Lua API.html b/Lua API.html index 7851e1d20..eec1a0ea2 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1220,6 +1220,14 @@ operations accordingly. The units are used to call
  • 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;