From e8ee876ddc230d55d38979ea2fa3136ecc9ef2fa Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Thu, 29 Mar 2012 21:18:57 -0500
Subject: [PATCH 001/108] Track.
---
library/xml | 2 +-
plugins/stonesense | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/library/xml b/library/xml
index 3e1c72864..b41c666c6 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit 3e1c728640d8f5a9501908064a2d5385a156058c
+Subproject commit b41c666c6be6fe18906a98dababdc4ff681b8382
diff --git a/plugins/stonesense b/plugins/stonesense
index 906d6439d..07ddc8c13 160000
--- a/plugins/stonesense
+++ b/plugins/stonesense
@@ -1 +1 @@
-Subproject commit 906d6439d4cb694cf287d63badabb675a8b89329
+Subproject commit 07ddc8c13e0bb38cde77937803e9320e2b26f2d9
From 564150bb192d1598a5ac277fb449551615eb5cd7 Mon Sep 17 00:00:00 2001
From: RossM
Date: Wed, 4 Apr 2012 23:33:26 -0700
Subject: [PATCH 002/108] Persist autolabor config in the savefile.
---
plugins/autolabor.cpp | 262 ++++++++++++++++++++++++++++++++++++------
1 file changed, 227 insertions(+), 35 deletions(-)
diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp
index fedde8f33..bcf725559 100644
--- a/plugins/autolabor.cpp
+++ b/plugins/autolabor.cpp
@@ -9,6 +9,8 @@
#include
#include
+#include "modules/World.h"
+
// DF data structure definition headers
#include "DataDefs.h"
#include
@@ -62,6 +64,13 @@ static bool print_debug = 0;
static std::vector state_count(5);
+static PersistentDataItem config;
+
+enum ConfigFlags {
+ CF_ENABLED = 1,
+};
+
+
// Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
command_result autolabor (color_ostream &out, std::vector & parameters);
@@ -333,6 +342,27 @@ static const dwarf_state dwarf_states[] = {
};
struct labor_info
+{
+ PersistentDataItem config;
+
+ bool is_exclusive;
+ int active_dwarfs;
+
+ labor_mode mode() { return (labor_mode) config.ival(1); }
+ void set_mode(labor_mode mode) { config.ival(1) = mode; }
+
+ int minimum_dwarfs() { return config.ival(2); }
+ void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(2) = minimum_dwarfs; }
+
+ int maximum_dwarfs() { return config.ival(3); }
+ void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(3) = maximum_dwarfs; }
+
+ df::enums::unit_labor::unit_labor labor() { return (df::enums::unit_labor::unit_labor) config.ival(0); }
+ void set_labor(df::enums::unit_labor::unit_labor labor) { config.ival(0) = labor; }
+
+};
+
+struct labor_default
{
labor_mode mode;
bool is_exclusive;
@@ -341,9 +371,9 @@ struct labor_info
int active_dwarfs;
};
-static struct labor_info* labor_infos;
+static std::vector labor_infos;
-static const struct labor_info default_labor_infos[] = {
+static const struct labor_default default_labor_infos[] = {
/* MINE */ {AUTOMATIC, true, 2, 200, 0},
/* HAUL_STONE */ {HAULERS, false, 1, 200, 0},
/* HAUL_WOOD */ {HAULERS, false, 1, 200, 0},
@@ -436,15 +466,102 @@ struct dwarf_info_t
bool has_exclusive_labor;
};
+static bool isOptionEnabled(unsigned flag)
+{
+ return config.isValid() && (config.ival(0) & flag) != 0;
+}
+
+static void setOptionEnabled(ConfigFlags flag, bool on)
+{
+ if (!config.isValid())
+ return;
+
+ if (on)
+ config.ival(0) |= flag;
+ else
+ config.ival(0) &= ~flag;
+}
+
+static void cleanup_state()
+{
+ labor_infos.clear();
+}
+
+static void reset_labor(df::enums::unit_labor::unit_labor labor)
+{
+ labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs);
+ labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs);
+ labor_infos[labor].set_mode(default_labor_infos[labor].mode);
+ labor_infos[labor].set_labor(labor);
+}
+
+static void init_state()
+{
+ auto pworld = Core::getInstance().getWorld();
+
+ config = pworld->GetPersistentData("autolabor/config");
+ if (config.isValid() && config.ival(0) == -1)
+ config.ival(0) = 0;
+
+ enable_autolabor = isOptionEnabled(CF_ENABLED);
+
+ if (!enable_autolabor)
+ return;
+
+ // Load labors from save
+ labor_infos.resize(ARRAY_COUNT(default_labor_infos));
+
+ std::vector items;
+ pworld->GetPersistentData(&items, "autolabor/labors");
+
+ for (auto p = items.begin(); p != items.end(); p++)
+ {
+ df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) p->ival(0);
+ if (labor >= 0 && labor <= labor_infos.size())
+ {
+ labor_infos[labor].config = *p;
+ labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive;
+ labor_infos[labor].active_dwarfs = 0;
+ }
+ }
+
+ // Add default labors for those not in save
+ for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) {
+ if (labor_infos[i].config.isValid())
+ continue;
+
+ labor_infos[i].config = pworld->AddPersistentData("autolabor/labors");
+
+ labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive;
+ labor_infos[i].active_dwarfs = 0;
+ reset_labor((df::enums::unit_labor::unit_labor) i);
+ }
+}
+
+static void enable_plugin(color_ostream &out)
+{
+ auto pworld = Core::getInstance().getWorld();
+
+ if (!config.isValid())
+ {
+ config = pworld->AddPersistentData("autolabor/config");
+ config.ival(0) = 0;
+ }
+
+ setOptionEnabled(CF_ENABLED, true);
+ enable_autolabor = true;
+ out << "Enabling the plugin." << endl;
+
+ cleanup_state();
+ init_state();
+}
+
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands)
{
// initialize labor infos table from default table
if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1)
return CR_FAILURE;
- labor_infos = new struct labor_info[ARRAY_COUNT(default_labor_infos)];
- for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) {
- labor_infos[i] = default_labor_infos[i];
- }
+
// Fill the command list with your commands.
commands.push_back(PluginCommand(
"autolabor", "Automatically manage dwarf labors.",
@@ -459,8 +576,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector disable\n"
" Turn off autolabor for a specific labor.\n"
+ " autolabor reset\n"
+ " Return a labor to the default handling.\n"
+ " autolabor reset-all\n"
+ " Return all labors to the default handling.\n"
" autolabor list\n"
" List current status of all labors.\n"
+ " autolabor status\n"
+ " Show basic status information.\n"
"Function:\n"
" When enabled, autolabor periodically checks your dwarves and enables or\n"
" disables labors. It tries to keep as many dwarves as possible busy but\n"
@@ -477,13 +600,15 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & values;
};
+DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
+{
+ switch (event) {
+ case SC_MAP_LOADED:
+ cleanup_state();
+ init_state();
+ break;
+ case SC_MAP_UNLOADED:
+ cleanup_state();
+ break;
+ default:
+ break;
+ }
+
+ return CR_OK;
+}
+
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
static int step_count = 0;
@@ -757,7 +899,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
auto labor = *lp;
- if (labor_infos[labor].mode != DISABLE)
+ if (labor_infos[labor].mode() != DISABLE)
continue;
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
@@ -785,7 +927,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
df::job_skill skill = labor_to_skill[labor];
- if (labor_infos[labor].mode != AUTOMATIC)
+ if (labor_infos[labor].mode() != AUTOMATIC)
continue;
int best_dwarf = 0;
@@ -796,7 +938,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
std::map dwarf_skill;
std::vector previously_enabled(n_dwarfs);
- auto mode = labor_infos[labor].mode;
+ auto mode = labor_infos[labor].mode();
// Find candidate dwarfs, and calculate a preference value for each dwarf
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
@@ -867,8 +1009,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarfs[dwarf]->status.labors[labor] = false;
}
- int min_dwarfs = labor_infos[labor].minimum_dwarfs;
- int max_dwarfs = labor_infos[labor].maximum_dwarfs;
+ int min_dwarfs = labor_infos[labor].minimum_dwarfs();
+ int max_dwarfs = labor_infos[labor].maximum_dwarfs();
// Special - don't assign hunt without a butchers, or fish without a fishery
if (df::enums::unit_labor::HUNT == labor && !has_butchers)
@@ -964,7 +1106,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
assert(labor < ARRAY_COUNT(labor_infos));
*/
- if (labor_infos[labor].mode != HAULERS)
+ if (labor_infos[labor].mode() != HAULERS)
continue;
for (int i = 0; i < num_haulers; i++)
@@ -1009,31 +1151,58 @@ void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out)
out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' ';
- if (labor_infos[labor].mode == DISABLE)
+ if (labor_infos[labor].mode() == DISABLE)
out << "disabled" << endl;
else
{
- if (labor_infos[labor].mode == HAULERS)
+ if (labor_infos[labor].mode() == HAULERS)
out << "haulers";
else
- out << "minimum " << labor_infos[labor].minimum_dwarfs << ", maximum " << labor_infos[labor].maximum_dwarfs;
+ out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs();
out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl;
}
}
command_result autolabor (color_ostream &out, std::vector & parameters)
{
+ CoreSuspender suspend;
+
+ if (!Core::getInstance().isWorldLoaded()) {
+ out.printerr("World is not loaded: please load a game first.\n");
+ return CR_FAILURE;
+ }
+
if (parameters.size() == 1 &&
(parameters[0] == "0" || parameters[0] == "enable" ||
parameters[0] == "1" || parameters[0] == "disable"))
{
- if (parameters[0] == "0" || parameters[0] == "disable")
- enable_autolabor = 0;
+ bool enable = (parameters[0] == "1" || parameters[0] == "enable");
+ if (enable && !enable_autolabor)
+ {
+ enable_plugin(out);
+ }
else
- enable_autolabor = 1;
- out.print("autolabor %sactivated.\n", (enable_autolabor ? "" : "de"));
+ {
+ if (enable_autolabor)
+ {
+ enable_autolabor = false;
+ setOptionEnabled(CF_ENABLED, false);
+ }
+
+ out << "The plugin is disabled." << endl;
+ return CR_OK;
+ }
+
+ return CR_OK;
}
- else if (parameters.size() == 2 || parameters.size() == 3) {
+
+ if (!enable_autolabor)
+ {
+ out << "Error: The plugin is not enabled." << endl;
+ return CR_FAILURE;
+ }
+
+ if (parameters.size() == 2 || parameters.size() == 3) {
df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE;
FOR_ENUM_ITEMS(unit_labor, test_labor)
@@ -1050,13 +1219,19 @@ command_result autolabor (color_ostream &out, std::vector & parame
if (parameters[1] == "haulers")
{
- labor_infos[labor].mode = HAULERS;
+ labor_infos[labor].set_mode(HAULERS);
print_labor(labor, out);
return CR_OK;
}
if (parameters[1] == "disable")
{
- labor_infos[labor].mode = DISABLE;
+ labor_infos[labor].set_mode(DISABLE);
+ print_labor(labor, out);
+ return CR_OK;
+ }
+ if (parameters[1] == "reset")
+ {
+ reset_labor(labor);
print_labor(labor, out);
return CR_OK;
}
@@ -1072,12 +1247,22 @@ command_result autolabor (color_ostream &out, std::vector & parame
return CR_WRONG_USAGE;
}
- labor_infos[labor].minimum_dwarfs = minimum;
- labor_infos[labor].maximum_dwarfs = maximum;
- labor_infos[labor].mode = AUTOMATIC;
+ labor_infos[labor].set_minimum_dwarfs(minimum);
+ labor_infos[labor].set_maximum_dwarfs(maximum);
+ labor_infos[labor].set_mode(AUTOMATIC);
print_labor(labor, out);
+
+ return CR_OK;
}
- else if (parameters.size() == 1 && parameters[0] == "list") {
+ else if (parameters.size() == 1 && parameters[0] == "reset-all") {
+ for (int i = 0; i < labor_infos.size(); i++)
+ {
+ reset_labor((df::enums::unit_labor::unit_labor) i);
+ }
+ out << "All labors reset." << endl;
+ return CR_OK;
+ }
+ else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") {
if (!enable_autolabor)
{
out << "autolabor not activated." << endl;
@@ -1096,23 +1281,30 @@ command_result autolabor (color_ostream &out, std::vector & parame
}
out << endl;
- FOR_ENUM_ITEMS(unit_labor, labor)
+ if (parameters[0] == "list")
{
- if (labor == df::enums::unit_labor::NONE)
- continue;
+ FOR_ENUM_ITEMS(unit_labor, labor)
+ {
+ if (labor == df::enums::unit_labor::NONE)
+ continue;
- print_labor(labor, out);
+ print_labor(labor, out);
+ }
}
+
+ return CR_OK;
}
else if (parameters.size() == 1 && parameters[0] == "debug") {
print_debug = 1;
+
+ return CR_OK;
}
else
{
out.print("Automatically assigns labors to dwarves.\n"
"Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n"
"Current state: %d.\n", enable_autolabor);
- }
- return CR_OK;
+ return CR_OK;
+ }
}
From d1b27418a6a39aa15d51465e61975c5230dcc9d6 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 5 Apr 2012 11:32:23 +0400
Subject: [PATCH 003/108] Add a World::GetPersistentData version that auto-adds
if not found.
---
library/include/DataFuncs.h | 4 ++++
library/include/DataIdentity.h | 4 +++-
library/include/modules/World.h | 8 ++++++++
library/modules/World.cpp | 15 +++++++++++++++
4 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h
index 822dcd45d..109b2c176 100644
--- a/library/include/DataFuncs.h
+++ b/library/include/DataFuncs.h
@@ -32,6 +32,10 @@ distribution.
#include "DataIdentity.h"
#include "LuaWrapper.h"
+#ifndef BUILD_DFHACK_LIB
+#error Due to export issues this header is internal to the main library.
+#endif
+
namespace df {
// A very simple and stupid implementation of some stuff from boost
template struct is_same_type { static const bool value = false; };
diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h
index c83b2092a..bab87e1bb 100644
--- a/library/include/DataIdentity.h
+++ b/library/include/DataIdentity.h
@@ -160,6 +160,8 @@ namespace DFHack
};
}
+// Due to export issues, this stuff can only work in the main dll
+#ifdef BUILD_DFHACK_LIB
namespace df
{
using DFHack::function_identity_base;
@@ -575,4 +577,4 @@ namespace df
return &identity;
}
}
-
+#endif
diff --git a/library/include/modules/World.h b/library/include/modules/World.h
index 9ed6a3ed9..e7b17ce41 100644
--- a/library/include/modules/World.h
+++ b/library/include/modules/World.h
@@ -139,8 +139,16 @@ namespace DFHack
PersistentDataItem AddPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(int entry_id);
+ // Calls GetPersistentData(key); if not found, adds and sets added to true.
+ // The result can still be not isValid() e.g. if the world is not loaded.
+ PersistentDataItem GetPersistentData(const std::string &key, bool *added);
+ // Lists all items with the given key.
+ // If prefix is true, search for keys starting with key+"/".
+ // GetPersistentData(&vec,"",true) returns all items.
+ // Items have alphabetic order by key; same key ordering is undefined.
void GetPersistentData(std::vector *vec,
const std::string &key, bool prefix = false);
+ // Deletes the item; returns true if success.
bool DeletePersistentData(const PersistentDataItem &item);
void ClearPersistentCache();
diff --git a/library/modules/World.cpp b/library/modules/World.cpp
index d570abaec..ca5d0aee0 100644
--- a/library/modules/World.cpp
+++ b/library/modules/World.cpp
@@ -300,6 +300,21 @@ PersistentDataItem World::GetPersistentData(int entry_id)
return PersistentDataItem();
}
+PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
+{
+ *added = false;
+
+ PersistentDataItem rv = GetPersistentData(key);
+
+ if (!rv.isValid())
+ {
+ *added = true;
+ rv = AddPersistentData(key);
+ }
+
+ return rv;
+}
+
void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix)
{
if (!BuildPersistentCache())
From 3afed43cdb435c788964d983a358d97ee291c2fc Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 5 Apr 2012 11:59:39 +0400
Subject: [PATCH 004/108] Experimental: try wrapping a dfhack api function.
---
library/LuaTools.cpp | 6 ++++++
library/LuaTypes.cpp | 11 ++++++++---
library/LuaWrapper.cpp | 5 ++++-
library/include/DataFuncs.h | 22 ++++++++++++++++++++--
library/include/LuaWrapper.h | 5 +++++
5 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 5a50d0dd2..16c60457a 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -35,14 +35,18 @@ distribution.
// must be last due to MS stupidity
#include "DataDefs.h"
#include "DataIdentity.h"
+#include "DataFuncs.h"
#include "modules/World.h"
+#include "modules/Gui.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
#include "MiscUtils.h"
+#include "df/job.h"
+
#include
#include
#include
@@ -868,6 +872,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
OpenPersistent(state);
+ LuaWrapper::AddMethodWrapper(state, 0, -1, "getSelectedJob", df::wrap_function(&Gui::getSelectedJob));
+
lua_setglobal(state, "dfhack");
// load dfhack.lua
diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp
index 6d5961f8d..2cc929ee9 100644
--- a/library/LuaTypes.cpp
+++ b/library/LuaTypes.cpp
@@ -1029,11 +1029,16 @@ static int meta_call_function(lua_State *state)
/**
* Create a closure invoking the given function, and add it to the field table.
*/
-static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
- const char *name, function_identity_base *fun)
+void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
+ const char *name, function_identity_base *fun)
{
+ field_idx = lua_absindex(state, field_idx);
+
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
- lua_pushvalue(state, meta_idx);
+ if (meta_idx)
+ lua_pushvalue(state, meta_idx);
+ else
+ lua_pushlightuserdata(state, NULL); // can't be a metatable
lua_pushfstring(state, "%s()", name);
lua_pushlightuserdata(state, fun);
lua_pushcclosure(state, meta_call_function, 4);
diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp
index 818e6b9e1..2f3958efe 100644
--- a/library/LuaWrapper.cpp
+++ b/library/LuaWrapper.cpp
@@ -53,7 +53,10 @@ static luaL_Reg no_functions[] = { { NULL, NULL } };
*/
void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode)
{
- lua_getfield(state, UPVAL_METATABLE, "__metatable");
+ if (lua_islightuserdata(state, UPVAL_METATABLE))
+ lua_pushstring(state, "(global)");
+ else
+ lua_getfield(state, UPVAL_METATABLE, "__metatable");
const char *cname = lua_tostring(state, -1);
const char *fname = index ? lua_tostring(state, index) : "*";
luaL_error(state, "Cannot %s field %s.%s: %s.",
diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h
index 109b2c176..3529d8059 100644
--- a/library/include/DataFuncs.h
+++ b/library/include/DataFuncs.h
@@ -67,10 +67,15 @@ namespace df {
CT *self = (CT*)DFHack::LuaWrapper::get_object_addr(state, base++, UPVAL_METHOD_NAME, "invoke");
#define LOAD_ARG(type) \
type v##type; df::identity_traits::get()->lua_write(state, UPVAL_METHOD_NAME, &v##type, base++);
+#define OSTREAM_ARG DFHack::color_ostream&
+#define LOAD_OSTREAM(name) \
+ DFHack::color_ostream_proxy name(DFHack::Core::getInstance().getConsole());
-#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \
+#define INSTANTIATE_RETURN_TYPE(FArgs) \
template struct return_type
+
+
+
Thin wrappers around C++ functions, similar to the ones for virtual methods.
+
+
+
+dfhack.gui.getSelectedWorkshopJob(silent)
+When a job is selected in 'q' mode, returns the job, else
+prints error unless silent and returns nil.
+
+dfhack.gui.getSelectedJob(silent)
+Returns the job selected in a workshop or unit/jobs screen.
+
+dfhack.gui.getSelectedUnit(silent)
+Returns the unit selected via 'v', 'k', unit/jobs, or
+a full-screen item view of a cage or suchlike.
+
+dfhack.gui.getSelectedItem(silent)
+Returns the item selected via 'v' ->inventory, 'k', 't', or
+a full-screen item view of a container. Note that in the
+last case, the highlighted contained item is returned, not
+the container itself.
+
+dfhack.gui.showAnnouncement(text,color,is_bright)
+Adds a regular announcement with given text, color, and brightness.
+The is_bright boolean actually seems to invert the brightness.
+
+dfhack.gui.showPopupAnnouncement(text,color,is_bright)
+Pops up a titan-style modal announcement window.
+
+
+
+
+
+
+dfhack.job.cloneJobStruct(job)
+Creates a deep copy of the given job.
+
+dfhack.job.printJobDetails(job)
+Prints info about the job.
+
+dfhack.job.getJobHolder(job)
+Returns the building holding the job.
+
+dfhack.job.is_equal(job1,job2)
+Compares important fields in the job and nested item structures.
+
+dfhack.job.is_item_equal(job_item1,job_item2)
+Compares important fields in the job item structures.
+
+
+
+