#include "Core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::vector; using std::string; using std::endl; using namespace DFHack; using namespace df::enums; using df::global::world; using df::global::ui; using df::global::ui_workshop_job_cursor; using df::global::job_next_id; /* Plugin registration */ static command_result workflow_cmd(Core *c, vector & parameters); static void init_state(Core *c); static void cleanup_state(Core *c); DFhackCExport const char * plugin_name ( void ) { return "workflow"; } DFhackCExport command_result plugin_init (Core *c, std::vector &commands) { commands.clear(); if (!world || !ui) return CR_FAILURE; if (ui_workshop_job_cursor && job_next_id) { commands.push_back( PluginCommand( "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" " this is intended to reduce the frequency of jobs being toggled.\n" "Constraint examples:\n" " workflow limit AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" " workflow limit AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" " Keep metal bolts within 900-1000, and wood/bone within 150-200.\n" " workflow limit-count FOOD 120 30\n" " workflow limit-count DRINK 120 30\n" " Keep the number of prepared food & drink stacks between 90 and 120\n" " workflow limit BIN 30\n" " workflow limit BARREL 30\n" " workflow limit BOX/CLOTH,SILK,YARN 30\n" " Make sure there are always 25-30 empty bins/barrels/bags.\n" " workflow limit BAR//COAL 20\n" " workflow limit BAR//COPPER 30\n" " Make sure there are always 15-20 coal and 25-30 copper bars.\n" " workflow limit-count POWDER_MISC/SAND 20\n" " workflow limit-count BOULDER/CLAY 20\n" " Collect 15-20 sand bags and clay boulders.\n" " workflow limit POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20\n" " Make sure there are always 80-100 units of dimple dye.\n" " In order for this to work, you have to set the material of\n" " the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE\n" " using the 'job item-material' command.\n" ) ); } init_state(c); return CR_OK; } DFhackCExport command_result plugin_shutdown ( Core * c ) { cleanup_state(c); return CR_OK; } DFhackCExport command_result plugin_onstatechange(Core* c, state_change_event event) { switch (event) { case SC_GAME_LOADED: cleanup_state(c); init_state(c); break; case SC_GAME_UNLOADED: cleanup_state(c); break; default: break; } return CR_OK; } /*******************************/ struct ItemConstraint; struct ProtectedJob { int id; int building_id; int check_idx; bool live; df::building *holder; df::job *job_copy; int reaction_id; 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; reaction_id = -1; } ~ProtectedJob() { deleteJobStruct(job_copy); } void update(df::job *job) { actual_job = job; if (*job == *job_copy) return; reaction_id = -1; deleteJobStruct(job_copy); job_copy = cloneJobStruct(job); } }; typedef std::map, bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; ItemTypeInfo item; MaterialInfo material; df::dfhack_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) { TKnownJobs::iterator it = known_jobs.find(id); return (it != known_jobs.end()) ? it->second : NULL; } static bool isSupportedJob(df::job *job) { return job->misc_links.empty() && getJobHolder(job) && (!job->job_items.empty() || job->job_type == job_type::CollectClay || job->job_type == job_type::CollectSand); } static void enumLiveJobs(std::map &rv) { df::job_list_link *p = world->job_list.next; for (; p; p = p->next) rv[p->item->id] = p->item; } /*******************************/ 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 cleanup_state(Core *c) { config = PersistentDataItem(); stop_protect(c); for (unsigned i = 0; i < constraints.size(); i++) delete constraints[i]; constraints.clear(); } static bool check_lost_jobs(Core *c); static ItemConstraint *get_constraint(Core *c, const std::string &str, PersistentDataItem *cfg = NULL); static void start_protect(Core *c) { check_lost_jobs(c); if (!known_jobs.empty()) c->con.print("Protecting %d jobs.\n", known_jobs.size()); } static void init_state(Core *c) { config = c->getWorld()->GetPersistentData("workflow/config"); enabled = config.isValid() && config.ival(0) != -1 && (config.ival(0) & CF_ENABLED); // 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 (!enabled) return; start_protect(c); } static void enable_plugin(Core *c) { if (!config.isValid()) { config = c->getWorld()->AddPersistentData("workflow/config"); config.ival(0) = 0; } config.ival(0) |= CF_ENABLED; enabled = true; c->con << "Enabling the plugin." << endl; start_protect(c); } /*******************************/ static void forget_job(Core *c, ProtectedJob *pj) { known_jobs.erase(pj->id); delete pj; } static bool recover_job(Core *c, ProtectedJob *pj) { // Check that the building exists pj->holder = df::building::find(pj->building_id); if (!pj->holder) { c->con.printerr("Forgetting job %d (%s): holder building lost.", pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type)); forget_job(c, pj); return true; } // Check its state and postpone or cancel if invalid if (pj->holder->jobs.size() >= 10) { c->con.printerr("Forgetting job %d (%s): holder building has too many jobs.", pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type)); forget_job(c, pj); return true; } if (!pj->holder->jobs.empty()) { df::job_type ftype = pj->holder->jobs[0]->job_type; if (ftype == job_type::DestroyBuilding) return false; if (ENUM_ATTR(job_type,type,ftype) == job_type_class::StrangeMood) return false; } // 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 (!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; } pj->holder->jobs.push_back(recovered); // Done pj->actual_job = recovered; pj->live = true; return true; } static bool check_lost_jobs(Core *c) { static int check = 1; check++; df::job_list_link *p = world->job_list.next; for (; p; p = p->next) { df::job *job = p->item; 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) { 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->update(p->item); } } 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 (!enabled) return CR_OK; static unsigned cnt = 0; static unsigned last_rlen = 0; cnt++; if ((cnt % 5) == 0) { 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 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; df::dfhack_material_category mat_mask; std::string maskstr = vector_get(tokens,1); 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 != 0) weight += 100; MaterialInfo material; std::string matstr = vector_get(tokens,2); 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); 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; } 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 link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t isubtype, df::dfhack_material_category mat_mask, int16_t mat_type, int32_t 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 (!mat.matches(ct->material)) continue; if (ct->mat_mask.whole) { if (mat.isValid()) { if (!mat.matches(ct->mat_mask)) continue; } else { if (!(mat_mask.whole & ct->mat_mask.whole)) continue; } } if (linear_index(pj->constraints, ct) >= 0) continue; ct->jobs.push_back(pj); pj->constraints.push_back(ct); } } static void compute_custom_job(ProtectedJob *pj, df::job *job) { if (pj->reaction_id < 0) pj->reaction_id = linear_index(df::reaction::get_vector(), &df::reaction::code, job->reaction_name); df::reaction *r = df::reaction::find(pj->reaction_id); if (!r) return; for (unsigned i = 0; i < r->products.size(); i++) { using namespace df::enums::reaction_product_item_flags; VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); if (!prod || prod->item_type < 0) continue; MaterialInfo mat(prod); bool get_mat_prod = prod->flags.is_set(GET_MATERIAL_PRODUCT); if (get_mat_prod || prod->flags.is_set(GET_MATERIAL_SAME)) { int reagent_idx = linear_index(r->reagents, &df::reaction_reagent::code, prod->get_material.reagent_code); if (reagent_idx < 0) continue; int item_idx = linear_index(job->job_items, &df::job_item::reagent_index, reagent_idx); if (item_idx >= 0) mat.decode(job->job_items[item_idx]); else { VIRTUAL_CAST_VAR(src, df::reaction_reagent_itemst, r->reagents[reagent_idx]); if (!src) continue; mat.decode(src); } if (get_mat_prod) { if (!mat.isValid()) continue; int idx = linear_index(mat.material->reaction_product.id, prod->get_material.product_code); if (idx < 0) continue; mat.decode(mat.material->reaction_product.material, idx); } } link_job_constraint(pj, prod->item_type, prod->item_subtype, 0, mat.type, mat.index); } } static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_material_category &mat_mask) { using namespace df::enums::job_type; if (job->job_type == PrepareMeal) mat.decode(-1); else mat.decode(job); mat_mask.whole = job->material_category.whole; // Material from the job enum const char *job_material = ENUM_ATTR(job_type, material, job->job_type); if (job_material) { MaterialInfo info; if (info.findBuiltin(job_material)) mat = info; else parseJobMaterialCategory(&mat_mask, job_material); } // Material from the job reagent if (!mat.isValid() && job->job_items.size() == 1) { mat.decode(job->job_items[0]); switch (job->job_items[0]->item_type) { case item_type::WOOD: mat_mask.bits.wood = mat_mask.bits.wood2 = true; break; } } } static void compute_job_outputs(Core *c, ProtectedJob *pj) { using namespace df::enums::job_type; // Custom reactions handled in another function df::job *job = pj->job_copy; if (job->job_type == CustomReaction) { compute_custom_job(pj, job); return; } // Item type & subtype df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); int16_t isubtype = job->item_subtype; if (itype == item_type::NONE) return; // Item material & material category MaterialInfo mat; df::dfhack_material_category mat_mask; guess_job_material(job, mat, mat_mask); // Job-specific code switch (job->job_type) { case SmeltOre: if (mat.inorganic) { std::vector &ores = mat.inorganic->metal_ore.mat_index; for (unsigned i = 0; i < ores.size(); i++) link_job_constraint(pj, item_type::BAR, -1, 0, 0, ores[i]); } return; case ExtractMetalStrands: if (mat.inorganic) { std::vector &threads = mat.inorganic->thread_metal.mat_index; for (unsigned i = 0; i < threads.size(); i++) link_job_constraint(pj, item_type::THREAD, -1, 0, 0, threads[i]); } return; case PrepareMeal: if (job->mat_type != -1) { std::vector &food = df::itemdef_foodst::get_vector(); for (unsigned i = 0; i < food.size(); i++) if (food[i]->level == job->mat_type) link_job_constraint(pj, item_type::FOOD, i, 0, -1, -1); return; } break; #define PLANT_PROCESS_MAT(flag, tag) \ if (!mat.isValid() && !job->job_items.empty()\ && job->job_items[0]->item_type == item_type::PLANT) \ mat.decode(job->job_items[0]); \ if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \ mat.decode(mat.plant->material_defs.type_##tag, \ mat.plant->material_defs.idx_##tag); \ else mat.decode(-1); case MillPlants: PLANT_PROCESS_MAT(MILL, mill); break; case ProcessPlants: PLANT_PROCESS_MAT(THREAD, thread); break; case ProcessPlantsBag: PLANT_PROCESS_MAT(LEAVES, leaves); break; case ProcessPlantsBarrel: PLANT_PROCESS_MAT(EXTRACT_BARREL, extract_barrel); break; case ProcessPlantsVial: PLANT_PROCESS_MAT(EXTRACT_VIAL, extract_vial); break; case ExtractFromPlants: PLANT_PROCESS_MAT(EXTRACT_STILL_VIAL, extract_still_vial); break; #undef PLANT_PROCESS_MAT default: break; } link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index); } 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; compute_job_outputs(c, it->second); } } 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 bool itemInRealJob(df::item *item) { if (!item->flags.bits.in_job) return false; if (item->jobs.size() != 1 || item->jobs[0]->unk1 != 2 || item->jobs[0]->job == NULL) return true; return ENUM_ATTR(job_type, type, item->jobs[0]->job->job_type) != job_type_class::Hauling; } 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(artifact1); #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; if (itemInRealJob(item)) continue; 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); ok = mat.matches(cv->material) && (cv->mat_mask.whole == 0 || mat.matches(cv->mat_mask)); cv->material_cache[matkey] = ok; } if (!ok) continue; if (item->flags.bits.owned || item->isAssignedToStockpile() || itemNotEmpty(item)) { 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 (resume_weight >= 0 && resume_weight >= suspend_weight) goal = false; else if (suspend_weight >= 0 && suspend_weight >= resume_weight) goal = true; 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 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 << ": "; if (job->job_type != job_type::CustomReaction) c->con << ENUM_KEY_STR(job_type, job->job_type); else c->con << job->reaction_name; MaterialInfo mat; df::dfhack_material_category mat_mask; guess_job_material(job, mat, mat_mask); if (mat.isValid()) c->con << " [" << mat.toString() << "]"; else if (mat_mask.whole) c->con << " [" << bitfieldToString(mat_mask) << "]"; 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 command_result workflow_cmd(Core *c, vector & parameters) { CoreSuspender suspend(c); 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; if (dwarfmode_hotkey(c, c->getTopViewscreen()) && ui->main.mode == ui_sidebar_mode::QueryBuilding) { workshop = world->selected_building; job = getSelectedWorkshopJob(c, true); } 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; if (cmd == "jobs") { if (workshop) { for (unsigned i = 0; i < workshop->jobs.size(); i++) print_job(c, get_known(workshop->jobs[i]->id)); } else { for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) if (it->second->live) print_job(c, it->second); } bool pending = false; for (unsigned i = 0; i < pending_recover.size(); i++) { if (!workshop || pending_recover[i]->holder == workshop) { if (!pending) { c->con.print("\nPending recovery:\n"); pending = true; } printJobDetails(c, pending_recover[i]->job_copy); } } return CR_OK; } else if (cmd == "list") { 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; int limit = atoi(parameters[2].c_str()); if (limit <= 0) { c->con.printerr("Invalid limit value.\n"); return CR_FAILURE; } 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())); else icv->setGoalGap(-1); 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; }