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.
develop
Alexander Gavrilov 2014-04-15 21:52:39 +04:00
parent fc9826389b
commit c27c38e4c0
7 changed files with 146 additions and 2 deletions

@ -1220,6 +1220,14 @@ operations accordingly. The units are used to call <tt class="docutils literal">
<li><p class="first"><tt class="docutils literal">dfhack.job.getWorker(job)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.getWorker(job)</tt></p>
<p>Returns the unit performing the job.</p> <p>Returns the unit performing the job.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.job.setJobCooldown(building,worker,timeout)</tt></p>
<p>Prevent the worker from taking jobs at the specified workshop for the specified time.
This doesn't decrease the timeout in any circumstances.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.removeWorker(job,timeout)</tt></p>
<p>Removes the worker from the specified workshop job, and sets the cooldown.
Returns <em>true</em> on success.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkBuildingsNow()</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.checkBuildingsNow()</tt></p>
<p>Instructs the game to check buildings for jobs next frame and assign workers.</p> <p>Instructs the game to check buildings for jobs next frame and assign workers.</p>
</li> </li>

@ -938,6 +938,16 @@ Job module
Returns the unit performing the 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()`` * ``dfhack.job.checkBuildingsNow()``
Instructs the game to check buildings for jobs next frame and assign workers. Instructs the game to check buildings for jobs next frame and assign workers.

@ -18,10 +18,15 @@ DFHack future
Misc improvements: Misc improvements:
- digfort: improved csv parsing, add start() comment handling - digfort: improved csv parsing, add start() comment handling
- exterminate: allow specifying a caste (exterminate gob:male) - 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. - 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 DFHack v0.34.11-r4
New commands: New commands:

@ -1331,6 +1331,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,getSpecificRef), WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder), WRAPM(Job,getHolder),
WRAPM(Job,getWorker), WRAPM(Job,getWorker),
WRAPM(Job,setJobCooldown),
WRAPM(Job,removeWorker),
WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow), WRAPM(Job,checkDesignationsNow),
WRAPM(Job,isSuitableItem), WRAPM(Job,isSuitableItem),

@ -63,6 +63,9 @@ namespace DFHack
DFHACK_EXPORT df::building *getHolder(df::job *job); DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(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 // Instruct the game to check and assign workers
DFHACK_EXPORT void checkBuildingsNow(); DFHACK_EXPORT void checkBuildingsNow();
DFHACK_EXPORT void checkDesignationsNow(); DFHACK_EXPORT void checkDesignationsNow();

@ -44,6 +44,8 @@ using namespace std;
#include "DataDefs.h" #include "DataDefs.h"
#include "df/world.h" #include "df/world.h"
#include "df/ui.h" #include "df/ui.h"
#include "df/unit.h"
#include "df/building.h"
#include "df/job.h" #include "df/job.h"
#include "df/job_item.h" #include "df/job_item.h"
#include "df/job_list_link.h" #include "df/job_list_link.h"
@ -286,6 +288,63 @@ df::unit *DFHack::Job::getWorker(df::job *job)
return NULL; 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() void DFHack::Job::checkBuildingsNow()
{ {
if (df::global::process_jobs) if (df::global::process_jobs)

@ -55,6 +55,7 @@
#include "df/strain_type.h" #include "df/strain_type.h"
#include "df/material.h" #include "df/material.h"
#include "df/flow_type.h" #include "df/flow_type.h"
#include "df/invasion_info.h"
#include "MiscUtils.h" #include "MiscUtils.h"
@ -1459,6 +1460,55 @@ static int computeNearbyWeight(lua_State *L)
return 0; 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 * Projectile hook
*/ */
@ -1796,6 +1846,7 @@ struct building_hook : df::building_siegeenginest {
{ {
auto job = jobs[0]; auto job = jobs[0];
bool save_op = false; bool save_op = false;
bool load_op = false;
switch (job->job_type) switch (job->job_type)
{ {
@ -1830,12 +1881,18 @@ struct building_hook : df::building_siegeenginest {
// fallthrough // fallthrough
case job_type::LoadBallista: case job_type::LoadBallista:
load_op = true;
case job_type::FireCatapult: case job_type::FireCatapult:
case job_type::FireBallista: case job_type::FireBallista:
if (auto worker = Job::getWorker(job)) if (auto worker = Job::getWorker(job))
{ {
engine->operator_id = worker->id; engine->operator_id = worker->id;
engine->operator_frame = world->frame_counter; engine->operator_frame = world->frame_counter;
if (action == PrepareToFire && !load_op &&
(world->frame_counter%100) == 0 && isTired(worker))
releaseTiredWorker(engine, job, worker);
} }
break; break;