Petr Mrázek 2012-01-09 20:43:55 +01:00
commit 1652e99a0b
1 changed files with 611 additions and 200 deletions

@ -5,6 +5,7 @@
#include <MiscUtils.h>
#include <modules/Materials.h>
#include <modules/Items.h>
#include <modules/Gui.h>
#include <modules/Job.h>
#include <modules/World.h>
@ -22,6 +23,7 @@
#include <df/general_ref.h>
#include <df/general_ref_unit_workerst.h>
#include <df/general_ref_building_holderst.h>
#include <df/general_ref_contains_itemst.h>
using std::vector;
using std::string;
@ -36,7 +38,7 @@ using df::global::job_next_id;
/* Plugin registration */
static command_result protect_job(Core *c, vector <string> & parameters);
static command_result workflow_cmd(Core *c, vector <string> & parameters);
static void init_state(Core *c);
static void cleanup_state(Core *c);
@ -55,14 +57,35 @@ DFhackCExport command_result plugin_init (Core *c, std::vector <PluginCommand> &
if (ui_workshop_job_cursor && job_next_id) {
commands.push_back(
PluginCommand(
"protect-job", "Manage protection of workshop jobs from removal.",
protect_job, false,
" protect-job list\n"
" List protected jobs. If a workshop is selected, filters by it.\n"
" protect-job add [all]\n"
" Protect the selected job, or any repeat jobs (possibly in the workshop).\n"
" protect-job remove [all]\n"
" Unprotect the selected job, or any repeat jobs (possibly in the workshop).\n"
"workflow", "Manage control of repeat jobs.",
workflow_cmd, false,
" workflow enable\n"
" workflow disable\n"
" Enable or disable the plugin.\n"
" workflow jobs\n"
" List workflow-controlled jobs (if in a workshop, filtered by it).\n"
" workflow list\n"
" List active constraints, and their job counts.\n"
" workflow limit <constraint-spec> <cnt-limit> [cnt-gap]\n"
" workflow limit-count <constraint-spec> <cnt-limit> [cnt-gap]\n"
" Set a constraint. The second form counts each stack as 1 item.\n"
" workflow unlimit <constraint-spec>\n"
" Delete a constraint.\n"
"Function:\n"
" - When the plugin is enabled, it protects all repeat jobs from removal.\n"
" If they do disappear due to any cause, they are immediately re-added\n"
" to their workshop and suspended.\n"
" - In addition, when any constraints on item amounts are set, repeat jobs\n"
" that produce that kind of item are automatically suspended and resumed\n"
" as the item amount goes above or below the limit. The gap specifies how\n"
" much below the limit the amount has to drop before jobs are resumed.\n"
"Constraint examples:\n"
" workflow limit AMMO:ITEM_AMMO_BOLTS//WOOD,BONE 200 50\n"
" Keep wooden and bone bolts between 150 and 200.\n"
" workflow limit-count DRINK 120 30\n"
" Keep the number of drink barrels between 90 and 120\n"
" workflow limit-count BIN 30\n"
" Make sure there are always 25-30 empty bins.\n"
)
);
}
@ -98,6 +121,8 @@ DFhackCExport command_result plugin_onstatechange(Core* c, state_change_event ev
/*******************************/
struct ItemConstraint;
struct ProtectedJob {
int id;
int building_id;
@ -107,12 +132,17 @@ struct ProtectedJob {
df::building *holder;
df::job *job_copy;
df::job *actual_job;
std::vector<ItemConstraint*> constraints;
ProtectedJob(df::job *job) : id(job->id), live(true)
{
check_idx = 0;
holder = getJobHolder(job);
building_id = holder ? holder->id : -1;
job_copy = cloneJobStruct(job);
actual_job = job;
}
~ProtectedJob()
@ -122,6 +152,7 @@ struct ProtectedJob {
void update(df::job *job)
{
actual_job = job;
if (*job == *job_copy)
return;
@ -130,13 +161,73 @@ struct ProtectedJob {
}
};
PersistentDataItem config;
static std::vector<PersistentDataItem> protected_cfg;
typedef std::map<std::pair<int,int>, bool> TMaterialCache;
struct ItemConstraint {
PersistentDataItem config;
ItemTypeInfo item;
MaterialInfo material;
df::job_material_category mat_mask;
int weight;
std::vector<ProtectedJob*> jobs;
int item_amount, item_count, item_inuse;
bool request_suspend, request_resume;
TMaterialCache material_cache;
ItemConstraint() : weight(0), item_amount(0), item_count(0), item_inuse(0) {}
int goalCount() { return config.ival(0); }
void setGoalCount(int v) { config.ival(0) = v; }
int goalGap() {
int gcnt = std::max(1, goalCount()/2);
return std::min(gcnt, config.ival(1) <= 0 ? 5 : config.ival(1));
}
void setGoalGap(int v) { config.ival(1) = v; }
bool goalByCount() { return config.ival(2) & 1; }
void setGoalByCount(bool v) {
if (v)
config.ival(2) |= 1;
else
config.ival(2) &= ~1;
}
void init(const std::string &str)
{
config.val() = str;
config.ival(2) = 0;
}
void computeRequest()
{
int size = goalByCount() ? item_count : item_amount;
request_resume = (size <= goalCount()-goalGap());
request_suspend = (size >= goalCount());
}
};
/*******************************/
static bool enabled = false;
static PersistentDataItem config;
enum ConfigFlags {
CF_ENABLED = 1
};
typedef std::map<int, ProtectedJob*> TKnownJobs;
static TKnownJobs known_jobs;
static std::vector<ProtectedJob*> pending_recover;
static std::vector<ItemConstraint*> constraints;
/*******************************/
static ProtectedJob *get_known(int id)
{
@ -144,6 +235,13 @@ static ProtectedJob *get_known(int id)
return (it != known_jobs.end()) ? it->second : NULL;
}
static bool isSupportedJob(df::job *job)
{
return job->misc_links.empty() &&
!job->job_items.empty() &&
getJobHolder(job);
}
static void enumLiveJobs(std::map<int, df::job*> &rv)
{
df::job_list_link *p = world->job_list.next;
@ -151,129 +249,91 @@ static void enumLiveJobs(std::map<int, df::job*> &rv)
rv[p->item->id] = p->item;
}
static void cleanup_state(Core *)
{
config = PersistentDataItem();
protected_cfg.clear();
/*******************************/
static void stop_protect(Core *c)
{
pending_recover.clear();
if (!known_jobs.empty())
c->con.print("Unprotecting %d jobs.\n", known_jobs.size());
for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
delete it->second;
known_jobs.clear();
}
static void init_state(Core *c)
static void cleanup_state(Core *c)
{
config = c->getWorld()->GetPersistentData("workflow/config");
c->getWorld()->GetPersistentData(&protected_cfg, "workflow/protected-jobs");
config = PersistentDataItem();
std::map<int, df::job*> jobs;
enumLiveJobs(jobs);
stop_protect(c);
for (unsigned i = 0; i < protected_cfg.size(); i++)
{
PersistentDataItem &item = protected_cfg[i];
for (int j = 0; j < PersistentDataItem::NumInts; j++)
{
int id = item.ival(j);
if (id <= 0)
continue;
for (unsigned i = 0; i < constraints.size(); i++)
delete constraints[i];
constraints.clear();
}
if (get_known(id)) // duplicate
{
item.ival(j) = -1;
continue;
}
static bool check_lost_jobs(Core *c);
static ItemConstraint *get_constraint(Core *c, const std::string &str, PersistentDataItem *cfg = NULL);
df::job *job = jobs[id];
if (!job)
{
c->con.printerr("Protected job lost: %d\n", id);
item.ival(j) = -1;
continue;
}
static void start_protect(Core *c)
{
check_lost_jobs(c);
if (!job->misc_links.empty() || job->job_items.empty())
{
c->con.printerr("Protected job unsupported: %d (%s)\n",
id, ENUM_KEY_STR(job_type, job->job_type));
item.ival(j) = -1;
continue;
}
if (!known_jobs.empty())
c->con.print("Protecting %d jobs.\n", known_jobs.size());
}
ProtectedJob *pj = new ProtectedJob(job);
if (!pj->holder)
{
c->con.printerr("Protected job not in building: %d (%s)\n",
id, ENUM_KEY_STR(job_type, job->job_type));
delete pj;
item.ival(j) = -1;
continue;
}
static void init_state(Core *c)
{
config = c->getWorld()->GetPersistentData("workflow/config");
known_jobs[id] = pj;
enabled = config.isValid() && config.ival(0) != -1 &&
(config.ival(0) & CF_ENABLED);
if (!job->flags.bits.repeat) {
c->con.printerr("Protected job not repeating: %d\n", id);
job->flags.bits.repeat = true;
}
}
// Parse constraints
std::vector<PersistentDataItem> items;
c->getWorld()->GetPersistentData(&items, "workflow/constraints");
for (int i = items.size()-1; i >= 0; i--) {
if (get_constraint(c, items[i].val(), &items[i]))
continue;
c->con.printerr("Lost constraint %s\n", items[i].val().c_str());
c->getWorld()->DeletePersistentData(items[i]);
}
if (!known_jobs.empty())
c->con.print("Protecting %d jobs.\n", known_jobs.size());
if (!enabled)
return;
start_protect(c);
}
static int *find_protected_id_slot(Core *c, int key)
static void enable_plugin(Core *c)
{
for (unsigned i = 0; i < protected_cfg.size(); i++)
{
PersistentDataItem &item = protected_cfg[i];
for (int j = 0; j < PersistentDataItem::NumInts; j++)
if (!config.isValid())
{
if (item.ival(j) == key)
return &item.ival(j);
}
config = c->getWorld()->AddPersistentData("workflow/config");
config.ival(0) = 0;
}
if (key == -1) {
protected_cfg.push_back(c->getWorld()->AddPersistentData("workflow/protected-jobs"));
PersistentDataItem &item = protected_cfg.back();
return &item.ival(0);
}
config.ival(0) |= CF_ENABLED;
enabled = true;
c->con << "Enabling the plugin." << endl;
return NULL;
start_protect(c);
}
/*******************************/
static void forget_job(Core *c, ProtectedJob *pj)
{
known_jobs.erase(pj->id);
if (int *p = find_protected_id_slot(c, pj->id))
*p = -1;
delete pj;
}
static void remember_job(Core *c, df::job *job)
{
if (get_known(job->id))
return;
if (!job->misc_links.empty() || job->job_items.empty())
{
c->con.printerr("Unsupported job type: %d (%s)\n",
job->id, ENUM_KEY_STR(job_type, job->job_type));
return;
}
known_jobs[job->id] = new ProtectedJob(job);
*find_protected_id_slot(c, -1) = job->id;
}
static bool recover_job(Core *c, ProtectedJob *pj)
{
// Check that the building exists
@ -305,45 +365,31 @@ static bool recover_job(Core *c, ProtectedJob *pj)
return false;
}
// Find the position in the job list
df::job_list_link *ins_pos = &world->job_list;
while (ins_pos->next && ins_pos->next->item->id < pj->id)
ins_pos = ins_pos->next;
// Create and link in the actual job structure
df::job *recovered = cloneJobStruct(pj->job_copy);
recovered->flags.bits.repeat = true;
recovered->flags.bits.suspend = true;
if (ins_pos->next && ins_pos->next->item->id == pj->id)
if (!linkJobIntoWorld(recovered, false)) // reuse same id
{
deleteJobStruct(recovered);
c->con.printerr("Inconsistency: job %d (%s) already in list.",
pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type));
pj->live = true;
return true;
}
// Create the actual job structure
df::job *recovered = cloneJobStruct(pj->job_copy);
recovered->flags.bits.repeat = true;
recovered->flags.bits.suspend = true;
// Link the job into the global list
df::job_list_link *link = new df::job_list_link();
recovered->list_link = link;
link->item = recovered;
link->next = ins_pos->next;
if (ins_pos->next)
ins_pos->next->prev = link;
link->prev = ins_pos;
ins_pos->next = link;
// Add to building jobs
pj->holder->jobs.push_back(recovered);
// Done
pj->actual_job = recovered;
pj->live = true;
return true;
}
static void check_lost_jobs(Core *c)
static bool check_lost_jobs(Core *c)
{
static int check = 1;
check++;
@ -351,14 +397,26 @@ static void check_lost_jobs(Core *c)
df::job_list_link *p = world->job_list.next;
for (; p; p = p->next)
{
ProtectedJob *pj = get_known(p->item->id);
if (!pj)
continue;
pj->check_idx = check;
df::job *job = p->item;
// force repeat
p->item->flags.bits.repeat = true;
ProtectedJob *pj = get_known(job->id);
if (pj)
{
if (!job->flags.bits.repeat)
forget_job(c, pj);
else
pj->check_idx = check;
}
else if (job->flags.bits.repeat && isSupportedJob(job))
{
pj = new ProtectedJob(job);
assert(pj->holder);
known_jobs[pj->id] = pj;
pj->check_idx = check;
}
}
bool any_lost = false;
for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
{
@ -366,8 +424,12 @@ static void check_lost_jobs(Core *c)
continue;
it->second->live = false;
it->second->actual_job = NULL;
pending_recover.push_back(it->second);
any_lost = true;
}
return any_lost;
}
static void update_job_data(Core *c)
@ -382,35 +444,363 @@ static void update_job_data(Core *c)
}
}
static void recover_jobs(Core *c)
{
for (int i = pending_recover.size()-1; i >= 0; i--)
if (recover_job(c, pending_recover[i]))
vector_erase_at(pending_recover, i);
}
static void process_constraints(Core *c);
DFhackCExport command_result plugin_onupdate(Core* c)
{
if (known_jobs.empty())
if (!enabled)
return CR_OK;
static unsigned cnt = 0;
static unsigned last_rlen = 0;
cnt++;
if ((cnt % 10) == 0)
if ((cnt % 5) == 0)
{
for (int i = pending_recover.size()-1; i >= 0; i--)
if (recover_job(c, pending_recover[i]))
vector_erase_at(pending_recover, i);
check_lost_jobs(c);
}
if (pending_recover.size() != last_rlen || (cnt % 50) == 0)
{
recover_jobs(c);
last_rlen = pending_recover.size();
if ((cnt % 500) == 0)
{
update_job_data(c);
process_constraints(c);
}
}
}
return CR_OK;
}
static command_result protect_job(Core *c, vector <string> & parameters)
/*******************************/
static ItemConstraint *get_constraint(Core *c, const std::string &str, PersistentDataItem *cfg)
{
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
if (tokens.size() > 3)
return NULL;
int weight = 0;
ItemTypeInfo item;
if (!item.find(tokens[0]) || !item.isValid()) {
c->con.printerr("Cannot find item type: %s\n", tokens[0].c_str());
return NULL;
}
if (item.subtype >= 0)
weight += 10000;
MaterialInfo material;
std::string matstr = vector_get(tokens,1);
if (!matstr.empty() && (!material.find(matstr) || !material.isValid())) {
c->con.printerr("Cannot find material: %s\n", matstr.c_str());
return NULL;
}
if (material.type >= 0)
weight += (material.index >= 0 ? 5000 : 1000);
df::job_material_category mat_mask;
std::string maskstr = vector_get(tokens,2);
if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) {
c->con.printerr("Cannot decode material mask: %s\n", maskstr.c_str());
return NULL;
}
if (mat_mask.whole && material.isValid() && !material.matches(mat_mask)) {
c->con.printerr("Material %s doesn't match mask %s\n", matstr.c_str(), maskstr.c_str());
return NULL;
}
if (mat_mask.whole != 0)
weight += 100;
for (unsigned i = 0; i < constraints.size(); i++)
{
ItemConstraint *ct = constraints[i];
if (ct->item == item && ct->material == material &&
ct->mat_mask.whole == mat_mask.whole)
return ct;
}
ItemConstraint *nct = new ItemConstraint;
nct->item = item;
nct->material = material;
nct->mat_mask = mat_mask;
nct->weight = weight;
if (cfg)
nct->config = *cfg;
else
{
nct->config = c->getWorld()->AddPersistentData("workflow/constraints");
nct->init(str);
}
constraints.push_back(nct);
return nct;
}
static void delete_constraint(Core *c, ItemConstraint *cv)
{
int idx = linear_index(constraints, cv);
if (idx >= 0)
vector_erase_at(constraints, idx);
c->getWorld()->DeletePersistentData(cv->config);
delete cv;
}
static void print_constraint(Core *c, ItemConstraint *cv, bool no_job = false, std::string prefix = "")
{
c->con << prefix << "Constraint " << cv->config.val() << ": "
<< (cv->goalByCount() ? "count " : "amount ")
<< cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl;
if (cv->item_count || cv->item_inuse)
c->con << prefix << " items: amount " << cv->item_amount << "; "
<< cv->item_count << " stacks available, "
<< cv->item_inuse << " in use." << endl;
if (no_job) return;
if (cv->jobs.empty())
c->con.printerr(" (no jobs)\n");
for (int i = 0; i < cv->jobs.size(); i++)
{
ProtectedJob *pj = cv->jobs[i];
df::job *job = pj->actual_job;
c->con << prefix << " job " << job->id << ": "
<< ENUM_KEY_STR(job_type, job->job_type);
if (job->flags.bits.suspend)
c->con << " (suspended)";
c->con << endl;
}
}
static void print_job(Core *c, ProtectedJob *pj)
{
if (!pj)
return;
printJobDetails(c, pj->job_copy);
for (int i = 0; i < pj->constraints.size(); i++)
print_constraint(c, pj->constraints[i], true, " ");
}
static void map_job_constraints(Core *c)
{
for (unsigned i = 0; i < constraints.size(); i++)
constraints[i]->jobs.clear();
for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
{
it->second->constraints.clear();
if (!it->second->live)
continue;
df::job *job = it->second->job_copy;
df::item_type itype = ENUM_ATTR(job_type, item, job->job_type);
if (itype == item_type::NONE)
continue;
int16_t isubtype = job->item_subtype;
int16_t mat_type = job->mat_type;
int32_t mat_index = job->mat_index;
if (itype == item_type::FOOD)
mat_type = -1;
if (mat_type == -1 && job->job_items.size() == 1) {
mat_type = job->job_items[0]->mat_type;
mat_index = job->job_items[0]->mat_index;
}
MaterialInfo mat(mat_type, mat_index);
for (unsigned i = 0; i < constraints.size(); i++)
{
ItemConstraint *ct = constraints[i];
if (ct->item.type != itype ||
(ct->item.subtype != -1 && ct->item.subtype != isubtype))
continue;
if (ct->material.isValid() && ct->material != mat)
continue;
if (ct->mat_mask.whole)
{
if (mat.isValid() && !mat.matches(ct->mat_mask))
continue;
else if (!(job->material_category.whole & ct->mat_mask.whole))
continue;
}
ct->jobs.push_back(it->second);
it->second->constraints.push_back(ct);
}
}
}
static bool itemNotEmpty(df::item *item)
{
for (unsigned i = 0; i < item->itemrefs.size(); i++)
if (strict_virtual_cast<df::general_ref_contains_itemst>(item->itemrefs[i]))
return true;
return false;
}
static void map_job_items(Core *c)
{
for (unsigned i = 0; i < constraints.size(); i++)
{
constraints[i]->item_amount = 0;
constraints[i]->item_count = 0;
constraints[i]->item_inuse = 0;
}
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_colect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(in_job);
#undef F
std::vector<df::item*> &items = df::item::get_vector();
for (unsigned i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
bool in_use = item->isAssignedToStockpile() || itemNotEmpty(item);
df::item_type itype = item->getType();
int16_t isubtype = item->getSubtype();
int16_t imattype = item->getActualMaterial();
int32_t imatindex = item->getActualMaterialIndex();
TMaterialCache::key_type matkey(imattype, imatindex);
for (unsigned i = 0; i < constraints.size(); i++)
{
ItemConstraint *cv = constraints[i];
if (cv->item.type != itype ||
(cv->item.subtype != -1 && cv->item.subtype != isubtype))
continue;
TMaterialCache::iterator it = cv->material_cache.find(matkey);
bool ok = true;
if (it != cv->material_cache.end())
ok = it->second;
else
{
MaterialInfo mat(imattype, imatindex);
bool ok = (!cv->material.isValid() || mat == cv->material) &&
(cv->mat_mask.whole == 0 || (mat.isValid() && mat.matches(cv->mat_mask)));
cv->material_cache[matkey] = ok;
}
if (!ok)
continue;
if (in_use)
cv->item_inuse++;
else
{
cv->item_count++;
cv->item_amount += item->getStackSize();
}
}
}
for (unsigned i = 0; i < constraints.size(); i++)
constraints[i]->computeRequest();
}
static void update_jobs_by_constraints(Core *c)
{
for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
{
ProtectedJob *pj = it->second;
if (!pj->live || pj->constraints.empty())
continue;
int resume_weight = -1;
int suspend_weight = -1;
for (unsigned i = 0; i < pj->constraints.size(); i++)
{
if (pj->constraints[i]->request_resume)
resume_weight = std::max(resume_weight, pj->constraints[i]->weight);
if (pj->constraints[i]->request_suspend)
suspend_weight = std::max(suspend_weight, pj->constraints[i]->weight);
}
bool goal = pj->actual_job->flags.bits.suspend;
if (suspend_weight >= 0 && suspend_weight >= resume_weight)
goal = true;
else if (resume_weight >= 0)
goal = false;
if (goal != pj->actual_job->flags.bits.suspend)
{
pj->actual_job->flags.bits.suspend = goal;
c->con.print("%s job %d: %s\n",
(goal ? "Suspending" : "Resuming"), pj->id,
ENUM_KEY_STR(job_type, pj->actual_job->job_type));
}
}
}
static void process_constraints(Core *c)
{
if (constraints.empty())
return;
map_job_constraints(c);
map_job_items(c);
update_jobs_by_constraints(c);
}
/*******************************/
static command_result workflow_cmd(Core *c, vector <string> & parameters)
{
CoreSuspender suspend(c);
if (parameters.empty())
return CR_WRONG_USAGE;
if (enabled) {
check_lost_jobs(c);
recover_jobs(c);
update_job_data(c);
map_job_constraints(c);
map_job_items(c);
}
df::building *workshop = NULL;
df::job *job = NULL;
@ -422,24 +812,53 @@ static command_result protect_job(Core *c, vector <string> & parameters)
job = getSelectedWorkshopJob(c, true);
}
std::map<int, df::job*> jobs;
enumLiveJobs(jobs);
update_job_data(c);
std::string cmd = parameters.empty() ? "list" : parameters[0];
if (cmd == "enable")
{
if (enabled)
{
c->con << "The plugin is already enabled." << endl;
return CR_OK;
}
enable_plugin(c);
return CR_OK;
}
else if (cmd == "disable")
{
if (!enabled)
{
c->con << "The plugin is already disabled." << endl;
return CR_OK;
}
std::string cmd = parameters[0];
if (cmd == "list")
enabled = false;
config.ival(0) &= ~CF_ENABLED;
stop_protect(c);
return CR_OK;
}
else if (cmd == "limit" || cmd == "limit-count")
{
if (!enabled)
enable_plugin(c);
}
if (!enabled)
c->con << "Note: the plugin is not enabled." << endl;
if (cmd == "jobs")
{
if (workshop)
{
for (unsigned i = 0; i < workshop->jobs.size(); i++)
if (get_known(workshop->jobs[i]->id))
printJobDetails(c, workshop->jobs[i]);
print_job(c, get_known(workshop->jobs[i]->id));
}
else
{
for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
if (df::job *job = jobs[it->first])
printJobDetails(c, job);
if (it->second->live)
print_job(c, it->second);
}
bool pending = false;
@ -457,65 +876,57 @@ static command_result protect_job(Core *c, vector <string> & parameters)
printJobDetails(c, pending_recover[i]->job_copy);
}
}
}
else if (cmd == "add" || cmd == "remove")
{
bool add = (cmd == "add");
bool all = (parameters.size() >= 2 && parameters[1] == "all");
if (parameters.size() >= 2 && !all)
return CR_WRONG_USAGE;
if (workshop && all)
{
for (unsigned i = 0; i < workshop->jobs.size(); i++)
{
df::job *job = workshop->jobs[i];
if (add)
{
if (!job->flags.bits.repeat)
continue;
remember_job(c, job);
return CR_OK;
}
else
else if (cmd == "list")
{
if (ProtectedJob *pj = get_known(job->id))
forget_job(c, pj);
}
}
for (int i = 0; i < constraints.size(); i++)
print_constraint(c, constraints[i]);
return CR_OK;
}
else if (workshop)
else if (cmd == "limit" || cmd == "limit-count")
{
if (!job) {
c->con.printerr("No job is selected in the current building.\n");
if (parameters.size() < 3)
return CR_WRONG_USAGE;
int limit = atoi(parameters[2].c_str());
if (limit <= 0) {
c->con.printerr("Invalid limit value.\n");
return CR_FAILURE;
}
if (add)
remember_job(c, job);
else if (ProtectedJob *pj = get_known(job->id))
forget_job(c, pj);
ItemConstraint *icv = get_constraint(c, parameters[1]);
if (!icv)
return CR_FAILURE;
icv->setGoalByCount(cmd == "limit-count");
icv->setGoalCount(limit);
if (parameters.size() >= 4)
icv->setGoalGap(atoi(parameters[3].c_str()));
map_job_constraints(c);
map_job_items(c);
print_constraint(c, icv);
return CR_OK;
}
else
else if (cmd == "unlimit")
{
if (!all) {
c->con.printerr("Please either select a job, or specify 'all'.\n");
if (parameters.size() != 2)
return CR_WRONG_USAGE;
}
if (add)
for (int i = 0; i < constraints.size(); i++)
{
for (std::map<int,df::job*>::iterator it = jobs.begin(); it != jobs.end(); it++)
if (it->second->flags.bits.repeat)
remember_job(c, it->second);
}
else
{
pending_recover.clear();
if (constraints[i]->config.val() != parameters[1])
continue;
while (!known_jobs.empty())
forget_job(c, known_jobs.begin()->second);
}
delete_constraint(c, constraints[i]);
return CR_OK;
}
c->con.printerr("Constraint not found: %s\n", parameters[1].c_str());
return CR_FAILURE;
}
else
return CR_WRONG_USAGE;