diff --git a/Memory.xml b/Memory.xml
index 729759445..62f20f9ab 100644
--- a/Memory.xml
+++ b/Memory.xml
@@ -1094,6 +1094,7 @@
+
@@ -2345,6 +2346,8 @@
+
+
cmake
@@ -3230,6 +3233,8 @@
+
+
diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h
index d3899854d..ff61d08f5 100644
--- a/library/include/DataDefs.h
+++ b/library/include/DataDefs.h
@@ -204,6 +204,7 @@ namespace df {
GLOBAL(gview,interface) \
GLOBAL(init,init) \
GLOBAL(d_init,d_init) \
+ SIMPLE_GLOBAL(job_next_id,int) \
SIMPLE_GLOBAL(ui_look_cursor,int) \
SIMPLE_GLOBAL(ui_workshop_job_cursor,int) \
SIMPLE_GLOBAL(ui_workshop_in_add,bool) \
diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h
index 05be881ee..961904e5c 100644
--- a/library/include/MiscUtils.h
+++ b/library/include/MiscUtils.h
@@ -172,6 +172,20 @@ CT *binsearch_in_vector(const std::vector &vec, FT CT::*field, FT value)
return idx < 0 ? NULL : vec[idx];
}
+/*
+ * List
+ */
+
+template
+Link *linked_list_append(Link *head, Link *tail)
+{
+ while (head->next)
+ head = head->next;
+ head->next = tail;
+ tail->prev = head;
+ return tail;
+}
+
/*
* MISC
*/
diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp
index 71ecc2b2c..bca8696f5 100644
--- a/plugins/jobutils.cpp
+++ b/plugins/jobutils.cpp
@@ -17,8 +17,11 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
using std::vector;
using std::string;
@@ -30,13 +33,16 @@ using df::global::world;
using df::global::ui;
using df::global::ui_build_selector;
using df::global::ui_workshop_job_cursor;
+using df::global::job_next_id;
-static bool wokshop_job_hotkey(Core *c, df::viewscreen *top);
-static bool build_selector_hotkey(Core *c, df::viewscreen *top);
+/* Plugin registration */
+static bool workshop_job_hotkey(Core *c, df::viewscreen *top);
+static bool build_selector_hotkey(Core *c, df::viewscreen *top);
static bool job_material_hotkey(Core *c, df::viewscreen *top);
-static command_result job_material(Core *c, vector & parameters);
+static command_result job_material(Core *c, vector & parameters);
+static command_result job_duplicate(Core *c, vector & parameters);
static command_result job_cmd(Core *c, vector & parameters);
DFhackCExport const char * plugin_name ( void )
@@ -75,6 +81,17 @@ DFhackCExport command_result plugin_init (Core *c, std::vector &
);
}
+ if (ui_workshop_job_cursor && job_next_id) {
+ commands.push_back(
+ PluginCommand(
+ "job-duplicate", "Duplicate the selected job in a workshop.",
+ job_duplicate, workshop_job_hotkey,
+ " - In 'q' mode, when a job is highlighted within a workshop\n"
+ " or furnace building, instantly duplicates the job.\n"
+ )
+ );
+ }
+
return CR_OK;
}
@@ -83,6 +100,8 @@ DFhackCExport command_result plugin_shutdown ( Core * c )
return CR_OK;
}
+/* UI state guards */
+
static bool workshop_job_hotkey(Core *c, df::viewscreen *top)
{
using namespace ui_sidebar_mode;
@@ -150,6 +169,8 @@ static bool job_material_hotkey(Core *c, df::viewscreen *top)
build_selector_hotkey(c, top);
}
+/* job-material implementation */
+
static df::job *getWorkshopJob(Core *c)
{
df::building *selected = world->selected_building;
@@ -288,6 +309,79 @@ static command_result job_material(Core * c, vector & parameters)
return CR_WRONG_USAGE;
}
+/* job-duplicate implementation */
+
+static df::job *clone_job(df::job *job)
+{
+ df::job *pnew = new df::job(*job);
+
+ pnew->id = (*job_next_id)++;
+
+ // Clean out transient fields
+ pnew->flags.whole = 0;
+ pnew->flags.bits.repeat = job->flags.bits.repeat;
+
+ pnew->completion_timer = -1;
+ pnew->items.clear();
+ pnew->misc_links.clear();
+
+ // Link the job into the global list
+ pnew->list_link = new df::job_list_link();
+ pnew->list_link->item = pnew;
+
+ linked_list_append(&world->job_list, pnew->list_link);
+
+ // Clone refs
+ for (int i = pnew->references.size()-1; i >= 0; i--)
+ {
+ df::general_ref *ref = pnew->references[i];
+
+ if (virtual_cast(ref))
+ pnew->references.erase(pnew->references.begin()+i);
+ else
+ pnew->references[i] = ref->clone();
+ }
+
+ // Clone items
+ for (int i = pnew->job_items.size()-1; i >= 0; i--)
+ pnew->job_items[i] = new df::job_item(*pnew->job_items[i]);
+
+ return pnew;
+}
+
+static command_result job_duplicate(Core * c, vector & parameters)
+{
+ if (!parameters.empty())
+ return CR_WRONG_USAGE;
+
+ df::job *job = getWorkshopJob(c);
+ if (!job)
+ return CR_FAILURE;
+
+ if (!job->misc_links.empty() || job->job_items.empty())
+ {
+ c->con.printerr("Cannot duplicate job %s\n", ENUM_KEY_STR(job_type,job->job_type));
+ return CR_FAILURE;
+ }
+
+ df::building *building = world->selected_building;
+ if (building->jobs.size() >= 10)
+ {
+ c->con.printerr("Job list is already full.\n");
+ return CR_FAILURE;
+ }
+
+ // Actually clone
+ df::job *pnew = clone_job(job);
+
+ int pos = ++*ui_workshop_job_cursor;
+ building->jobs.insert(building->jobs.begin()+pos, pnew);
+
+ return CR_OK;
+}
+
+/* Main job command implementation */
+
static void print_job_item_details(Core *c, df::job *job, df::job_item *item)
{
c->con << " Input Item: " << ENUM_KEY_STR(item_type,item->item_type);