apply canonical class 3 plugin structure

develop
myk002 2022-08-03 22:40:55 -07:00
parent 1dec977476
commit 1695919411
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
2 changed files with 221 additions and 214 deletions

@ -1,16 +1,19 @@
// - full automation of marking live-stock for slaughtering // full automation of marking live-stock for slaughtering
// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive // races can be added to a watchlist and it can be set how many male/female kids/adults are left alive
// adding to the watchlist can be automated as well. // adding to the watchlist can be automated as well.
// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started // config for autobutcher (state and sleep setting) is saved the first time autobutcher is started
// config for watchlist entries is saved when they are created or modified // config for watchlist entries is saved when they are created or modified
#include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector>
#include "df/building_cagest.h" #include "df/building_cagest.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
#include "df/world.h" #include "df/world.h"
#include "Core.h"
#include "Debug.h" #include "Debug.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "PluginManager.h" #include "PluginManager.h"
@ -33,6 +36,56 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command.
namespace DFHack {
// for configuration-related logging
DBG_DECLARE(autobutcher, status, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(autobutcher, cycle, DebugCategory::LINFO);
}
static const string CONFIG_KEY = string(plugin_name) + "/config";
static const string WATCHLIST_CONFIG_KEY_PREFIX = string(plugin_name) + "/watchlist/";
static PersistentDataItem config;
enum ConfigValues {
CONFIG_IS_ENABLED = 0,
CONFIG_CYCLE_TICKS = 1,
CONFIG_AUTOWATCH = 2,
CONFIG_DEFAULT_FK = 3,
CONFIG_DEFAULT_MK = 4,
CONFIG_DEFAULT_FA = 5,
CONFIG_DEFAULT_MA = 6,
};
static int get_config_val(int index) {
if (!config.isValid())
return -1;
return config.ival(index);
}
static bool get_config_bool(int index) {
return get_config_val(index) == 1;
}
static void set_config_val(int index, int value) {
if (config.isValid())
config.ival(index) = value;
}
static void set_config_bool(int index, bool value) {
set_config_val(index, value ? 1 : 0);
}
struct WatchedRace;
// vector of races handled by autobutcher
// the name is a bit misleading since entries can be set to 'unwatched'
// to ignore them for a while but still keep the target count settings
static unordered_map<int, WatchedRace*> watched_races;
static unordered_map<string, int> race_to_id;
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static void init_autobutcher(color_ostream &out);
static void cleanup_autobutcher(color_ostream &out);
static command_result df_autobutcher(color_ostream &out, vector<string> &parameters);
static void autobutcher_cycle(color_ostream &out);
const string autobutcher_help = const string autobutcher_help =
"Automatically butcher excess livestock. This plugin monitors how many pets\n" "Automatically butcher excess livestock. This plugin monitors how many pets\n"
"you have of each gender and age and assigns excess lifestock for slaughter\n" "you have of each gender and age and assigns excess lifestock for slaughter\n"
@ -115,44 +168,90 @@ const string autobutcher_help =
" and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n"
" for slaughter as soon as they arrive in your fortress.\n"; " for slaughter as soon as they arrive in your fortress.\n";
namespace DFHack { DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DBG_DECLARE(autobutcher, status); commands.push_back(PluginCommand(
DBG_DECLARE(autobutcher, cycle); plugin_name,
"Automatically butcher excess livestock.",
df_autobutcher,
false,
autobutcher_help.c_str()));
return CR_OK;
} }
static const string CONFIG_KEY = "autobutcher/config"; DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
static const string WATCHLIST_CONFIG_KEY_PREFIX = "autobutcher/watchlist/"; if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
static PersistentDataItem config; if (enable != is_enabled) {
enum ConfigValues { is_enabled = enable;
CONFIG_IS_ENABLED = 0, DEBUG(status,out).print("%s from the API; persisting\n",
CONFIG_CYCLE_TICKS = 1, is_enabled ? "enabled" : "disabled");
CONFIG_AUTOWATCH = 2, set_config_bool(CONFIG_IS_ENABLED, is_enabled);
CONFIG_DEFAULT_FK = 3, } else {
CONFIG_DEFAULT_MK = 4, DEBUG(status,out).print("%s from the API, but already %s; no action\n",
CONFIG_DEFAULT_FA = 5, is_enabled ? "enabled" : "disabled",
CONFIG_DEFAULT_MA = 6, is_enabled ? "enabled" : "disabled");
}; }
static int get_config_val(int index) { return CR_OK;
if (!config.isValid())
return -1;
return config.ival(index);
} }
static bool get_config_bool(int index) {
return get_config_val(index) == 1; DFhackCExport command_result plugin_shutdown (color_ostream &out) {
DEBUG(status,out).print("shutting down %s\n", plugin_name);
cleanup_autobutcher(out);
return CR_OK;
} }
static void set_config_val(int index, int value) {
if (config.isValid()) DFhackCExport command_result plugin_load_data (color_ostream &out) {
config.ival(index) = value; config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) {
DEBUG(status,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
set_config_val(CONFIG_CYCLE_TICKS, 6000);
set_config_bool(CONFIG_AUTOWATCH, false);
set_config_val(CONFIG_DEFAULT_FK, 5);
set_config_val(CONFIG_DEFAULT_MK, 1);
set_config_val(CONFIG_DEFAULT_FA, 5);
set_config_val(CONFIG_DEFAULT_MA, 1);
}
// we have to copy our enabled flag into the global plugin variable, but
// all the other state we can directly read/modify from the persistent
// data structure.
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
DEBUG(status,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
// load the persisted watchlist
init_autobutcher(out);
return CR_OK;
} }
static void set_config_bool(int index, bool value) {
set_config_val(index, value ? 1 : 0); DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
if (is_enabled) {
DEBUG(status,out).print("world unloaded; disabling %s\n",
plugin_name);
is_enabled = false;
}
cleanup_autobutcher(out);
}
return CR_OK;
} }
static unordered_map<string, int> race_to_id; DFhackCExport command_result plugin_onupdate(color_ostream &out) {
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS))
autobutcher_cycle(out);
return CR_OK;
}
static size_t DEFAULT_CYCLE_TICKS = 6000; /////////////////////////////////////////////////////
// autobutcher config logic
//
struct autobutcher_options { struct autobutcher_options {
// whether to display help // whether to display help
@ -199,68 +298,30 @@ static const struct_field_info autobutcher_options_fields[] = {
}; };
struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn<autobutcher_options>, NULL, "autobutcher_options", NULL, autobutcher_options_fields); struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn<autobutcher_options>, NULL, "autobutcher_options", NULL, autobutcher_options_fields);
static void init_autobutcher(color_ostream &out); static bool get_options(color_ostream &out,
static void cleanup_autobutcher(color_ostream &out); autobutcher_options &opts,
static command_result df_autobutcher(color_ostream &out, vector<string> &parameters); const vector<string> &parameters)
static void autobutcher_cycle(color_ostream &out); {
auto L = Lua::Core::State;
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) { Lua::StackUnwinder top(L);
commands.push_back(PluginCommand(
"autobutcher",
"Automatically butcher excess livestock.",
df_autobutcher,
false,
autobutcher_help.c_str()));
init_autobutcher(out);
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Maps::IsValid()) {
out.printerr("Cannot run autobutcher without a loaded map.\n");
return CR_FAILURE;
}
if (enable != is_enabled) { if (!lua_checkstack(L, parameters.size() + 2) ||
is_enabled = enable; !Lua::PushModulePublic(
if (is_enabled) out, L, "plugins.autobutcher", "parse_commandline")) {
init_autobutcher(out); out.printerr("Failed to load autobutcher Lua code\n");
else return false;
cleanup_autobutcher(out);
} }
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out) { Lua::Push(L, &opts);
cleanup_autobutcher(out); for (const string &param : parameters)
return CR_OK; Lua::Push(L, param);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (!Lua::SafeCall(out, L, parameters.size() + 1, 0))
switch (event) { return false;
case DFHack::SC_MAP_LOADED:
init_autobutcher(out);
break;
case DFHack::SC_MAP_UNLOADED:
cleanup_autobutcher(out);
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_onupdate(color_ostream &out) { return true;
if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS))
autobutcher_cycle(out);
return CR_OK;
} }
/////////////////////////////////////////////////////
// autobutcher config logic
//
static void doMarkForSlaughter(df::unit *unit) { static void doMarkForSlaughter(df::unit *unit) {
unit->flags2.bits.slaughter = 1; unit->flags2.bits.slaughter = 1;
} }
@ -460,35 +521,7 @@ public:
} }
}; };
// vector of races handled by autobutcher
// the name is a bit misleading since entries can be set to 'unwatched'
// to ignore them for a while but still keep the target count settings
static unordered_map<int, WatchedRace*> watched_races;
static void init_autobutcher(color_ostream &out) { static void init_autobutcher(color_ostream &out) {
config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid())
config = World::AddPersistentData(CONFIG_KEY);
if (get_config_val(CONFIG_IS_ENABLED) == -1) {
set_config_bool(CONFIG_IS_ENABLED, false);
set_config_val(CONFIG_CYCLE_TICKS, DEFAULT_CYCLE_TICKS);
set_config_bool(CONFIG_AUTOWATCH, false);
set_config_val(CONFIG_DEFAULT_FK, 5);
set_config_val(CONFIG_DEFAULT_MK, 1);
set_config_val(CONFIG_DEFAULT_FA, 5);
set_config_val(CONFIG_DEFAULT_MA, 1);
}
if (is_enabled)
set_config_bool(CONFIG_IS_ENABLED, true);
else
is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1);
if (!config.isValid())
return;
if (!race_to_id.size()) { if (!race_to_id.size()) {
const size_t num_races = world->raws.creatures.all.size(); const size_t num_races = world->raws.creatures.all.size();
for(size_t i = 0; i < num_races; ++i) for(size_t i = 0; i < num_races; ++i)
@ -505,37 +538,13 @@ static void init_autobutcher(color_ostream &out) {
} }
static void cleanup_autobutcher(color_ostream &out) { static void cleanup_autobutcher(color_ostream &out) {
is_enabled = false; DEBUG(status,out).print("cleaning %s state\n", plugin_name);
race_to_id.clear(); race_to_id.clear();
for (auto w : watched_races) for (auto w : watched_races)
delete w.second; delete w.second;
watched_races.clear(); watched_races.clear();
} }
static bool get_options(color_ostream &out,
autobutcher_options &opts,
const vector<string> &parameters)
{
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, parameters.size() + 2) ||
!Lua::PushModulePublic(
out, L, "plugins.autobutcher", "parse_commandline")) {
out.printerr("Failed to load autobutcher Lua code\n");
return false;
}
Lua::Push(L, &opts);
for (const string &param : parameters)
Lua::Push(L, param);
if (!Lua::SafeCall(out, L, parameters.size() + 1, 0))
return false;
return true;
}
static void autobutcher_export(color_ostream &out); static void autobutcher_export(color_ostream &out);
static void autobutcher_status(color_ostream &out); static void autobutcher_status(color_ostream &out);
static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); static void autobutcher_target(color_ostream &out, const autobutcher_options &opts);
@ -544,8 +553,8 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o
static command_result df_autobutcher(color_ostream &out, vector<string> &parameters) { static command_result df_autobutcher(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend; CoreSuspender suspend;
if (!Maps::IsValid()) { if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run autobutcher without a loaded map.\n"); out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE; return CR_FAILURE;
} }
@ -763,7 +772,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o
} }
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
// autobutcher cycle logic // cycle logic
// //
// check if contained in item (e.g. animals in cages) // check if contained in item (e.g. animals in cages)
@ -806,7 +815,7 @@ static void autobutcher_cycle(color_ostream &out) {
// mark that we have recently run // mark that we have recently run
cycle_timestamp = world->frame_counter; cycle_timestamp = world->frame_counter;
DEBUG(cycle,out).print("running autobutcher cycle\n"); DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
// check if there is anything to watch before walking through units vector // check if there is anything to watch before walking through units vector
if (!get_config_bool(CONFIG_AUTOWATCH)) { if (!get_config_bool(CONFIG_AUTOWATCH)) {

@ -4,6 +4,9 @@
// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds
// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) // state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used)
#include <string>
#include <vector>
#include "df/building_cagest.h" #include "df/building_cagest.h"
#include "df/building_civzonest.h" #include "df/building_civzonest.h"
#include "df/building_nest_boxst.h" #include "df/building_nest_boxst.h"
@ -16,7 +19,6 @@
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/Persistence.h" #include "modules/Persistence.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
@ -50,11 +52,13 @@ static const string autonestbox_help =
" The default is 6000 (about 8 days)\n"; " The default is 6000 (about 8 days)\n";
namespace DFHack { namespace DFHack {
DBG_DECLARE(autonestbox, status); // for configuration-related logging
DBG_DECLARE(autonestbox, cycle); DBG_DECLARE(autonestbox, status, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(autonestbox, cycle, DebugCategory::LINFO);
} }
static const string CONFIG_KEY = "autonestbox/config"; static const string CONFIG_KEY = string(plugin_name) + "/config";
static PersistentDataItem config; static PersistentDataItem config;
enum ConfigValues { enum ConfigValues {
CONFIG_IS_ENABLED = 0, CONFIG_IS_ENABLED = 0,
@ -79,95 +83,65 @@ static void set_config_bool(int index, bool value) {
static bool did_complain = false; // avoids message spam static bool did_complain = false; // avoids message spam
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
struct autonestbox_options {
// whether to display help
bool help = false;
// whether to run a cycle right now
bool now = false;
// how many ticks to wait between automatic cycles, -1 means unset
int32_t ticks = -1;
static struct_identity _identity;
};
static const struct_field_info autonestbox_options_fields[] = {
{ struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits<int32_t>::identity, 0, 0 },
{ struct_field_info::END }
};
struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn<autonestbox_options>, NULL, "autonestbox_options", NULL, autonestbox_options_fields);
static command_result df_autonestbox(color_ostream &out, vector<string> &parameters); static command_result df_autonestbox(color_ostream &out, vector<string> &parameters);
static void autonestbox_cycle(color_ostream &out); static void autonestbox_cycle(color_ostream &out);
static void init_autonestbox(color_ostream &out) {
config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) {
DEBUG(status,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, false);
set_config_val(CONFIG_CYCLE_TICKS, 6000);
}
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
DEBUG(status,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
did_complain = false;
}
static void cleanup_autonestbox(color_ostream &out) {
if (is_enabled) {
DEBUG(status,out).print("disabling (not persisting)\n");
is_enabled = false;
}
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autonestbox", plugin_name,
"Auto-assign egg-laying female pets to nestbox zones.", "Auto-assign egg-laying female pets to nestbox zones.",
df_autonestbox, df_autonestbox,
false, false,
autonestbox_help.c_str())); autonestbox_help.c_str()));
init_autonestbox(out);
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Maps::IsValid()) { if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run autonestbox without a loaded map.\n"); out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
return CR_FAILURE; return CR_FAILURE;
} }
if (enable != is_enabled) { if (enable != is_enabled) {
is_enabled = enable; is_enabled = enable;
DEBUG(status,out).print("%s from the API, persisting\n", DEBUG(status,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled"); is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_bool(CONFIG_IS_ENABLED, is_enabled);
init_autonestbox(out); } else {
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled",
is_enabled ? "enabled" : "disabled");
} }
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown (color_ostream &out) { DFhackCExport command_result plugin_load_data (color_ostream &out) {
cleanup_autonestbox(out); config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) {
DEBUG(status,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
set_config_val(CONFIG_CYCLE_TICKS, 6000);
}
// we have to copy our enabled flag into the global plugin variable, but
// all the other state we can directly read/modify from the persistent
// data structure.
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
DEBUG(status,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
did_complain = false;
return CR_OK; return CR_OK;
} }
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) { if (event == DFHack::SC_WORLD_UNLOADED) {
case DFHack::SC_MAP_LOADED: if (is_enabled) {
init_autonestbox(out); DEBUG(status,out).print("world unloaded; disabling %s\n",
break; plugin_name);
case DFHack::SC_MAP_UNLOADED: is_enabled = false;
cleanup_autonestbox(out); }
break;
default:
break;
} }
return CR_OK; return CR_OK;
} }
@ -178,6 +152,30 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) {
return CR_OK; return CR_OK;
} }
/////////////////////////////////////////////////////
// configuration interface
//
struct autonestbox_options {
// whether to display help
bool help = false;
// whether to run a cycle right now
bool now = false;
// how many ticks to wait between automatic cycles, -1 means unset
int32_t ticks = -1;
static struct_identity _identity;
};
static const struct_field_info autonestbox_options_fields[] = {
{ struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits<int32_t>::identity, 0, 0 },
{ struct_field_info::END }
};
struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn<autonestbox_options>, NULL, "autonestbox_options", NULL, autonestbox_options_fields);
static bool get_options(color_ostream &out, static bool get_options(color_ostream &out,
autonestbox_options &opts, autonestbox_options &opts,
const vector<string> &parameters) const vector<string> &parameters)
@ -205,8 +203,8 @@ static bool get_options(color_ostream &out,
static command_result df_autonestbox(color_ostream &out, vector<string> &parameters) { static command_result df_autonestbox(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend; CoreSuspender suspend;
if (!Maps::IsValid()) { if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run autonestbox without a loaded map.\n"); out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE; return CR_FAILURE;
} }
@ -228,7 +226,7 @@ static command_result df_autonestbox(color_ostream &out, vector<string> &paramet
} }
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
// autonestbox cycle logic // cycle logic
// //
static bool isEmptyPasture(df::building *building) { static bool isEmptyPasture(df::building *building) {