diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index b04733ee7..6a7bf068e 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -277,6 +277,7 @@ bool Plugin::load(color_ostream &con) plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable"); plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled"); plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); + plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports"); index_lua(plug); this->name = *plug_name; plugin_lib = plug; @@ -531,6 +532,16 @@ Plugin::plugin_state Plugin::getState() const return state; } +PluginExports *Plugin::getExports() +{ + if (!plugin_get_exports) + return NULL; + PluginExports *exports = plugin_get_exports(); + if (!exports->bind(plugin_lib)) + return NULL; + return exports; +}; + void Plugin::index_lua(DFLibrary *lib) { if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) @@ -703,6 +714,19 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) lua_pushcclosure(state, lua_fun_wrapper, 4); } +bool PluginExports::bind(DFLibrary *lib) +{ + for (auto it = bindings.begin(); it != bindings.end(); ++it) + { + std::string name = it->first; + void** dest = it->second; + *dest = LookupPlugin(lib, name.c_str()); + if (!*dest) + return false; + } + return true; +} + PluginManager::PluginManager(Core * core) { cmdlist_mutex = new mutex(); @@ -766,6 +790,16 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command) return NULL; } +void *PluginManager::getPluginExports(const std::string &name) +{ + Plugin *plug = getPluginByName(name); + if (!plug) + return NULL; + if (plug->getState() != Plugin::plugin_state::PS_LOADED) + return NULL; + return plug->getExports(); +} + // FIXME: handle name collisions... command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters) { diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 35de36297..f051fe76d 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -50,6 +50,7 @@ namespace df namespace DFHack { class Core; + class PluginExports; class PluginManager; class virtual_identity; class RPCService; @@ -165,6 +166,7 @@ namespace DFHack command_result invoke(color_ostream &out, const std::string & command, std::vector & parameters); bool can_invoke_hotkey(const std::string & command, df::viewscreen *top ); plugin_state getState () const; + PluginExports *getExports(); RPCService *rpc_connect(color_ostream &out); @@ -227,7 +229,15 @@ namespace DFHack command_result (*plugin_enable)(color_ostream &, bool); RPCService* (*plugin_rpcconnect)(color_ostream &); command_result (*plugin_eval_ruby)(color_ostream &, const char*); + PluginExports* (*plugin_get_exports)(void); }; + class DFHACK_EXPORT PluginExports { + protected: + friend class Plugin; + std::map bindings; + bool bind(DFLibrary* lib); + }; + #define PLUGIN_EXPORT_BIND(name) bindings.insert(std::pair(#name, (void**)&this->name)) class DFHACK_EXPORT PluginManager { // PRIVATE METHODS @@ -244,6 +254,7 @@ namespace DFHack public: Plugin *getPluginByName (const std::string & name); Plugin *getPluginByCommand (const std::string &command); + void *getPluginExports(const std::string &name); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); Plugin* operator[] (std::size_t index) @@ -293,6 +304,17 @@ namespace DFHack DFhackDataExport bool plugin_is_enabled = false; \ bool &varname = plugin_is_enabled; +#define DFHACK_PLUGIN_EXPORTS(clsname) \ + DFhackCExport PluginExports* plugin_get_exports() \ + { \ + static clsname* instance = NULL; \ + if (!instance) \ + instance = new clsname; \ + return (PluginExports*)instance; \ + } +#define GET_PLUGIN_EXPORTS(plugname, clsname) \ + (clsname*)DFHack::Core::getInstance().getPluginManager()->getPluginExports(plugname) + #define DFHACK_PLUGIN_LUA_COMMANDS \ DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = #define DFHACK_PLUGIN_LUA_FUNCTIONS \ diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 20b5a913c..b7f9675f5 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -396,6 +396,11 @@ bool MaterialInfo::matches(const df::job_material_category &cat) using namespace df::enums::material_flags; TEST(plant, STRUCTURAL_PLANT_MAT); + TEST(plant, SEED_MAT); + TEST(plant, THREAD_PLANT); + TEST(plant, ALCOHOL_PLANT); + TEST(plant, POWDER_MISC_PLANT); + TEST(plant, LIQUID_MISC_PLANT); TEST(wood, WOOD); TEST(cloth, THREAD_PLANT); TEST(silk, SILK); diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 31f5aa077..0ab28ca65 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -109,6 +109,9 @@ enum dwarf_state { // Busy with a useful task BUSY, + // Busy with a useful task that requires a tool + EXCLUSIVE, + // In the military, can't work MILITARY, @@ -119,11 +122,12 @@ enum dwarf_state { OTHER }; -const int NUM_STATE = 5; +const int NUM_STATE = 6; static const char *state_names[] = { "IDLE", "BUSY", + "EXCLUSIVE", "MILITARY", "CHILD", "OTHER", @@ -133,13 +137,13 @@ static const dwarf_state dwarf_states[] = { BUSY /* CarveFortification */, BUSY /* DetailWall */, BUSY /* DetailFloor */, - BUSY /* Dig */, - BUSY /* CarveUpwardStaircase */, - BUSY /* CarveDownwardStaircase */, - BUSY /* CarveUpDownStaircase */, - BUSY /* CarveRamp */, - BUSY /* DigChannel */, - BUSY /* FellTree */, + EXCLUSIVE /* Dig */, + EXCLUSIVE /* CarveUpwardStaircase */, + EXCLUSIVE /* CarveDownwardStaircase */, + EXCLUSIVE /* CarveUpDownStaircase */, + EXCLUSIVE /* CarveRamp */, + EXCLUSIVE /* DigChannel */, + EXCLUSIVE /* FellTree */, BUSY /* GatherPlants */, BUSY /* RemoveConstruction */, BUSY /* CollectWebs */, @@ -154,7 +158,7 @@ static const dwarf_state dwarf_states[] = { OTHER /* Sleep */, BUSY /* CollectSand */, BUSY /* Fish */, - BUSY /* Hunt */, + EXCLUSIVE /* Hunt */, OTHER /* HuntVermin */, BUSY /* Kidnap */, BUSY /* BeatCriminal */, @@ -183,7 +187,7 @@ static const dwarf_state dwarf_states[] = { OTHER /* GoShopping2 */, BUSY /* Clean */, OTHER /* Rest */, - BUSY /* PickupEquipment */, + EXCLUSIVE /* PickupEquipment */, BUSY /* DumpItem */, OTHER /* StrangeMoodCrafter */, OTHER /* StrangeMoodJeweller */, @@ -393,8 +397,15 @@ struct labor_default int active_dwarfs; }; +// The percentage of the dwarves assigned as haulers at any one time. static int hauler_pct = 33; +// The maximum percentage of dwarves who will be allowed to be idle. +// Decreasing this will encourage autolabor to keep dwarves busy, +// at the expense of making it harder for dwarves to specialize in +// specific skills. +static int idler_pct = 10; + static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { @@ -521,7 +532,6 @@ struct dwarf_info_t bool medical; // this dwarf has medical responsibility bool trader; // this dwarf has trade responsibility bool diplomacy; // this dwarf meets with diplomats - int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none) }; static bool isOptionEnabled(unsigned flag) @@ -738,7 +748,13 @@ struct laborinfo_sorter { bool operator() (int i,int j) { - return labor_infos[i].mode() < labor_infos[j].mode(); + if (labor_infos[i].mode() != labor_infos[j].mode()) + return labor_infos[i].mode() < labor_infos[j].mode(); + if (labor_infos[i].is_exclusive != labor_infos[j].is_exclusive) + return labor_infos[i].is_exclusive; + if (labor_infos[i].maximum_dwarfs() != labor_infos[j].maximum_dwarfs()) + return labor_infos[i].maximum_dwarfs() < labor_infos[j].maximum_dwarfs(); + return false; }; }; @@ -769,6 +785,7 @@ static void assign_labor(unit_labor::unit_labor labor, int best_dwarf = 0; int best_value = -10000; + int best_skill = 0; std::vector values(n_dwarfs); std::vector candidates; @@ -813,6 +830,9 @@ static void assign_labor(unit_labor::unit_labor labor, dwarf_skill[dwarf] = skill_level; dwarf_skillxp[dwarf] = skill_experience; + if (best_skill < skill_level) + best_skill = skill_level; + value += skill_level * 100; value += skill_experience / 20; if (skill_level > 0 || skill_experience > 0) @@ -832,6 +852,9 @@ static void assign_labor(unit_labor::unit_labor labor, value += 350; } + if (dwarf_info[dwarf].has_exclusive_labor) + value -= 500; + // bias by happiness //value += dwarfs[dwarf]->status.happiness; @@ -897,15 +920,28 @@ static void assign_labor(unit_labor::unit_labor labor, if (unit_labor::FISH == labor && !has_fishery) min_dwarfs = max_dwarfs = 0; - bool want_idle_dwarf = true; - if (state_count[IDLE] < 2) - want_idle_dwarf = false; + // If there are enough idle dwarves to choose from, enter an aggressive assignment + // mode. "Enough" idle dwarves is defined as 2 or 10% of the total number of dwarves, + // whichever is higher. + // + // In aggressive mode, we will always pick at least one idle dwarf for each skill, + // in order to try to get the idle dwarves to start doing something. We also pick + // any dwarf more preferable to the idle dwarf, since we'd rather have a more + // preferable dwarf do a new job if one becomes available (probably because that + // dwarf just finished a job). + // + // In non-aggressive mode, only dwarves that are good at a labor will be assigned + // to it. Dwarves good at nothing, or nothing that needs doing, will tend to get + // assigned to hauling by the hauler code. If there are no hauling jobs to do, + // they will sit around idle and when enough build up they will trigger aggressive + // mode again. + bool aggressive_mode = state_count[IDLE] >= 2 && state_count[IDLE] >= n_dwarfs * idler_pct / 100; /* * Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in * order of preference, and then assign additional dwarfs that meet any of these conditions: - * - The dwarf is idle and there are no idle dwarves assigned to this labor - * - The dwarf has nonzero skill associated with the labor + * - We are in aggressive mode and have not yet assigned an idle dwarf + * - The dwarf is good at this skill * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. * We stop assigning dwarfs when we reach the maximum allowed. * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs @@ -917,24 +953,23 @@ static void assign_labor(unit_labor::unit_labor labor, { int dwarf = candidates[i]; + if (dwarf_info[dwarf].trader && trader_requested) + continue; + if (dwarf_info[dwarf].diplomacy) + continue; + assert(dwarf >= 0); assert(dwarf < n_dwarfs); bool preferred_dwarf = false; - if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) - preferred_dwarf = true; - if (dwarf_skill[dwarf] > 0) + if (dwarf_skillxp[dwarf] > 0 && dwarf_skill[dwarf] >= best_skill / 2) preferred_dwarf = true; - if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) + if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive && dwarf_info[dwarf].state == EXCLUSIVE) preferred_dwarf = true; if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) preferred_dwarf = true; - if (dwarf_info[dwarf].trader && trader_requested) - continue; - if (dwarf_info[dwarf].diplomacy) - continue; - if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) + if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf && !aggressive_mode) continue; if (!dwarfs[dwarf]->status.labors[labor]) @@ -952,11 +987,11 @@ static void assign_labor(unit_labor::unit_labor labor, if (print_debug) out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) labor_infos[labor].active_dwarfs++; if (dwarf_info[dwarf].state == IDLE) - want_idle_dwarf = false; + aggressive_mode = false; } } @@ -1049,8 +1084,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { - dwarf_info[dwarf].single_labor = -1; - if (dwarfs[dwarf]->status.souls.size() <= 0) continue; @@ -1214,7 +1247,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) laborinfo_sorter lasorter; std::sort(labors.begin(), labors.end(), lasorter); - // Handle DISABLED skills (just bookkeeping) + // Handle DISABLED skills (just bookkeeping). + // Note that autolabor should *NEVER* enable or disable a skill that has been marked as DISABLED, for any reason. + // The user has told us that they want manage this skill manually, and we must respect that. for (auto lp = labors.begin(); lp != labors.end(); ++lp) { auto labor = *lp; @@ -1224,12 +1259,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { - if ((dwarf_info[dwarf].trader && trader_requested) || - dwarf_info[dwarf].diplomacy) - { - dwarfs[dwarf]->status.labors[labor] = false; - } - if (dwarfs[dwarf]->status.labors[labor]) { if (labor_infos[labor].is_exclusive) @@ -1252,7 +1281,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps // make sure that hauling jobs are handled quickly rather than building up. - int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100; + int num_haulers = state_count[IDLE] + (state_count[BUSY] + state_count[EXCLUSIVE]) * hauler_pct / 100; if (num_haulers < 1) num_haulers = 1; @@ -1274,7 +1303,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) continue; } - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) hauler_ids.push_back(dwarf); } dwarfinfo_sorter sorter(dwarf_info); @@ -1305,7 +1334,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) dwarfs[dwarf]->status.labors[labor] = true; dwarf_info[dwarf].assigned_jobs++; - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) labor_infos[labor].active_dwarfs++; if (print_debug) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index b7f6406a9..dcec1248d 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -203,12 +203,20 @@ for job,flag in pairs(plant_products) do local itag = 'idx_'..string.lower(flag) job_outputs[job] = function(callback, job) local mat_type, mat_index = -1, -1 + local seed_type, seed_index = -1, -1 local mat = dfhack.matinfo.decode(job.job_items[0]) if mat and mat.plant and mat.plant.flags[flag] then mat_type = mat.plant.material_defs[ttag] mat_index = mat.plant.material_defs[itag] + seed_type = mat.plant.material_defs['type_seed'] + seed_index = mat.plant.material_defs['idx_seed'] end - default_output(callback, job, mat_type, mat_index) + local mat_mask = { } + if flag ~= 'LEAVES' then + mat_mask.plant = true + end + default_output(callback, job, mat_type, mat_index, mat_mask) + callback{ item_type = df.item_type.SEEDS, mat_type = seed_type, mat_index = seed_index } end end