Better job assignment algorithm for labormanager.

develop
Kelly Kinkade 2016-11-08 12:01:24 -06:00
parent a04ed641b7
commit 8fec45696d
1 changed files with 149 additions and 117 deletions

@ -414,23 +414,23 @@ struct labor_default
static std::vector<struct labor_info> labor_infos; static std::vector<struct labor_info> labor_infos;
static const struct labor_default default_labor_infos[] = { static const struct labor_default default_labor_infos[] = {
/* MINEa */ {200, 0, TOOL_PICK}, /* MINE */ {100, 0, TOOL_PICK},
/* HAUL_STONE */ {100, 0, TOOL_NONE}, /* HAUL_STONE */ {100, 0, TOOL_NONE},
/* HAUL_WOOD */ {100, 0, TOOL_NONE}, /* HAUL_WOOD */ {100, 0, TOOL_NONE},
/* HAUL_BODY */ {200, 0, TOOL_NONE}, /* HAUL_BODY */ {1000, 0, TOOL_NONE},
/* HAUL_FOOD */ {300, 0, TOOL_NONE}, /* HAUL_FOOD */ {500, 0, TOOL_NONE},
/* HAUL_REFUSE */ {100, 0, TOOL_NONE}, /* HAUL_REFUSE */ {200, 0, TOOL_NONE},
/* HAUL_ITEM */ {100, 0, TOOL_NONE}, /* HAUL_ITEM */ {100, 0, TOOL_NONE},
/* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, /* HAUL_FURNITURE */ {100, 0, TOOL_NONE},
/* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, /* HAUL_ANIMAL */ {100, 0, TOOL_NONE},
/* CLEAN */ {200, 0, TOOL_NONE}, /* CLEAN */ {100, 0, TOOL_NONE},
/* CUTWOOD */ {200, 0, TOOL_AXE}, /* CUTWOOD */ {100, 0, TOOL_AXE},
/* CARPENTER */ {200, 0, TOOL_NONE}, /* CARPENTER */ {100, 0, TOOL_NONE},
/* DETAIL */ {200, 0, TOOL_NONE}, /* DETAIL */ {100, 0, TOOL_NONE},
/* MASON */ {200, 0, TOOL_NONE}, /* MASON */ {100, 0, TOOL_NONE},
/* ARCHITECT */ {400, 0, TOOL_NONE}, /* ARCHITECT */ {100, 0, TOOL_NONE},
/* ANIMALTRAIN */ {200, 0, TOOL_NONE}, /* ANIMALTRAIN */ {100, 0, TOOL_NONE},
/* ANIMALCARE */ {200, 0, TOOL_NONE}, /* ANIMALCARE */ {100, 0, TOOL_NONE},
/* DIAGNOSE */ {1000, 0, TOOL_NONE}, /* DIAGNOSE */ {1000, 0, TOOL_NONE},
/* SURGERY */ {1000, 0, TOOL_NONE}, /* SURGERY */ {1000, 0, TOOL_NONE},
/* BONE_SETTING */ {1000, 0, TOOL_NONE}, /* BONE_SETTING */ {1000, 0, TOOL_NONE},
@ -438,65 +438,65 @@ static const struct labor_default default_labor_infos[] = {
/* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE},
/* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE},
/* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE},
/* BUTCHER */ {200, 0, TOOL_NONE}, /* BUTCHER */ {500, 0, TOOL_NONE},
/* TRAPPER */ {200, 0, TOOL_NONE}, /* TRAPPER */ {100, 0, TOOL_NONE},
/* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, /* DISSECT_VERMIN */ {100, 0, TOOL_NONE},
/* LEATHER */ {200, 0, TOOL_NONE}, /* LEATHER */ {100, 0, TOOL_NONE},
/* TANNER */ {200, 0, TOOL_NONE}, /* TANNER */ {100, 0, TOOL_NONE},
/* BREWER */ {200, 0, TOOL_NONE}, /* BREWER */ {100, 0, TOOL_NONE},
/* ALCHEMIST */ {200, 0, TOOL_NONE}, /* ALCHEMIST */ {100, 0, TOOL_NONE},
/* SOAP_MAKER */ {200, 0, TOOL_NONE}, /* SOAP_MAKER */ {100, 0, TOOL_NONE},
/* WEAVER */ {200, 0, TOOL_NONE}, /* WEAVER */ {100, 0, TOOL_NONE},
/* CLOTHESMAKER */ {200, 0, TOOL_NONE}, /* CLOTHESMAKER */ {100, 0, TOOL_NONE},
/* MILLER */ {200, 0, TOOL_NONE}, /* MILLER */ {100, 0, TOOL_NONE},
/* PROCESS_PLANT */ {200, 0, TOOL_NONE}, /* PROCESS_PLANT */ {100, 0, TOOL_NONE},
/* MAKE_CHEESE */ {200, 0, TOOL_NONE}, /* MAKE_CHEESE */ {100, 0, TOOL_NONE},
/* MILK */ {200, 0, TOOL_NONE}, /* MILK */ {100, 0, TOOL_NONE},
/* COOK */ {200, 0, TOOL_NONE}, /* COOK */ {100, 0, TOOL_NONE},
/* PLANT */ {200, 0, TOOL_NONE}, /* PLANT */ {100, 0, TOOL_NONE},
/* HERBALIST */ {200, 0, TOOL_NONE}, /* HERBALIST */ {100, 0, TOOL_NONE},
/* FISH */ {100, 0, TOOL_NONE}, /* FISH */ {100, 0, TOOL_NONE},
/* CLEAN_FISH */ {200, 0, TOOL_NONE}, /* CLEAN_FISH */ {100, 0, TOOL_NONE},
/* DISSECT_FISH */ {200, 0, TOOL_NONE}, /* DISSECT_FISH */ {100, 0, TOOL_NONE},
/* HUNT */ {100, 0, TOOL_CROSSBOW}, /* HUNT */ {100, 0, TOOL_CROSSBOW},
/* SMELT */ {200, 0, TOOL_NONE}, /* SMELT */ {100, 0, TOOL_NONE},
/* FORGE_WEAPON */ {200, 0, TOOL_NONE}, /* FORGE_WEAPON */ {100, 0, TOOL_NONE},
/* FORGE_ARMOR */ {200, 0, TOOL_NONE}, /* FORGE_ARMOR */ {100, 0, TOOL_NONE},
/* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, /* FORGE_FURNITURE */ {100, 0, TOOL_NONE},
/* METAL_CRAFT */ {200, 0, TOOL_NONE}, /* METAL_CRAFT */ {100, 0, TOOL_NONE},
/* CUT_GEM */ {200, 0, TOOL_NONE}, /* CUT_GEM */ {100, 0, TOOL_NONE},
/* ENCRUST_GEM */ {200, 0, TOOL_NONE}, /* ENCRUST_GEM */ {100, 0, TOOL_NONE},
/* WOOD_CRAFT */ {200, 0, TOOL_NONE}, /* WOOD_CRAFT */ {100, 0, TOOL_NONE},
/* STONE_CRAFT */ {200, 0, TOOL_NONE}, /* STONE_CRAFT */ {100, 0, TOOL_NONE},
/* BONE_CARVE */ {200, 0, TOOL_NONE}, /* BONE_CARVE */ {100, 0, TOOL_NONE},
/* GLASSMAKER */ {200, 0, TOOL_NONE}, /* GLASSMAKER */ {100, 0, TOOL_NONE},
/* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, /* EXTRACT_STRAND */ {100, 0, TOOL_NONE},
/* SIEGECRAFT */ {200, 0, TOOL_NONE}, /* SIEGECRAFT */ {100, 0, TOOL_NONE},
/* SIEGEOPERATE */ {200, 0, TOOL_NONE}, /* SIEGEOPERATE */ {100, 0, TOOL_NONE},
/* BOWYER */ {200, 0, TOOL_NONE}, /* BOWYER */ {100, 0, TOOL_NONE},
/* MECHANIC */ {200, 0, TOOL_NONE}, /* MECHANIC */ {100, 0, TOOL_NONE},
/* POTASH_MAKING */ {200, 0, TOOL_NONE}, /* POTASH_MAKING */ {100, 0, TOOL_NONE},
/* LYE_MAKING */ {200, 0, TOOL_NONE}, /* LYE_MAKING */ {100, 0, TOOL_NONE},
/* DYER */ {200, 0, TOOL_NONE}, /* DYER */ {100, 0, TOOL_NONE},
/* BURN_WOOD */ {200, 0, TOOL_NONE}, /* BURN_WOOD */ {100, 0, TOOL_NONE},
/* OPERATE_PUMP */ {200, 0, TOOL_NONE}, /* OPERATE_PUMP */ {100, 0, TOOL_NONE},
/* SHEARER */ {200, 0, TOOL_NONE}, /* SHEARER */ {100, 0, TOOL_NONE},
/* SPINNER */ {200, 0, TOOL_NONE}, /* SPINNER */ {100, 0, TOOL_NONE},
/* POTTERY */ {200, 0, TOOL_NONE}, /* POTTERY */ {100, 0, TOOL_NONE},
/* GLAZING */ {200, 0, TOOL_NONE}, /* GLAZING */ {100, 0, TOOL_NONE},
/* PRESSING */ {200, 0, TOOL_NONE}, /* PRESSING */ {100, 0, TOOL_NONE},
/* BEEKEEPING */ {200, 0, TOOL_NONE}, /* BEEKEEPING */ {100, 0, TOOL_NONE},
/* WAX_WORKING */ {200, 0, TOOL_NONE}, /* WAX_WORKING */ {100, 0, TOOL_NONE},
/* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE}, /* PUSH_HAUL_VEHICLES */ {100, 0, TOOL_NONE},
/* HAUL_TRADE */ {200, 0, TOOL_NONE}, /* HAUL_TRADE */ {1000, 0, TOOL_NONE},
/* PULL_LEVER */ {200, 0, TOOL_NONE}, /* PULL_LEVER */ {1000, 0, TOOL_NONE},
/* REMOVE_CONSTRUCTION */ {200, 0, TOOL_NONE}, /* REMOVE_CONSTRUCTION */ {100, 0, TOOL_NONE},
/* HAUL_WATER */ {200, 0, TOOL_NONE}, /* HAUL_WATER */ {100, 0, TOOL_NONE},
/* GELD */ {200, 0, TOOL_NONE}, /* GELD */ {100, 0, TOOL_NONE},
/* BUILD_ROAD */ {200, 0, TOOL_NONE}, /* BUILD_ROAD */ {100, 0, TOOL_NONE},
/* BUILD_CONSTRUCTION */ {200, 0, TOOL_NONE}, /* BUILD_CONSTRUCTION */ {100, 0, TOOL_NONE},
/* PAPERMAKING */ {200, 0, TOOL_NONE}, /* PAPERMAKING */ {100, 0, TOOL_NONE},
/* BOOKBINDING */ {200, 0, TOOL_NONE} /* BOOKBINDING */ {100, 0, TOOL_NONE}
}; };
void debug (char* fmt, ...); void debug (char* fmt, ...);
@ -852,12 +852,13 @@ private:
return df::unit_labor::TRAPPER; return df::unit_labor::TRAPPER;
case df::building_type::Civzone: case df::building_type::Civzone:
case df::building_type::Nest: case df::building_type::Nest:
case df::building_type::RoadDirt:
case df::building_type::Stockpile: case df::building_type::Stockpile:
case df::building_type::Weapon: case df::building_type::Weapon:
return df::unit_labor::NONE; return df::unit_labor::NONE;
case df::building_type::SiegeEngine: case df::building_type::SiegeEngine:
return df::unit_labor::SIEGECRAFT; return df::unit_labor::SIEGECRAFT;
case df::building_type::RoadDirt:
return df::unit_labor::BUILD_ROAD;
} }
debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n", debug ("LABORMANAGER: Cannot deduce labor for construct building job of type %s\n",
@ -1167,13 +1168,13 @@ public:
job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor;
job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor;
job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY);
job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling;
job_to_labor_table[df::job_type::StoreItemInBag] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling;
job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling;
job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling;
job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling;
job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling;
job_to_labor_table[df::job_type::StoreItemInBin] = jlf_no_labor; // Can arise from many different labors, but will never appear in a pending job list job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling;
job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor;
job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor;
job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor;
@ -1676,6 +1677,13 @@ private:
if (bld != -1) if (bld != -1)
{ {
df::building* b = binsearch_in_vector(world->buildings.all, bld); df::building* b = binsearch_in_vector(world->buildings.all, bld);
// check if this job is the first nonsuspended job on this building; if not, ignore it
// (except for farms and trade depots)
if (b->getType() != df::building_type::FarmPlot &&
b->getType() != df::building_type::TradeDepot)
{
int fjid = -1; int fjid = -1;
for (int jn = 0; jn < b->jobs.size(); jn++) for (int jn = 0; jn < b->jobs.size(); jn++)
{ {
@ -1684,13 +1692,10 @@ private:
fjid = b->jobs[jn]->id; fjid = b->jobs[jn]->id;
break; break;
} }
// check if this job is the first nonsuspended job on this building; if not, ignore it if (fjid != j->id)
// (except for farms and trade depots)
if (fjid != j->id &&
b->getType() != df::building_type::FarmPlot &&
b->getType() != df::building_type::TradeDepot)
return; return;
} }
}
df::unit_labor labor = labor_mapper->find_job_labor (j); df::unit_labor labor = labor_mapper->find_job_labor (j);
@ -2056,7 +2061,8 @@ private:
out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all);
// determine if dwarf has medical needs // determine if dwarf has medical needs
if (dwarf->dwarf->health) // babies cannot currently receive health care even if they need it
if (dwarf->dwarf->profession != profession::BABY && dwarf->dwarf->health)
{ {
if (dwarf->dwarf->health->flags.bits.needs_recovery) if (dwarf->dwarf->health->flags.bits.needs_recovery)
cnt_recover_wounded++; cnt_recover_wounded++;
@ -2160,13 +2166,10 @@ private:
if (labor == df::unit_labor::OPERATE_PUMP) if (labor == df::unit_labor::OPERATE_PUMP)
score += 50000; score += 50000;
else else
score += 1000; score += 25000;
if (default_labor_infos[labor].tool != TOOL_NONE && if (default_labor_infos[labor].tool != TOOL_NONE &&
d->has_tool[default_labor_infos[labor].tool]) d->has_tool[default_labor_infos[labor].tool])
score += 30000; score += 10000000;
if (default_labor_infos[labor].tool != TOOL_NONE &&
!d->has_tool[default_labor_infos[labor].tool])
score -= 30000;
if (d->has_children && labor_outside[labor]) if (d->has_children && labor_outside[labor])
score -= 15000; score -= 15000;
if (d->armed && labor_outside[labor]) if (d->armed && labor_outside[labor])
@ -2361,8 +2364,6 @@ public:
} }
labor_needed[df::unit_labor::CLEAN] = 1;
if (print_debug) if (print_debug)
{ {
for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) for (auto i = labor_needed.begin(); i != labor_needed.end(); i++)
@ -2372,7 +2373,9 @@ public:
} }
} }
std::map<df::unit_labor, int> base_priority;
priority_queue<pair<int, df::unit_labor>> pq; priority_queue<pair<int, df::unit_labor>> pq;
priority_queue<pair<int, df::unit_labor>> pq2;
for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) for (auto i = labor_needed.begin(); i != labor_needed.end(); i++)
{ {
@ -2383,15 +2386,16 @@ public:
if (labor_infos[l].maximum_dwarfs() > 0 && if (labor_infos[l].maximum_dwarfs() > 0 &&
i->second > labor_infos[l].maximum_dwarfs()) i->second > labor_infos[l].maximum_dwarfs())
i->second = labor_infos[l].maximum_dwarfs(); i->second = labor_infos[l].maximum_dwarfs();
if (i->second > 0)
{
int priority = labor_infos[l].priority(); int priority = labor_infos[l].priority();
if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS)
priority += labor_infos[l].time_since_last_assigned()/12; priority += labor_infos[l].time_since_last_assigned()/12;
priority -= labor_infos[l].busy_dwarfs;
base_priority[l] = priority;
for (int n = 0; n < labor_infos[l].busy_dwarfs; n++) if (i->second > 0)
priority /= 2; {
pq.push(make_pair(priority, l)); pq.push(make_pair(priority, l));
} }
} }
@ -2418,9 +2422,22 @@ public:
if (--labor_needed[labor] > 0) if (--labor_needed[labor] > 0)
{ {
priority /= 2; priority-=10;
pq.push(make_pair(priority, labor)); pq2.push(make_pair(priority, labor));
}
if (pq.empty())
while(!pq2.empty())
{
pq.push(pq2.top());
pq2.pop();
}
} }
while (!pq2.empty())
{
pq.push(pq2.top());
pq2.pop();
} }
int canary = (1 << df::unit_labor::HAUL_STONE) | int canary = (1 << df::unit_labor::HAUL_STONE) |
@ -2475,7 +2492,7 @@ public:
if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) ) if (l == best_labor && ( t == TOOL_NONE || tool_in_use[t] < tool_count[t]) )
{ {
set_labor(*bestdwarf, l, true); set_labor(*bestdwarf, l, true);
if (t != TOOL_NONE && (*bestdwarf)->has_tool[t]) if (t != TOOL_NONE && !((*bestdwarf)->has_tool[t]))
{ {
df::job_type j; df::job_type j;
j = df::job_type::NONE; j = df::job_type::NONE;
@ -2526,8 +2543,7 @@ public:
continue; continue;
int score = score_labor (*d, l); int score = score_labor (*d, l);
if (l < df::unit_labor::HAUL_STONE || l > df::unit_labor::HAUL_ANIMALS)
score += labor_infos[l].time_since_last_assigned()/12;
if (l == df::unit_labor::HAUL_FOOD && priority_food > 0) if (l == df::unit_labor::HAUL_FOOD && priority_food > 0)
score += 1000000; score += 1000000;
@ -2538,7 +2554,9 @@ public:
{ {
set_labor(*d, l, true); set_labor(*d, l, true);
} }
if ((*d)->using_labor != df::unit_labor::NONE && score > current_score + 5000 && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) if ((*d)->using_labor != df::unit_labor::NONE &&
(score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) &&
default_labor_infos[(*d)->using_labor].tool == TOOL_NONE)
set_labor(*d, (*d)->using_labor, false); set_labor(*d, (*d)->using_labor, false);
} }
} }
@ -2579,17 +2597,22 @@ public:
/* Assign any leftover dwarfs to "standard" labors */ /* Assign any leftover dwarfs to "standard" labors */
if (print_debug)
out.print ("After assignment, %d dwarfs left over\n", available_dwarfs.size());
for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++)
{ {
FOR_ENUM_ITEMS (unit_labor, l) FOR_ENUM_ITEMS (unit_labor, l)
{ {
if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS && if (l == df::unit_labor::NONE)
canary & (1 << l)) continue;
set_labor(*d, l, true);
else if (l == df::unit_labor::CLEAN || l == df::unit_labor::REMOVE_CONSTRUCTION || l == df::unit_labor::PULL_LEVER) set_labor(*d, l,
set_labor(*d, l, true); (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMALS) ||
else l == df::unit_labor::CLEAN ||
set_labor(*d, l, false); l == df::unit_labor::REMOVE_CONSTRUCTION ||
l == df::unit_labor::PULL_LEVER ||
l == df::unit_labor::HAUL_TRADE);
} }
} }
@ -2648,7 +2671,8 @@ public:
bool has_tool = (*d)->has_tool[t]; bool has_tool = (*d)->has_tool[t];
bool needs_tool = (*d)->dwarf->status.labors[l]; bool needs_tool = (*d)->dwarf->status.labors[l];
if (has_tool != needs_tool) if ((needs_tool && !has_tool) ||
(has_tool && !needs_tool && tool_in_use[t] >= tool_count[t]))
{ {
df::job_type j = df::job_type::NONE; df::job_type j = df::job_type::NONE;
@ -2675,6 +2699,10 @@ public:
*df::global::process_jobs = true; *df::global::process_jobs = true;
} }
if (print_debug) {
*df::global::pause_state = true;
}
print_debug = 0; print_debug = 0;
} }
@ -2709,8 +2737,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
return CR_OK; return CR_OK;
} }
if (++step_count < 60) // if (++step_count < 60)
// return CR_OK;
if (*df::global::process_jobs)
return CR_OK; return CR_OK;
step_count = 0; step_count = 0;
debug_stream = &out; debug_stream = &out;