diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 0a1319d2c..464cf468d 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include 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 & parameters); +static command_result workflow_cmd(Core *c, vector & 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 & 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 [cnt-gap]\n" + " workflow limit-count [cnt-gap]\n" + " Set a constraint. The second form counts each stack as 1 item.\n" + " workflow unlimit \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 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 protected_cfg; +typedef std::map, bool> TMaterialCache; + +struct ItemConstraint { + PersistentDataItem config; + + ItemTypeInfo item; + + MaterialInfo material; + df::job_material_category mat_mask; + + int weight; + std::vector 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 TKnownJobs; static TKnownJobs known_jobs; static std::vector pending_recover; +static std::vector 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 &rv) { df::job_list_link *p = world->job_list.next; @@ -151,129 +249,91 @@ static void enumLiveJobs(std::map &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 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 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++) + if (!config.isValid()) { - PersistentDataItem &item = protected_cfg[i]; - for (int j = 0; j < PersistentDataItem::NumInts; j++) - { - 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,23 +397,39 @@ 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) { if (it->second->check_idx == check || !it->second->live) 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 ((cnt % 500) == 0) - update_job_data(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 & parameters) +/*******************************/ + +static ItemConstraint *get_constraint(Core *c, const std::string &str, PersistentDataItem *cfg) +{ + std::vector 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(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 &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 & 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 & parameters) job = getSelectedWorkshopJob(c, true); } - std::map 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; + } + + 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; - std::string cmd = parameters[0]; - if (cmd == "list") + 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 & parameters) printJobDetails(c, pending_recover[i]->job_copy); } } + + return CR_OK; } - else if (cmd == "add" || cmd == "remove") + else if (cmd == "list") { - bool add = (cmd == "add"); - bool all = (parameters.size() >= 2 && parameters[1] == "all"); - if (parameters.size() >= 2 && !all) + for (int i = 0; i < constraints.size(); i++) + print_constraint(c, constraints[i]); + + return CR_OK; + } + else if (cmd == "limit" || cmd == "limit-count") + { + if (parameters.size() < 3) 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); - } - else - { - if (ProtectedJob *pj = get_known(job->id)) - forget_job(c, pj); - } - } + int limit = atoi(parameters[2].c_str()); + if (limit <= 0) { + c->con.printerr("Invalid limit value.\n"); + return CR_FAILURE; } - else if (workshop) - { - if (!job) { - c->con.printerr("No job is selected in the current building.\n"); - return CR_FAILURE; - } - if (add) - remember_job(c, job); - else if (ProtectedJob *pj = get_known(job->id)) - forget_job(c, pj); - } - else - { - if (!all) { - c->con.printerr("Please either select a job, or specify 'all'.\n"); - return CR_WRONG_USAGE; - } + ItemConstraint *icv = get_constraint(c, parameters[1]); + if (!icv) + return CR_FAILURE; - if (add) - { - for (std::map::iterator it = jobs.begin(); it != jobs.end(); it++) - if (it->second->flags.bits.repeat) - remember_job(c, it->second); - } - else - { - pending_recover.clear(); + icv->setGoalByCount(cmd == "limit-count"); + icv->setGoalCount(limit); + if (parameters.size() >= 4) + icv->setGoalGap(atoi(parameters[3].c_str())); - while (!known_jobs.empty()) - forget_job(c, known_jobs.begin()->second); - } + map_job_constraints(c); + map_job_items(c); + print_constraint(c, icv); + return CR_OK; + } + else if (cmd == "unlimit") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + for (int i = 0; i < constraints.size(); i++) + { + if (constraints[i]->config.val() != parameters[1]) + continue; + + 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;