From c27c38e4c0b5ad36e9288d59649266cf201d3f9a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 15 Apr 2014 21:52:39 +0400 Subject: [PATCH] Remove tired or hungry siege operators from duty when at peace. The threshold is set at the level when they start to blink - normally they would continue on with the job until they get a thirsty/hungry thought, but immediately run off to eat if they lose the job (thus refusing to load the engine after firing it). The code checks for active sieges and whether there is a free replacement unit. --- Lua API.html | 8 +++++ Lua API.rst | 10 ++++++ NEWS | 9 ++++-- library/LuaApi.cpp | 2 ++ library/include/modules/Job.h | 3 ++ library/modules/Job.cpp | 59 +++++++++++++++++++++++++++++++++++ plugins/siege-engine.cpp | 57 +++++++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 2 deletions(-) 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;