Merge pull request #2451 from ab9rf/autolabor-refactor

autolabor/autohauler refactor
develop
Myk 2022-12-01 07:46:41 -08:00 committed by GitHub
commit 26a4a93d7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 335 deletions

@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `tiletypes`: no longer resets dig priority to the default when updating other properties of a tile - `tiletypes`: no longer resets dig priority to the default when updating other properties of a tile
- `automaterial`: fix rendering errors with box boundary markers - `automaterial`: fix rendering errors with box boundary markers
- `autolabor` & `autohauler`: properly handle jobs 241, 242, and 243 - `autolabor` & `autohauler`: properly handle jobs 241, 242, and 243
- `autolabor` & `autohauler`: refactored to use DFHack's messaging system for info/debug/trace messages
- `autofarm`: add missing output flushes - `autofarm`: add missing output flushes
- `hotkeys`: correctly detect hotkeys bound to number keys, F11, or F12 - `hotkeys`: correctly detect hotkeys bound to number keys, F11, or F12
- `labormanager`: fix quern construction labor - `labormanager`: fix quern construction labor

@ -22,6 +22,9 @@ Autohauler allows a skill to be used as a flag to exempt a dwarf from
autohauler's effects. By default, this is the unused ALCHEMIST labor, but it autohauler's effects. By default, this is the unused ALCHEMIST labor, but it
can be changed by the user. can be changed by the user.
Autohauler uses DFHack's `debug` functionality to display information about the changes it makes. The amount of
information displayed can be controlled through appropriate use of the ``debugfilter`` command.
Usage Usage
----- -----
@ -42,8 +45,6 @@ Usage
Show the active configuration for all labors. Show the active configuration for all labors.
``autohauler frameskip <number>`` ``autohauler frameskip <number>``
Set the number of frames between runs of autohauler. Set the number of frames between runs of autohauler.
``autohauler debug``
In the next cycle, output the state of every dwarf.
Examples Examples
-------- --------

@ -56,6 +56,9 @@ labor, then to additional dwarfs that meet any of these conditions:
We stop assigning dwarves when we reach the maximum allowed. We stop assigning dwarves when we reach the maximum allowed.
Autolabor uses DFHack's `debug` functionality to display information about the changes it makes. The amount of
information displayed can be controlled through appropriate use of the ``debugfilter`` command.
Examples Examples
-------- --------

@ -1,8 +1,6 @@
/**
* All includes copied from autolabor for now
*/
#include "Core.h" #include "Core.h"
#include <Console.h> #include <Console.h>
#include <Debug.h>
#include <Export.h> #include <Export.h>
#include <PluginManager.h> #include <PluginManager.h>
@ -45,21 +43,15 @@
#include "laborstatemap.h" #include "laborstatemap.h"
// Not sure what this does, but may have to figure it out later
#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0]))
// I can see a reason for having all of these
using std::string;
using std::endl;
using std::vector;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
// idk what this does
DFHACK_PLUGIN("autohauler"); DFHACK_PLUGIN("autohauler");
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0]))
/* /*
* Autohauler module for dfhack * Autohauler module for dfhack
* Fork of autolabor, DFHack version 0.40.24-r2 * Fork of autolabor, DFHack version 0.40.24-r2
@ -86,37 +78,31 @@ REQUIRE_GLOBAL(world);
* autolabor.plug.dll as this plugin is mutually exclusive with it. * autolabor.plug.dll as this plugin is mutually exclusive with it.
*/ */
// Yep...don't know what it does
DFHACK_PLUGIN_IS_ENABLED(enable_autohauler); DFHACK_PLUGIN_IS_ENABLED(enable_autohauler);
// This is the configuration saved into the world save file namespace DFHack {
static PersistentDataItem config; DBG_DECLARE(autohauler, cycle, DebugCategory::LINFO);
}
// There is a possibility I will add extensive, line-by-line debug capability static std::vector<int> state_count(NUM_STATE);
// later
static bool print_debug = false;
// Default number of frames between autohauler updates
const static int DEFAULT_FRAME_SKIP = 30; const static int DEFAULT_FRAME_SKIP = 30;
// Number of frames between autohauler updates static PersistentDataItem config;
static int frame_skip;
// Don't know what this does
command_result autohauler (color_ostream &out, std::vector <std::string> & parameters); command_result autohauler (color_ostream &out, std::vector <std::string> & parameters);
// Don't know what this does either static int frame_skip;
static bool isOptionEnabled(unsigned flag) static bool isOptionEnabled(unsigned flag)
{ {
return config.isValid() && (config.ival(0) & flag) != 0; return config.isValid() && (config.ival(0) & flag) != 0;
} }
// Not sure about the purpose of this
enum ConfigFlags { enum ConfigFlags {
CF_ENABLED = 1, CF_ENABLED = 1,
}; };
// Don't know what this does
static void setOptionEnabled(ConfigFlags flag, bool on) static void setOptionEnabled(ConfigFlags flag, bool on)
{ {
if (!config.isValid()) if (!config.isValid())
@ -128,49 +114,34 @@ static void setOptionEnabled(ConfigFlags flag, bool on)
config.ival(0) &= ~flag; config.ival(0) &= ~flag;
} }
// This is a vector of states and number of dwarves in that state
static std::vector<int> state_count(NUM_STATE);
// Mode assigned to labors. Either it's a hauling job, or it's not.
enum labor_mode { enum labor_mode {
ALLOW, ALLOW,
HAULERS, HAULERS,
FORBID FORBID
}; };
// This is the default treatment of a particular labor.
struct labor_default
{
labor_mode mode;
int active_dwarfs;
};
// This is the current treatment of a particular labor.
// This would have been more cleanly presented as a class
struct labor_info struct labor_info
{ {
// It seems as if this is the means of accessing the world data
PersistentDataItem config; PersistentDataItem config;
// Number of dwarves assigned this labor
int active_dwarfs; int active_dwarfs;
// Set the persistent data item associated with this labor treatment
// We Java folk hate pointers, but that's what the parameter will be
void set_config(PersistentDataItem a) { config = a; }
// Return the labor_mode associated with this labor
labor_mode mode() { return (labor_mode) config.ival(0); } labor_mode mode() { return (labor_mode) config.ival(0); }
// Set the labor_mode associated with this labor
void set_mode(labor_mode mode) { config.ival(0) = mode; } void set_mode(labor_mode mode) { config.ival(0) = mode; }
void set_config(PersistentDataItem a) { config = a; }
};
struct labor_default
{
labor_mode mode;
int active_dwarfs;
}; };
// This is a vector of all the current labor treatments
static std::vector<struct labor_info> labor_infos; static std::vector<struct labor_info> labor_infos;
// This is just an array of all the labors, whether it should be untouched
// (DISABLE) or treated as a last-resort job (HAULERS).
static const struct labor_default default_labor_infos[] = { static const struct labor_default default_labor_infos[] = {
/* MINE */ {ALLOW, 0}, /* MINE */ {ALLOW, 0},
/* HAUL_STONE */ {HAULERS, 0}, /* HAUL_STONE */ {HAULERS, 0},
@ -257,36 +228,24 @@ static const struct labor_default default_labor_infos[] = {
/* BOOKBINDING */ {ALLOW, 0} /* BOOKBINDING */ {ALLOW, 0}
}; };
/**
* Reset labor to default treatment
*/
static void reset_labor(df::unit_labor labor)
{
labor_infos[labor].set_mode(default_labor_infos[labor].mode);
}
/**
* This is individualized dwarf info populated in plugin_onupdate
*/
struct dwarf_info_t struct dwarf_info_t
{ {
// Current simplified employment status of dwarf
dwarf_state state; dwarf_state state;
// Set to true if for whatever reason we are exempting this dwarf
// from hauling
bool haul_exempt; bool haul_exempt;
}; };
/**
* Disable autohauler labor lists
*/
static void cleanup_state() static void cleanup_state()
{ {
enable_autohauler = false; enable_autohauler = false;
labor_infos.clear(); labor_infos.clear();
} }
static void reset_labor(df::unit_labor labor)
{
labor_infos[labor].set_mode(default_labor_infos[labor].mode);
}
static void enable_alchemist(color_ostream &out) static void enable_alchemist(color_ostream &out)
{ {
if (!Units::setLaborValidity(unit_labor::ALCHEMIST, true)) if (!Units::setLaborValidity(unit_labor::ALCHEMIST, true))
@ -297,29 +256,18 @@ static void enable_alchemist(color_ostream &out)
} }
} }
/**
* Initialize the plugin labor lists
*/
static void init_state(color_ostream &out) static void init_state(color_ostream &out)
{ {
// This obtains the persistent data from the world save file
config = World::GetPersistentData("autohauler/config"); config = World::GetPersistentData("autohauler/config");
// Check to ensure that the persistent data item actually exists and that
// the first item in the array of ints isn't -1 (implies disabled)
if (config.isValid() && config.ival(0) == -1) if (config.isValid() && config.ival(0) == -1)
config.ival(0) = 0; config.ival(0) = 0;
// Check to see if the plugin is enabled in the persistent data, if so then
// enable the local flag for autohauler being enabled
enable_autohauler = isOptionEnabled(CF_ENABLED); enable_autohauler = isOptionEnabled(CF_ENABLED);
// If autohauler is not enabled then it's pretty pointless to do the rest
if (!enable_autohauler) if (!enable_autohauler)
return; return;
// First get the frame skip from persistent data, or create the item
// if not present
auto cfg_frameskip = World::GetPersistentData("autohauler/frameskip"); auto cfg_frameskip = World::GetPersistentData("autohauler/frameskip");
if (cfg_frameskip.isValid()) if (cfg_frameskip.isValid())
{ {
@ -327,126 +275,78 @@ static void init_state(color_ostream &out)
} }
else else
{ {
// Add to persistent data then get it to assert it's actually there
cfg_frameskip = World::AddPersistentData("autohauler/frameskip"); cfg_frameskip = World::AddPersistentData("autohauler/frameskip");
cfg_frameskip.ival(0) = DEFAULT_FRAME_SKIP; cfg_frameskip.ival(0) = DEFAULT_FRAME_SKIP;
frame_skip = cfg_frameskip.ival(0); frame_skip = cfg_frameskip.ival(0);
} }
labor_infos.resize(ARRAY_COUNT(default_labor_infos));
/* Here we are going to populate the labor list by loading persistent data
* from the world save */
// This is a vector of all the persistent data items from config
std::vector<PersistentDataItem> items; std::vector<PersistentDataItem> items;
// This populates the aforementioned vector
World::GetPersistentData(&items, "autohauler/labors/", true); World::GetPersistentData(&items, "autohauler/labors/", true);
// Resize the list of current labor treatments to size of list of default
// labor treatments
labor_infos.resize(ARRAY_COUNT(default_labor_infos));
// For every persistent data item... for (auto& p : items)
for (auto p = items.begin(); p != items.end(); p++)
{ {
// Load as a string the key associated with the persistent data item std::string key = p.key();
string key = p->key();
// Translate the string into a labor defined by global dfhack constants
df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str()); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str());
// Ensure that the labor is defined in the existing list
if (labor >= 0 && size_t(labor) < labor_infos.size()) if (labor >= 0 && size_t(labor) < labor_infos.size())
{ {
// Link the labor treatment with the associated persistent data item labor_infos[labor].set_config(p);
labor_infos[labor].set_config(*p);
// Set the number of dwarves associated with labor to zero
labor_infos[labor].active_dwarfs = 0; labor_infos[labor].active_dwarfs = 0;
} }
} }
// Add default labors for those not in save // Add default labors for those not in save
for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) {
// Determine if the labor is already present. If so, exit the for loop
if (labor_infos[i].config.isValid()) if (labor_infos[i].config.isValid())
continue; continue;
// Not sure of the mechanics, but it seems to give an output stream
// giving a string for the new persistent data item
std::stringstream name; std::stringstream name;
name << "autohauler/labors/" << i; name << "autohauler/labors/" << i;
// Add a new persistent data item as it is not currently in the save
labor_infos[i].set_config(World::AddPersistentData(name.str())); labor_infos[i].set_config(World::AddPersistentData(name.str()));
// Set the active counter to zero
labor_infos[i].active_dwarfs = 0; labor_infos[i].active_dwarfs = 0;
// Reset labor to default treatment
reset_labor((df::unit_labor) i); reset_labor((df::unit_labor) i);
} }
// Allow Alchemist to be set in the labor-related UI screens so the player
// can actually use it as a flag without having to run Dwarf Therapist
enable_alchemist(out); enable_alchemist(out);
} }
/**
* Call this method to enable the plugin.
*/
static void enable_plugin(color_ostream &out) static void enable_plugin(color_ostream &out)
{ {
// If there is no config persistent item, make one
if (!config.isValid()) if (!config.isValid())
{ {
config = World::AddPersistentData("autohauler/config"); config = World::AddPersistentData("autohauler/config");
config.ival(0) = 0; config.ival(0) = 0;
} }
// I think this is already done in init_state(), but it can't hurt
setOptionEnabled(CF_ENABLED, true); setOptionEnabled(CF_ENABLED, true);
enable_autohauler = true; enable_autohauler = true;
out << "Enabling the plugin." << std::endl;
// Output to console that the plugin is enabled
out << "Enabling the plugin." << endl;
// Disable autohauler and clear the labor list
cleanup_state(); cleanup_state();
// Initialize the plugin
init_state(out); init_state(out);
} }
/**
* Initialize the plugin
*/
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
// This seems to verify that the default labor list and the current labor
// list are the same size
if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1)
{ {
out.printerr("autohauler: labor size mismatch\n"); out.printerr("autohauler: labor size mismatch\n");
return CR_FAILURE; return CR_FAILURE;
} }
// Essentially an introduction dumped to the console
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autohauler", "autohauler",
"Automatically manage hauling labors.", "Automatically manage hauling labors.",
autohauler)); autohauler));
// Initialize plugin labor lists
init_state(out); init_state(out);
return CR_OK; return CR_OK;
} }
/**
* Shut down the plugin
*/
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
cleanup_state(); cleanup_state();
@ -454,9 +354,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK; return CR_OK;
} }
/**
* This method responds to the map being loaded, or unloaded.
*/
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
@ -474,59 +371,38 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK; return CR_OK;
} }
/**
* This method is called every frame in Dwarf Fortress from my understanding.
*/
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
// This makes it so that the plugin is only run every 60 steps, in order to
// save FPS. Since it is static, this is declared before this method is called
static int step_count = 0; static int step_count = 0;
if(!world || !world->map.block_index || !enable_autohauler)
{
return CR_OK;
}
// Cancel run if the world doesn't exist or plugin isn't enabled if (++step_count < frame_skip)
if(!world || !world->map.block_index || !enable_autohauler) { return CR_OK; } return CR_OK;
// Increment step count
step_count++;
// Run aforementioned step count and return unless threshold is reached.
if (step_count < frame_skip) return CR_OK;
// Reset step count since at this point it has reached 60
step_count = 0; step_count = 0;
// Create a vector of units. This will be populated in the following for loop.
std::vector<df::unit *> dwarfs; std::vector<df::unit *> dwarfs;
// Scan the world and look for any citizens in the player's civilization. for (auto& cre : world->units.active)
// Add these to the list of dwarves.
// xxx Does it need to be ++i?
for (size_t i = 0; i < world->units.active.size(); ++i)
{ {
df::unit* cre = world->units.active[i];
if (Units::isCitizen(cre)) if (Units::isCitizen(cre))
{ {
dwarfs.push_back(cre); dwarfs.push_back(cre);
} }
} }
// This just keeps track of the number of civilians from the previous for loop.
int n_dwarfs = dwarfs.size(); int n_dwarfs = dwarfs.size();
// This will return if there are no civilians. Otherwise could call
// nonexistent elements of array.
if (n_dwarfs == 0) if (n_dwarfs == 0)
return CR_OK; return CR_OK;
// This is a matching of assigned jobs with a dwarf's state
// xxx but wouldn't it be better if this and "dwarfs" were in the same object?
std::vector<dwarf_info_t> dwarf_info(n_dwarfs); std::vector<dwarf_info_t> dwarf_info(n_dwarfs);
// Reset the counter for number of dwarves in states to zero
state_count.clear(); state_count.clear();
state_count.resize(NUM_STATE); state_count.resize(NUM_STATE);
// Find the activity state for each dwarf
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
/* Before determining how to handle employment status, handle /* Before determining how to handle employment status, handle
@ -551,11 +427,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Scan a dwarf's miscellaneous traits for on break or migrant status. // Scan a dwarf's miscellaneous traits for on break or migrant status.
// If either of these are present, disable hauling because we want them // If either of these are present, disable hauling because we want them
// to try to find real jobs first // to try to find real jobs first
for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) auto v = dwarfs[dwarf]->status.misc_traits;
{ auto test_migrant = [](df::unit_misc_trait* t) { return t->id == misc_trait_type::Migrant; };
if ((*p)->id == misc_trait_type::Migrant) is_migrant = std::find_if(v.begin(), v.end(), test_migrant ) != v.end();
is_migrant = true;
}
/* Now determine a dwarf's employment status and decide whether /* Now determine a dwarf's employment status and decide whether
* to assign hauling */ * to assign hauling */
@ -583,7 +457,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
dwarf_info[dwarf].state = IDLE; dwarf_info[dwarf].state = IDLE;
} }
// If it gets to this point we look at the task and assign either BUSY or OTHER
else else
{ {
int job = dwarfs[dwarf]->job.current_job->job_type; int job = dwarfs[dwarf]->job.current_job->job_type;
@ -591,25 +464,17 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].state = dwarf_states[job]; dwarf_info[dwarf].state = dwarf_states[job];
else else
{ {
// Warn the console that the dwarf has an unregistered labor, default to BUSY WARN(cycle, out).print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); dwarf_info[dwarf].state = OTHER;
dwarf_info[dwarf].state = BUSY;
} }
} }
// Debug: Output dwarf job and state data
if(print_debug)
out.print("Dwarf %i %s State: %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(),
dwarf_info[dwarf].state);
// Increment corresponding labor in default_labor_infos struct
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
TRACE(cycle, out).print("Dwarf %i \"%s\": state %s\n",
dwarf, dwarfs[dwarf]->name.first_name.c_str(), state_names[dwarf_info[dwarf].state]);
} }
// At this point the debug if present has been completed
print_debug = false;
// This is a vector of all the labors // This is a vector of all the labors
std::vector<df::unit_labor> labors; std::vector<df::unit_labor> labors;
@ -639,19 +504,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// There was no reason to put potential haulers in an array. All of them are // There was no reason to put potential haulers in an array. All of them are
// covered in the following for loop. // covered in the following for loop.
// Equivalent of Java for(unit_labor : labor)
// For every labor...
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
// If this is a non-labor continue to next item
if (labor == unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
// If this is not a hauling labor continue to next item
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
// For every dwarf...
for(size_t dwarf = 0; dwarf < dwarfs.size(); dwarf++) for(size_t dwarf = 0; dwarf < dwarfs.size(); dwarf++)
{ {
if (!Units::isValidLabor(dwarfs[dwarf], labor)) if (!Units::isValidLabor(dwarfs[dwarf], labor))
@ -676,21 +535,30 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
labor_infos[labor].active_dwarfs++; labor_infos[labor].active_dwarfs++;
} }
// CHILD ignored // CHILD ignored
} }
// Let's play a game of "find the missing bracket!" I hope this is correct.
} }
// This would be the last statement of the method
return CR_OK; return CR_OK;
} }
/** void print_labor (df::unit_labor labor, color_ostream &out)
* xxx Isn't this a repeat of enable_plugin? If it is separately called, then {
* passing a constructor should suffice. std::string labor_name = ENUM_KEY_STR(unit_labor, labor);
*/ out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' ';
if (labor_infos[labor].mode() == ALLOW) out << "allow" << std::endl;
else if(labor_infos[labor].mode() == FORBID) out << "forbid" << std::endl;
else if(labor_infos[labor].mode() == HAULERS)
{
out << "haulers, currently " << labor_infos[labor].active_dwarfs << " dwarfs" << std::endl;
}
else
{
out << "Warning: Invalid labor mode!" << std::endl;
}
}
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
{ {
if (!Core::getInstance().isWorldLoaded()) { if (!Core::getInstance().isWorldLoaded()) {
@ -707,36 +575,12 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
enable_autohauler = false; enable_autohauler = false;
setOptionEnabled(CF_ENABLED, false); setOptionEnabled(CF_ENABLED, false);
out << "Autohauler is disabled." << endl; out << "Autohauler is disabled." << std::endl;
} }
return CR_OK; return CR_OK;
} }
/**
* Print the aggregate labor status to the console.
*/
void print_labor (df::unit_labor labor, color_ostream &out)
{
string labor_name = ENUM_KEY_STR(unit_labor, labor);
out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' ';
if (labor_infos[labor].mode() == ALLOW) out << "allow" << endl;
else if(labor_infos[labor].mode() == FORBID) out << "forbid" << endl;
else if(labor_infos[labor].mode() == HAULERS)
{
out << "haulers, currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl;
}
else
{
out << "Warning: Invalid labor mode!" << endl;
}
}
/**
* This responds to input from the command prompt.
*/
command_result autohauler (color_ostream &out, std::vector <std::string> & parameters) command_result autohauler (color_ostream &out, std::vector <std::string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
@ -761,13 +605,13 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
{ {
int newValue = atoi(parameters[1].c_str()); int newValue = atoi(parameters[1].c_str());
cfg_frameskip.ival(0) = newValue; cfg_frameskip.ival(0) = newValue;
out << "Setting frame skip to " << newValue << endl; out << "Setting frame skip to " << newValue << std::endl;
frame_skip = cfg_frameskip.ival(0); frame_skip = cfg_frameskip.ival(0);
return CR_OK; return CR_OK;
} }
else else
{ {
out << "Warning! No persistent data for frame skip!" << endl; out << "Warning! No persistent data for frame skip!" << std::endl;
return CR_OK; return CR_OK;
} }
} }
@ -775,7 +619,7 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
{ {
if (!enable_autohauler) if (!enable_autohauler)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -826,7 +670,7 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
{ {
if (!enable_autohauler) if (!enable_autohauler)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -834,14 +678,14 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
{ {
reset_labor((df::unit_labor) i); reset_labor((df::unit_labor) i);
} }
out << "All labors reset." << endl; out << "All labors reset." << std::endl;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status"))
{ {
if (!enable_autohauler) if (!enable_autohauler)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -855,9 +699,9 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
out << state_count[i] << ' ' << state_names[i]; out << state_count[i] << ' ' << state_names[i];
need_comma = true; need_comma = true;
} }
out << endl; out << std::endl;
out << "Autohauler is running every " << frame_skip << " frames." << endl; out << "Autohauler is running every " << frame_skip << " frames." << std::endl;
if (parameters[0] == "list") if (parameters[0] == "list")
{ {
@ -872,18 +716,6 @@ command_result autohauler (color_ostream &out, std::vector <std::string> & param
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "debug")
{
if (!enable_autohauler)
{
out << "Error: The plugin is not enabled." << endl;
return CR_FAILURE;
}
print_debug = true;
return CR_OK;
}
else else
{ {
out.print("Automatically assigns hauling labors to dwarves.\n" out.print("Automatically assigns hauling labors to dwarves.\n"

@ -1,5 +1,6 @@
#include "Core.h" #include "Core.h"
#include <Console.h> #include <Console.h>
#include <Debug.h>
#include <Export.h> #include <Export.h>
#include <PluginManager.h> #include <PluginManager.h>
@ -42,9 +43,6 @@
#include "laborstatemap.h" #include "laborstatemap.h"
using std::string;
using std::endl;
using std::vector;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -81,20 +79,36 @@ REQUIRE_GLOBAL(world);
DFHACK_PLUGIN_IS_ENABLED(enable_autolabor); DFHACK_PLUGIN_IS_ENABLED(enable_autolabor);
static bool print_debug = 0; namespace DFHack {
DBG_DECLARE(autolabor, cycle, DebugCategory::LINFO);
}
static std::vector<int> state_count(5); static std::vector<int> state_count(NUM_STATE);
static PersistentDataItem config; static PersistentDataItem config;
command_result autolabor (color_ostream &out, std::vector <std::string> & parameters);
static bool isOptionEnabled(unsigned flag)
{
return config.isValid() && (config.ival(0) & flag) != 0;
}
enum ConfigFlags { enum ConfigFlags {
CF_ENABLED = 1, CF_ENABLED = 1,
}; };
static void setOptionEnabled(ConfigFlags flag, bool on)
{
if (!config.isValid())
return;
if (on)
config.ival(0) |= flag;
else
config.ival(0) &= ~flag;
}
// 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 <std::string> & parameters);
static void generate_labor_to_skill_map(); static void generate_labor_to_skill_map();
@ -104,7 +118,6 @@ enum labor_mode {
AUTOMATIC, AUTOMATIC,
}; };
struct labor_info struct labor_info
{ {
PersistentDataItem config; PersistentDataItem config;
@ -113,6 +126,7 @@ struct labor_info
int active_dwarfs; int active_dwarfs;
labor_mode mode() { return (labor_mode) config.ival(0); } labor_mode mode() { return (labor_mode) config.ival(0); }
void set_mode(labor_mode mode) { config.ival(0) = mode; } void set_mode(labor_mode mode) { config.ival(0) = mode; }
int minimum_dwarfs() { return config.ival(1); } int minimum_dwarfs() { return config.ival(1); }
@ -273,22 +287,6 @@ struct dwarf_info_t
bool diplomacy; // this dwarf meets with diplomats bool diplomacy; // this dwarf meets with diplomats
}; };
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() static void cleanup_state()
{ {
enable_autolabor = false; enable_autolabor = false;
@ -330,13 +328,13 @@ static void init_state()
std::vector<PersistentDataItem> items; std::vector<PersistentDataItem> items;
World::GetPersistentData(&items, "autolabor/labors/", true); World::GetPersistentData(&items, "autolabor/labors/", true);
for (auto p = items.begin(); p != items.end(); p++) for (auto& p : items)
{ {
string key = p->key(); std::string key = p.key();
df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str());
if (labor >= 0 && size_t(labor) < labor_infos.size()) if (labor >= 0 && size_t(labor) < labor_infos.size())
{ {
labor_infos[labor].config = *p; labor_infos[labor].config = p;
labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive;
labor_infos[labor].active_dwarfs = 0; labor_infos[labor].active_dwarfs = 0;
} }
@ -397,7 +395,7 @@ static void enable_plugin(color_ostream &out)
setOptionEnabled(CF_ENABLED, true); setOptionEnabled(CF_ENABLED, true);
enable_autolabor = true; enable_autolabor = true;
out << "Enabling autolabor." << endl; out << "Enabling autolabor." << std::endl;
cleanup_state(); cleanup_state();
init_state(); init_state();
@ -412,7 +410,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_FAILURE; return CR_FAILURE;
} }
// Fill the command list with your commands.
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autolabor", "autolabor",
"Automatically manage dwarf labors.", "Automatically manage dwarf labors.",
@ -513,12 +510,12 @@ static void assign_labor(unit_labor::unit_labor labor,
int skill_level = 0; int skill_level = 0;
int skill_experience = 0; int skill_experience = 0;
for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) for (auto s : dwarfs[dwarf]->status.souls[0]->skills)
{ {
if ((*s)->id == skill) if (s->id == skill)
{ {
skill_level = (*s)->rating; skill_level = s->rating;
skill_experience = (*s)->experience; skill_experience = s->experience;
break; break;
} }
} }
@ -681,8 +678,10 @@ static void assign_labor(unit_labor::unit_labor labor,
dwarfs[dwarf]->military.pickup_flags.bits.update = 1; dwarfs[dwarf]->military.pickup_flags.bits.update = 1;
} }
if (print_debug) TRACE(cycle, out).print("Dwarf % i \"%s\" assigned %s: value %i %s %s\n",
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)" : ""); 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 || dwarf_info[dwarf].state == EXCLUSIVE) if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE)
labor_infos[labor].active_dwarfs++; labor_infos[labor].active_dwarfs++;
@ -713,10 +712,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
static int step_count = 0; static int step_count = 0;
// check run conditions
if(!world || !world->map.block_index || !enable_autolabor) if(!world || !world->map.block_index || !enable_autolabor)
{ {
// give up if we shouldn't be running'
return CR_OK; return CR_OK;
} }
@ -730,9 +727,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
bool has_fishery = false; bool has_fishery = false;
bool trader_requested = false; bool trader_requested = false;
for (size_t i = 0; i < world->buildings.all.size(); ++i) for (auto& build : world->buildings.all)
{ {
df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (building_type::Workshop == type) if (building_type::Workshop == type)
{ {
@ -746,19 +742,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
df::building_tradedepotst* depot = (df::building_tradedepotst*) build; df::building_tradedepotst* depot = (df::building_tradedepotst*) build;
trader_requested = trader_requested || depot->trade_flags.bits.trader_requested; trader_requested = trader_requested || depot->trade_flags.bits.trader_requested;
if (print_debug) INFO(cycle,out).print(trader_requested
{ ? "Trade depot found and trader requested, trader will be excluded from all labors.\n"
if (trader_requested) : "Trade depot found but trader is not requested.\n"
out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); );
else
out.print("Trade depot found but trader is not requested.\n");
}
} }
} }
for (size_t i = 0; i < world->units.active.size(); ++i) for (auto& cre : world->units.active)
{ {
df::unit* cre = world->units.active[i];
if (Units::isCitizen(cre)) if (Units::isCitizen(cre))
{ {
if (cre->burrows.size() > 0) if (cre->burrows.size() > 0)
@ -787,9 +779,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id);
if(hf!=NULL) //can be NULL. E.g. script created citizens if(hf!=NULL) //can be NULL. E.g. script created citizens
for (size_t i = 0; i < hf->entity_links.size(); i++) for (auto& hfelink : hf->entity_links)
{ {
df::histfig_entity_link* hfelink = hf->entity_links.at(i);
if (hfelink->getType() == df::histfig_entity_link_type::POSITION) if (hfelink->getType() == df::histfig_entity_link_type::POSITION)
{ {
df::histfig_entity_link_positionst *epos = df::histfig_entity_link_positionst *epos =
@ -822,9 +813,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// identify dwarfs who are needed for meetings and mark them for exclusion // identify dwarfs who are needed for meetings and mark them for exclusion
for (size_t i = 0; i < ui->activities.size(); ++i) for (auto& act : ui->activities)
{ {
df::activity_info *act = ui->activities[i];
if (!act) continue; if (!act) continue;
bool p1 = act->unit_actor == dwarfs[dwarf]; bool p1 = act->unit_actor == dwarfs[dwarf];
bool p2 = act->unit_noble == dwarfs[dwarf]; bool p2 = act->unit_noble == dwarfs[dwarf];
@ -832,19 +822,17 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (p1 || p2) if (p1 || p2)
{ {
dwarf_info[dwarf].diplomacy = true; dwarf_info[dwarf].diplomacy = true;
if (print_debug) INFO(cycle, out).print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n",
out.print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", dwarf, dwarfs[dwarf]->name.first_name.c_str()); dwarf, dwarfs[dwarf]->name.first_name.c_str());
break; break;
} }
} }
for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) for (auto& skill : dwarfs[dwarf]->status.souls[0]->skills)
{ {
df::job_skill skill = (*s)->id; df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill->id);
df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); int skill_level = skill->rating;
int skill_level = (*s)->rating;
// Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.)
@ -907,15 +895,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].state = dwarf_states[job]; dwarf_info[dwarf].state = dwarf_states[job];
else else
{ {
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); WARN(cycle, out).print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;
} }
} }
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
if (print_debug) INFO(cycle, out).print("Dwarf %i \"%s\": penalty %i, state %s\n",
out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]);
} }
std::vector<df::unit_labor> labors; std::vector<df::unit_labor> labors;
@ -935,10 +923,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// 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. // 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. // 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) for (auto& labor: labors)
{ {
auto labor = *lp;
if (labor_infos[labor].mode() != DISABLE) if (labor_infos[labor].mode() != DISABLE)
continue; continue;
@ -956,10 +942,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Handle all skills except those marked HAULERS // Handle all skills except those marked HAULERS
for (auto lp = labors.begin(); lp != labors.end(); ++lp) for (auto& labor : labors)
{ {
auto labor = *lp;
assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out);
} }
@ -1026,8 +1010,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE)
labor_infos[labor].active_dwarfs++; labor_infos[labor].active_dwarfs++;
if (print_debug) TRACE(cycle, out).print("Dwarf %i \"%s\" assigned %s: hauler\n",
out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str());
} }
for (size_t i = num_haulers; i < hauler_ids.size(); i++) for (size_t i = num_haulers; i < hauler_ids.size(); i++)
@ -1043,19 +1027,17 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
} }
} }
print_debug = 0;
return CR_OK; return CR_OK;
} }
void print_labor (df::unit_labor labor, color_ostream &out) void print_labor (df::unit_labor labor, color_ostream &out)
{ {
string labor_name = ENUM_KEY_STR(unit_labor, labor); std::string labor_name = ENUM_KEY_STR(unit_labor, labor);
out << labor_name << ": "; out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++) for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' '; out << ' ';
if (labor_infos[labor].mode() == DISABLE) if (labor_infos[labor].mode() == DISABLE)
out << "disabled" << endl; out << "disabled" << std::endl;
else else
{ {
if (labor_infos[labor].mode() == HAULERS) if (labor_infos[labor].mode() == HAULERS)
@ -1063,7 +1045,7 @@ void print_labor (df::unit_labor labor, color_ostream &out)
else 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()
<< ", pool " << labor_infos[labor].talent_pool(); << ", pool " << labor_infos[labor].talent_pool();
out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << std::endl;
} }
} }
@ -1083,7 +1065,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
enable_autolabor = false; enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false); setOptionEnabled(CF_ENABLED, false);
out << "Autolabor is disabled." << endl; out << "Autolabor is disabled." << std::endl;
} }
return CR_OK; return CR_OK;
@ -1110,7 +1092,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -1122,7 +1104,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -1186,7 +1168,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -1194,14 +1176,14 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
reset_labor((df::unit_labor) i); reset_labor((df::unit_labor) i);
} }
out << "All labors reset." << endl; out << "All labors reset." << std::endl;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status"))
{ {
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
@ -1215,7 +1197,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
out << state_count[i] << ' ' << state_names[i]; out << state_count[i] << ' ' << state_names[i];
need_comma = 1; need_comma = 1;
} }
out << endl; out << std::endl;
if (parameters[0] == "list") if (parameters[0] == "list")
{ {
@ -1230,18 +1212,6 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "debug")
{
if (!enable_autolabor)
{
out << "Error: The plugin is not enabled." << endl;
return CR_FAILURE;
}
print_debug = 1;
return CR_OK;
}
else else
{ {
out.print("Automatically assigns labors to dwarves.\n" out.print("Automatically assigns labors to dwarves.\n"