From 813d8d912b3a73cc1f4ed19d2b4c0fb6da5704b3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 09:16:52 -0500 Subject: [PATCH 01/86] Convert autofarm from ruby to C++ Reimplement the Ruby autofarm script as a C++ plugin --- plugins/devel/CMakeLists.txt | 1 + plugins/devel/autofarm.cpp | 406 +++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 plugins/devel/autofarm.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 4fb4b5cf5..be2bdac98 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -3,6 +3,7 @@ DFHACK_PLUGIN(vectors vectors.cpp) endif() ADD_DEFINITIONS(-DDEV_PLUGIN) +DFHACK_PLUGIN(autofarm autofarm.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp new file mode 100644 index 000000000..8a79d3f61 --- /dev/null +++ b/plugins/devel/autofarm.cpp @@ -0,0 +1,406 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/building_type.h" +#include "df/building_farmplotst.h" +#include "df/buildings_other_id.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/item_plantst.h" +#include "df/items_other_id.h" +#include "df/unit.h" +#include "df/building.h" +#include "df/plant_raw.h" +#include "df/plant_raw_flags.h" +#include "df/biome_type.h" +#include "modules/Items.h" +#include "modules/Maps.h" +#include "modules/World.h" + +using std::vector; +using std::string; +using std::map; +using std::set; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; + +static command_result autofarm(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("autofarm"); + +DFHACK_PLUGIN_IS_ENABLED(enabled); + +static void process(color_ostream &out) +{ + CoreSuspender suspend; + +} + +const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; +const char *usage = ( + "autofarm enable\n" + "autofarm default 30\n" + "autofarm threshold 150 helmet_plump tail_pig\n" + ); + +class AutoFarm { + +private: + map thresholds; + int defaultThreshold = 50; + + map lastCounts; + +public: + void initialize() + { + thresholds.clear(); + defaultThreshold = 50; + + lastCounts.clear(); + } + + void setThreshold(int id, int val) + { + thresholds[id] = val; + } + + int getThreshold(int id) + { + return (thresholds.count(id) > 0) ? thresholds[id] : defaultThreshold; + } + + void setDefault(int val) + { + defaultThreshold = val; + } + +private: + const df::plant_raw_flags seasons[4] = { df::plant_raw_flags::SPRING, df::plant_raw_flags::SUMMER, df::plant_raw_flags::AUTUMN, df::plant_raw_flags::WINTER }; + +public: + + bool is_plantable(df::plant_raw* plant) + { + bool has_seed = plant->flags.is_set(df::plant_raw_flags::SEED); + bool is_tree = plant->flags.is_set(df::plant_raw_flags::TREE); + + int8_t season = *df::global::cur_season; + int harvest = (*df::global::cur_season_tick) + plant->growdur * 10; + bool can_plant = has_seed && !is_tree && plant->flags.is_set(seasons[season]); + while (can_plant && harvest >= 10080) { + season = (season + 1) % 4; + harvest -= 10080; + can_plant = can_plant && plant->flags.is_set(seasons[season]); + } + + return can_plant; + } + +private: + map> plantable_plants; + + const map biomeFlagMap = { + { df::plant_raw_flags::BIOME_MOUNTAIN, df::biome_type::MOUNTAIN }, + { df::plant_raw_flags::BIOME_GLACIER, df::biome_type::GLACIER }, + { df::plant_raw_flags::BIOME_TUNDRA, df::biome_type::TUNDRA }, + { df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_FRESHWATER, df::biome_type::SWAMP_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_SALTWATER, df::biome_type::SWAMP_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_MARSH_TEMPERATE_FRESHWATER, df::biome_type::MARSH_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_MARSH_TEMPERATE_SALTWATER, df::biome_type::MARSH_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TROPICAL_FRESHWATER, df::biome_type::SWAMP_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TROPICAL_SALTWATER, df::biome_type::SWAMP_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_SWAMP_MANGROVE, df::biome_type::SWAMP_MANGROVE }, + { df::plant_raw_flags::BIOME_MARSH_TROPICAL_FRESHWATER, df::biome_type::MARSH_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_MARSH_TROPICAL_SALTWATER, df::biome_type::MARSH_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_FOREST_TAIGA, df::biome_type::FOREST_TAIGA }, + { df::plant_raw_flags::BIOME_FOREST_TEMPERATE_CONIFER, df::biome_type::FOREST_TEMPERATE_CONIFER }, + { df::plant_raw_flags::BIOME_FOREST_TEMPERATE_BROADLEAF, df::biome_type::FOREST_TEMPERATE_BROADLEAF }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_CONIFER, df::biome_type::FOREST_TROPICAL_CONIFER }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_DRY_BROADLEAF, df::biome_type::FOREST_TROPICAL_DRY_BROADLEAF }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_MOIST_BROADLEAF, df::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF }, + { df::plant_raw_flags::BIOME_GRASSLAND_TEMPERATE, df::biome_type::GRASSLAND_TEMPERATE }, + { df::plant_raw_flags::BIOME_SAVANNA_TEMPERATE, df::biome_type::SAVANNA_TEMPERATE }, + { df::plant_raw_flags::BIOME_SHRUBLAND_TEMPERATE, df::biome_type::SHRUBLAND_TEMPERATE }, + { df::plant_raw_flags::BIOME_GRASSLAND_TROPICAL, df::biome_type::GRASSLAND_TROPICAL }, + { df::plant_raw_flags::BIOME_SAVANNA_TROPICAL, df::biome_type::SAVANNA_TROPICAL }, + { df::plant_raw_flags::BIOME_SHRUBLAND_TROPICAL, df::biome_type::SHRUBLAND_TROPICAL }, + { df::plant_raw_flags::BIOME_DESERT_BADLAND, df::biome_type::DESERT_BADLAND }, + { df::plant_raw_flags::BIOME_DESERT_ROCK, df::biome_type::DESERT_ROCK }, + { df::plant_raw_flags::BIOME_DESERT_SAND, df::biome_type::DESERT_SAND }, + { df::plant_raw_flags::BIOME_OCEAN_TROPICAL, df::biome_type::OCEAN_TROPICAL }, + { df::plant_raw_flags::BIOME_OCEAN_TEMPERATE, df::biome_type::OCEAN_TEMPERATE }, + { df::plant_raw_flags::BIOME_OCEAN_ARCTIC, df::biome_type::OCEAN_ARCTIC }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_FRESHWATER, df::biome_type::POOL_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_BRACKISHWATER, df::biome_type::POOL_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_SALTWATER, df::biome_type::POOL_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_FRESHWATER, df::biome_type::POOL_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_BRACKISHWATER, df::biome_type::POOL_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_SALTWATER, df::biome_type::POOL_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_FRESHWATER, df::biome_type::LAKE_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_BRACKISHWATER, df::biome_type::LAKE_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_SALTWATER, df::biome_type::LAKE_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_FRESHWATER, df::biome_type::LAKE_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_BRACKISHWATER, df::biome_type::LAKE_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_SALTWATER, df::biome_type::LAKE_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_FRESHWATER, df::biome_type::RIVER_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_BRACKISHWATER, df::biome_type::RIVER_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_SALTWATER, df::biome_type::RIVER_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_FRESHWATER, df::biome_type::RIVER_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_BRACKISHWATER, df::biome_type::RIVER_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_SALTWATER, df::biome_type::RIVER_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_WATER, df::biome_type::SUBTERRANEAN_WATER }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_CHASM, df::biome_type::SUBTERRANEAN_CHASM }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_LAVA, df::biome_type::SUBTERRANEAN_LAVA } + }; + + +public: + void find_plantable_plants() + { + plantable_plants.clear(); + + map counts; + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); +#undef F + + for (auto ii : world->items.other[df::items_other_id::SEEDS]) + { + df::item_plantst* i = (df::item_plantst*)ii; + if ((i->flags.whole & bad_flags.whole) == 0) + counts[i->mat_index] += i->stack_size; + + } + + for (auto ci : counts) + { + if (df::global::ui->tasks.discovered_plants[ci.first]) + { + df::plant_raw* plant = world->raws.plants.all[ci.first]; + if (is_plantable(plant)) + for (auto flagmap : biomeFlagMap) + if (plant->flags.is_set(flagmap.first)) + plantable_plants[plant->index].insert(flagmap.second); + } + } + } + + void set_farms(color_ostream& out, vector plants, vector farms) + { + if (farms.empty()) + return; + + if (plants.empty()) + plants = vector{ -1 }; + + int season = *df::global::cur_season; + + for (int idx = 0; idx < farms.size(); idx++) + { + df::building_farmplotst* farm = farms[idx]; + int o = farm->plant_id[season]; + int n = plants[idx % plants.size()]; + if (n != o) + { + farm->plant_id[season] = plants[idx % plants.size()]; + out << "autofarm: changing farm #" << farm->id << + " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << + " to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl; + } + } + } + + void process(color_ostream& out) + { + if (!enabled) + return; + + find_plantable_plants(); + + lastCounts.clear(); + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); +#undef F + + for (auto ii : world->items.other[df::items_other_id::PLANT]) + { + df::item_plantst* i = (df::item_plantst*)ii; + if ((i->flags.whole & bad_flags.whole) == 0 && + plantable_plants.count(i->mat_index) > 0) + { + lastCounts[i->mat_index] += i->stack_size; + } + } + + map> plants; + plants.clear(); + + for (auto plantable : plantable_plants) + { + df::plant_raw* plant = world->raws.plants.all[plantable.first]; + if (lastCounts[plant->index] < getThreshold(plant->index)) + for (auto biome : plantable.second) + { + plants[biome].push_back(plant->index); + } + } + + map> farms; + farms.clear(); + + for (auto bb : world->buildings.other[df::buildings_other_id::FARM_PLOT]) + { + df::building_farmplotst* farm = (df::building_farmplotst*) bb; + if (farm->flags.bits.exists) + { + df::biome_type biome; + if (Maps::getTileDesignation(bb->centerx, bb->centery, bb->z)->bits.subterranean) + biome = biome_type::SUBTERRANEAN_WATER; + else { + df::coord2d region(Maps::getTileBiomeRgn(df::coord(bb->centerx, bb->centery, bb->z))); + biome = Maps::GetBiomeType(region.x, region.y); + } + farms[biome].push_back(farm); + } + } + + for (auto ff : farms) + { + set_farms(out, plants[ff.first], ff.second); + } + } + + void status(color_ostream& out) + { + out << (enabled ? "Running." : "Stopped.") << endl; + for (auto lc : lastCounts) + { + auto plant = world->raws.plants.all[lc.first]; + out << plant->id << " limit " << getThreshold(lc.first) << " current " << lc.second << endl; + } + + for (auto th : thresholds) + { + if (lastCounts[th.first] > 0) + continue; + auto plant = world->raws.plants.all[th.first]; + out << plant->id << " limit " << getThreshold(th.first) << " current 0" << endl; + } + out << "Default: " << defaultThreshold << endl; + } + +}; + +static AutoFarm* autofarmInstance; + + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + if (world && ui) { + commands.push_back( + PluginCommand("autofarm", tagline, + autofarm, false, usage + ) + ); + } + autofarmInstance = new AutoFarm(); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + delete autofarmInstance; + + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!autofarmInstance) + return CR_OK; + + if (!Maps::IsValid()) + return CR_OK; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if (world->frame_counter % 50 != 0) // Check every hour + return CR_OK; + + autofarmInstance->process(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + enabled = enable; + return CR_OK; +} + +static command_result autofarm(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + if (parameters.size() == 1 && parameters[0] == "enable") + plugin_enable(out, true); + else if (parameters.size() == 1 && parameters[0] == "disable") + plugin_enable(out, false); + else if (parameters.size() == 2 && parameters[0] == "default") + autofarmInstance->setDefault(atoi(parameters[1].c_str())); + else if (parameters.size() >= 3 && parameters[0] == "threshold") + { + int val = atoi(parameters[1].c_str()); + for (int i = 2; i < parameters.size(); i++) + { + string id = parameters[i]; + transform(id.begin(), id.end(), id.begin(), ::toupper); + + bool ok = false; + for (auto plant : world->raws.plants.all) + { + if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id)) + { + autofarmInstance->setThreshold(plant->index, val); + ok = true; + break; + } + } + if (!ok) + { + out << "Cannot find plant with id " << id << endl; + return CR_WRONG_USAGE; + } + } + } + else if (parameters.size() == 0 || parameters.size() == 1 && parameters[0] == "status") + autofarmInstance->status(out); + else + return CR_WRONG_USAGE; + + return CR_OK; +} + From 3802412d688262bcdeb4dd7239c45667e5730b00 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 18 Aug 2018 14:57:26 -0500 Subject: [PATCH 02/86] autofarm: suspend while processing --- plugins/devel/autofarm.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index 8a79d3f61..aaee2764e 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -39,12 +39,6 @@ DFHACK_PLUGIN("autofarm"); DFHACK_PLUGIN_IS_ENABLED(enabled); -static void process(color_ostream &out) -{ - CoreSuspender suspend; - -} - const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; const char *usage = ( "autofarm enable\n" @@ -350,7 +344,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) if (world->frame_counter % 50 != 0) // Check every hour return CR_OK; - autofarmInstance->process(out); + { + CoreSuspender suspend; + autofarmInstance->process(out); + } return CR_OK; } From d3b335105c6cbaff26763a98d4005d8a3c488bda Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 25 Aug 2018 11:59:28 -0500 Subject: [PATCH 03/86] Add "tailor" plugin, to provide clothing management --- plugins/devel/CMakeLists.txt | 1 + plugins/devel/tailor.cpp | 523 +++++++++++++++++++++++++++++++++++ 2 files changed, 524 insertions(+) create mode 100644 plugins/devel/tailor.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 4fb4b5cf5..4609059bb 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,6 +19,7 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(stepBetween stepBetween.cpp) DFHACK_PLUGIN(stockcheck stockcheck.cpp) DFHACK_PLUGIN(stripcaged stripcaged.cpp) +DFHACK_PLUGIN(tailor tailor.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp) DFHACK_PLUGIN(zoom zoom.cpp) diff --git a/plugins/devel/tailor.cpp b/plugins/devel/tailor.cpp new file mode 100644 index 000000000..0435f84b6 --- /dev/null +++ b/plugins/devel/tailor.cpp @@ -0,0 +1,523 @@ +/* + * Tailor plugin. Automatically manages keeping your dorfs clothed. + * For best effect, place "tailor enable" in your dfhack.init configuration, + * or set AUTOENABLE to true. + */ + +#include "Core.h" +#include "DataDefs.h" +#include "PluginManager.h" + +#include "df/creature_raw.h" +#include "df/global_objects.h" +#include "df/historical_entity.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_pantsst.h" +#include "df/itemdef_shoesst.h" +#include "df/items_other_id.h" +#include "df/job.h" +#include "df/job_type.h" +#include "df/manager_order.h" +#include "df/ui.h" +#include "df/world.h" + +#include "modules/Maps.h" +#include "modules/Units.h" +#include "modules/Translation.h" +#include "modules/World.h" + +using namespace DFHack; +using namespace std; + +using df::global::world; +using df::global::ui; + +DFHACK_PLUGIN("tailor"); +#define AUTOENABLE false +DFHACK_PLUGIN_IS_ENABLED(enabled); + +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); + +const char *tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing."; +const char *usage = ( + " tailor enable\n" + " Enable the plugin.\n" + " tailor disable\n" + " Disable the plugin.\n" + " tailor use LEATHER,CLOTH,YARN,SILK\n" + " Use these materials, in this order, when requesting additional clothing\n" + " tailor status\n" + " Display plugin status\n" + "\n" + "Whenever the bookkeeper updates stockpile records, the plugin will scan every unit in the fort, \n" + "count up the number that are worn, and then order enough more made to replace all worn items.\n" + "If there are enough replacement items in inventory to replace all worn items, the units wearing them\n" + "will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll\n" + "reeequip with replacement items.\n" +); + +// ARMOR, SHOES, HELM, GLOVES, PANTS + +// ah, if only STL had a bimap + +static map jobTypeMap = { + { df::job_type::MakeArmor, df::item_type::ARMOR }, + { df::job_type::MakePants, df::item_type::PANTS }, + { df::job_type::MakeHelm, df::item_type::HELM }, + { df::job_type::MakeGloves, df::item_type::GLOVES }, + { df::job_type::MakeShoes, df::item_type::SHOES } +}; + +static map itemTypeMap = { + { df::item_type::ARMOR, df::job_type::MakeArmor }, + { df::item_type::PANTS, df::job_type::MakePants }, + { df::item_type::HELM, df::job_type::MakeHelm}, + { df::item_type::GLOVES, df::job_type::MakeGloves}, + { df::item_type::SHOES, df::job_type::MakeShoes} +}; + +void do_scan(color_ostream& out) +{ + map, int> available; // key is item type & size + map, int> needed; // same + map, int> queued; // same + + map sizes; // this maps body size to races + + map, int> orders; // key is item type, item subtype, size + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(owned); +#undef F + + available.empty(); + needed.empty(); + queued.empty(); + orders.empty(); + + int silk = 0, yarn = 0, cloth = 0, leather = 0; + + // scan for useable clothing + + for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing" + { + if (i->flags.whole & bad_flags.whole) + continue; + if (i->flags.bits.owned) + continue; + if (i->getWear() >= 1) + continue; + df::item_type t = i->getType(); + int size = world->raws.creatures.all[i->getMakerRace()]->adultsize; + + available[make_pair(t, size)] += 1; + } + + // scan for clothing raw materials + + for (auto i : world->items.other[df::items_other_id::CLOTH]) + { + if (i->flags.whole & bad_flags.whole) + continue; + if (!i->hasImprovements()) // only count dyed + continue; + MaterialInfo mat(i); + int ss = i->getStackSize(); + + if (mat.material) + { + + if (mat.material->flags.is_set(df::material_flags::SILK)) + silk += ss; + else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT)) + cloth += ss; + else if (mat.material->flags.is_set(df::material_flags::YARN)) + yarn += ss; + } + } + + for (auto i : world->items.other[df::items_other_id::SKIN_TANNED]) + { + if (i->flags.whole & bad_flags.whole) + continue; + leather += i->getStackSize(); + } + + out.print("available: silk %d yarn %d cloth %d leather %d\n", silk, yarn, cloth, leather); + + // scan for units who need replacement clothing + + for (auto u : world->units.active) + { + if (!Units::isOwnCiv(u) || + !Units::isOwnGroup(u) || + !Units::isActive(u) || + Units::isBaby(u)) + continue; // skip units we don't control + + set wearing; + wearing.empty(); + + deque worn; + worn.empty(); + + for (auto inv : u->inventory) + { + if (inv->mode != df::unit_inventory_item::Worn) + continue; + if (inv->item->getWear() > 0) + worn.push_back(inv->item); + else + wearing.insert(inv->item->getType()); + } + + int size = world->raws.creatures.all[u->race]->adultsize; + sizes[size] = u->race; + + for (auto ty : set{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES }) + { + if (wearing.count(ty) == 0) + needed[make_pair(ty, size)] += 1; + } + + for (auto w : worn) + { + auto ty = w->getType(); + auto oo = itemTypeMap.find(ty); + if (oo == itemTypeMap.end()) + continue; + df::job_type o = oo->second; + + int size = world->raws.creatures.all[w->getMakerRace()]->adultsize; + std::string description; + w->getItemDescription(&description, 0); + + if (available[make_pair(ty, size)] > 0) + { + if (w->flags.bits.owned) + { + bool confiscated = Items::setOwner(w, NULL); + + out.print( + "%s %s from %s.\n", + (confiscated ? "Confiscated" : "Could not confiscate"), + description.c_str(), + Translation::TranslateName(&u->name, false).c_str() + ); + } + + if (wearing.count(ty) == 0) + available[make_pair(ty, size)] -= 1; + + if (w->getWear() > 1) + w->flags.bits.dump = true; + } + else + { +// out.print("%s worn by %s needs replacement\n", +// description.c_str(), +// Translation::TranslateName(&u->name, false).c_str() +// ); + orders[make_tuple(o, w->getSubtype(), size)] += 1; + } + } + } + + auto entity = world->entities.all[ui->civ_id]; + + for (auto a : needed) + { + df::item_type ty = a.first.first; + int size = a.first.second; + int count = a.second; + + int sub = 0; + vector v; + + switch (ty) { + case df::item_type::ARMOR: v = entity->resources.armor_type; break; + case df::item_type::GLOVES: v = entity->resources.gloves_type; break; + case df::item_type::HELM: v = entity->resources.helm_type; break; + case df::item_type::PANTS: v = entity->resources.pants_type; break; + case df::item_type::SHOES: v = entity->resources.shoes_type; break; + } + + for (auto vv : v) { + bool isClothing = false; + switch (ty) { + case df::item_type::ARMOR: isClothing = world->raws.itemdefs.armor[vv] ->armorlevel == 0; break; + case df::item_type::GLOVES: isClothing = world->raws.itemdefs.gloves[vv]->armorlevel == 0; break; + case df::item_type::HELM: isClothing = world->raws.itemdefs.helms[vv] ->armorlevel == 0; break; + case df::item_type::PANTS: isClothing = world->raws.itemdefs.pants[vv] ->armorlevel == 0; break; + case df::item_type::SHOES: isClothing = world->raws.itemdefs.shoes[vv] ->armorlevel == 0; break; + } + if (isClothing) + { + sub = vv; + break; + } + } + + orders[make_tuple(itemTypeMap[ty], sub, size)] += count; + } + + + // scan orders + + for (auto o : world->manager_orders) + { + auto f = jobTypeMap.find(o->job_type); + if (f == jobTypeMap.end()) + continue; + + auto sub = o->item_subtype; + int race = o->hist_figure_id; + if (race == -1) + continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs + + int size = world->raws.creatures.all[race]->adultsize; + + orders[make_tuple(o->job_type, sub, size)] -= o->amount_left; + } + + // place orders + + for (auto o : orders) + { + df::job_type ty; + int sub; + int size; + + tie(ty, sub, size) = o.first; + int count = o.second; + + if (count > 0) + { + vector v; + BitArray* fl; + string name_s, name_p; + + switch (ty) { + case df::job_type::MakeArmor: + name_s = world->raws.itemdefs.armor[sub]->name; + name_p = world->raws.itemdefs.armor[sub]->name_plural; + v = entity->resources.armor_type; + fl = &world->raws.itemdefs.armor[sub]->props.flags; + break; + case df::job_type::MakeGloves: + name_s = world->raws.itemdefs.gloves[sub]->name; + name_p = world->raws.itemdefs.gloves[sub]->name_plural; + v = entity->resources.gloves_type; + fl = &world->raws.itemdefs.gloves[sub]->props.flags; + break; + case df::job_type::MakeHelm: + name_s = world->raws.itemdefs.helms[sub]->name; + name_p = world->raws.itemdefs.helms[sub]->name_plural; + v = entity->resources.helm_type; + fl = &world->raws.itemdefs.helms[sub]->props.flags; + break; + case df::job_type::MakePants: + name_s = world->raws.itemdefs.pants[sub]->name; + name_p = world->raws.itemdefs.pants[sub]->name_plural; + v = entity->resources.pants_type; + fl = &world->raws.itemdefs.pants[sub]->props.flags; + break; + case df::job_type::MakeShoes: + name_s = world->raws.itemdefs.shoes[sub]->name; + name_p = world->raws.itemdefs.shoes[sub]->name_plural; + v = entity->resources.shoes_type; + fl = &world->raws.itemdefs.shoes[sub]->props.flags; + break; + } + + bool can_make = false; + for (auto vv : v) + { + if (vv == sub) + { + can_make = true; + break; + } + } + + if (!can_make) + { + out.print("Cannot make %s, skipped\n", name_p); + continue; // this civilization does not know how to make this item, so sorry + + } + + switch (ty) { + case df::item_type::ARMOR: break; + case df::item_type::GLOVES: break; + case df::item_type::HELM: break; + case df::item_type::PANTS: break; + case df::item_type::SHOES: break; + } + + df::job_material_category mat; + + if (silk > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { + mat.whole = df::job_material_category::mask_silk; + silk -= count; + } + else if (cloth > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { + mat.whole = df::job_material_category::mask_cloth; + cloth -= count; + } + else if (yarn > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { + mat.whole = df::job_material_category::mask_yarn; + yarn -= count; + } + else if (leather > count + 10 && fl->is_set(df::armor_general_flags::LEATHER)) { + mat.whole = df::job_material_category::mask_leather; + leather -= count; + } + else // not enough appropriate material available + continue; + + auto order = new df::manager_order(); + order->job_type = ty; + order->item_type = df::item_type::NONE; + order->item_subtype = sub; + order->mat_type = -1; + order->mat_index = -1; + order->amount_left = count; + order->amount_total = count; + order->status.bits.validated = false; + order->status.bits.active = false; + order->id = world->manager_order_next_id++; + order->hist_figure_id = sizes[size]; + order->material_category = mat; + + world->manager_orders.push_back(order); + + out.print("Added order #%d for %d %s %s (sized for %s)\n", + order->id, + count, + bitfield_to_string(order->material_category), + (count > 1) ? name_p : name_s, + world->raws.creatures.all[order->hist_figure_id]->name[1] + ); + } + + } + +} + +#define DELTA_TICKS 600 + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!enabled) + return CR_OK; + + if (!Maps::IsValid()) + return CR_OK; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if (world->frame_counter % DELTA_TICKS != 0) + return CR_OK; + + bool found = false; + + for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next) + { + if (link->item == NULL) continue; + if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords) + { + found = true; + break; + } + } + + if (found) + { + do_scan(out); + } + + return CR_OK; +} + +static bool tailor_set_materials(string parameter) +{ + return false; +} + + +static command_result tailor_cmd(color_ostream &out, vector & parameters) { + bool desired = enabled; + if (parameters.size() == 1) + { + if (parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1") + { + desired = true; + } + else if (parameters[0] == "disable" || parameters[0] == "off" || parameters[0] == "0") + { + desired = false; + } + else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") + { + out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); + return CR_OK; + } + else if (parameters[0] == "test") + { + do_scan(out); + return CR_OK; + } + else if (parameters[0] != "status") + { + return CR_WRONG_USAGE; + } + } + else if (parameters.size() == 2 && parameters[1] == "use") + { + return ( + tailor_set_materials(parameters[2]) ? + CR_OK : CR_WRONG_USAGE + ); + } + + out.print("Tailor is %s %s.\n", (desired == enabled)? "currently": "now", desired? "enabled": "disabled"); + enabled = desired; + + return CR_OK; +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) +{ + enabled = enable; + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + if (AUTOENABLE) { + enabled = true; + } + + commands.push_back(PluginCommand(plugin_name, tagline, tailor_cmd, false, usage)); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +} From 98903d9d7120a25f5414731f35b7b696d814d1a9 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 26 Aug 2018 09:30:08 -0500 Subject: [PATCH 04/86] [tailor] tidy --- plugins/devel/tailor.cpp | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/plugins/devel/tailor.cpp b/plugins/devel/tailor.cpp index 0435f84b6..18db67b38 100644 --- a/plugins/devel/tailor.cpp +++ b/plugins/devel/tailor.cpp @@ -47,8 +47,6 @@ const char *usage = ( " Enable the plugin.\n" " tailor disable\n" " Disable the plugin.\n" - " tailor use LEATHER,CLOTH,YARN,SILK\n" - " Use these materials, in this order, when requesting additional clothing\n" " tailor status\n" " Display plugin status\n" "\n" @@ -133,8 +131,7 @@ void do_scan(color_ostream& out) int ss = i->getStackSize(); if (mat.material) - { - + { if (mat.material->flags.is_set(df::material_flags::SILK)) silk += ss; else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT)) @@ -269,7 +266,6 @@ void do_scan(color_ostream& out) orders[make_tuple(itemTypeMap[ty], sub, size)] += count; } - // scan orders for (auto o : world->manager_orders) @@ -280,7 +276,7 @@ void do_scan(color_ostream& out) auto sub = o->item_subtype; int race = o->hist_figure_id; - if (race == -1) + if (race == -1) continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs int size = world->raws.creatures.all[race]->adultsize; @@ -352,7 +348,6 @@ void do_scan(color_ostream& out) { out.print("Cannot make %s, skipped\n", name_p); continue; // this civilization does not know how to make this item, so sorry - } switch (ty) { @@ -408,9 +403,7 @@ void do_scan(color_ostream& out) world->raws.creatures.all[order->hist_figure_id]->name[1] ); } - } - } #define DELTA_TICKS 600 @@ -449,12 +442,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; } -static bool tailor_set_materials(string parameter) -{ - return false; -} - - static command_result tailor_cmd(color_ostream &out, vector & parameters) { bool desired = enabled; if (parameters.size() == 1) @@ -482,13 +469,8 @@ static command_result tailor_cmd(color_ostream &out, vector & parameter return CR_WRONG_USAGE; } } - else if (parameters.size() == 2 && parameters[1] == "use") - { - return ( - tailor_set_materials(parameters[2]) ? - CR_OK : CR_WRONG_USAGE - ); - } + else + return CR_WRONG_USAGE; out.print("Tailor is %s %s.\n", (desired == enabled)? "currently": "now", desired? "enabled": "disabled"); enabled = desired; From 6afb76d41c400f4b6045910f113c855d49b57b66 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Aug 2018 22:29:14 -0500 Subject: [PATCH 05/86] autofarm: make crop assignment more stable --- plugins/devel/autofarm.cpp | 61 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index aaee2764e..0fc02728b 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -22,10 +22,13 @@ #include "modules/Maps.h" #include "modules/World.h" +#include + using std::vector; using std::string; using std::map; using std::set; +using std::queue; using std::endl; using namespace DFHack; using namespace df::enums; @@ -195,27 +198,53 @@ public: } } - void set_farms(color_ostream& out, vector plants, vector farms) + void set_farms(color_ostream& out, set plants, vector farms) { - if (farms.empty()) - return; - - if (plants.empty()) - plants = vector{ -1 }; + // this algorithm attempts to change as few farms as possible, while ensuring that + // the number of farms planting each eligible plant is "as equal as possible" + + if (farms.empty() || plants.empty()) + return; // do nothing if there are no farms or no plantable plants int season = *df::global::cur_season; - for (int idx = 0; idx < farms.size(); idx++) + int min = farms.size() / plants.size(); // the number of farms that should plant each eligible plant, rounded down + int extra = farms.size() - min * plants.size(); // the remainder that cannot be evenly divided + + map counters; + counters.empty(); + + queue toChange; + toChange.empty(); + + for (auto farm : farms) { - df::building_farmplotst* farm = farms[idx]; int o = farm->plant_id[season]; - int n = plants[idx % plants.size()]; - if (n != o) + if (plants.count(o)==0 || counters[o] > min || (counters[o] == min && extra == 0)) + toChange.push(farm); // this farm is an excess instance for the plant it is currently planting + else { - farm->plant_id[season] = plants[idx % plants.size()]; + if (counters[o] == min) + extra--; // allocate off one of the remainder farms + counters[o]++; + } + } + + for (auto n : plants) + { + int c = counters[n]; + while (toChange.size() > 0 && (c < min || (c == min && extra > 0))) + { + // pick one of the excess farms and change it to plant this plant + df::building_farmplotst* farm = toChange.front(); + int o = farm->plant_id[season]; + farm->plant_id[season] = n; out << "autofarm: changing farm #" << farm->id << - " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << + " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << " to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl; + toChange.pop(); + if (c++ == min) + extra--; } } } @@ -248,7 +277,7 @@ public: } } - map> plants; + map> plants; plants.clear(); for (auto plantable : plantable_plants) @@ -257,7 +286,7 @@ public: if (lastCounts[plant->index] < getThreshold(plant->index)) for (auto biome : plantable.second) { - plants[biome].push_back(plant->index); + plants[biome].insert(plant->index); } } @@ -362,7 +391,9 @@ static command_result autofarm(color_ostream &out, vector & parameters) { CoreSuspender suspend; - if (parameters.size() == 1 && parameters[0] == "enable") + if (parameters.size() == 1 && parameters[0] == "runonce") + autofarmInstance->process(out); + else if (parameters.size() == 1 && parameters[0] == "enable") plugin_enable(out, true); else if (parameters.size() == 1 && parameters[0] == "disable") plugin_enable(out, false); From f0632347d0b06ad88676836c4b5170bba1312f54 Mon Sep 17 00:00:00 2001 From: Pauli Date: Sat, 7 Jul 2018 13:27:29 +0300 Subject: [PATCH 06/86] Remove Core.h include from DataDefs.h Core.h isn't required for DataDefs.h which removes Core.h dependency from DataStatics*. --- library/DataStatics.cpp | 1 + library/DataStaticsFields.cpp | 2 ++ library/TileTypes.cpp | 1 + library/include/DataDefs.h | 2 +- library/include/DataFuncs.h | 4 ++++ library/include/DataIdentity.h | 1 + library/include/LuaTools.h | 1 + library/include/modules/Buildings.h | 3 +++ library/include/modules/Job.h | 2 ++ plugins/embark-assistant/matcher.cpp | 1 + plugins/stockpiles/StockpileSerializer.cpp | 2 ++ 11 files changed, 19 insertions(+), 1 deletion(-) diff --git a/library/DataStatics.cpp b/library/DataStatics.cpp index 31c8c0200..61b7b6378 100644 --- a/library/DataStatics.cpp +++ b/library/DataStatics.cpp @@ -1,3 +1,4 @@ +#include "Core.h" #include "Internal.h" #include "DataDefs.h" #include "MiscUtils.h" diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index d6f0414bb..8318b523d 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -1,5 +1,7 @@ #include +#include + #ifndef STATIC_FIELDS_GROUP #include "DataDefs.h" #endif diff --git a/library/TileTypes.cpp b/library/TileTypes.cpp index bd2c52a11..fc2d044ee 100644 --- a/library/TileTypes.cpp +++ b/library/TileTypes.cpp @@ -26,6 +26,7 @@ distribution. #include "TileTypes.h" #include "Export.h" +#include #include using namespace DFHack; diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 149ac47a0..7c3b534b6 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -32,7 +32,6 @@ distribution. #include #include -#include "Core.h" #include "BitArray.h" // Stop some MS stupidity @@ -48,6 +47,7 @@ typedef struct lua_State lua_State; namespace DFHack { + class Core; class virtual_class {}; enum identity_type { diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 6541ae900..63b40ef52 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,6 +32,10 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" +namespace DFHack { + class color_ostream; +} + 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 62a9ff274..88a96f9f3 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -24,6 +24,7 @@ distribution. #pragma once +#include #include #include #include diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 814afe179..df89d184f 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -30,6 +30,7 @@ distribution. #include #include +#include "ColorText.h" #include "DataDefs.h" #include diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 4032b96af..50cbc898f 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -53,6 +53,9 @@ namespace df namespace DFHack { + +class color_ostream; + namespace Buildings { /** diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 814f1e062..cde5c64dd 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -47,6 +47,8 @@ namespace df namespace DFHack { + class color_ostream; + namespace Job { // Duplicate the job structure. It is not linked into any DF lists. DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepEverything=false); diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 7b3d385cb..e724e565f 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -2,6 +2,7 @@ #include +#include "Core.h" #include "DataDefs.h" #include "df/biome_type.h" #include "df/inorganic_raw.h" diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index ef8c557c3..cdeced990 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -30,6 +30,8 @@ // protobuf #include +#include + using std::endl; using namespace DFHack; using namespace df::enums; From bc05c9b1a1f97350d4944a7b87545cfc838e7b0b Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 06:59:27 -0500 Subject: [PATCH 07/86] started work on new plugin --- plugins/CMakeLists.txt | 1 + plugins/autoclothing.cpp | 113 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 plugins/autoclothing.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 284922cc1..2f4eb68a3 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -84,6 +84,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(add-spatter add-spatter.cpp) #DFHACK_PLUGIN(advtools advtools.cpp) DFHACK_PLUGIN(autochop autochop.cpp) + DFHACK_PLUGIN(autoclothing autoclothing.cpp) DFHACK_PLUGIN(autodump autodump.cpp) DFHACK_PLUGIN(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(autohauler autohauler.cpp) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp new file mode 100644 index 000000000..313b67257 --- /dev/null +++ b/plugins/autoclothing.cpp @@ -0,0 +1,113 @@ +// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D + +// some headers required for a plugin. Nothing special, just the basics. +#include "Core.h" +#include +#include +#include + +// DF data structure definition headers +#include "DataDefs.h" + +#include "modules/World.h" + +#include "df/manager_order.h" +#include "df/world.h" + +using namespace DFHack; +using namespace df::enums; + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - +// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case +DFHACK_PLUGIN("autoclothing"); + +// Any globals a plugin requires (e.g. world) should be listed here. +// For example, this line expands to "using df::global::world" and prevents the +// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): +// +REQUIRE_GLOBAL(world); + +// Only run if this is enabled +DFHACK_PLUGIN_IS_ENABLED(active); + +// 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 autoclothing(color_ostream &out, std::vector & parameters); + +// Mandatory init function. If you have some global state, create it here. +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "autoclothing", "Automatically manage clothing work orders", + autoclothing, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " skeleton\n" + " Does nothing.\n" + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_WORLD_LOADED: + // initialize from the world just loaded + break; + case SC_WORLD_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} + + +// Whatever you put here will be done in each game step. Don't abuse it. +// It's optional, so you can just comment it out like this if you don't need it. + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + // whetever. You don't need to suspend DF execution here. + return CR_OK; +} + + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result autoclothing(color_ostream &out, std::vector & parameters) +{ + // It's nice to print a help message you get invalid options + // from the user instead of just acting strange. + // This can be achieved by adding the extended help string to the + // PluginCommand registration as show above, and then returning + // CR_WRONG_USAGE from the function. The same string will also + // be used by 'help your-command'. + if (!parameters.empty()) + return CR_WRONG_USAGE; + // Commands are called from threads other than the DF one. + // Suspend this thread until DF has time for us. If you + // use CoreSuspender, it'll automatically resume DF when + // execution leaves the current scope. + CoreSuspender suspend; + // Actually do something here. Yay. + out.print("Hello! I do nothing, remember?\n"); + // Give control back to DF. + return CR_OK; +} From 78bd70c858eb7d938b47e189e6ce7292a7472b8d Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 07:53:23 -0500 Subject: [PATCH 08/86] Loop through all owned items of all units. --- plugins/autoclothing.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 313b67257..f24bac165 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -9,12 +9,15 @@ // DF data structure definition headers #include "DataDefs.h" +#include "modules/Maps.h" +#include "modules/Units.h" #include "modules/World.h" #include "df/manager_order.h" #include "df/world.h" using namespace DFHack; +using namespace DFHack::Units; using namespace df::enums; // A plugin must be able to return its name and version. @@ -29,12 +32,14 @@ DFHACK_PLUGIN("autoclothing"); REQUIRE_GLOBAL(world); // Only run if this is enabled -DFHACK_PLUGIN_IS_ENABLED(active); +DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled); // 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 autoclothing(color_ostream &out, std::vector & parameters); +static void do_autoclothing(); + // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -85,7 +90,20 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - // whetever. You don't need to suspend DF execution here. + if (!autoclothing_enabled) + return CR_OK; + + if (!Maps::IsValid()) + return CR_OK; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if ((world->frame_counter + 500) % 1200 != 0) // Check every day, but not the same day as other things + return CR_OK; + + do_autoclothing(); + return CR_OK; } @@ -111,3 +129,17 @@ command_result autoclothing(color_ostream &out, std::vector & para // Give control back to DF. return CR_OK; } + +static void do_autoclothing() +{ + for (auto&& unit : world->units.active) + { + if (!isCitizen(unit)) + continue; + + for (auto&& ownedItem : unit->owned_items) + { + + } + } +} From f89a3db6feef6ddb8321313b0aae54e22312f2e2 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 08:56:50 -0500 Subject: [PATCH 09/86] Loop through citizens to find owned items. --- plugins/autoclothing.cpp | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index f24bac165..b83d9fcfc 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -6,20 +6,36 @@ #include #include +#include + // DF data structure definition headers #include "DataDefs.h" +#include "modules/Items.h" #include "modules/Maps.h" #include "modules/Units.h" #include "modules/World.h" #include "df/manager_order.h" +#include "df/creature_raw.h" #include "df/world.h" using namespace DFHack; +using namespace DFHack::Items; using namespace DFHack::Units; using namespace df::enums; +struct ClothingRequirement +{ + df::job_type job_type; + df::item_type item_type; + int16_t item_subtype; + int16_t needed_per_citizen; + std::map total_needed_per_size; +}; + +std::vectorclothingOrders; + // A plugin must be able to return its name and version. // The name string provided must correspond to the filename - // skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case @@ -132,14 +148,33 @@ command_result autoclothing(color_ostream &out, std::vector & para static void do_autoclothing() { + if (clothingOrders.size() == 0) + return; + for (auto&& unit : world->units.active) { if (!isCitizen(unit)) continue; - - for (auto&& ownedItem : unit->owned_items) + for (auto&& clothingOrder : clothingOrders) { + int alreadyOwnedAmount = 0; + + for (auto&& ownedItem : unit->owned_items) + { + auto item = findItemByID(ownedItem); + + if (item->getType() != clothingOrder.item_type) + continue; + if (item->getSubtype() != clothingOrder.item_subtype) + continue; + + alreadyOwnedAmount++; + } + + alreadyOwnedAmount -= clothingOrder.needed_per_citizen; + if (alreadyOwnedAmount <= 0) + continue; } } } From a3eafbbc1b6e00d675244c2792d675435db499a0 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 09:24:52 -0500 Subject: [PATCH 10/86] is not capitalized --- plugins/autoclothing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index b83d9fcfc..ca1bbbfd3 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include // DF data structure definition headers #include "DataDefs.h" From eb04d513b41f4e192a44eeb6bb601db81bc0b3c8 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 11:02:50 -0500 Subject: [PATCH 11/86] Got basic logic for checking and processing clothing orders. Not done: actually adding orders to the list. --- plugins/autoclothing.cpp | 98 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index ca1bbbfd3..71d9906af 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -13,6 +13,7 @@ #include "modules/Items.h" #include "modules/Maps.h" +#include "modules/Materials.h" #include "modules/Units.h" #include "modules/World.h" @@ -30,8 +31,9 @@ struct ClothingRequirement df::job_type job_type; df::item_type item_type; int16_t item_subtype; + df::job_material_category material_category; int16_t needed_per_citizen; - std::map total_needed_per_size; + std::map total_needed_per_race; }; std::vectorclothingOrders; @@ -104,7 +106,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan // Whatever you put here will be done in each game step. Don't abuse it. // It's optional, so you can just comment it out like this if you don't need it. -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (!autoclothing_enabled) return CR_OK; @@ -151,14 +153,19 @@ static void do_autoclothing() if (clothingOrders.size() == 0) return; + //first we look through all the units on the map to see who needs new clothes. for (auto&& unit : world->units.active) { + //obviously we don't care about illegal aliens. if (!isCitizen(unit)) continue; + + //now check each clothing order to see what the unit might be missing. for (auto&& clothingOrder : clothingOrders) { int alreadyOwnedAmount = 0; + //looping through the items first, then clothing order might be a little faster, but this way is cleaner. for (auto&& ownedItem : unit->owned_items) { auto item = findItemByID(ownedItem); @@ -168,13 +175,96 @@ static void do_autoclothing() if (item->getSubtype() != clothingOrder.item_subtype) continue; + MaterialInfo matInfo; + matInfo.decode(item); + + if (!matInfo.matches(clothingOrder.material_category)) + continue; + alreadyOwnedAmount++; } + int neededAmount = clothingOrder.needed_per_citizen - alreadyOwnedAmount; + + if (neededAmount <= 0) + continue; + + //technically, there's some leeway in sizes, but only caring about exact sizes is simpler. + clothingOrder.total_needed_per_race[unit->race] += alreadyOwnedAmount; + + } + } + + //Now we go through all the items in the map to see how many clothing items we have but aren't owned yet. + for (auto&& item : world->items.all) + { + //skip any owned items. + if (getOwner(item)) + continue; + + //again, for each item, find if any clothing order matches the + for (auto&& clothingOrder : clothingOrders) + { + if (item->getType() != clothingOrder.item_type) + continue; + if (item->getSubtype() != clothingOrder.item_subtype) + continue; - alreadyOwnedAmount -= clothingOrder.needed_per_citizen; + MaterialInfo matInfo; + matInfo.decode(item); - if (alreadyOwnedAmount <= 0) + if (!matInfo.matches(clothingOrder.material_category)) continue; + + clothingOrder.total_needed_per_race[item->getMakerRace] --; + } + } + + //Finally loop through the clothing orders to find ones that need more made. + for (auto&& clothingOrder : clothingOrders) + { + for (auto&& orderNeeded : clothingOrder.total_needed_per_race) + { + auto race = orderNeeded.first; + auto amount = orderNeeded.second; + //Previous operations can easily make this negative. That jus means we have more than we need already. + if (amount <= 0) + continue; + + bool orderExistedAlready = false; + for (auto&& managerOrder : world->manager_orders) + { + //Annoyingly, the manager orders store the job type for clothing orders, and actual item type is left at -1; + if (managerOrder->job_type != clothingOrder.job_type) + continue; + if (managerOrder->item_subtype != clothingOrder.item_subtype) + continue; + if (managerOrder->hist_figure_id != race) + continue; + + //We found a work order, that means we don't need to make a new one. + orderExistedAlready = true; + amount -= managerOrder->amount_left; + if (amount > 0) + { + managerOrder->amount_left += amount; + managerOrder->amount_total += amount; + } + } + //if it wasn't there, we need to make a new one. + if (!orderExistedAlready) + { + df::manager_order newOrder; + + newOrder.id = world->manager_order_next_id; + world->manager_order_next_id++; + newOrder.job_type = clothingOrder.job_type; + newOrder.item_subtype = clothingOrder.item_subtype; + newOrder.hist_figure_id = race; + newOrder.material_category = clothingOrder.material_category; + newOrder.amount_left = amount; + newOrder.amount_total = amount; + world->manager_orders.push_back(&newOrder); + } } } } From 14ff66d551d563626d393c166592b0255469318f Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 11:11:16 -0500 Subject: [PATCH 12/86] Split up the big update function to satisfy codefactor. --- plugins/autoclothing.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 71d9906af..6abca667c 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -148,12 +148,8 @@ command_result autoclothing(color_ostream &out, std::vector & para return CR_OK; } -static void do_autoclothing() +static void find_needed_clothing_items() { - if (clothingOrders.size() == 0) - return; - - //first we look through all the units on the map to see who needs new clothes. for (auto&& unit : world->units.active) { //obviously we don't care about illegal aliens. @@ -190,11 +186,12 @@ static void do_autoclothing() //technically, there's some leeway in sizes, but only caring about exact sizes is simpler. clothingOrder.total_needed_per_race[unit->race] += alreadyOwnedAmount; - } } +} - //Now we go through all the items in the map to see how many clothing items we have but aren't owned yet. +static void remove_available_clothing() +{ for (auto&& item : world->items.all) { //skip any owned items. @@ -218,8 +215,10 @@ static void do_autoclothing() clothingOrder.total_needed_per_race[item->getMakerRace] --; } } +} - //Finally loop through the clothing orders to find ones that need more made. +static void add_clothing_orders() +{ for (auto&& clothingOrder : clothingOrders) { for (auto&& orderNeeded : clothingOrder.total_needed_per_race) @@ -268,3 +267,18 @@ static void do_autoclothing() } } } + +static void do_autoclothing() +{ + if (clothingOrders.size() == 0) + return; + + //first we look through all the units on the map to see who needs new clothes. + find_needed_clothing_items(); + + //Now we go through all the items in the map to see how many clothing items we have but aren't owned yet. + remove_available_clothing(); + + //Finally loop through the clothing orders to find ones that need more made. + add_clothing_orders(); +} From 74daa6bced39878b8e9ab16f98a7a409c3f6b329 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 11:20:36 -0500 Subject: [PATCH 13/86] add missing parentheses. --- plugins/autoclothing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 6abca667c..b340585a6 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -212,7 +212,7 @@ static void remove_available_clothing() if (!matInfo.matches(clothingOrder.material_category)) continue; - clothingOrder.total_needed_per_race[item->getMakerRace] --; + clothingOrder.total_needed_per_race[item->getMakerRace()] --; } } } From b2d59fd14378b6522810a9a8aa7248688c2d32ff Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 12:22:51 -0500 Subject: [PATCH 14/86] Parse item name --- plugins/autoclothing.cpp | 123 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index b340585a6..28bb9cfee 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -17,6 +17,11 @@ #include "modules/Units.h" #include "modules/World.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_pantsst.h" #include "df/manager_order.h" #include "df/creature_raw.h" #include "df/world.h" @@ -66,10 +71,12 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" "Example:\n" - " skeleton\n" - " Does nothing.\n" + " autoclothing cloth dress 10\n" + " Sets the desired number of cloth dresses available per citizen to 1.\n" + " autoclothing cloth dress\n" + " Displays the currently set number of cloth dresses chosen per citizen.\n" )); return CR_OK; } @@ -125,6 +132,99 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; } +static bool setItemFromName(std::string name, ClothingRequirement* requirement) +{ + for (auto&& itemdef : world->raws.itemdefs.armor) + { + if (itemdef->name == name) + { + requirement->job_type = job_type::MakeArmor; + requirement->item_type = item_type::ARMOR; + requirement->item_subtype = itemdef->subtype; + return true; + } + } + for (auto&& itemdef : world->raws.itemdefs.gloves) + { + if (itemdef->name == name) + { + requirement->job_type = job_type::MakeGloves; + requirement->item_type = item_type::GLOVES; + requirement->item_subtype = itemdef->subtype; + return true; + } + } + for (auto&& itemdef : world->raws.itemdefs.shoes) + { + if (itemdef->name == name) + { + requirement->job_type = job_type::MakeShoes; + requirement->item_type = item_type::SHOES; + requirement->item_subtype = itemdef->subtype; + return true; + } + } + for (auto&& itemdef : world->raws.itemdefs.helms) + { + if (itemdef->name == name) + { + requirement->job_type = job_type::MakeHelm; + requirement->item_type = item_type::HELM; + requirement->item_subtype = itemdef->subtype; + return true; + } + } + for (auto&& itemdef : world->raws.itemdefs.pants) + { + if (itemdef->name == name) + { + requirement->job_type = job_type::MakePants; + requirement->item_type = item_type::PANTS; + requirement->item_subtype = itemdef->subtype; + return true; + } + } + return false; +} + +static bool setItemFromToken(std::string token, ClothingRequirement* requirement) +{ + ItemTypeInfo itemInfo; + if (!itemInfo.find(token)) + return false; + switch (itemInfo.type) + { + case item_type::ARMOR: + requirement->job_type = job_type::MakeArmor; + break; + case item_type::GLOVES: + requirement->job_type = job_type::MakeGloves; + break; + case item_type::SHOES: + requirement->job_type = job_type::MakeShoes; + break; + case item_type::HELM: + requirement->job_type = job_type::MakeHelm; + break; + case item_type::PANTS: + requirement->job_type = job_type::MakePants; + break; + default: + return false; + } + requirement->item_type = itemInfo.type; + requirement->item_subtype = itemInfo.subtype; + return true; +} + +static bool setItem(std::string name, ClothingRequirement* requirement) +{ + if (setItemFromName(name, requirement)) + return true; + if (setItemFromToken(name, requirement)) + return true; + return false; +} // A command! It sits around and looks pretty. And it's nice and friendly. command_result autoclothing(color_ostream &out, std::vector & parameters) @@ -135,15 +235,28 @@ command_result autoclothing(color_ostream &out, std::vector & para // PluginCommand registration as show above, and then returning // CR_WRONG_USAGE from the function. The same string will also // be used by 'help your-command'. - if (!parameters.empty()) + if (parameters.size() < 2 || parameters.size() > 3) + { + out << "Wrong number of arguments." << endl; return CR_WRONG_USAGE; + } // Commands are called from threads other than the DF one. // Suspend this thread until DF has time for us. If you // use CoreSuspender, it'll automatically resume DF when // execution leaves the current scope. CoreSuspender suspend; // Actually do something here. Yay. - out.print("Hello! I do nothing, remember?\n"); + ClothingRequirement newRequirement; + if (!setItem(parameters[1], &newRequirement)) + { + out << "Unrecognized item name or token: " << parameters[1] << endl; + return CR_WRONG_USAGE; + } + for (auto&& param : parameters) + { + out.print(param.c_str()); + out.print("\n"); + } // Give control back to DF. return CR_OK; } From cae4f6d0912a5ec74242b6a76a106bb93309f2e6 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 12:41:25 -0500 Subject: [PATCH 15/86] simplified the setItemFromName function --- plugins/autoclothing.cpp | 67 ++++++++++------------------------------ 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 28bb9cfee..85cb6c728 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -134,56 +134,23 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) static bool setItemFromName(std::string name, ClothingRequirement* requirement) { - for (auto&& itemdef : world->raws.itemdefs.armor) - { - if (itemdef->name == name) - { - requirement->job_type = job_type::MakeArmor; - requirement->item_type = item_type::ARMOR; - requirement->item_subtype = itemdef->subtype; - return true; - } - } - for (auto&& itemdef : world->raws.itemdefs.gloves) - { - if (itemdef->name == name) - { - requirement->job_type = job_type::MakeGloves; - requirement->item_type = item_type::GLOVES; - requirement->item_subtype = itemdef->subtype; - return true; - } - } - for (auto&& itemdef : world->raws.itemdefs.shoes) - { - if (itemdef->name == name) - { - requirement->job_type = job_type::MakeShoes; - requirement->item_type = item_type::SHOES; - requirement->item_subtype = itemdef->subtype; - return true; - } - } - for (auto&& itemdef : world->raws.itemdefs.helms) - { - if (itemdef->name == name) - { - requirement->job_type = job_type::MakeHelm; - requirement->item_type = item_type::HELM; - requirement->item_subtype = itemdef->subtype; - return true; - } - } - for (auto&& itemdef : world->raws.itemdefs.pants) - { - if (itemdef->name == name) - { - requirement->job_type = job_type::MakePants; - requirement->item_type = item_type::PANTS; - requirement->item_subtype = itemdef->subtype; - return true; - } - } +#define SEARCH_ITEM_RAWS(rawType, jobType, itemType) \ +for (auto&& itemdef : world->raws.itemdefs.rawType) \ +{ \ + if (itemdef->name == name) \ + { \ + requirement->job_type = job_type::jobType; \ + requirement->item_type = item_type::itemType; \ + requirement->item_subtype = itemdef->subtype; \ + return true; \ + } \ +} + + SEARCH_ITEM_RAWS(armor, MakeArmor, ARMOR); + SEARCH_ITEM_RAWS(gloves, MakeGloves, GLOVES); + SEARCH_ITEM_RAWS(shoes, MakeShoes, SHOES); + SEARCH_ITEM_RAWS(helms, MakeHelm, HELM); + SEARCH_ITEM_RAWS(pants, MakePants, PANTS); return false; } From 25f767b96b7a248df89788b610f3aa16b5b0c4cc Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 13:48:14 -0500 Subject: [PATCH 16/86] match material categories with valid clothing materials. --- plugins/autoclothing.cpp | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 85cb6c728..181ebc73d 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -1,4 +1,3 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D // some headers required for a plugin. Nothing special, just the basics. #include "Core.h" @@ -193,6 +192,44 @@ static bool setItem(std::string name, ClothingRequirement* requirement) return false; } +static bool armorFlagsMatch(BitArray * flags, df::job_material_category * category) +{ + if (&flags[df::armor_general_flags::SOFT] && category->bits.cloth) + return true; + if (&flags[df::armor_general_flags::BARRED] && category->bits.bone) + return true; + if (&flags[df::armor_general_flags::SCALED] && category->bits.shell) + return true; + if (&flags[df::armor_general_flags::LEATHER] && category->bits.leather) + return true; + return false; +} + +static bool validateMaterialCategory(ClothingRequirement * requirement) +{ + auto itemDef = getSubtypeDef(requirement->item_type, requirement->item_subtype); + switch (requirement->item_type) + { + case item_type::ARMOR: + if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_armorst, itemDef)) + return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + case item_type::GLOVES: + if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_glovesst, itemDef)) + return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + case item_type::SHOES: + if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_shoesst, itemDef)) + return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + case item_type::HELM: + if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_helmst, itemDef)) + return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + case item_type::PANTS: + if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_pantsst, itemDef)) + return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + default: + return false; + } +} + // A command! It sits around and looks pretty. And it's nice and friendly. command_result autoclothing(color_ostream &out, std::vector & parameters) { @@ -214,11 +251,23 @@ command_result autoclothing(color_ostream &out, std::vector & para CoreSuspender suspend; // Actually do something here. Yay. ClothingRequirement newRequirement; + if (!set_bitfield_field(&newRequirement.material_category, parameters[0], 1)) + { + out << "Unrecognized material type: " << parameters[0] << endl; + } if (!setItem(parameters[1], &newRequirement)) { out << "Unrecognized item name or token: " << parameters[1] << endl; return CR_WRONG_USAGE; } + if (!validateMaterialCategory(&newRequirement)) + { + out << parameters[0] << " is not a valid material category for " << parameters[1] << endl; + return CR_WRONG_USAGE; + } + //all checks are passed. Now we either show or set the amount. + + for (auto&& param : parameters) { out.print(param.c_str()); From f07f65a1a3e80de97990b585e1a68306850e2c5d Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 13:50:52 -0500 Subject: [PATCH 17/86] Silk, yarn, and strands are also soft. --- plugins/autoclothing.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 181ebc73d..8eb5a89e9 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -196,6 +196,12 @@ static bool armorFlagsMatch(BitArray * flags, df::job_m { if (&flags[df::armor_general_flags::SOFT] && category->bits.cloth) return true; + if (&flags[df::armor_general_flags::SOFT] && category->bits.yarn) + return true; + if (&flags[df::armor_general_flags::SOFT] && category->bits.silk) + return true; + if (&flags[df::armor_general_flags::SOFT] && category->bits.strand) + return true; if (&flags[df::armor_general_flags::BARRED] && category->bits.bone) return true; if (&flags[df::armor_general_flags::SCALED] && category->bits.shell) From c997f666bbc2669f3ef33a61d0cc70da7769e345 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 14:15:45 -0500 Subject: [PATCH 18/86] Fix wrong pointer dereference. --- plugins/autoclothing.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 8eb5a89e9..a6f002ae8 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -194,19 +194,19 @@ static bool setItem(std::string name, ClothingRequirement* requirement) static bool armorFlagsMatch(BitArray * flags, df::job_material_category * category) { - if (&flags[df::armor_general_flags::SOFT] && category->bits.cloth) + if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.cloth) return true; - if (&flags[df::armor_general_flags::SOFT] && category->bits.yarn) + if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.yarn) return true; - if (&flags[df::armor_general_flags::SOFT] && category->bits.silk) + if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.silk) return true; - if (&flags[df::armor_general_flags::SOFT] && category->bits.strand) + if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.strand) return true; - if (&flags[df::armor_general_flags::BARRED] && category->bits.bone) + if (flags->is_set(df::armor_general_flags::BARRED) && category->bits.bone) return true; - if (&flags[df::armor_general_flags::SCALED] && category->bits.shell) + if (flags->is_set(df::armor_general_flags::SCALED) && category->bits.shell) return true; - if (&flags[df::armor_general_flags::LEATHER] && category->bits.leather) + if (flags->is_set(df::armor_general_flags::LEATHER) && category->bits.leather) return true; return false; } From 51b9d7a275af6eb849a39a1df198a217f87b2794 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 14:31:37 -0500 Subject: [PATCH 19/86] able to set clothing requirements via console. --- plugins/autoclothing.cpp | 65 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index a6f002ae8..fc3d07b33 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -38,6 +38,18 @@ struct ClothingRequirement df::job_material_category material_category; int16_t needed_per_citizen; std::map total_needed_per_race; + + bool matches(ClothingRequirement * b) + { + if (b->job_type != this->job_type) + return false; + if (b->item_type != this->item_type) + return false; + if (b->item_subtype != this->item_subtype) + return false; + if (b->material_category.whole != this->material_category.whole) + return false; + } }; std::vectorclothingOrders; @@ -272,12 +284,55 @@ command_result autoclothing(color_ostream &out, std::vector & para return CR_WRONG_USAGE; } //all checks are passed. Now we either show or set the amount. - - - for (auto&& param : parameters) + bool settingSize = false; + bool matchedExisting = false; + if (parameters.size() > 2) + { + newRequirement.needed_per_citizen = std::stoi(parameters[0]); + settingSize = true; + } + for (size_t i = 0; i < clothingOrders.size(); i++) { - out.print(param.c_str()); - out.print("\n"); + if (!clothingOrders[i].matches(&newRequirement)) + continue; + matchedExisting = true; + if (settingSize) + { + if (newRequirement.needed_per_citizen == 0) + { + clothingOrders.erase(clothingOrders.begin() + i); + out << "Unset " << parameters[0] << " " << parameters[1] << endl; + } + else + { + clothingOrders[i] = newRequirement; + out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[3] << endl; + } + } + else + { + out << parameters[0] << " " << parameters[1] << " is set to " << clothingOrders[i].needed_per_citizen << endl; + } + break; + } + if (!matchedExisting) + { + if (settingSize) + { + if (newRequirement.needed_per_citizen == 0) + { + out << parameters[0] << " " << parameters[1] << " already unset." << endl; + } + else + { + clothingOrders.push_back(newRequirement); + out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[3] << endl; + } + } + else + { + out << parameters[0] << " " << parameters[1] << " is not set." << endl; + } } // Give control back to DF. return CR_OK; From 1b387a8ccff5536b3c67d8529b5a14d8c4b840d7 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 15:56:58 -0500 Subject: [PATCH 20/86] Got initial working version. --- plugins/autoclothing.cpp | 72 ++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index fc3d07b33..ed1c09dbb 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -49,6 +49,7 @@ struct ClothingRequirement return false; if (b->material_category.whole != this->material_category.whole) return false; + return true; } }; @@ -84,8 +85,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" "Example:\n" - " autoclothing cloth dress 10\n" - " Sets the desired number of cloth dresses available per citizen to 1.\n" + " autoclothing cloth \"short skirt\" 10\n" + " Sets the desired number of cloth short skirts available per citizen to 10.\n" " autoclothing cloth dress\n" " Displays the currently set number of cloth dresses chosen per citizen.\n" )); @@ -146,9 +147,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) static bool setItemFromName(std::string name, ClothingRequirement* requirement) { #define SEARCH_ITEM_RAWS(rawType, jobType, itemType) \ -for (auto&& itemdef : world->raws.itemdefs.rawType) \ +for (auto& itemdef : world->raws.itemdefs.rawType) \ { \ - if (itemdef->name == name) \ + std::string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \ + if (fullName == name) \ { \ requirement->job_type = job_type::jobType; \ requirement->item_type = item_type::itemType; \ @@ -156,7 +158,6 @@ for (auto&& itemdef : world->raws.itemdefs.rawType) \ return true; \ } \ } - SEARCH_ITEM_RAWS(armor, MakeArmor, ARMOR); SEARCH_ITEM_RAWS(gloves, MakeGloves, GLOVES); SEARCH_ITEM_RAWS(shoes, MakeShoes, SHOES); @@ -212,8 +213,6 @@ static bool armorFlagsMatch(BitArray * flags, df::job_m return true; if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.silk) return true; - if (flags->is_set(df::armor_general_flags::SOFT) && category->bits.strand) - return true; if (flags->is_set(df::armor_general_flags::BARRED) && category->bits.bone) return true; if (flags->is_set(df::armor_general_flags::SCALED) && category->bits.shell) @@ -288,7 +287,15 @@ command_result autoclothing(color_ostream &out, std::vector & para bool matchedExisting = false; if (parameters.size() > 2) { - newRequirement.needed_per_citizen = std::stoi(parameters[0]); + try + { + newRequirement.needed_per_citizen = std::stoi(parameters[2]); + } + catch (const std::exception&) + { + out << parameters[2] << " is not a valid number." << endl; + return CR_WRONG_USAGE; + } settingSize = true; } for (size_t i = 0; i < clothingOrders.size(); i++) @@ -306,7 +313,7 @@ command_result autoclothing(color_ostream &out, std::vector & para else { clothingOrders[i] = newRequirement; - out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[3] << endl; + out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[2] << endl; } } else @@ -326,7 +333,7 @@ command_result autoclothing(color_ostream &out, std::vector & para else { clothingOrders.push_back(newRequirement); - out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[3] << endl; + out << "Added order for " << parameters[0] << " " << parameters[1] << " to " << parameters[2] << endl; } } else @@ -334,25 +341,34 @@ command_result autoclothing(color_ostream &out, std::vector & para out << parameters[0] << " " << parameters[1] << " is not set." << endl; } } + if (settingSize) + { + if (!autoclothing_enabled) + { + out << "Enabling automatic clothing management" << endl; + autoclothing_enabled = true; + } + do_autoclothing(); + } // Give control back to DF. return CR_OK; } static void find_needed_clothing_items() { - for (auto&& unit : world->units.active) + for (auto& unit : world->units.active) { //obviously we don't care about illegal aliens. if (!isCitizen(unit)) continue; //now check each clothing order to see what the unit might be missing. - for (auto&& clothingOrder : clothingOrders) + for (auto& clothingOrder : clothingOrders) { int alreadyOwnedAmount = 0; //looping through the items first, then clothing order might be a little faster, but this way is cleaner. - for (auto&& ownedItem : unit->owned_items) + for (auto& ownedItem : unit->owned_items) { auto item = findItemByID(ownedItem); @@ -375,21 +391,21 @@ static void find_needed_clothing_items() continue; //technically, there's some leeway in sizes, but only caring about exact sizes is simpler. - clothingOrder.total_needed_per_race[unit->race] += alreadyOwnedAmount; + clothingOrder.total_needed_per_race[unit->race] += neededAmount; } } } static void remove_available_clothing() { - for (auto&& item : world->items.all) + for (auto& item : world->items.all) { //skip any owned items. if (getOwner(item)) continue; //again, for each item, find if any clothing order matches the - for (auto&& clothingOrder : clothingOrders) + for (auto& clothingOrder : clothingOrders) { if (item->getType() != clothingOrder.item_type) continue; @@ -409,9 +425,9 @@ static void remove_available_clothing() static void add_clothing_orders() { - for (auto&& clothingOrder : clothingOrders) + for (auto& clothingOrder : clothingOrders) { - for (auto&& orderNeeded : clothingOrder.total_needed_per_race) + for (auto& orderNeeded : clothingOrder.total_needed_per_race) { auto race = orderNeeded.first; auto amount = orderNeeded.second; @@ -420,7 +436,7 @@ static void add_clothing_orders() continue; bool orderExistedAlready = false; - for (auto&& managerOrder : world->manager_orders) + for (auto& managerOrder : world->manager_orders) { //Annoyingly, the manager orders store the job type for clothing orders, and actual item type is left at -1; if (managerOrder->job_type != clothingOrder.job_type) @@ -442,17 +458,17 @@ static void add_clothing_orders() //if it wasn't there, we need to make a new one. if (!orderExistedAlready) { - df::manager_order newOrder; + df::manager_order * newOrder = new df::manager_order(); - newOrder.id = world->manager_order_next_id; + newOrder->id = world->manager_order_next_id; world->manager_order_next_id++; - newOrder.job_type = clothingOrder.job_type; - newOrder.item_subtype = clothingOrder.item_subtype; - newOrder.hist_figure_id = race; - newOrder.material_category = clothingOrder.material_category; - newOrder.amount_left = amount; - newOrder.amount_total = amount; - world->manager_orders.push_back(&newOrder); + newOrder->job_type = clothingOrder.job_type; + newOrder->item_subtype = clothingOrder.item_subtype; + newOrder->hist_figure_id = race; + newOrder->material_category = clothingOrder.material_category; + newOrder->amount_left = amount; + newOrder->amount_total = amount; + world->manager_orders.push_back(newOrder); } } } From 64e0884d9536c785ea477b57ebde5be0709b5f74 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 16:29:30 -0500 Subject: [PATCH 21/86] Don't keep re-adding the job orders. --- plugins/autoclothing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index ed1c09dbb..655b13140 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -431,6 +431,7 @@ static void add_clothing_orders() { auto race = orderNeeded.first; auto amount = orderNeeded.second; + orderNeeded.second = 0; //once we get what we need, set it back to zero so we don't add it to further counts. //Previous operations can easily make this negative. That jus means we have more than we need already. if (amount <= 0) continue; From 6bed39233143dbebea20d57031570c1e017bef5c Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 16:58:21 -0500 Subject: [PATCH 22/86] Satisfy travis. --- plugins/autoclothing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 655b13140..675a7c2fe 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -404,7 +404,7 @@ static void remove_available_clothing() if (getOwner(item)) continue; - //again, for each item, find if any clothing order matches the + //again, for each item, find if any clothing order matches for (auto& clothingOrder : clothingOrders) { if (item->getType() != clothingOrder.item_type) From 42226342dca0990c05621b141f6b0b0b95cab8b5 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 27 Apr 2019 21:01:12 -0500 Subject: [PATCH 23/86] Save state in persistent data. --- plugins/autoclothing.cpp | 216 +++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 32 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 675a7c2fe..f09269492 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -30,6 +30,34 @@ using namespace DFHack::Items; using namespace DFHack::Units; using namespace df::enums; + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - +// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case +DFHACK_PLUGIN("autoclothing"); + +// Any globals a plugin requires (e.g. world) should be listed here. +// For example, this line expands to "using df::global::world" and prevents the +// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): +// +REQUIRE_GLOBAL(world); + +// Only run if this is enabled +DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled); + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom +struct ClothingRequirement; +command_result autoclothing(color_ostream &out, std::vector & parameters); +static void init_state(color_ostream &out); +static void save_state(color_ostream &out); +static void cleanup_state(color_ostream &out); +static void do_autoclothing(); +static bool validateMaterialCategory(ClothingRequirement * requirement); +static bool setItem(std::string name, ClothingRequirement* requirement); + +std::vectorclothingOrders; + struct ClothingRequirement { df::job_type job_type; @@ -51,29 +79,87 @@ struct ClothingRequirement return false; return true; } -}; -std::vectorclothingOrders; + std::string Serialize() + { + stringstream stream; + stream << job_type << " "; + stream << item_type << " "; + stream << item_subtype << " "; + stream << material_category.whole << " "; + stream << needed_per_citizen; + return stream.str(); + } -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - -// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case -DFHACK_PLUGIN("autoclothing"); + void Deserialize(std::string s) + { + stringstream stream(s); + stream >> (int16_t&)job_type; + stream >> (int16_t&)item_type; + stream >> item_subtype; + stream >> material_category.whole; + stream >> needed_per_citizen; + } -// Any globals a plugin requires (e.g. world) should be listed here. -// For example, this line expands to "using df::global::world" and prevents the -// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): -// -REQUIRE_GLOBAL(world); + bool SetFromParameters(color_ostream &out, std::vector & parameters) + { + if (!set_bitfield_field(&material_category, parameters[0], 1)) + { + out << "Unrecognized material type: " << parameters[0] << endl; + } + if (!setItem(parameters[1], this)) + { + out << "Unrecognized item name or token: " << parameters[1] << endl; + return false; + } + if (!validateMaterialCategory(this)) + { + out << parameters[0] << " is not a valid material category for " << parameters[1] << endl; + return false; + } + return true; + } -// Only run if this is enabled -DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled); + std::string ToReadableLabel() + { + stringstream stream; + stream << bitfield_to_string(material_category) << " "; + std::string adjective = ""; + std::string name = ""; + switch (item_type) + { + case df::enums::item_type::ARMOR: + adjective = world->raws.itemdefs.armor[item_subtype]->adjective; + name = world->raws.itemdefs.armor[item_subtype]->name; + break; + case df::enums::item_type::SHOES: + adjective = world->raws.itemdefs.shoes[item_subtype]->adjective; + name = world->raws.itemdefs.shoes[item_subtype]->name; + break; + case df::enums::item_type::HELM: + adjective = world->raws.itemdefs.helms[item_subtype]->adjective; + name = world->raws.itemdefs.helms[item_subtype]->name; + break; + case df::enums::item_type::GLOVES: + adjective = world->raws.itemdefs.gloves[item_subtype]->adjective; + name = world->raws.itemdefs.gloves[item_subtype]->name; + break; + case df::enums::item_type::PANTS: + adjective = world->raws.itemdefs.pants[item_subtype]->adjective; + name = world->raws.itemdefs.pants[item_subtype]->name; + break; + default: + break; + } + if (!adjective.empty()) + stream << adjective << " "; + stream << name << " "; + stream << needed_per_citizen; -// 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 autoclothing(color_ostream &out, std::vector & parameters); + return stream.str(); + } +}; -static void do_autoclothing(); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) @@ -99,6 +185,8 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) // You *MUST* kill all threads you created before this returns. // If everything fails, just return CR_FAILURE. Your plugin will be // in a zombie state, but things won't crash. + cleanup_state(out); + return CR_OK; } @@ -110,10 +198,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_WORLD_LOADED: - // initialize from the world just loaded + init_state(out); break; case SC_WORLD_UNLOADED: - // cleanup + cleanup_state(out); break; default: break; @@ -247,6 +335,8 @@ static bool validateMaterialCategory(ClothingRequirement * requirement) } } + + // A command! It sits around and looks pretty. And it's nice and friendly. command_result autoclothing(color_ostream &out, std::vector & parameters) { @@ -268,20 +358,8 @@ command_result autoclothing(color_ostream &out, std::vector & para CoreSuspender suspend; // Actually do something here. Yay. ClothingRequirement newRequirement; - if (!set_bitfield_field(&newRequirement.material_category, parameters[0], 1)) - { - out << "Unrecognized material type: " << parameters[0] << endl; - } - if (!setItem(parameters[1], &newRequirement)) - { - out << "Unrecognized item name or token: " << parameters[1] << endl; - return CR_WRONG_USAGE; - } - if (!validateMaterialCategory(&newRequirement)) - { - out << parameters[0] << " is not a valid material category for " << parameters[1] << endl; + if (!newRequirement.SetFromParameters(out, parameters)) return CR_WRONG_USAGE; - } //all checks are passed. Now we either show or set the amount. bool settingSize = false; bool matchedExisting = false; @@ -298,6 +376,7 @@ command_result autoclothing(color_ostream &out, std::vector & para } settingSize = true; } + for (size_t i = 0; i < clothingOrders.size(); i++) { if (!clothingOrders[i].matches(&newRequirement)) @@ -350,6 +429,8 @@ command_result autoclothing(color_ostream &out, std::vector & para } do_autoclothing(); } + save_state(out); + // Give control back to DF. return CR_OK; } @@ -489,3 +570,74 @@ static void do_autoclothing() //Finally loop through the clothing orders to find ones that need more made. add_clothing_orders(); } + +static void cleanup_state(color_ostream &out) +{ + clothingOrders.clear(); + autoclothing_enabled = false; +} + +static void init_state(color_ostream &out) +{ + auto enabled = World::GetPersistentData("autoclothing/enabled"); + if (enabled.isValid() && enabled.ival(0) == 1) + { + out << "autoclothing enabled" << endl; + autoclothing_enabled = true; + } + else + { + autoclothing_enabled = false; + } + + + // Parse constraints + std::vector items; + World::GetPersistentData(&items, "autoclothing/clothingItems"); + + for (auto& item : items) + { + if (!item.isValid()) + continue; + ClothingRequirement req; + req.Deserialize(item.val()); + clothingOrders.push_back(req); + out << "autoclothing added " << req.ToReadableLabel() << endl; + } +} + +static void save_state(color_ostream &out) +{ + auto enabled = World::GetPersistentData("autoclothing/enabled"); + if (!enabled.isValid()) + enabled = World::AddPersistentData("autoclothing/enabled"); + enabled.ival(0) = autoclothing_enabled; + + for (auto& order : clothingOrders) + { + auto orderSave = World::AddPersistentData("autoclothing/clothingItems"); + orderSave.val() = order.Serialize(); + } + + + // Parse constraints + std::vector items; + World::GetPersistentData(&items, "autoclothing/clothingItems"); + + for (int i = 0; i < items.size(); i++) + { + if (i < clothingOrders.size()) + { + items[i].val() = clothingOrders[i].Serialize(); + } + else + { + World::DeletePersistentData(items[i]); + } + } + for (int i = items.size(); i < clothingOrders.size(); i++) + { + auto item = World::AddPersistentData("autoclothing/clothingItems"); + item.val() = clothingOrders[i].Serialize(); + } +} From c13bd6b065d3cf09f1d6f021c6270686e283078e Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sat, 18 May 2019 09:14:32 -0500 Subject: [PATCH 24/86] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 2be1fc4af..c192f9798 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2be1fc4afea4d3345b9b76d0f27f56087ac9b6e0 +Subproject commit c192f9798b5d134e777b1f37c8ebc0df4bd53bda diff --git a/scripts b/scripts index c8394fa75..bd7f501e0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c8394fa75ff20abcdcdbe975dbf157d21882172e +Subproject commit bd7f501e07ffa3b59917acf228762dd88a5c8b10 From a07b568597601bc114a48f2bf356bb78b770c314 Mon Sep 17 00:00:00 2001 From: JapaMala Date: Tue, 13 Aug 2019 17:15:06 -0500 Subject: [PATCH 25/86] Changed the serialization to use the actual enum item names instead of converting them to numbers. --- plugins/autoclothing.cpp | 85 +++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index f09269492..71127119e 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -60,8 +60,8 @@ std::vectorclothingOrders; struct ClothingRequirement { - df::job_type job_type; - df::item_type item_type; + df::job_type jobType; + df::item_type itemType; int16_t item_subtype; df::job_material_category material_category; int16_t needed_per_citizen; @@ -69,9 +69,9 @@ struct ClothingRequirement bool matches(ClothingRequirement * b) { - if (b->job_type != this->job_type) + if (b->jobType != this->jobType) return false; - if (b->item_type != this->item_type) + if (b->itemType != this->itemType) return false; if (b->item_subtype != this->item_subtype) return false; @@ -83,8 +83,8 @@ struct ClothingRequirement std::string Serialize() { stringstream stream; - stream << job_type << " "; - stream << item_type << " "; + stream << ENUM_KEY_STR(job_type, jobType) << " "; + stream << ENUM_KEY_STR(item_type,itemType) << " "; stream << item_subtype << " "; stream << material_category.whole << " "; stream << needed_per_citizen; @@ -94,8 +94,26 @@ struct ClothingRequirement void Deserialize(std::string s) { stringstream stream(s); - stream >> (int16_t&)job_type; - stream >> (int16_t&)item_type; + std::string loadedJob; + stream >> loadedJob; + FOR_ENUM_ITEMS(job_type, job) + { + if (ENUM_KEY_STR(job_type, job) == loadedJob) + { + jobType = job; + break; + } + } + std::string loadedItem; + stream >> loadedItem; + FOR_ENUM_ITEMS(item_type, item) + { + if (ENUM_KEY_STR(item_type, item) == loadedItem) + { + itemType = item; + break; + } + } stream >> item_subtype; stream >> material_category.whole; stream >> needed_per_citizen; @@ -126,7 +144,7 @@ struct ClothingRequirement stream << bitfield_to_string(material_category) << " "; std::string adjective = ""; std::string name = ""; - switch (item_type) + switch (itemType) { case df::enums::item_type::ARMOR: adjective = world->raws.itemdefs.armor[item_subtype]->adjective; @@ -234,14 +252,14 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) static bool setItemFromName(std::string name, ClothingRequirement* requirement) { -#define SEARCH_ITEM_RAWS(rawType, jobType, itemType) \ +#define SEARCH_ITEM_RAWS(rawType, job, item) \ for (auto& itemdef : world->raws.itemdefs.rawType) \ { \ std::string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \ if (fullName == name) \ { \ - requirement->job_type = job_type::jobType; \ - requirement->item_type = item_type::itemType; \ + requirement->jobType = job_type::job; \ + requirement->itemType = item_type::item; \ requirement->item_subtype = itemdef->subtype; \ return true; \ } \ @@ -262,24 +280,24 @@ static bool setItemFromToken(std::string token, ClothingRequirement* requirement switch (itemInfo.type) { case item_type::ARMOR: - requirement->job_type = job_type::MakeArmor; + requirement->jobType = job_type::MakeArmor; break; case item_type::GLOVES: - requirement->job_type = job_type::MakeGloves; + requirement->jobType = job_type::MakeGloves; break; case item_type::SHOES: - requirement->job_type = job_type::MakeShoes; + requirement->jobType = job_type::MakeShoes; break; case item_type::HELM: - requirement->job_type = job_type::MakeHelm; + requirement->jobType = job_type::MakeHelm; break; case item_type::PANTS: - requirement->job_type = job_type::MakePants; + requirement->jobType = job_type::MakePants; break; default: return false; } - requirement->item_type = itemInfo.type; + requirement->itemType = itemInfo.type; requirement->item_subtype = itemInfo.subtype; return true; } @@ -312,8 +330,8 @@ static bool armorFlagsMatch(BitArray * flags, df::job_m static bool validateMaterialCategory(ClothingRequirement * requirement) { - auto itemDef = getSubtypeDef(requirement->item_type, requirement->item_subtype); - switch (requirement->item_type) + auto itemDef = getSubtypeDef(requirement->itemType, requirement->item_subtype); + switch (requirement->itemType) { case item_type::ARMOR: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_armorst, itemDef)) @@ -346,7 +364,16 @@ command_result autoclothing(color_ostream &out, std::vector & para // PluginCommand registration as show above, and then returning // CR_WRONG_USAGE from the function. The same string will also // be used by 'help your-command'. - if (parameters.size() < 2 || parameters.size() > 3) + if (parameters.size() == 0) + { + out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl; + for (size_t i = 0; i < clothingOrders.size(); i++) + { + out << clothingOrders[i].ToReadableLabel() << endl; + } + return CR_OK; + } + else if (parameters.size() < 2 || parameters.size() > 3) { out << "Wrong number of arguments." << endl; return CR_WRONG_USAGE; @@ -356,7 +383,9 @@ command_result autoclothing(color_ostream &out, std::vector & para // use CoreSuspender, it'll automatically resume DF when // execution leaves the current scope. CoreSuspender suspend; - // Actually do something here. Yay. + + + // Create a new requirement from the available parameters. ClothingRequirement newRequirement; if (!newRequirement.SetFromParameters(out, parameters)) return CR_WRONG_USAGE; @@ -453,7 +482,7 @@ static void find_needed_clothing_items() { auto item = findItemByID(ownedItem); - if (item->getType() != clothingOrder.item_type) + if (item->getType() != clothingOrder.itemType) continue; if (item->getSubtype() != clothingOrder.item_subtype) continue; @@ -488,7 +517,7 @@ static void remove_available_clothing() //again, for each item, find if any clothing order matches for (auto& clothingOrder : clothingOrders) { - if (item->getType() != clothingOrder.item_type) + if (item->getType() != clothingOrder.itemType) continue; if (item->getSubtype() != clothingOrder.item_subtype) continue; @@ -521,7 +550,7 @@ static void add_clothing_orders() for (auto& managerOrder : world->manager_orders) { //Annoyingly, the manager orders store the job type for clothing orders, and actual item type is left at -1; - if (managerOrder->job_type != clothingOrder.job_type) + if (managerOrder->job_type != clothingOrder.jobType) continue; if (managerOrder->item_subtype != clothingOrder.item_subtype) continue; @@ -544,7 +573,7 @@ static void add_clothing_orders() newOrder->id = world->manager_order_next_id; world->manager_order_next_id++; - newOrder->job_type = clothingOrder.job_type; + newOrder->job_type = clothingOrder.jobType; newOrder->item_subtype = clothingOrder.item_subtype; newOrder->hist_figure_id = race; newOrder->material_category = clothingOrder.material_category; @@ -624,7 +653,7 @@ static void save_state(color_ostream &out) std::vector items; World::GetPersistentData(&items, "autoclothing/clothingItems"); - for (int i = 0; i < items.size(); i++) + for (size_t i = 0; i < items.size(); i++) { if (i < clothingOrders.size()) { @@ -635,7 +664,7 @@ static void save_state(color_ostream &out) World::DeletePersistentData(items[i]); } } - for (int i = items.size(); i < clothingOrders.size(); i++) + for (size_t i = items.size(); i < clothingOrders.size(); i++) { auto item = World::AddPersistentData("autoclothing/clothingItems"); item.val() = clothingOrders[i].Serialize(); From d17820a596663cf347e04e33d2263069a02de6a2 Mon Sep 17 00:00:00 2001 From: japamala Date: Thu, 22 Aug 2019 19:26:19 -0600 Subject: [PATCH 26/86] Made RemoteFortressReader pulll from the active units list, not from the full units list. --- plugins/remotefortressreader/remotefortressreader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index cd9ebd87e..4cb479129 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1670,9 +1670,9 @@ void GetWounds(df::unit_wound * wound, UnitWound * send_wound) static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) { auto world = df::global::world; - for (size_t i = 0; i < world->units.all.size(); i++) + for (size_t i = 0; i < world->units.active.size(); i++) { - df::unit * unit = world->units.all[i]; + df::unit * unit = world->units.active[i]; auto send_unit = out->add_creature_list(); send_unit->set_id(unit->id); send_unit->set_pos_x(unit->pos.x); From 417212814ddac7eeeacffee2ab712fcb5255535c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 25 Aug 2019 21:32:29 -0400 Subject: [PATCH 27/86] embark-assistant: add in-game key to activate Closes #1384 --- dfhack.init-example | 3 + plugins/embark-assistant/embark-assistant.cpp | 63 ++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index bdf035bd3..d8cf832c7 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -262,6 +262,9 @@ enable \ # enable mouse controls and sand indicator in embark screen embark-tools enable sticky sand mouse +# enable option to enter embark assistant +enable embark-assistant + ########### # Scripts # ########### diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 1eed98593..4d83669e5 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -1,11 +1,13 @@ +#include + #include "Core.h" -#include -#include -#include +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" -#include -#include -#include +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "../uicommon.h" #include "DataDefs.h" #include "df/coord2d.h" @@ -27,6 +29,7 @@ #include "survey.h" DFHACK_PLUGIN("embark-assistant"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); using namespace DFHack; using namespace df::enums; @@ -134,11 +137,41 @@ command_result embark_assistant (color_ostream &out, std::vector & //======================================================================================= +struct start_site_hook : df::viewscreen_choose_start_sitest { + typedef df::viewscreen_choose_start_sitest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (embark_assist::main::state) + return; + int x = 60; + int y = Screen::getWindowSize().y - 2; + OutputString(COLOR_LIGHTRED, x, y, " " + Screen::getKeyDisplay(interface_key::CUSTOM_A)); + OutputString(COLOR_WHITE, x, y, ": Embark Assistant"); + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + if (!embark_assist::main::state && input->count(interface_key::CUSTOM_A)) + { + Core::getInstance().setHotkeyCmd("embark-assistant"); + return; + } + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(start_site_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(start_site_hook, feed); + +//======================================================================================= + DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "embark-assistant", "Embark site selection support.", - embark_assistant, true, /* true means that the command can't be used from non-interactive user interface */ + embark_assistant, false, /* false means that the command can be used from non-interactive user interface */ // Extended help string. Used by CR_WRONG_USAGE and the help command: " This command starts the embark-assist plugin that provides embark site\n" " selection help. It has to be called while the pre-embark screen is\n" @@ -160,6 +193,22 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) //======================================================================================= +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) +{ + if (is_enabled != enable) + { + if (!INTERPOSE_HOOK(start_site_hook, render).apply(enable) || + !INTERPOSE_HOOK(start_site_hook, feed).apply(enable)) + { + return CR_FAILURE; + } + is_enabled = enable; + } + return CR_OK; +} + +//======================================================================================= + DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { From 7a5902418e10aed3b15cdc2edc2d45c0491bdea2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 25 Aug 2019 21:49:41 -0400 Subject: [PATCH 28/86] Abbreviate label on narrow screens --- plugins/embark-assistant/embark-assistant.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 4d83669e5..465435a38 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -145,10 +145,12 @@ struct start_site_hook : df::viewscreen_choose_start_sitest { INTERPOSE_NEXT(render)(); if (embark_assist::main::state) return; + auto dims = Screen::getWindowSize(); int x = 60; - int y = Screen::getWindowSize().y - 2; + int y = dims.y - 2; OutputString(COLOR_LIGHTRED, x, y, " " + Screen::getKeyDisplay(interface_key::CUSTOM_A)); - OutputString(COLOR_WHITE, x, y, ": Embark Assistant"); + OutputString(COLOR_WHITE, x, y, ": Embark "); + OutputString(COLOR_WHITE, x, y, dims.x > 82 ? "Assistant" : "Asst."); } DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) From df756f22f88bb9fc50dad25106949cd293f3233f Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 2 Sep 2019 18:35:46 +0300 Subject: [PATCH 29/86] Change dwarfmonitor to use global lua state. This needs more CoreSuspends but they are needed either way. This way you can access other script environments and access this plugin enviroment from lua. --- plugins/dwarfmonitor.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 29d0198b3..7fadff32f 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -163,7 +163,6 @@ namespace dm_lua { delete out; out = NULL; } - lua_close(state); } bool init_call (const char *func) { @@ -1873,6 +1872,12 @@ static bool set_monitoring_mode(const string &mode, const bool &state) if (!is_enabled) return false; + /* + NOTE: although we are not touching DF directly but there might be + code running that uses these values. So this could use another mutex + or just suspend the core while we edit our values. + */ + CoreSuspender guard; if (mode == "work" || mode == "all") { @@ -1908,7 +1913,10 @@ static bool load_config() DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable) + { + CoreSuspender guard; load_config(); + } if (is_enabled != enable) { if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) @@ -1963,16 +1971,19 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector & par } else if (cmd == 's' || cmd == 'S') { + CoreSuspender guard; if(Maps::IsValid()) Screen::show(dts::make_unique(), plugin_self); } else if (cmd == 'p' || cmd == 'P') { + CoreSuspender guard; if(Maps::IsValid()) Screen::show(dts::make_unique(), plugin_self); } else if (cmd == 'r' || cmd == 'R') { + CoreSuspender guard; load_config(); } else @@ -2024,7 +2035,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Mon, 2 Sep 2019 18:40:50 +0300 Subject: [PATCH 30/86] Whitespace fix --- plugins/dwarfmonitor.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 7fadff32f..13995a83b 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1872,12 +1872,12 @@ static bool set_monitoring_mode(const string &mode, const bool &state) if (!is_enabled) return false; - /* - NOTE: although we are not touching DF directly but there might be - code running that uses these values. So this could use another mutex - or just suspend the core while we edit our values. - */ - CoreSuspender guard; + /* + NOTE: although we are not touching DF directly but there might be + code running that uses these values. So this could use another mutex + or just suspend the core while we edit our values. + */ + CoreSuspender guard; if (mode == "work" || mode == "all") { @@ -1913,10 +1913,10 @@ static bool load_config() DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable) - { - CoreSuspender guard; + { + CoreSuspender guard; load_config(); - } + } if (is_enabled != enable) { if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable)) @@ -1971,19 +1971,19 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector & par } else if (cmd == 's' || cmd == 'S') { - CoreSuspender guard; + CoreSuspender guard; if(Maps::IsValid()) Screen::show(dts::make_unique(), plugin_self); } else if (cmd == 'p' || cmd == 'P') { - CoreSuspender guard; + CoreSuspender guard; if(Maps::IsValid()) Screen::show(dts::make_unique(), plugin_self); } else if (cmd == 'r' || cmd == 'R') { - CoreSuspender guard; + CoreSuspender guard; load_config(); } else @@ -2035,7 +2035,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Mon, 2 Sep 2019 19:50:00 +0300 Subject: [PATCH 31/86] Delete trailing whitespace --- plugins/dwarfmonitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 13995a83b..fc1679a19 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1877,7 +1877,7 @@ static bool set_monitoring_mode(const string &mode, const bool &state) code running that uses these values. So this could use another mutex or just suspend the core while we edit our values. */ - CoreSuspender guard; + CoreSuspender guard; if (mode == "work" || mode == "all") { From f00a7f9b3995080c1d5473b61e0451ed061e871f Mon Sep 17 00:00:00 2001 From: JapaMala Date: Sun, 8 Sep 2019 15:33:39 -0500 Subject: [PATCH 32/86] Bump RFR Version number. --- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 4cb479129..e3cfc2e1f 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.20.2" +#define RFR_VERSION "0.20.3" #include #include From 5eceab2794fe7bcebfc9fc1e230c6c38edf4453e Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Mon, 23 Sep 2019 13:13:04 +0200 Subject: [PATCH 33/86] Added 'fileresult' optional parameter, improved world traversal --- docs/changelog.txt | 2 + plugins/embark-assistant/defs.h | 4 ++ plugins/embark-assistant/embark-assistant.cpp | 18 +++++- plugins/embark-assistant/finder_ui.cpp | 11 +++- plugins/embark-assistant/finder_ui.h | 2 +- plugins/embark-assistant/help_ui.cpp | 58 +++++++++++-------- plugins/embark-assistant/matcher.cpp | 47 ++++++++------- plugins/embark-assistant/overlay.cpp | 26 ++++++++- plugins/embark-assistant/overlay.h | 1 + 9 files changed, 118 insertions(+), 51 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 47dcaa58b..c1d7bae72 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -85,6 +85,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - changed matching to take incursions, i.e. parts of other biomes, into consideration when evaluating tiles. This allows for e.g. finding multiple biomes on single tile embarks. - changed overlay display to show when incursion surveying is incomplete - changed overlay display to show evil weather + - added optional parameter "fileresult" for crude external harness automated match support + - improved focus movement logic to go to only required world tiles, increasing speed of subsequent searches considerably - `exportlegends`: added rivers to custom XML export - `exterminate`: added support for a special ``enemy`` caste - `modtools/create-unit`: diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index 9d1c8e888..3696c789d 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -11,6 +11,8 @@ using std::ostringstream; using std::string; using std::vector; +#define fileresult_file_name "./data/init/embark_assistant_fileresult.txt" + namespace embark_assist { namespace defs { // Survey types @@ -315,6 +317,8 @@ namespace embark_assist { bool y_down; bool inhibit_x_turn; bool inhibit_y_turn; + uint16_t target_location_x; + uint16_t target_location_y; uint16_t count; finders finder; }; diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 465435a38..68db916cf 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -253,6 +253,13 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan command_result embark_assistant(color_ostream &out, std::vector & parameters) { + bool fileresult = false; + + if (parameters.size() == 1 && + parameters[0] == "fileresult") { + remove(fileresult_file_name); + fileresult = true; + } else if (!parameters.empty()) return CR_WRONG_USAGE; @@ -277,8 +284,11 @@ command_result embark_assistant(color_ostream &out, std::vector & // Find the end of the normal inorganic definitions. embark_assist::main::state->max_inorganic = 0; - for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { - if (!world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i; + for (uint16_t i = world->raws.inorganics.size() - 1; i >= 0 ; i--) { + if (!world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) { + embark_assist::main::state->max_inorganic = i; + break; + } } embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement @@ -348,5 +358,9 @@ command_result embark_assistant(color_ostream &out, std::vector & embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->survey_results, &embark_assist::main::state->site_info, false); embark_assist::overlay::set_embark(&embark_assist::main::state->site_info); + if (fileresult) { + embark_assist::overlay::fileresult(); + } + return CR_OK; } diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 80c61dc6e..bae87aaa2 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -1557,11 +1557,18 @@ namespace embark_assist { // Exported operations //=============================================================================== -void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { +void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic, bool fileresult) { if (!embark_assist::finder_ui::state) { // First call. Have to do the setup embark_assist::finder_ui::ui_setup(find_callback, max_inorganic); } - Screen::show(dts::make_unique(), plugin_self); + if (!fileresult) { + Screen::show(dts::make_unique(), plugin_self); + } + else + { + load_profile(); + find(); + } } //=============================================================================== diff --git a/plugins/embark-assistant/finder_ui.h b/plugins/embark-assistant/finder_ui.h index 70bf4ce42..1011bf251 100644 --- a/plugins/embark-assistant/finder_ui.h +++ b/plugins/embark-assistant/finder_ui.h @@ -10,7 +10,7 @@ using namespace DFHack; namespace embark_assist { namespace finder_ui { - void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic); + void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic, bool fileresult); void activate(); void shutdown(); } diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index ef1bc2b39..ebb1deb78 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -167,25 +167,25 @@ namespace embark_assist{ help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the"); help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth"); help_text.push_back("into consideration, so it can display resources that actually are cut away."); - help_text.push_back("Thirdly, it takes 'intrusions', i.e. small sections of neighboring tiles'"); + help_text.push_back("Thirdly, it takes 'incursions', i.e. small sections of neighboring tiles'"); help_text.push_back("biomes into consideration for many fields."); help_text.push_back("(It can be noted that the DFHack Sand indicator does take the first two"); help_text.push_back("elements into account)."); help_text.push_back("The info the Embark Assistant displays is:"); - help_text.push_back("Incompl. Survey if all intrusions couldn't be examined because that requires"); + help_text.push_back("Incompl. Survey if all incursions couldn't be examined because that requires"); help_text.push_back("info from neighboring world tiles that haven't been surveyed."); - help_text.push_back("Sand, if present, including through intrusions."); - help_text.push_back("Clay, if present, including thorugh intrusions."); - help_text.push_back("Min and Max soil depth in the embark rectangle, including intrusions."); - help_text.push_back("Flat indicator if all the tiles and intrusions have the same elevation."); - help_text.push_back("Aquifer indicator: Part(ial) or Full, when present, including intrusions."); + help_text.push_back("Sand, if present, including through incursions."); + help_text.push_back("Clay, if present, including thorugh incursions."); + help_text.push_back("Min and Max soil depth in the embark rectangle, including incursions."); + help_text.push_back("Flat indicator if all the tiles and incursions have the same elevation."); + help_text.push_back("Aquifer indicator: Part(ial) or Full, when present, including incursions."); help_text.push_back("Waterfall and largest Z level drop if the river has elevation differences"); help_text.push_back("Evil weather, when present: BR = Blood Rain, TS = Temporary Syndrome"); - help_text.push_back("PS = Permanent Syndrome, Re = Reanimating, and Th = Thralling. Intrusions."); - help_text.push_back("Flux, if present. NOT allowing for small intrusion bits."); - help_text.push_back("A list of all metals present in the embark. Not intrusions."); + help_text.push_back("PS = Permanent Syndrome, Re = Reanimating, and Th = Thralling. Incursions."); + help_text.push_back("Flux, if present. NOT allowing for small incursion bits."); + help_text.push_back("A list of all metals present in the embark. Not incursions."); help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux"); - help_text.push_back("stones are economic, so they show up here as well. Not intrusions."); + help_text.push_back("stones are economic, so they show up here as well. Not incursions."); help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); help_text.push_back("overlays over the Local, Region, and World maps to indicate where"); help_text.push_back("matching embarks are found. The Local display marks the top left corner of"); @@ -256,7 +256,7 @@ namespace embark_assist{ help_text.push_back("block) at a time, and the results are displayed as green inverted X on"); help_text.push_back("the same map (replacing or erasing the yellow ones). Local map overlay"); help_text.push_back("data is generated as well."); - help_text.push_back("Since 'intrusion' processing requires that the neighboring tiles that may"); + help_text.push_back("Since 'incursion' processing requires that the neighboring tiles that may"); help_text.push_back("provide them are surveyed before the current tile and tiles have to be"); help_text.push_back("surveyed in some order, the find function can not perform a complete"); help_text.push_back("survey of prospective embarks that border world tiles yet to be surveyed"); @@ -266,6 +266,19 @@ namespace embark_assist{ help_text.push_back("ones."); help_text.push_back(""); help_text.push_back("Caveats & technical stuff:"); + help_text.push_back("- The plugin does in fact allow for a single, optional case sensitive"); + help_text.push_back(" parameter when invoked: 'fileresult'. When this parameter is provided"); + help_text.push_back(" The plugin will read the search profile stored to file and immediately"); + help_text.push_back(" initiate a search for matches. This search is performed twice to ensure"); + help_text.push_back(" incursions are handled correctly, and then the number of matching world"); + help_text.push_back(" is written to the file /data/init/embark_assistant_fileresult.txt."); + help_text.push_back(" It can be noted that this file is deleted before the first search is"); + help_text.push_back(" started. The purpose of this mode is to allow external harnesses to"); + help_text.push_back(" generate worlds, search them for matches, and use the file results to"); + help_text.push_back(" to determine which worlds to keep. It can be noted that after the search"); + help_text.push_back(" the plugin continues to work essentially as usual, including external"); + help_text.push_back(" to terminate, and that the author plugin can provide no help when it comes"); + help_text.push_back(" to setting up any kind of harness using the plugin functionality."); help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it"); help_text.push_back(" to load feature shells and detailed region data, and this costs the"); help_text.push_back(" least when done one feature shell at a time."); @@ -274,14 +287,17 @@ namespace embark_assist{ help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search."); help_text.push_back(" Note that the first search can miss a fair number of matches for"); help_text.push_back(" technical reasons discussed above and below."); + + break; + + case pages::Caveats_2: + Screen::drawBorder(" Embark Assistant Help/Info Caveats 2 Page "); + help_text.push_back("- The site info is deduced by the author, so there may be errors and"); help_text.push_back(" there are probably site types that end up not being identified."); help_text.push_back("- Aquifer indications are based on the author's belief that they occur"); help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or"); help_text.push_back(" more."); - help_text.push_back("- The biome determination logic comes from code provided by Ragundo,"); - help_text.push_back(" with only marginal changes by the author. References can be found in"); - help_text.push_back(" the source file."); help_text.push_back("- Thralling is determined by whether material interactions causes"); help_text.push_back(" blinking, which the author believes is one of 4 thralling changes."); help_text.push_back("- The geo information is gathered by code which is essentially a"); @@ -293,12 +309,6 @@ namespace embark_assist{ help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" This plugin does not address this but scripts can correct it."); - - break; - - case pages::Caveats_2: - Screen::drawBorder(" Embark Assistant Help/Info Caveats 2 Page "); - help_text.push_back("- The plugin detects 'incursions' of neighboring tiles into embarks, but"); help_text.push_back(" this functionality is incomplete when the incursion comes from a"); help_text.push_back(" neighboring tile that hasn't been surveyed yet. The embark info displays"); @@ -314,9 +324,9 @@ namespace embark_assist{ help_text.push_back(" economics/minerals (including Flux and Coal) as any volumes are typically"); help_text.push_back(" too small to be of interest. Rivers, Waterfalls, Spires, and Magma Pools"); help_text.push_back(" are not incursion related features."); - help_text.push_back("- There are special rules for handing of intrusions from Lakes and Oceans,"); + help_text.push_back("- There are special rules for handing of incursions from Lakes and Oceans,"); help_text.push_back(" as well as Mountains into everything that isn't a Lake or Ocean, and the"); - help_text.push_back(" rules state that these intrusions should be reversed (i.e. 'normal' biomes"); + help_text.push_back(" rules state that these incursions should be reversed (i.e. 'normal' biomes"); help_text.push_back(" should push into Lakes, Oceans, and Mountains, even when the indicators"); help_text.push_back(" say otherwise). This rule is clear for edges, but not for corners, as it"); help_text.push_back(" does not specify which of the potentially multiple 'superior' biomes"); @@ -325,7 +335,7 @@ namespace embark_assist{ help_text.push_back(" the N, followed by the one to the W, and lastly the one acting as the"); help_text.push_back(" reference. This means there's a risk embarks with such 'trouble' corners"); help_text.push_back(" may get affected corner(s) evaluated incorrectly."); - help_text.push_back("Version 0.9 2019-07-12"); + help_text.push_back("Version 0.10 2019-09-21"); break; } diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index a3975a980..40d2ead91 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -176,8 +176,8 @@ namespace embark_assist { result->sand_found = true; } - // Flux. N/A for intrusions. - // Coal. N/A for intrusions + // Flux. N/A for incursions. + // Coal. N/A for incursions // Min Soil if (finder->soil_min != embark_assist::defs::soil_ranges::NA && @@ -270,8 +270,8 @@ namespace embark_assist { result->thralling_found = true; } - // Spires. N/A for intrusions - // Magma. N/A for intrusions + // Spires. N/A for incursions + // Magma. N/A for incursions // Biomes result->biomes[survey_results->at(x).at(y).biome[mlt->biome_offset]] = true; @@ -279,8 +279,8 @@ namespace embark_assist { // Region Type result->region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->biome_offset]]->type] = true; - // Metals. N/A for intrusions - // Economics. N/A for intrusions + // Metals. N/A for incursions + // Economics. N/A for incursions } //======================================================================================= @@ -2299,6 +2299,8 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); } + iterator->target_location_x = 0; + iterator->target_location_y = 0; iterator->active = true; iterator->i = 0; iterator->k = 0; @@ -2327,21 +2329,24 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter for (uint16_t l = 0; l <= x_end; l++) { for (uint16_t m = 0; m <= y_end; m++) { // This is where the payload goes - if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { + if (!survey_results->at(iterator->target_location_x).at(iterator->target_location_y).surveyed || + match_results->at(iterator->target_location_x).at(iterator->target_location_y).preliminary_match) { + move_cursor(iterator->target_location_x, iterator->target_location_y); + match_world_tile(geo_summary, survey_results, &iterator->finder, match_results, - screen->location.region_pos.x, - screen->location.region_pos.y); - if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { + iterator->target_location_x, + iterator->target_location_y); + if (match_results->at(iterator->target_location_x).at(iterator->target_location_y).contains_match) { iterator->count++; } } else { for (uint16_t n = 0; n < 16; n++) { for (uint16_t p = 0; p < 16; p++) { - match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; + match_results->at(iterator->target_location_x).at(iterator->target_location_y).mlt_match[n][p] = false; } } } @@ -2349,15 +2354,15 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter if (m != y_end) { if (iterator->y_down) { - screen->feed_key(df::interface_key::CURSOR_DOWN); + if (iterator->target_location_y < world->worldgen.worldgen_parms.dim_y - 1) iterator->target_location_y++; } else { - screen->feed_key(df::interface_key::CURSOR_UP); + if (iterator->target_location_y > 0) iterator->target_location_y--; } } else { - if (screen->location.region_pos.x != 0 && - screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { + if (iterator->target_location_x != 0 && + iterator->target_location_x != world->worldgen.worldgen_parms.dim_x - 1) { turn = true; } else { @@ -2370,24 +2375,24 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter } else { if (iterator->y_down) { - screen->feed_key(df::interface_key::CURSOR_DOWN); + if (iterator->target_location_y < world->worldgen.worldgen_parms.dim_y - 1) iterator->target_location_y++; } else { - screen->feed_key(df::interface_key::CURSOR_UP); + if (iterator->target_location_y > 0) iterator->target_location_y--; } } } } if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. - screen->feed_key(df::interface_key::CURSOR_RIGHT); + if (iterator->target_location_x < world->worldgen.worldgen_parms.dim_x - 1) iterator->target_location_x++; } else { - screen->feed_key(df::interface_key::CURSOR_LEFT); + if (iterator->target_location_x > 0) iterator->target_location_x--; } if (!iterator->x_right && - screen->location.region_pos.x == 0) { + iterator->target_location_x == 0) { turn = !turn; if (turn) { @@ -2395,7 +2400,7 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter } } else if (iterator->x_right && - screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { + iterator->target_location_x == world->worldgen.worldgen_parms.dim_x - 1) { turn = !turn; if (turn) { diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index a0defd0d5..c689f21ad 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -54,6 +54,9 @@ namespace embark_assist { uint16_t match_count = 0; uint16_t max_inorganic; + + bool fileresult = false; + uint8_t fileresult_pass = 0; }; static states *state = nullptr; @@ -113,7 +116,7 @@ namespace embark_assist { } else if (input->count(df::interface_key::CUSTOM_F)) { if (!state->match_active && !state->matching) { - embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic); + embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, false); } } else if (input->count(df::interface_key::CUSTOM_I)) { @@ -311,6 +314,7 @@ void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs: // color_ostream_proxy out(Core::getInstance().getConsole()); state->matching = !done; state->match_count = count; + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { if (match_results->at(i).at(k).preliminary_match) { @@ -324,6 +328,18 @@ void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs: } } } + + if (done && state->fileresult) { + state->fileresult_pass++; + if (state->fileresult_pass == 1) { + embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, true); + } + else { + FILE* outfile = fopen(fileresult_file_name, "w"); + fprintf(outfile, "%i\n", count); + fclose(outfile); + } + } } //==================================================================== @@ -463,6 +479,14 @@ void embark_assist::overlay::clear_match_results() { //==================================================================== +void embark_assist::overlay::fileresult() { + // Have to search twice, as the first pass cannot be complete due to mutual dependencies. + state->fileresult = true; + embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, true); +} + +//==================================================================== + void embark_assist::overlay::shutdown() { if (state && state->world_match_grid) { diff --git a/plugins/embark-assistant/overlay.h b/plugins/embark-assistant/overlay.h index d66d6d7fd..f4f7cf3eb 100644 --- a/plugins/embark-assistant/overlay.h +++ b/plugins/embark-assistant/overlay.h @@ -32,6 +32,7 @@ namespace embark_assist { void set_embark(embark_assist::defs::site_infos *site_info); void set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches); void clear_match_results(); + void fileresult(); void shutdown(); } } \ No newline at end of file From 79791505660f3dc55895aa103c8907214ec10afb Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 24 Sep 2019 11:14:50 +0200 Subject: [PATCH 34/86] Removed trailing blank --- plugins/embark-assistant/matcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 40d2ead91..c013a695e 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -2329,7 +2329,7 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter for (uint16_t l = 0; l <= x_end; l++) { for (uint16_t m = 0; m <= y_end; m++) { // This is where the payload goes - if (!survey_results->at(iterator->target_location_x).at(iterator->target_location_y).surveyed || + if (!survey_results->at(iterator->target_location_x).at(iterator->target_location_y).surveyed || match_results->at(iterator->target_location_x).at(iterator->target_location_y).preliminary_match) { move_cursor(iterator->target_location_x, iterator->target_location_y); From a2b5c1ddd9db24595a67c8a8ec6aa80f4f541686 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 30 Sep 2019 22:00:06 +0300 Subject: [PATCH 35/86] lua/widgets filtered list input fix FilteredList was not consuming input events. --- library/lua/gui/widgets.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 741d722dd..132c6da57 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -714,13 +714,16 @@ end function FilteredList:onInput(keys) if self.edit_key and keys[self.edit_key] and not self.edit.active then self.edit.active = true + return true elseif keys.LEAVESCREEN and self.edit.active then self.edit.active = false + return true else - self:inputToSubviews(keys) + return self:inputToSubviews(keys) end end + function FilteredList:getChoices() return self.choices end From ca7d0f1cac3394215666687a4f5176e5c4c805f9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 3 Oct 2019 23:26:57 -0400 Subject: [PATCH 36/86] Add algorithm include for MSVC --- library/include/Types.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/include/Types.h b/library/include/Types.h index 157b23a0e..38830f53c 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -25,6 +25,8 @@ distribution. #pragma once +#include + #include "Pragma.h" #include "Export.h" From 7d5970303f7067661a04a0493670866f4d6fcce4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 4 Oct 2019 00:14:08 -0400 Subject: [PATCH 37/86] Update xml (dfhack/df-structures#286) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 4388fbfb8..abdcb2e17 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7 +Subproject commit abdcb2e17ff29e754a8a7661b697035a0ee702ba From dcce9ae5991a2bb8310c1fd9ba446cd91c999592 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 4 Oct 2019 13:06:13 -0400 Subject: [PATCH 38/86] Add quotes around dfhack_llimits.h include for MSVC Fixes #1455 --- depends/lua/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index fd3a59c08..17bb2d73e 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -96,7 +96,8 @@ ADD_LIBRARY ( lua SHARED ${SRC_LIBLUA} ) TARGET_LINK_LIBRARIES ( lua ${LIBS}) if (MSVC) - target_compile_options(lua PRIVATE /FI dfhack_llimits.h) + # need quotes to prevent /FI from being stripped: https://github.com/DFHack/dfhack/issues/1455 + target_compile_options(lua PRIVATE "/FI dfhack_llimits.h") else () target_compile_options(lua PRIVATE -include dfhack_llimits.h) endif () From 8675ff660caefaf6ce5c6ce4318c7609b6b1aa6e Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 4 Oct 2019 13:15:58 -0400 Subject: [PATCH 39/86] Windows+Ninja: Fix build error due to trying to include " dfhack_llimits.h" Ref #1455 --- depends/lua/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 17bb2d73e..5c01eae89 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -96,8 +96,8 @@ ADD_LIBRARY ( lua SHARED ${SRC_LIBLUA} ) TARGET_LINK_LIBRARIES ( lua ${LIBS}) if (MSVC) - # need quotes to prevent /FI from being stripped: https://github.com/DFHack/dfhack/issues/1455 - target_compile_options(lua PRIVATE "/FI dfhack_llimits.h") + # need no space to prevent /FI from being stripped: https://github.com/DFHack/dfhack/issues/1455 + target_compile_options(lua PRIVATE "/FIdfhack_llimits.h") else () target_compile_options(lua PRIVATE -include dfhack_llimits.h) endif () From 08aeb6faeb33b93e378af9ed308cd5acbdaf9efc Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 Oct 2019 20:26:31 -0400 Subject: [PATCH 40/86] Fix EventManager building ID type The pointer to the building ID was sometimes not dereferenced properly. Addressed by switching to an ID cast to a pointer type for consistency with other types of events. Fixes #1434 Ref #1003 (19695b4ee7e8b370c8ce53aa2dcd399e1e9ca94d) Broken in #1253 (a7dfacd1c57490d08fc92a43eea963cff47c05eb) --- library/modules/Buildings.cpp | 2 +- library/modules/EventManager.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 732956a3e..607f63a36 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1199,7 +1199,7 @@ void Buildings::clearBuildings(color_ostream& out) { void Buildings::updateBuildings(color_ostream& out, void* ptr) { - int32_t id = *((int32_t*)ptr); + int32_t id = (int32_t)(intptr_t)ptr; auto building = df::building::find(id); if (building) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 404d2342c..7d8b7e6d5 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -273,7 +273,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } for ( size_t a = 0; a < df::global::world->buildings.all.size(); a++ ) { df::building* b = df::global::world->buildings.all[a]; - Buildings::updateBuildings(out, (void*)&(b->id)); + Buildings::updateBuildings(out, (void*)intptr_t(b->id)); buildings.insert(b->id); } lastSyndromeTime = -1; @@ -609,7 +609,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)&a); + bob.eventHandler(out, (void*)intptr_t(a)); } } nextBuilding = *df::global::building_next_id; @@ -625,7 +625,7 @@ static void manageBuildingEvent(color_ostream& out) { for ( auto b = copy.begin(); b != copy.end(); b++ ) { EventHandler bob = (*b).second; - bob.eventHandler(out, (void*)&id); + bob.eventHandler(out, (void*)intptr_t(id)); } a = buildings.erase(a); } From 582169e0a5bdcb5bceb60563ddca435d5a51095b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 5 Oct 2019 21:05:26 -0400 Subject: [PATCH 41/86] eventExample: make unitAttack a bit safer --- plugins/devel/eventExample.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index b5f9d34d4..ba9c2a00d 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -175,8 +175,15 @@ void unitAttack(color_ostream& out, void* ptr) { EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; out.print("unit %d attacks unit %d\n", data->attacker, data->defender); df::unit* defender = df::unit::find(data->defender); + if (!defender) { + out.printerr("defender %d does not exist\n", data->defender); + return; + } int32_t woundIndex = df::unit_wound::binsearch_index(defender->body.wounds, data->wound); - df::unit_wound* wound = defender->body.wounds[woundIndex]; + df::unit_wound* wound = vector_get(defender->body.wounds, woundIndex); + if (!wound) { + return; + } set parts; for ( auto a = wound->parts.begin(); a != wound->parts.end(); a++ ) { parts.insert((*a)->body_part_id); From cffc30b433f7e1fa437bd3b0495b4c0ad75ab97c Mon Sep 17 00:00:00 2001 From: jimcarreer Date: Sun, 6 Oct 2019 14:25:33 -0400 Subject: [PATCH 42/86] Output Castes on Error --- plugins/createitem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 70794e38c..0d61e4e36 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -311,12 +311,14 @@ command_result df_createitem (color_ostream &out, vector & parameters) for (size_t i = 0; i < world->raws.creatures.all.size(); i++) { + string castes = ""; df::creature_raw *creature = world->raws.creatures.all[i]; if (creature->creature_id == tokens[0]) { for (size_t j = 0; j < creature->caste.size(); j++) { df::caste_raw *caste = creature->caste[j]; + castes += " "+creature->caste[j]->caste_id; if (creature->caste[j]->caste_id == tokens[1]) { mat_type = i; @@ -326,7 +328,7 @@ command_result df_createitem (color_ostream &out, vector & parameters) } if (mat_type == -1) { - out.printerr("The creature you specified has no such caste!\n"); + out.printerr("The creature you specified has no such caste!\nValid castes:%s\n", castes.c_str()); return CR_FAILURE; } } From 7e501fd70b733f5e42d1c61d52eb97231d299615 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Thu, 31 Oct 2019 20:07:34 +0100 Subject: [PATCH 43/86] Fixed bug in region type determination in new code --- plugins/embark-assistant/survey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 3574219bc..5e9e44b5f 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -1366,7 +1366,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { - tile->region_type[i][k] = world_data->regions[tile->biome[mlt->at(i).at(k).biome_offset]]->type; + tile->region_type[i][k] = world_data->regions[tile->biome_index[mlt->at(i).at(k).biome_offset]]->type; } } From f3d5a185ba7e1e857f24821d383278b1a5fc3ed7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Nov 2019 22:42:08 -0400 Subject: [PATCH 44/86] macOS launcher: remove quarantine flags Fixes #1465 --- package/darwin/dfhack | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 445b600f7..9ededf5d5 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -11,6 +11,12 @@ else export DYLD_FALLBACK_FRAMEWORK_PATH="./hack:./libs:./hack/libs" fi +# attempt to remove quarantine flag: https://github.com/DFHack/dfhack/issues/1465 +if ! test -f hack/quarantine-removed; then + find hack/ libs/ dwarfort.exe -name '*.dylib' -or -name '*.exe' -print0 | xargs -0 xattr -d com.apple.quarantine 2>&1 | grep -iv 'no such xattr' + echo "quarantine flag removed on $(date); remove this file to re-run" > hack/quarantine-removed +fi + old_tty_settings=$(stty -g) DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" From 5190257864fdba21161937259d469c989c09eeb0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Nov 2019 23:46:42 -0400 Subject: [PATCH 45/86] createitem: Improve error handling when no caste is specified Extension of #1463 --- plugins/createitem.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 0d61e4e36..939afebc2 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -303,7 +303,12 @@ command_result df_createitem (color_ostream &out, vector & parameters) case item_type::PET: case item_type::EGG: split_string(&tokens, material_str, ":"); - if (tokens.size() != 2) + if (tokens.size() == 1) + { + // default to empty caste to display a list of valid castes later + tokens.push_back(""); + } + else if (tokens.size() != 2) { out.printerr("You must specify a creature ID and caste for this item type!\n"); return CR_FAILURE; @@ -318,7 +323,7 @@ command_result df_createitem (color_ostream &out, vector & parameters) for (size_t j = 0; j < creature->caste.size(); j++) { df::caste_raw *caste = creature->caste[j]; - castes += " "+creature->caste[j]->caste_id; + castes += " " + creature->caste[j]->caste_id; if (creature->caste[j]->caste_id == tokens[1]) { mat_type = i; @@ -328,7 +333,15 @@ command_result df_createitem (color_ostream &out, vector & parameters) } if (mat_type == -1) { - out.printerr("The creature you specified has no such caste!\nValid castes:%s\n", castes.c_str()); + if (tokens[1].empty()) + { + out.printerr("You must also specify a caste.\n"); + } + else + { + out.printerr("The creature you specified has no such caste!\n"); + } + out.printerr("Valid castes:%s\n", castes.c_str()); return CR_FAILURE; } } From 119450109ff6aece4932ae370219f10e95b51797 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Nov 2019 23:53:55 -0400 Subject: [PATCH 46/86] Add jimcarreer to authors (#1463) --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 5c3aae2fd..c30776358 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -57,6 +57,7 @@ James Logsdon jlogsdon Japa JapaMala Jared Adams Jim Lisi stonetoad +jimcarreer jimcarreer jj jjyg jj`` John Beisley huin John Shade gsvslto From c2e5ae844c75474bed1ca7757ac8379bc9822b6d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 21:07:10 -0400 Subject: [PATCH 47/86] tailor: add docs Ref #1398 --- docs/Plugins.rst | 17 ++++++++++++++++- plugins/devel/tailor.cpp | 8 ++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 2b78fd700..670b93e73 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1480,7 +1480,7 @@ Some widgets support additional options: .. _dwarfvet: dwarfvet -============ +======== Enables Animal Caretaker functionality Always annoyed your dragons become useless after a minor injury? Well, with @@ -1831,6 +1831,21 @@ nestboxes Automatically scan for and forbid fertile eggs incubating in a nestbox. Toggle status with `enable` or `disable`. +.. _tailor: + +tailor +====== + +Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, +count up the number that are worn, and then order enough more made to replace all worn items. +If there are enough replacement items in inventory to replace all worn items, the units wearing them +will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll +reeequip with replacement items. + +Use the `enable` and `disable` commands to toggle this plugin's status, or run +``tailor status`` to check its current status. + + ================ Map modification ================ diff --git a/plugins/devel/tailor.cpp b/plugins/devel/tailor.cpp index 18db67b38..d59316677 100644 --- a/plugins/devel/tailor.cpp +++ b/plugins/devel/tailor.cpp @@ -50,7 +50,7 @@ const char *usage = ( " tailor status\n" " Display plugin status\n" "\n" - "Whenever the bookkeeper updates stockpile records, the plugin will scan every unit in the fort, \n" + "Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort,\n" "count up the number that are worn, and then order enough more made to replace all worn items.\n" "If there are enough replacement items in inventory to replace all worn items, the units wearing them\n" "will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll\n" @@ -76,7 +76,7 @@ static map itemTypeMap = { { df::item_type::GLOVES, df::job_type::MakeGloves}, { df::item_type::SHOES, df::job_type::MakeShoes} }; - + void do_scan(color_ostream& out) { map, int> available; // key is item type & size @@ -86,7 +86,7 @@ void do_scan(color_ostream& out) map sizes; // this maps body size to races map, int> orders; // key is item type, item subtype, size - + df::item_flags bad_flags; bad_flags.whole = 0; @@ -192,7 +192,7 @@ void do_scan(color_ostream& out) if (oo == itemTypeMap.end()) continue; df::job_type o = oo->second; - + int size = world->raws.creatures.all[w->getMakerRace()]->adultsize; std::string description; w->getItemDescription(&description, 0); From 150edcfff8b6d67d4c7b98ebe3f07b0b7fcb64cb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 21:12:41 -0400 Subject: [PATCH 48/86] Move tailor to supported plugins --- plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/{devel => }/tailor.cpp | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{devel => }/tailor.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fbca6bca9..bee9f5546 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -164,6 +164,7 @@ if (BUILD_SUPPORTED) add_subdirectory(stockpiles) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) + DFHACK_PLUGIN(tailor tailor.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) DFHACK_PLUGIN(title-folder title-folder.cpp) DFHACK_PLUGIN(title-version title-version.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 3dca5d05e..245bc518c 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,7 +18,6 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(stepBetween stepBetween.cpp) DFHACK_PLUGIN(stockcheck stockcheck.cpp) DFHACK_PLUGIN(stripcaged stripcaged.cpp) -DFHACK_PLUGIN(tailor tailor.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp) DFHACK_PLUGIN(zoom zoom.cpp) diff --git a/plugins/devel/tailor.cpp b/plugins/tailor.cpp similarity index 100% rename from plugins/devel/tailor.cpp rename to plugins/tailor.cpp From bf4dec6c6ad362b7a3d315d7ebe5ba78116fa26b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 21:13:17 -0400 Subject: [PATCH 49/86] Update scripts, stonesense, authors --- docs/Authors.rst | 1 + plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index c30776358..dd0f691f3 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -59,6 +59,7 @@ Jared Adams Jim Lisi stonetoad jimcarreer jimcarreer jj jjyg jj`` +Joel Meador janxious John Beisley huin John Shade gsvslto Jonas Ask diff --git a/plugins/stonesense b/plugins/stonesense index 03e96477c..4fdb2be54 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 03e96477ca84e42c87db93bd2d781c73687795a8 +Subproject commit 4fdb2be54365442b8abea86f21746795f83fbdc2 diff --git a/scripts b/scripts index 8ef283377..abcb0cffb 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf +Subproject commit abcb0cffbbcaaeefc7effca25ff947ab8ea91c43 From 6a005102728f1f284f7a743240da472159de01a8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 21:30:48 -0400 Subject: [PATCH 50/86] tailor: add missing c_str() calls --- plugins/tailor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index d59316677..a9dc14750 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -346,7 +346,7 @@ void do_scan(color_ostream& out) if (!can_make) { - out.print("Cannot make %s, skipped\n", name_p); + out.print("Cannot make %s, skipped\n", name_p.c_str()); continue; // this civilization does not know how to make this item, so sorry } @@ -399,8 +399,8 @@ void do_scan(color_ostream& out) order->id, count, bitfield_to_string(order->material_category), - (count > 1) ? name_p : name_s, - world->raws.creatures.all[order->hist_figure_id]->name[1] + (count > 1) ? name_p.c_str() : name_s.c_str(), + world->raws.creatures.all[order->hist_figure_id]->name[1].c_str() ); } } From 44f36403446ac08c1b2193844874727e12b2e028 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 21:46:10 -0400 Subject: [PATCH 51/86] tailor: add another missing c_str() call --- plugins/tailor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index a9dc14750..6ac308258 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -398,7 +398,7 @@ void do_scan(color_ostream& out) out.print("Added order #%d for %d %s %s (sized for %s)\n", order->id, count, - bitfield_to_string(order->material_category), + bitfield_to_string(order->material_category).c_str(), (count > 1) ? name_p.c_str() : name_s.c_str(), world->raws.creatures.all[order->hist_figure_id]->name[1].c_str() ); From a885e3b9be98ab7c8f4bbabc10909f3e2c91e31e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 22:00:43 -0400 Subject: [PATCH 52/86] autoclothing: add docs Ref #1437 --- docs/Plugins.rst | 16 ++++++++++++++++ plugins/autoclothing.cpp | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 670b93e73..f6341e5aa 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1845,6 +1845,22 @@ reeequip with replacement items. Use the `enable` and `disable` commands to toggle this plugin's status, or run ``tailor status`` to check its current status. +.. _autoclothing: + +autoclothing +============ + +Automatically manage clothing work orders, allowing the user to set how many of +each clothing type every citizen should have. Usage:: + + autoclothing [number] + +Examples: + +* ``autoclothing cloth "short skirt" 10``: + Sets the desired number of cloth short skirts available per citizen to 10. +* ``autoclothing cloth dress``: + Displays the currently set number of cloth dresses chosen per citizen. ================ Map modification diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 71127119e..d19e647d5 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -187,7 +187,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" + " autoclothing [number]\n" "Example:\n" " autoclothing cloth \"short skirt\" 10\n" " Sets the desired number of cloth short skirts available per citizen to 10.\n" @@ -383,7 +383,7 @@ command_result autoclothing(color_ostream &out, std::vector & para // use CoreSuspender, it'll automatically resume DF when // execution leaves the current scope. CoreSuspender suspend; - + // Create a new requirement from the available parameters. ClothingRequirement newRequirement; From e7353ba9bfd7912b43853979bf6198c99850113b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Nov 2019 22:13:28 -0400 Subject: [PATCH 53/86] embark-assistant: clean up "else if" style Ref #1456 --- plugins/embark-assistant/embark-assistant.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 68db916cf..cac6eb900 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -259,9 +259,9 @@ command_result embark_assistant(color_ostream &out, std::vector & parameters[0] == "fileresult") { remove(fileresult_file_name); fileresult = true; - } else - if (!parameters.empty()) + } else if (!parameters.empty()) { return CR_WRONG_USAGE; + } CoreSuspender suspend; From 4abd410b1b415d74417a91454ae18006104d36f5 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 09:16:52 -0500 Subject: [PATCH 54/86] Convert autofarm from ruby to C++ Reimplement the Ruby autofarm script as a C++ plugin --- plugins/devel/CMakeLists.txt | 1 + plugins/devel/autofarm.cpp | 406 +++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 plugins/devel/autofarm.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 245bc518c..a431fcaae 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -3,6 +3,7 @@ DFHACK_PLUGIN(vectors vectors.cpp) endif() ADD_DEFINITIONS(-DDEV_PLUGIN) +DFHACK_PLUGIN(autofarm autofarm.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp new file mode 100644 index 000000000..8a79d3f61 --- /dev/null +++ b/plugins/devel/autofarm.cpp @@ -0,0 +1,406 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/building_type.h" +#include "df/building_farmplotst.h" +#include "df/buildings_other_id.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/item_plantst.h" +#include "df/items_other_id.h" +#include "df/unit.h" +#include "df/building.h" +#include "df/plant_raw.h" +#include "df/plant_raw_flags.h" +#include "df/biome_type.h" +#include "modules/Items.h" +#include "modules/Maps.h" +#include "modules/World.h" + +using std::vector; +using std::string; +using std::map; +using std::set; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; + +static command_result autofarm(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("autofarm"); + +DFHACK_PLUGIN_IS_ENABLED(enabled); + +static void process(color_ostream &out) +{ + CoreSuspender suspend; + +} + +const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; +const char *usage = ( + "autofarm enable\n" + "autofarm default 30\n" + "autofarm threshold 150 helmet_plump tail_pig\n" + ); + +class AutoFarm { + +private: + map thresholds; + int defaultThreshold = 50; + + map lastCounts; + +public: + void initialize() + { + thresholds.clear(); + defaultThreshold = 50; + + lastCounts.clear(); + } + + void setThreshold(int id, int val) + { + thresholds[id] = val; + } + + int getThreshold(int id) + { + return (thresholds.count(id) > 0) ? thresholds[id] : defaultThreshold; + } + + void setDefault(int val) + { + defaultThreshold = val; + } + +private: + const df::plant_raw_flags seasons[4] = { df::plant_raw_flags::SPRING, df::plant_raw_flags::SUMMER, df::plant_raw_flags::AUTUMN, df::plant_raw_flags::WINTER }; + +public: + + bool is_plantable(df::plant_raw* plant) + { + bool has_seed = plant->flags.is_set(df::plant_raw_flags::SEED); + bool is_tree = plant->flags.is_set(df::plant_raw_flags::TREE); + + int8_t season = *df::global::cur_season; + int harvest = (*df::global::cur_season_tick) + plant->growdur * 10; + bool can_plant = has_seed && !is_tree && plant->flags.is_set(seasons[season]); + while (can_plant && harvest >= 10080) { + season = (season + 1) % 4; + harvest -= 10080; + can_plant = can_plant && plant->flags.is_set(seasons[season]); + } + + return can_plant; + } + +private: + map> plantable_plants; + + const map biomeFlagMap = { + { df::plant_raw_flags::BIOME_MOUNTAIN, df::biome_type::MOUNTAIN }, + { df::plant_raw_flags::BIOME_GLACIER, df::biome_type::GLACIER }, + { df::plant_raw_flags::BIOME_TUNDRA, df::biome_type::TUNDRA }, + { df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_FRESHWATER, df::biome_type::SWAMP_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TEMPERATE_SALTWATER, df::biome_type::SWAMP_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_MARSH_TEMPERATE_FRESHWATER, df::biome_type::MARSH_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_MARSH_TEMPERATE_SALTWATER, df::biome_type::MARSH_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TROPICAL_FRESHWATER, df::biome_type::SWAMP_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_SWAMP_TROPICAL_SALTWATER, df::biome_type::SWAMP_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_SWAMP_MANGROVE, df::biome_type::SWAMP_MANGROVE }, + { df::plant_raw_flags::BIOME_MARSH_TROPICAL_FRESHWATER, df::biome_type::MARSH_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_MARSH_TROPICAL_SALTWATER, df::biome_type::MARSH_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_FOREST_TAIGA, df::biome_type::FOREST_TAIGA }, + { df::plant_raw_flags::BIOME_FOREST_TEMPERATE_CONIFER, df::biome_type::FOREST_TEMPERATE_CONIFER }, + { df::plant_raw_flags::BIOME_FOREST_TEMPERATE_BROADLEAF, df::biome_type::FOREST_TEMPERATE_BROADLEAF }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_CONIFER, df::biome_type::FOREST_TROPICAL_CONIFER }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_DRY_BROADLEAF, df::biome_type::FOREST_TROPICAL_DRY_BROADLEAF }, + { df::plant_raw_flags::BIOME_FOREST_TROPICAL_MOIST_BROADLEAF, df::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF }, + { df::plant_raw_flags::BIOME_GRASSLAND_TEMPERATE, df::biome_type::GRASSLAND_TEMPERATE }, + { df::plant_raw_flags::BIOME_SAVANNA_TEMPERATE, df::biome_type::SAVANNA_TEMPERATE }, + { df::plant_raw_flags::BIOME_SHRUBLAND_TEMPERATE, df::biome_type::SHRUBLAND_TEMPERATE }, + { df::plant_raw_flags::BIOME_GRASSLAND_TROPICAL, df::biome_type::GRASSLAND_TROPICAL }, + { df::plant_raw_flags::BIOME_SAVANNA_TROPICAL, df::biome_type::SAVANNA_TROPICAL }, + { df::plant_raw_flags::BIOME_SHRUBLAND_TROPICAL, df::biome_type::SHRUBLAND_TROPICAL }, + { df::plant_raw_flags::BIOME_DESERT_BADLAND, df::biome_type::DESERT_BADLAND }, + { df::plant_raw_flags::BIOME_DESERT_ROCK, df::biome_type::DESERT_ROCK }, + { df::plant_raw_flags::BIOME_DESERT_SAND, df::biome_type::DESERT_SAND }, + { df::plant_raw_flags::BIOME_OCEAN_TROPICAL, df::biome_type::OCEAN_TROPICAL }, + { df::plant_raw_flags::BIOME_OCEAN_TEMPERATE, df::biome_type::OCEAN_TEMPERATE }, + { df::plant_raw_flags::BIOME_OCEAN_ARCTIC, df::biome_type::OCEAN_ARCTIC }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_FRESHWATER, df::biome_type::POOL_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_BRACKISHWATER, df::biome_type::POOL_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_POOL_TEMPERATE_SALTWATER, df::biome_type::POOL_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_FRESHWATER, df::biome_type::POOL_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_BRACKISHWATER, df::biome_type::POOL_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_POOL_TROPICAL_SALTWATER, df::biome_type::POOL_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_FRESHWATER, df::biome_type::LAKE_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_BRACKISHWATER, df::biome_type::LAKE_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TEMPERATE_SALTWATER, df::biome_type::LAKE_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_FRESHWATER, df::biome_type::LAKE_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_BRACKISHWATER, df::biome_type::LAKE_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_LAKE_TROPICAL_SALTWATER, df::biome_type::LAKE_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_FRESHWATER, df::biome_type::RIVER_TEMPERATE_FRESHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_BRACKISHWATER, df::biome_type::RIVER_TEMPERATE_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TEMPERATE_SALTWATER, df::biome_type::RIVER_TEMPERATE_SALTWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_FRESHWATER, df::biome_type::RIVER_TROPICAL_FRESHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_BRACKISHWATER, df::biome_type::RIVER_TROPICAL_BRACKISHWATER }, + { df::plant_raw_flags::BIOME_RIVER_TROPICAL_SALTWATER, df::biome_type::RIVER_TROPICAL_SALTWATER }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_WATER, df::biome_type::SUBTERRANEAN_WATER }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_CHASM, df::biome_type::SUBTERRANEAN_CHASM }, + { df::plant_raw_flags::BIOME_SUBTERRANEAN_LAVA, df::biome_type::SUBTERRANEAN_LAVA } + }; + + +public: + void find_plantable_plants() + { + plantable_plants.clear(); + + map counts; + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); +#undef F + + for (auto ii : world->items.other[df::items_other_id::SEEDS]) + { + df::item_plantst* i = (df::item_plantst*)ii; + if ((i->flags.whole & bad_flags.whole) == 0) + counts[i->mat_index] += i->stack_size; + + } + + for (auto ci : counts) + { + if (df::global::ui->tasks.discovered_plants[ci.first]) + { + df::plant_raw* plant = world->raws.plants.all[ci.first]; + if (is_plantable(plant)) + for (auto flagmap : biomeFlagMap) + if (plant->flags.is_set(flagmap.first)) + plantable_plants[plant->index].insert(flagmap.second); + } + } + } + + void set_farms(color_ostream& out, vector plants, vector farms) + { + if (farms.empty()) + return; + + if (plants.empty()) + plants = vector{ -1 }; + + int season = *df::global::cur_season; + + for (int idx = 0; idx < farms.size(); idx++) + { + df::building_farmplotst* farm = farms[idx]; + int o = farm->plant_id[season]; + int n = plants[idx % plants.size()]; + if (n != o) + { + farm->plant_id[season] = plants[idx % plants.size()]; + out << "autofarm: changing farm #" << farm->id << + " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << + " to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl; + } + } + } + + void process(color_ostream& out) + { + if (!enabled) + return; + + find_plantable_plants(); + + lastCounts.clear(); + + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); +#undef F + + for (auto ii : world->items.other[df::items_other_id::PLANT]) + { + df::item_plantst* i = (df::item_plantst*)ii; + if ((i->flags.whole & bad_flags.whole) == 0 && + plantable_plants.count(i->mat_index) > 0) + { + lastCounts[i->mat_index] += i->stack_size; + } + } + + map> plants; + plants.clear(); + + for (auto plantable : plantable_plants) + { + df::plant_raw* plant = world->raws.plants.all[plantable.first]; + if (lastCounts[plant->index] < getThreshold(plant->index)) + for (auto biome : plantable.second) + { + plants[biome].push_back(plant->index); + } + } + + map> farms; + farms.clear(); + + for (auto bb : world->buildings.other[df::buildings_other_id::FARM_PLOT]) + { + df::building_farmplotst* farm = (df::building_farmplotst*) bb; + if (farm->flags.bits.exists) + { + df::biome_type biome; + if (Maps::getTileDesignation(bb->centerx, bb->centery, bb->z)->bits.subterranean) + biome = biome_type::SUBTERRANEAN_WATER; + else { + df::coord2d region(Maps::getTileBiomeRgn(df::coord(bb->centerx, bb->centery, bb->z))); + biome = Maps::GetBiomeType(region.x, region.y); + } + farms[biome].push_back(farm); + } + } + + for (auto ff : farms) + { + set_farms(out, plants[ff.first], ff.second); + } + } + + void status(color_ostream& out) + { + out << (enabled ? "Running." : "Stopped.") << endl; + for (auto lc : lastCounts) + { + auto plant = world->raws.plants.all[lc.first]; + out << plant->id << " limit " << getThreshold(lc.first) << " current " << lc.second << endl; + } + + for (auto th : thresholds) + { + if (lastCounts[th.first] > 0) + continue; + auto plant = world->raws.plants.all[th.first]; + out << plant->id << " limit " << getThreshold(th.first) << " current 0" << endl; + } + out << "Default: " << defaultThreshold << endl; + } + +}; + +static AutoFarm* autofarmInstance; + + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + if (world && ui) { + commands.push_back( + PluginCommand("autofarm", tagline, + autofarm, false, usage + ) + ); + } + autofarmInstance = new AutoFarm(); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + delete autofarmInstance; + + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!autofarmInstance) + return CR_OK; + + if (!Maps::IsValid()) + return CR_OK; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if (world->frame_counter % 50 != 0) // Check every hour + return CR_OK; + + autofarmInstance->process(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + enabled = enable; + return CR_OK; +} + +static command_result autofarm(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + if (parameters.size() == 1 && parameters[0] == "enable") + plugin_enable(out, true); + else if (parameters.size() == 1 && parameters[0] == "disable") + plugin_enable(out, false); + else if (parameters.size() == 2 && parameters[0] == "default") + autofarmInstance->setDefault(atoi(parameters[1].c_str())); + else if (parameters.size() >= 3 && parameters[0] == "threshold") + { + int val = atoi(parameters[1].c_str()); + for (int i = 2; i < parameters.size(); i++) + { + string id = parameters[i]; + transform(id.begin(), id.end(), id.begin(), ::toupper); + + bool ok = false; + for (auto plant : world->raws.plants.all) + { + if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id)) + { + autofarmInstance->setThreshold(plant->index, val); + ok = true; + break; + } + } + if (!ok) + { + out << "Cannot find plant with id " << id << endl; + return CR_WRONG_USAGE; + } + } + } + else if (parameters.size() == 0 || parameters.size() == 1 && parameters[0] == "status") + autofarmInstance->status(out); + else + return CR_WRONG_USAGE; + + return CR_OK; +} + From 7c8c6546c7c4a6d6f3613d30303a46962791b863 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 18 Aug 2018 14:57:26 -0500 Subject: [PATCH 55/86] autofarm: suspend while processing --- plugins/devel/autofarm.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index 8a79d3f61..aaee2764e 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -39,12 +39,6 @@ DFHACK_PLUGIN("autofarm"); DFHACK_PLUGIN_IS_ENABLED(enabled); -static void process(color_ostream &out) -{ - CoreSuspender suspend; - -} - const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; const char *usage = ( "autofarm enable\n" @@ -350,7 +344,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) if (world->frame_counter % 50 != 0) // Check every hour return CR_OK; - autofarmInstance->process(out); + { + CoreSuspender suspend; + autofarmInstance->process(out); + } return CR_OK; } From 5d92026bbe66dd5f34c2de897bf86d721b1801f1 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 27 Aug 2018 22:29:14 -0500 Subject: [PATCH 56/86] autofarm: make crop assignment more stable --- plugins/devel/autofarm.cpp | 61 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index aaee2764e..0fc02728b 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -22,10 +22,13 @@ #include "modules/Maps.h" #include "modules/World.h" +#include + using std::vector; using std::string; using std::map; using std::set; +using std::queue; using std::endl; using namespace DFHack; using namespace df::enums; @@ -195,27 +198,53 @@ public: } } - void set_farms(color_ostream& out, vector plants, vector farms) + void set_farms(color_ostream& out, set plants, vector farms) { - if (farms.empty()) - return; - - if (plants.empty()) - plants = vector{ -1 }; + // this algorithm attempts to change as few farms as possible, while ensuring that + // the number of farms planting each eligible plant is "as equal as possible" + + if (farms.empty() || plants.empty()) + return; // do nothing if there are no farms or no plantable plants int season = *df::global::cur_season; - for (int idx = 0; idx < farms.size(); idx++) + int min = farms.size() / plants.size(); // the number of farms that should plant each eligible plant, rounded down + int extra = farms.size() - min * plants.size(); // the remainder that cannot be evenly divided + + map counters; + counters.empty(); + + queue toChange; + toChange.empty(); + + for (auto farm : farms) { - df::building_farmplotst* farm = farms[idx]; int o = farm->plant_id[season]; - int n = plants[idx % plants.size()]; - if (n != o) + if (plants.count(o)==0 || counters[o] > min || (counters[o] == min && extra == 0)) + toChange.push(farm); // this farm is an excess instance for the plant it is currently planting + else { - farm->plant_id[season] = plants[idx % plants.size()]; + if (counters[o] == min) + extra--; // allocate off one of the remainder farms + counters[o]++; + } + } + + for (auto n : plants) + { + int c = counters[n]; + while (toChange.size() > 0 && (c < min || (c == min && extra > 0))) + { + // pick one of the excess farms and change it to plant this plant + df::building_farmplotst* farm = toChange.front(); + int o = farm->plant_id[season]; + farm->plant_id[season] = n; out << "autofarm: changing farm #" << farm->id << - " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << + " from " << ((o == -1) ? "NONE" : world->raws.plants.all[o]->name) << " to " << ((n == -1) ? "NONE" : world->raws.plants.all[n]->name) << endl; + toChange.pop(); + if (c++ == min) + extra--; } } } @@ -248,7 +277,7 @@ public: } } - map> plants; + map> plants; plants.clear(); for (auto plantable : plantable_plants) @@ -257,7 +286,7 @@ public: if (lastCounts[plant->index] < getThreshold(plant->index)) for (auto biome : plantable.second) { - plants[biome].push_back(plant->index); + plants[biome].insert(plant->index); } } @@ -362,7 +391,9 @@ static command_result autofarm(color_ostream &out, vector & parameters) { CoreSuspender suspend; - if (parameters.size() == 1 && parameters[0] == "enable") + if (parameters.size() == 1 && parameters[0] == "runonce") + autofarmInstance->process(out); + else if (parameters.size() == 1 && parameters[0] == "enable") plugin_enable(out, true); else if (parameters.size() == 1 && parameters[0] == "disable") plugin_enable(out, false); From dfafafdf9b1489eade5139ecd37dc99a0ecfa90e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 21 Nov 2019 18:58:06 -0600 Subject: [PATCH 57/86] autofarm.cpp: whitespace, complexity Clean up autofarm.cpp --- plugins/devel/autofarm.cpp | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/plugins/devel/autofarm.cpp b/plugins/devel/autofarm.cpp index 0fc02728b..991dfd222 100644 --- a/plugins/devel/autofarm.cpp +++ b/plugins/devel/autofarm.cpp @@ -50,7 +50,6 @@ const char *usage = ( ); class AutoFarm { - private: map thresholds; int defaultThreshold = 50; @@ -85,7 +84,6 @@ private: const df::plant_raw_flags seasons[4] = { df::plant_raw_flags::SPRING, df::plant_raw_flags::SUMMER, df::plant_raw_flags::AUTUMN, df::plant_raw_flags::WINTER }; public: - bool is_plantable(df::plant_raw* plant) { bool has_seed = plant->flags.is_set(df::plant_raw_flags::SEED); @@ -182,7 +180,6 @@ public: df::item_plantst* i = (df::item_plantst*)ii; if ((i->flags.whole & bad_flags.whole) == 0) counts[i->mat_index] += i->stack_size; - } for (auto ci : counts) @@ -200,9 +197,9 @@ public: void set_farms(color_ostream& out, set plants, vector farms) { - // this algorithm attempts to change as few farms as possible, while ensuring that - // the number of farms planting each eligible plant is "as equal as possible" - + // this algorithm attempts to change as few farms as possible, while ensuring that + // the number of farms planting each eligible plant is "as equal as possible" + if (farms.empty() || plants.empty()) return; // do nothing if there are no farms or no plantable plants @@ -224,7 +221,7 @@ public: toChange.push(farm); // this farm is an excess instance for the plant it is currently planting else { - if (counters[o] == min) + if (counters[o] == min) extra--; // allocate off one of the remainder farms counters[o]++; } @@ -333,7 +330,6 @@ public: } out << "Default: " << defaultThreshold << endl; } - }; static AutoFarm* autofarmInstance; @@ -387,6 +383,33 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) return CR_OK; } +static command_result setThresholds(color_ostream& out, vector & parameters) +{ + int val = atoi(parameters[1].c_str()); + for (int i = 2; i < parameters.size(); i++) + { + string id = parameters[i]; + transform(id.begin(), id.end(), id.begin(), ::toupper); + + bool ok = false; + for (auto plant : world->raws.plants.all) + { + if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id)) + { + autofarmInstance->setThreshold(plant->index, val); + ok = true; + break; + } + } + if (!ok) + { + out << "Cannot find plant with id " << id << endl; + return CR_WRONG_USAGE; + } + } + return CR_OK; +} + static command_result autofarm(color_ostream &out, vector & parameters) { CoreSuspender suspend; @@ -400,30 +423,7 @@ static command_result autofarm(color_ostream &out, vector & parameters) else if (parameters.size() == 2 && parameters[0] == "default") autofarmInstance->setDefault(atoi(parameters[1].c_str())); else if (parameters.size() >= 3 && parameters[0] == "threshold") - { - int val = atoi(parameters[1].c_str()); - for (int i = 2; i < parameters.size(); i++) - { - string id = parameters[i]; - transform(id.begin(), id.end(), id.begin(), ::toupper); - - bool ok = false; - for (auto plant : world->raws.plants.all) - { - if (plant->flags.is_set(df::plant_raw_flags::SEED) && (plant->id == id)) - { - autofarmInstance->setThreshold(plant->index, val); - ok = true; - break; - } - } - if (!ok) - { - out << "Cannot find plant with id " << id << endl; - return CR_WRONG_USAGE; - } - } - } + return setThresholds(out, parameters); else if (parameters.size() == 0 || parameters.size() == 1 && parameters[0] == "status") autofarmInstance->status(out); else From 07dedfb3334db220defb4b1ef215404732e6a648 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 24 Nov 2019 21:32:07 +0100 Subject: [PATCH 58/86] Fixed/improved river tile detection --- docs/changelog.txt | 2 ++ plugins/embark-assistant/matcher.cpp | 2 +- plugins/embark-assistant/survey.cpp | 49 ++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c1d7bae72..818f60d3c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `embark-assistant`: - fixed bug causing crash on worlds without generated metals (as well as pruning vectors as originally intended). - fixed bug causing mineral matching to fail to cut off at the magma sea, reporting presence of things that aren't (like DF does currently). + - fixed bug causing half of the river tiles not to be recognized. + - added logic to detect some river tiles DF doesn't generate data for (but are definitely present). - `gui/autogems`: fixed error when no world is loaded - `gui/companion-order`: - fixed error when resetting group leaders diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index c013a695e..715453551 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -2076,7 +2076,7 @@ namespace embark_assist { uint32_t preliminary_world_match(embark_assist::defs::world_tile_data *survey_results, embark_assist::defs::finders *finder, embark_assist::defs::match_results *match_results) { - // color_ostream_proxy out(Core::getInstance().getConsole()); +// color_ostream_proxy out(Core::getInstance().getConsole()); uint32_t count = 0; for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 5e9e44b5f..a2fb648f2 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -1043,11 +1043,11 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data mlt->at(i).at(k).river_present = false; mlt->at(i).at(k).river_elevation = 100; - if (details->rivers_vertical.active[i][k] == 1) { + if (details->rivers_vertical.active[i][k] != 0) { mlt->at(i).at(k).river_present = true; mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; } - else if (details->rivers_horizontal.active[i][k] == 1) { + else if (details->rivers_horizontal.active[i][k] != 0) { mlt->at(i).at(k).river_present = true; mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; } @@ -1179,6 +1179,51 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data } } + // This is messy. DF has some weird logic to leave out river bends with a South and an East connection, as well + // as river sources (and presumably sinks) that are to the North or the West of the connecting river. + // Experiments indicate these implicit river bends inherit their River Elevation from the lower of the two + // "parents", and it's assumed river sources and sinks similarly inherit it from their sole "parent". + // Two issues are known: + // - Lake and Ocean tiles may be marked as having a river when DF doesn't. However, DF does allow for rivers to + // exist in Ocean/Lake tiles, as well as sources/sinks. + // - DF generates rivers on/under glaciers, but does not display them (as they're frozen), nor are their names + // displayed. + // + for (uint8_t i = 1; i < 16; i++) { + for (uint8_t k = 0; k < 15; k++) { + if (details->rivers_horizontal.active[i][k] != 0 && + details->rivers_vertical.active[i - 1][k + 1] != 0 && + !mlt->at(i - 1).at(k).river_present) { // Probably never true + mlt->at(i - 1).at(k).river_present = true; + mlt->at(i - 1).at(k).river_elevation = mlt->at(i).at(k).river_elevation; + + if (mlt->at(i - 1).at(k).river_elevation > mlt->at(i - 1).at(k + 1).river_elevation) { + mlt->at(i - 1).at(k).river_elevation = mlt->at(i - 1).at(k + 1).river_elevation; + } + } + } + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 1; k < 16; k++) { + if (details->rivers_vertical.active[i][k] != 0 && + !mlt->at(i).at(k - 1).river_present) { + mlt->at(i).at(k - 1).river_present = true; + mlt->at(i).at(k - 1).river_elevation = mlt->at(i).at(k).river_elevation; + } + } + } + + for (uint8_t i = 1; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (details->rivers_horizontal.active[i][k] != 0 && + !mlt->at(i - 1).at(k).river_present) { + mlt->at(i - 1).at(k).river_present = true; + mlt->at(i - 1).at(k).river_elevation = mlt->at(i).at(k).river_elevation; + } + } + } + survey_results->at(x).at(y).aquifer_count = 0; survey_results->at(x).at(y).clay_count = 0; survey_results->at(x).at(y).sand_count = 0; From fd1d490163b3226a7fc10ba770a31cee66426b13 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Dec 2019 12:24:27 -0600 Subject: [PATCH 59/86] Fix tailor plugin switch statement warnings (no functionality change) --- plugins/tailor.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index 6ac308258..4ab6dc4f7 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -245,6 +245,7 @@ void do_scan(color_ostream& out) case df::item_type::HELM: v = entity->resources.helm_type; break; case df::item_type::PANTS: v = entity->resources.pants_type; break; case df::item_type::SHOES: v = entity->resources.shoes_type; break; + default: break; } for (auto vv : v) { @@ -255,6 +256,7 @@ void do_scan(color_ostream& out) case df::item_type::HELM: isClothing = world->raws.itemdefs.helms[vv] ->armorlevel == 0; break; case df::item_type::PANTS: isClothing = world->raws.itemdefs.pants[vv] ->armorlevel == 0; break; case df::item_type::SHOES: isClothing = world->raws.itemdefs.shoes[vv] ->armorlevel == 0; break; + default: break; } if (isClothing) { @@ -332,6 +334,8 @@ void do_scan(color_ostream& out) v = entity->resources.shoes_type; fl = &world->raws.itemdefs.shoes[sub]->props.flags; break; + default: + break; } bool can_make = false; @@ -356,6 +360,7 @@ void do_scan(color_ostream& out) case df::item_type::HELM: break; case df::item_type::PANTS: break; case df::item_type::SHOES: break; + default: break; } df::job_material_category mat; From 23b56c4492f549f3f9b51e150b5ae7e034731be9 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Fri, 6 Dec 2019 12:28:18 -0600 Subject: [PATCH 60/86] Fix "kittens" development plugin not building on Linux due to missing thread library --- plugins/devel/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 245bc518c..53157af47 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -1,6 +1,8 @@ IF(UNIX) DFHACK_PLUGIN(vectors vectors.cpp) -endif() +ENDIF() + +INCLUDE(FindThreads) ADD_DEFINITIONS(-DDEV_PLUGIN) DFHACK_PLUGIN(buildprobe buildprobe.cpp) From 910f9658389f8dccd0183428af4797a27202384f Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Sat, 7 Dec 2019 19:27:35 -0800 Subject: [PATCH 61/86] wip --- library/LuaApi.cpp | 1 + library/include/modules/Units.h | 1 + library/modules/Units.cpp | 10 ++++++++++ plugins/proto/RemoteFortressReader.proto | 1 + plugins/remotefortressreader/remotefortressreader.cpp | 2 ++ 5 files changed, 15 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 2840aa5b9..5dc881926 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1611,6 +1611,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isOwnCiv), WRAPM(Units, isOwnGroup), WRAPM(Units, isOwnRace), + WRAPM(Units, getDescription), WRAPM(Units, getRaceName), WRAPM(Units, getRaceNamePlural), WRAPM(Units, getRaceBabyName), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 09c39be55..49565960d 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -120,6 +120,7 @@ DFHACK_EXPORT bool isVisible(df::unit* unit); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); +DFHACK_EXPORT std::string getDescription(df::unit* unit); DFHACK_EXPORT std::string getRaceNamePluralById(int32_t race_id); DFHACK_EXPORT std::string getRaceNamePlural(df::unit* unit); DFHACK_EXPORT std::string getRaceBabyNameById(int32_t race_id); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 861c25ce9..5fec1c0a3 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -541,6 +541,16 @@ string Units::getRaceName(df::unit* unit) return getRaceNameById(unit->race); } +typedef void (*df_unit_desc_fn)(df::unit*, std::string*); +static df_unit_desc_fn df_unit_desc = reinterpret_cast(0x100cbb890); +string Units::getDescription(df::unit* unit) +{ + CHECK_NULL_POINTER(unit); + std::string str; + df_unit_desc(unit, &str); + return str; +} + // get plural of race name (used for display in autobutcher UI and for sorting the watchlist) string Units::getRaceNamePluralById(int32_t id) { diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index af971d55d..6b70a48cb 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -420,6 +420,7 @@ message UnitAppearance optional Hair beard = 6; optional Hair moustache = 7; optional Hair sideburns = 8; + optional string description = 9; } message InventoryItem diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 7dced3b9f..3efcd80a5 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1750,6 +1750,8 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques appearance->add_colors(unit->appearance.colors[j]); appearance->set_size_modifier(unit->appearance.size_modifier); + appearance->set_description(Units::getDescription(unit)); + send_unit->set_profession_id(unit->profession); std::vector pvec; From 9a378496038d15f391148e863054bfc9af4d8de1 Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Sat, 7 Dec 2019 22:41:55 -0800 Subject: [PATCH 62/86] use symbols.xml --- library/modules/Units.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 5fec1c0a3..5a50d4069 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -541,13 +541,21 @@ string Units::getRaceName(df::unit* unit) return getRaceNameById(unit->race); } -typedef void (*df_unit_desc_fn)(df::unit*, std::string*); -static df_unit_desc_fn df_unit_desc = reinterpret_cast(0x100cbb890); +typedef void (*df_unit_physical_description_fn)(df::unit*, string*); +void df_unit_physical_description(df::unit* unit, string* out_str) +{ + static df_unit_physical_description_fn fn = + reinterpret_cast( + Core::getInstance().vinfo->getAddress("unit_physical_description")); + if (fn) + fn(unit, out_str); +} + string Units::getDescription(df::unit* unit) { CHECK_NULL_POINTER(unit); - std::string str; - df_unit_desc(unit, &str); + string str; + df_unit_physical_description(unit, &str); return str; } From dfab521a71eb83087a29383216f9cb0f56766dbd Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Thu, 12 Dec 2019 17:51:41 -0800 Subject: [PATCH 63/86] specify __thiscall According to https://docs.microsoft.com/en-us/cpp/cpp/thiscall?view=vs-2019, "on ARM and x64 machines, __thiscall is accepted and ignored by the compiler.". So it should be OK to specify this for all win32 --- library/modules/Units.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 5a50d4069..b16d8e4bd 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -541,7 +541,13 @@ string Units::getRaceName(df::unit* unit) return getRaceNameById(unit->race); } -typedef void (*df_unit_physical_description_fn)(df::unit*, string*); +#ifdef _WIN32 +#define THISCALL __thiscall +#else +#define THISCALL +#endif + +typedef void (THISCALL *df_unit_physical_description_fn)(df::unit*, string*); void df_unit_physical_description(df::unit* unit, string* out_str) { static df_unit_physical_description_fn fn = From 7fce6fe0b029b91991aa26ca969308539da088fa Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Fri, 13 Dec 2019 23:54:27 -0800 Subject: [PATCH 64/86] move THISCALL define to header, clean up code a little --- library/include/MiscUtils.h | 11 +++++++++++ library/modules/Units.cpp | 15 ++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index ec2c7be58..35f8be73b 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -45,6 +45,17 @@ using std::endl; #define DFHACK_FUNCTION_SIG __func__ #endif +#ifdef _WIN32 +// On x86 MSVC, __thiscall passes |this| in ECX. On x86_64, __thiscall is the +// same as the standard calling convention. +// See https://docs.microsoft.com/en-us/cpp/cpp/thiscall for more info. +#define THISCALL __thiscall +#else +// On other platforms, there's no special calling convention for calling member +// functions. +#define THISCALL +#endif + namespace DFHack { class color_ostream; } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index b16d8e4bd..82d5e04d2 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -541,20 +541,13 @@ string Units::getRaceName(df::unit* unit) return getRaceNameById(unit->race); } -#ifdef _WIN32 -#define THISCALL __thiscall -#else -#define THISCALL -#endif - -typedef void (THISCALL *df_unit_physical_description_fn)(df::unit*, string*); void df_unit_physical_description(df::unit* unit, string* out_str) { - static df_unit_physical_description_fn fn = - reinterpret_cast( + static auto* const fn = + reinterpret_cast( Core::getInstance().vinfo->getAddress("unit_physical_description")); - if (fn) - fn(unit, out_str); + CHECK_NULL_POINTER(fn); + fn(unit, out_str); } string Units::getDescription(df::unit* unit) From 50e696acf68fb05a7d28b3a01872668facc3307c Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Sat, 14 Dec 2019 09:34:46 -0800 Subject: [PATCH 65/86] getDescription => getPhysicalDescription --- library/LuaApi.cpp | 2 +- library/include/modules/Units.h | 2 +- library/modules/Units.cpp | 2 +- plugins/proto/RemoteFortressReader.proto | 2 +- plugins/remotefortressreader/remotefortressreader.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5dc881926..7c3ef965d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1611,7 +1611,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isOwnCiv), WRAPM(Units, isOwnGroup), WRAPM(Units, isOwnRace), - WRAPM(Units, getDescription), + WRAPM(Units, getPhysicalDescription), WRAPM(Units, getRaceName), WRAPM(Units, getRaceNamePlural), WRAPM(Units, getRaceBabyName), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 49565960d..f5995583b 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -120,7 +120,7 @@ DFHACK_EXPORT bool isVisible(df::unit* unit); DFHACK_EXPORT std::string getRaceNameById(int32_t race_id); DFHACK_EXPORT std::string getRaceName(df::unit* unit); -DFHACK_EXPORT std::string getDescription(df::unit* unit); +DFHACK_EXPORT std::string getPhysicalDescription(df::unit* unit); DFHACK_EXPORT std::string getRaceNamePluralById(int32_t race_id); DFHACK_EXPORT std::string getRaceNamePlural(df::unit* unit); DFHACK_EXPORT std::string getRaceBabyNameById(int32_t race_id); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 82d5e04d2..2b7243f05 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -550,7 +550,7 @@ void df_unit_physical_description(df::unit* unit, string* out_str) fn(unit, out_str); } -string Units::getDescription(df::unit* unit) +string Units::getPhysicalDescription(df::unit* unit) { CHECK_NULL_POINTER(unit); string str; diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 6b70a48cb..a61bfbe21 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -420,7 +420,7 @@ message UnitAppearance optional Hair beard = 6; optional Hair moustache = 7; optional Hair sideburns = 8; - optional string description = 9; + optional string physical_description = 9; } message InventoryItem diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 3efcd80a5..dd338c705 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1750,7 +1750,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques appearance->add_colors(unit->appearance.colors[j]); appearance->set_size_modifier(unit->appearance.size_modifier); - appearance->set_description(Units::getDescription(unit)); + appearance->set_physical_description(Units::getPhysicalDescription(unit)); send_unit->set_profession_id(unit->profession); From 7644dde9e4f0e010081203a1ffc97d8f51b0eb97 Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Sat, 14 Dec 2019 12:01:13 -0800 Subject: [PATCH 66/86] default to empty description if symbol unavailable --- library/modules/Units.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 2b7243f05..09912ef56 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -546,8 +546,10 @@ void df_unit_physical_description(df::unit* unit, string* out_str) static auto* const fn = reinterpret_cast( Core::getInstance().vinfo->getAddress("unit_physical_description")); - CHECK_NULL_POINTER(fn); - fn(unit, out_str); + if (fn) + fn(unit, out_str); + else + *out_str = ""; } string Units::getPhysicalDescription(df::unit* unit) From 5de368a1ede318359ac14b468a99663cf8b868fe Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Sat, 4 Jan 2020 22:17:03 +1100 Subject: [PATCH 67/86] unit_{=> get_}physical_description --- library/modules/Units.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 09912ef56..3e56e79df 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -541,11 +541,11 @@ string Units::getRaceName(df::unit* unit) return getRaceNameById(unit->race); } -void df_unit_physical_description(df::unit* unit, string* out_str) +void df_unit_get_physical_description(df::unit* unit, string* out_str) { static auto* const fn = reinterpret_cast( - Core::getInstance().vinfo->getAddress("unit_physical_description")); + Core::getInstance().vinfo->getAddress("unit_get_physical_description")); if (fn) fn(unit, out_str); else @@ -556,7 +556,7 @@ string Units::getPhysicalDescription(df::unit* unit) { CHECK_NULL_POINTER(unit); string str; - df_unit_physical_description(unit, &str); + df_unit_get_physical_description(unit, &str); return str; } From be5dc2d4a4511335b3e1e07e6a69c9a8418bf8da Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 9 Jan 2020 14:48:30 -0600 Subject: [PATCH 68/86] Make Units::isGay act consistently when called on an asexual unit. The function appears to be used to determine whether heterosexual relationships are possible, so asexual units will always return true for isGay. Old behavior was to treat asexual units as male. --- library/modules/Units.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 861c25ce9..b2dd7b679 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1549,8 +1549,8 @@ bool Units::isGay(df::unit* unit) if (!unit->status.current_soul) return false; df::orientation_flags orientation = unit->status.current_soul->orientation_flags; - return (Units::isFemale(unit) && ! (orientation.whole & (orientation.mask_marry_male | orientation.mask_romance_male))) - || (!Units::isFemale(unit) && ! (orientation.whole & (orientation.mask_marry_female | orientation.mask_romance_female))); + return (!Units::isFemale(unit) || !(orientation.whole & (orientation.mask_marry_male | orientation.mask_romance_male))) + && (!Units::isMale(unit) || !(orientation.whole & (orientation.mask_marry_female | orientation.mask_romance_female))); } bool Units::isNaked(df::unit* unit) From 47fa9e1159c983f6b3b04e329fcfae8a176b4fad Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 13 Jan 2020 23:35:31 -0500 Subject: [PATCH 69/86] Update authors, submodules --- docs/Authors.rst | 2 ++ library/xml | 2 +- scripts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index dd0f691f3..fa315677a 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -56,7 +56,9 @@ IndigoFenix James Logsdon jlogsdon Japa JapaMala Jared Adams +Jeremy Apthorp nornagon Jim Lisi stonetoad +Jimbo Whales jimbowhales jimcarreer jimcarreer jj jjyg jj`` Joel Meador janxious diff --git a/library/xml b/library/xml index abdcb2e17..779de2311 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit abdcb2e17ff29e754a8a7661b697035a0ee702ba +Subproject commit 779de23111409635b5644daae5338ba0199945e4 diff --git a/scripts b/scripts index abcb0cffb..5d0f00eae 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit abcb0cffbbcaaeefc7effca25ff947ab8ea91c43 +Subproject commit 5d0f00eae3bd8eef41c860369550e05f95a1282a From 96d8dffd325dd002a2adb93951df61d24bcab740 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Tue, 14 Jan 2020 17:53:52 -0600 Subject: [PATCH 70/86] Make dfhack.run_command return the command_result value. Rename local variables to match dfhack.run_command_silent for clarity. --- library/lua/dfhack.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 93ab48dca..049297385 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -765,14 +765,15 @@ function dfhack.run_command_silent(...) end function dfhack.run_command(...) - local output, status = _run_command(...) - for i, fragment in pairs(output) do - if type(fragment) == 'table' then - dfhack.color(fragment[1]) - dfhack.print(fragment[2]) + local result = _run_command(...) + for i, f in pairs(result) do + if type(f) == 'table' then + dfhack.color(f[1]) + dfhack.print(f[2]) end end dfhack.color(COLOR_RESET) + return result.status end -- Per-save init file From 045d8bfd1e01cac8d964b6aad5166f451ec23f4e Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 14 Jan 2020 23:54:51 -0500 Subject: [PATCH 71/86] Linux launcher: attempt to fall back to a working architecture for setarch Also include directions if this fails Closes #1466 --- package/linux/dfhack | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package/linux/dfhack b/package/linux/dfhack index d0c70765b..1bd3e8d92 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -69,6 +69,16 @@ fi PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}${LIBSAN}${LIB}" setarch_arch=$(cat hack/dfhack_setarch.txt || printf i386) +if ! setarch "$setarch_arch" -R true 2>/dev/null; then + echo "warn: architecture '$setarch_arch' not supported by setarch" >&2 + if [ "$setarch_arch" = "i386" ]; then + setarch_arch=linux32 + else + setarch_arch=linux64 + fi + echo "using '$setarch_arch' instead. To silence this warning, edit" >&2 + echo "hack/dfhack_setarch.txt to contain an architecture that works on your system." >&2 +fi case "$1" in -g | --gdb) From 15e06640f6b756a82b9da73cb166f64e90bfac0e Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Wed, 15 Jan 2020 12:40:43 +0100 Subject: [PATCH 72/86] Partial correction of issue 1479 and added verbose flag --- docs/changelog.txt | 1 + plugins/getplants.cpp | 227 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 216 insertions(+), 12 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c1d7bae72..46f71cf6c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `embark-assistant`: - fixed bug causing crash on worlds without generated metals (as well as pruning vectors as originally intended). - fixed bug causing mineral matching to fail to cut off at the magma sea, reporting presence of things that aren't (like DF does currently). +- `getplants`: fixed designation of plants out of season and added verbose flag, but failed to identify picked plants (which are still designated incorrectly) - `gui/autogems`: fixed error when no world is loaded - `gui/companion-order`: - fixed error when resetting group leaders diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 2cf382d01..8e4384879 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -1,5 +1,9 @@ // (un)designate matching plants for gathering/cutting - +// Known issue: +// DF is capable of determining that a shrub has already been picked, leaving an unusable structure part +// behind. This code does not perform such a check (as the location of the required information is +// unknown to the writer of this comment). This leads to some shrubs being designated when they +// shouldn't be, causing a plant gatherer to walk there and do nothing (except clearing the designation). #include #include "Core.h" @@ -11,12 +15,14 @@ #include "df/map_block.h" #include "df/plant.h" +#include "df/plant_growth.h" #include "df/plant_raw.h" #include "df/tile_dig_designation.h" #include "df/world.h" #include "modules/Designations.h" #include "modules/Maps.h" +#include "modules/Materials.h" using std::string; using std::vector; @@ -27,15 +33,136 @@ using namespace df::enums; DFHACK_PLUGIN("getplants"); REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cur_year_tick); + +enum class selectability { + Selectable, + Grass, + Nonselectable, + OutOfSeason, + Unselected +}; + +//selectability selectablePlant(color_ostream &out, const df::plant_raw *plant) +selectability selectablePlant(const df::plant_raw *plant) +{ + const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant->material_defs.type_basic_mat, plant->material_defs.idx_basic_mat); + bool outOfSeason = false; + + if (plant->flags.is_set(plant_raw_flags::TREE)) + { +// out.print("%s is a selectable tree\n", plant->id.c_str()); + return selectability::Selectable; + } + else if (plant->flags.is_set(plant_raw_flags::GRASS)) + { +// out.print("%s is a non selectable Grass\n", plant->id.c_str()); + return selectability::Grass; + } + + if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || + basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) + { +// out.print("%s is edible\n", plant->id.c_str()); + return selectability::Selectable; + } + + if (plant->flags.is_set(plant_raw_flags::THREAD) || + plant->flags.is_set(plant_raw_flags::MILL) || + plant->flags.is_set(plant_raw_flags::EXTRACT_VIAL) || + plant->flags.is_set(plant_raw_flags::EXTRACT_BARREL) || + plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) + { +// out.print("%s is thread/mill/extract\n", plant->id.c_str()); + return selectability::Selectable; + } + + if (basic_mat.material->reaction_product.id.size() > 0 || + basic_mat.material->reaction_class.size() > 0) + { +// out.print("%s has a reaction\n", plant->id.c_str()); + return selectability::Selectable; + } + + for (auto i = 0; i < plant->growths.size(); i++) + { + if (plant->growths[i]->item_type == df::item_type::SEEDS || // Only trees have seed growths in vanilla, but raws can be modded... + plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) + { + const DFHack::MaterialInfo growth_mat = DFHack::MaterialInfo(plant->growths[i]->mat_type, plant->growths[i]->mat_index); + if ((plant->growths[i]->item_type == df::item_type::SEEDS && + (growth_mat.material->flags.is_set(material_flags::EDIBLE_COOKED) || + growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW))) || + (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH && + growth_mat.material->flags.is_set(material_flags::STOCKPILE_PLANT_GROWTH))) + { + if (*cur_year_tick >= plant->growths[i]->timing_1 && + (plant->growths[i]->timing_2 == -1 || + *cur_year_tick <= plant->growths[i]->timing_2)) + { +// out.print("%s has an edible seed or a stockpile growth\n", plant->id.c_str()); + return selectability::Selectable; + } + else + { + outOfSeason = true; + } + } + } +/* else if (plant->growths[i]->behavior.bits.has_seed) // This code designates beans, etc. when DF doesn't, but plant gatherers still fail to collect anything, so it's useless: bug #0006940. + { + const DFHack::MaterialInfo seed_mat = DFHack::MaterialInfo(plant->material_defs.type_seed, plant->material_defs.idx_seed); + + if (seed_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || + seed_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) + { + if (*cur_year_tick >= plant->growths[i]->timing_1 && + (plant->growths[i]->timing_2 == -1 || + *cur_year_tick <= plant->growths[i]->timing_2)) + { + return selectability::Selectable; + } + else + { + outOfSeason = true; + } + } + } */ + } + + if (outOfSeason) + { +// out.print("%s has an out of season growth\n", plant->id.c_str()); + return selectability::OutOfSeason; + } + else + { +// out.printerr("%s cannot be gathered\n", plant->id.c_str()); + return selectability::Nonselectable; + } +} command_result df_getplants (color_ostream &out, vector & parameters) { string plantMatStr = ""; - set plantIDs; + std::vector plantSelections; + std::vector collectionCount; set plantNames; - bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false; + bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false; int count = 0; + + plantSelections.resize(world->raws.plants.all.size()); + collectionCount.resize(world->raws.plants.all.size()); + + for (auto i = 0; i < plantSelections.size(); i++) + { + plantSelections[i] = selectability::Unselected; + collectionCount[i] = 0; + } + + bool anyPlantsSelected = false; + for (size_t i = 0; i < parameters.size(); i++) { if(parameters[i] == "help" || parameters[i] == "?") @@ -50,6 +177,8 @@ command_result df_getplants (color_ostream &out, vector & parameters) exclude = true; else if(parameters[i] == "-a") all = true; + else if(parameters[i] == "-v") + verbose = true; else plantNames.insert(parameters[i]); } @@ -75,11 +204,39 @@ command_result df_getplants (color_ostream &out, vector & parameters) { df::plant_raw *plant = world->raws.plants.all[i]; if (all) - plantIDs.insert(i); - else if (plantNames.find(plant->id) != plantNames.end()) + { +// plantSelections[i] = selectablePlant(out, plant); + plantSelections[i] = selectablePlant(plant); + } + else if (plantNames.find(plant->id) != plantNames.end()) { plantNames.erase(plant->id); - plantIDs.insert(i); +// plantSelections[i] = selectablePlant(out, plant); + plantSelections[i] = selectablePlant(plant); + switch (plantSelections[i]) + { + case selectability::Grass: + { + out.printerr("%s is a Grass, and those can not be gathered\n", plant->id.c_str()); + break; + } + + case selectability::Nonselectable: + { + out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str()); + break; + } + case selectability::OutOfSeason: + { + out.printerr("%s is out of season, with nothing that can be gathered now\n", plant->id.c_str()); + break; + } + case selectability::Selectable: + break; + + case selectability::Unselected: + break; // We won't get to this option + } } } if (plantNames.size() > 0) @@ -91,15 +248,44 @@ command_result df_getplants (color_ostream &out, vector & parameters) return CR_FAILURE; } - if (plantIDs.size() == 0) + for (auto i = 0; i < plantSelections.size(); i++) + { + if (plantSelections[i] == selectability::OutOfSeason || + plantSelections[i] == selectability::Selectable) + { + anyPlantsSelected = true; + break; + } + } + + if (!anyPlantsSelected) { out.print("Valid plant IDs:\n"); for (size_t i = 0; i < world->raws.plants.all.size(); i++) { df::plant_raw *plant = world->raws.plants.all[i]; - if (plant->flags.is_set(plant_raw_flags::GRASS)) +// switch (selectablePlant(out, plant)) + switch (selectablePlant(plant)) + { + case selectability::Grass: + case selectability::Nonselectable: continue; - out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str()); + + case selectability::OutOfSeason: + { + out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str()); + break; + } + + case selectability::Selectable: + { + out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str()); + break; + } + + case selectability::Unselected: // Should never get this alternative + break; + } } return CR_OK; } @@ -113,9 +299,11 @@ command_result df_getplants (color_ostream &out, vector & parameters) int x = plant->pos.x % 16; int y = plant->pos.y % 16; - if (plantIDs.find(plant->material) != plantIDs.end()) + if (plantSelections[plant->material] == selectability::OutOfSeason || + plantSelections[plant->material] == selectability::Selectable) { - if (exclude) + if (exclude || + plantSelections[plant->material] == selectability::OutOfSeason) continue; } else @@ -134,15 +322,29 @@ command_result df_getplants (color_ostream &out, vector & parameters) continue; if (deselect && Designations::unmarkPlant(plant)) { + collectionCount[plant->material]++; ++count; } if (!deselect && Designations::markPlant(plant)) { +// out.print("Designated %s at (%i, %i, %i), %i\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, i); + collectionCount[plant->material]++; ++count; } } if (count) - out.print("Updated %d plant designations.\n", count); + if (verbose) + { + for (auto i = 0; i < plantSelections.size(); i++) + { + if (collectionCount [i] > 0) + out.print("Updated %i %s designations.\n", collectionCount [i], world->raws.plants.all [i]->id.c_str()); + } + out.print("\n"); + } + + out.print("Updated %d plant designations.\n", count); + return CR_OK; } @@ -159,6 +361,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Wed, 15 Jan 2020 19:31:19 +0100 Subject: [PATCH 73/86] Reverted STOCKPILE_PLANT_GROWTH to LEAF_MAT --- plugins/getplants.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 8e4384879..001400681 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -94,7 +94,7 @@ selectability selectablePlant(const df::plant_raw *plant) (growth_mat.material->flags.is_set(material_flags::EDIBLE_COOKED) || growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW))) || (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH && - growth_mat.material->flags.is_set(material_flags::STOCKPILE_PLANT_GROWTH))) + growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... { if (*cur_year_tick >= plant->growths[i]->timing_1 && (plant->growths[i]->timing_2 == -1 || @@ -333,16 +333,17 @@ command_result df_getplants (color_ostream &out, vector & parameters) } } if (count) + { if (verbose) { for (auto i = 0; i < plantSelections.size(); i++) { - if (collectionCount [i] > 0) - out.print("Updated %i %s designations.\n", collectionCount [i], world->raws.plants.all [i]->id.c_str()); + if (collectionCount[i] > 0) + out.print("Updated %i %s designations.\n", collectionCount[i], world->raws.plants.all[i]->id.c_str()); } out.print("\n"); } - + } out.print("Updated %d plant designations.\n", count); return CR_OK; From 8cebb6cef5c410988fadc08d87e2f5aa984f07dd Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Wed, 15 Jan 2020 21:29:01 +0100 Subject: [PATCH 74/86] removed trailing whitespace --- plugins/getplants.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 001400681..7db3b13a7 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -127,7 +127,7 @@ selectability selectablePlant(const df::plant_raw *plant) outOfSeason = true; } } - } */ + } */ } if (outOfSeason) From cbf5c5459aca2fbf41523fa142966b12e77b4ce1 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Wed, 15 Jan 2020 16:07:41 -0600 Subject: [PATCH 75/86] Don't print an error about not being able to get the SDL title in text mode. --- plugins/title-folder.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp index eae381b95..4f93a6c7f 100644 --- a/plugins/title-folder.cpp +++ b/plugins/title-folder.cpp @@ -5,11 +5,16 @@ #include "MemAccess.h" #include "PluginManager.h" +#include "df/init.h" + using namespace DFHack; +using namespace df::enums; DFHACK_PLUGIN("title-folder"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(init); + // SDL frees the old window title when changed static std::string original_title; @@ -36,6 +41,12 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { + if (init->display.flag.is_set(init_display_flags::TEXT)) + { + // Don't bother initializing in text mode. + return CR_OK; + } + for (auto it = sdl_libs.begin(); it != sdl_libs.end(); ++it) { if ((sdl_handle = OpenPlugin(it->c_str()))) @@ -92,6 +103,12 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool state) if (state) { + if (init->display.flag.is_set(init_display_flags::TEXT)) + { + out.printerr("title-folder: cannot enable with PRINT_MODE:TEXT.\n"); + return CR_FAILURE; + } + std::string path = Core::getInstance().p->getPath(); std::string folder; size_t pos = path.find_last_of('/'); From ee2259bbcaf9adab827c16f9858b42cfa33935ae Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Thu, 16 Jan 2020 12:57:09 +0100 Subject: [PATCH 76/86] Replaced poor auto with actual type --- plugins/getplants.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 7db3b13a7..1b326b8cd 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -84,7 +84,7 @@ selectability selectablePlant(const df::plant_raw *plant) return selectability::Selectable; } - for (auto i = 0; i < plant->growths.size(); i++) + for (size_t i = 0; i < plant->growths.size(); i++) { if (plant->growths[i]->item_type == df::item_type::SEEDS || // Only trees have seed growths in vanilla, but raws can be modded... plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) @@ -146,7 +146,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) { string plantMatStr = ""; std::vector plantSelections; - std::vector collectionCount; + std::vector collectionCount; set plantNames; bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false; @@ -155,7 +155,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) plantSelections.resize(world->raws.plants.all.size()); collectionCount.resize(world->raws.plants.all.size()); - for (auto i = 0; i < plantSelections.size(); i++) + for (size_t i = 0; i < plantSelections.size(); i++) { plantSelections[i] = selectability::Unselected; collectionCount[i] = 0; @@ -248,7 +248,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) return CR_FAILURE; } - for (auto i = 0; i < plantSelections.size(); i++) + for (size_t i = 0; i < plantSelections.size(); i++) { if (plantSelections[i] == selectability::OutOfSeason || plantSelections[i] == selectability::Selectable) @@ -327,7 +327,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) } if (!deselect && Designations::markPlant(plant)) { -// out.print("Designated %s at (%i, %i, %i), %i\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, i); +// out.print("Designated %s at (%i, %i, %i), %d\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, i); collectionCount[plant->material]++; ++count; } @@ -336,10 +336,10 @@ command_result df_getplants (color_ostream &out, vector & parameters) { if (verbose) { - for (auto i = 0; i < plantSelections.size(); i++) + for (size_t i = 0; i < plantSelections.size(); i++) { if (collectionCount[i] > 0) - out.print("Updated %i %s designations.\n", collectionCount[i], world->raws.plants.all[i]->id.c_str()); + out.print("Updated %d %s designations.\n", collectionCount[i], world->raws.plants.all[i]->id.c_str()); } out.print("\n"); } @@ -357,11 +357,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Thu, 16 Jan 2020 13:59:31 +0100 Subject: [PATCH 77/86] Explicit type conversion with %d warning silencing --- plugins/getplants.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 1b326b8cd..5fa1c8637 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -327,7 +327,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) } if (!deselect && Designations::markPlant(plant)) { -// out.print("Designated %s at (%i, %i, %i), %d\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, i); +// out.print("Designated %s at (%i, %i, %i), %d\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, (int)i); collectionCount[plant->material]++; ++count; } @@ -339,12 +339,12 @@ command_result df_getplants (color_ostream &out, vector & parameters) for (size_t i = 0; i < plantSelections.size(); i++) { if (collectionCount[i] > 0) - out.print("Updated %d %s designations.\n", collectionCount[i], world->raws.plants.all[i]->id.c_str()); + out.print("Updated %d %s designations.\n", (int)collectionCount[i], world->raws.plants.all[i]->id.c_str()); } out.print("\n"); } } - out.print("Updated %d plant designations.\n", count); + out.print("Updated %d plant designations.\n", (int)count); return CR_OK; } From 72fd32cb2a822f749a1ce8eb57a70230b89b0af9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 16:22:21 -0500 Subject: [PATCH 78/86] Tweak spelling/wording/style --- plugins/getplants.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 5fa1c8637..9e9b1dcbc 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -160,7 +160,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) plantSelections[i] = selectability::Unselected; collectionCount[i] = 0; } - + bool anyPlantsSelected = false; for (size_t i = 0; i < parameters.size(); i++) @@ -216,21 +216,17 @@ command_result df_getplants (color_ostream &out, vector & parameters) switch (plantSelections[i]) { case selectability::Grass: - { - out.printerr("%s is a Grass, and those can not be gathered\n", plant->id.c_str()); + out.printerr("%s is a grass and cannot be gathered\n", plant->id.c_str()); break; - } case selectability::Nonselectable: - { out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str()); break; - } + case selectability::OutOfSeason: - { out.printerr("%s is out of season, with nothing that can be gathered now\n", plant->id.c_str()); break; - } + case selectability::Selectable: break; @@ -360,7 +356,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Sat, 18 Jan 2020 16:34:29 -0500 Subject: [PATCH 79/86] Move getplants issue details to user-facing docs Ref #1479, #1481 --- docs/Plugins.rst | 10 ++++++++++ plugins/getplants.cpp | 5 ----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index f6341e5aa..f60afad11 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2283,6 +2283,16 @@ Options: Specifying both ``-t`` and ``-s`` will have no effect. If no plant IDs are specified, all valid plant IDs will be listed. +.. note:: + + DF is capable of determining that a shrub has already been picked, leaving + an unusable structure part behind. This plugin does not perform such a check + (as the location of the required information has not yet been identified). + This leads to some shrubs being designated when they shouldn't be, causing a + plant gatherer to walk there and do nothing (except clearing the + designation). See :issue:`1479` for details. + + .. _infiniteSky: infiniteSky diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 9e9b1dcbc..ebd64d8d1 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -1,9 +1,4 @@ // (un)designate matching plants for gathering/cutting -// Known issue: -// DF is capable of determining that a shrub has already been picked, leaving an unusable structure part -// behind. This code does not perform such a check (as the location of the required information is -// unknown to the writer of this comment). This leads to some shrubs being designated when they -// shouldn't be, causing a plant gatherer to walk there and do nothing (except clearing the designation). #include #include "Core.h" From 513b7e02a6d6af024960940a8139a8c745210bd3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 16:38:08 -0500 Subject: [PATCH 80/86] Add new verbose option to getplants docs From #1481 --- docs/Plugins.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index f60afad11..35eeb61e6 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2273,12 +2273,13 @@ by spaces. Options: -:-t: Select trees only (exclude shrubs) -:-s: Select shrubs only (exclude trees) -:-c: Clear designations instead of setting them -:-x: Apply selected action to all plants except those specified (invert +:``-t``: Select trees only (exclude shrubs) +:``-s``: Select shrubs only (exclude trees) +:``-c``: Clear designations instead of setting them +:``-x``: Apply selected action to all plants except those specified (invert selection) -:-a: Select every type of plant (obeys ``-t``/``-s``) +:``-a``: Select every type of plant (obeys ``-t``/``-s``) +:``-v``: Lists the number of (un)designations per plant Specifying both ``-t`` and ``-s`` will have no effect. If no plant IDs are specified, all valid plant IDs will be listed. From dc8b8b501bfba89f875a17aac90409a8faef8db4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 17:17:28 -0500 Subject: [PATCH 81/86] Move autofarm to supported plugins PR #1468 --- plugins/CMakeLists.txt | 1 + plugins/{devel => }/autofarm.cpp | 0 plugins/devel/CMakeLists.txt | 1 - 3 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{devel => }/autofarm.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 183de9a74..ea54a5a2c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -86,6 +86,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(autochop autochop.cpp) DFHACK_PLUGIN(autoclothing autoclothing.cpp) DFHACK_PLUGIN(autodump autodump.cpp) + DFHACK_PLUGIN(autofarm autofarm.cpp) DFHACK_PLUGIN(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(autohauler autohauler.cpp) DFHACK_PLUGIN(autolabor autolabor.cpp) diff --git a/plugins/devel/autofarm.cpp b/plugins/autofarm.cpp similarity index 100% rename from plugins/devel/autofarm.cpp rename to plugins/autofarm.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index cdebbce4f..53157af47 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -5,7 +5,6 @@ ENDIF() INCLUDE(FindThreads) ADD_DEFINITIONS(-DDEV_PLUGIN) -DFHACK_PLUGIN(autofarm autofarm.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(color-dfhack-text color-dfhack-text.cpp) DFHACK_PLUGIN(counters counters.cpp) From afd5d0b18466692bf401f1fcdb9bb107eda6d0e6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 18:49:15 -0500 Subject: [PATCH 82/86] Add autofarm docs --- docs/Plugins.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 35eeb61e6..4e7fa0542 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1862,6 +1862,31 @@ Examples: * ``autoclothing cloth dress``: Displays the currently set number of cloth dresses chosen per citizen. +.. _autofarm: + +autofarm +======== + +Automatically handles crop selection in farm plots based on current plant +stocks, and selects crops for planting if current stock is below a threshold. +Selected crops are dispatched on all farmplots. (Note that this plugin replaces +an older Ruby script of the same name.) + +Use the `enable` or `disable` commands to change whether this plugin is +enabled. + +Usage: + +* ``autofarm runonce``: + Updates all farm plots once, without enabling the plugin +* ``autofarm status``: + Prints status information, including any applied limits +* ``autofarm default 30``: + Sets the default threshold +* ``autofarm threshold 150 helmet_plump tail_pig``: + Sets thresholds of individual plants + + ================ Map modification ================ From 7554b307277d724384414c8d4dab3c46e7879d63 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 18:49:54 -0500 Subject: [PATCH 83/86] Call out infiniteSky docs warning --- docs/Plugins.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 4e7fa0542..d8e08594b 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2333,8 +2333,10 @@ Usage: ``infiniteSky enable/disable`` Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward. -:issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. -Saving and loading after creating new z-levels should fix the problem. +.. warning:: + + :issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. + Saving and loading after creating new z-levels should fix the problem. .. _liquids: From f070ac246e34ae52d3747b270c79cb13a8be1384 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 18:51:54 -0500 Subject: [PATCH 84/86] Fix link text for "disable" command references in docs --- docs/Plugins.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index d8e08594b..ac1f7dae8 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1829,7 +1829,7 @@ nestboxes ========= Automatically scan for and forbid fertile eggs incubating in a nestbox. -Toggle status with `enable` or `disable`. +Toggle status with `enable` or `disable `. .. _tailor: @@ -1842,7 +1842,7 @@ If there are enough replacement items in inventory to replace all worn items, th will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll reeequip with replacement items. -Use the `enable` and `disable` commands to toggle this plugin's status, or run +Use the `enable` and `disable ` commands to toggle this plugin's status, or run ``tailor status`` to check its current status. .. _autoclothing: @@ -1872,7 +1872,7 @@ stocks, and selects crops for planting if current stock is below a threshold. Selected crops are dispatched on all farmplots. (Note that this plugin replaces an older Ruby script of the same name.) -Use the `enable` or `disable` commands to change whether this plugin is +Use the `enable` or `disable ` commands to change whether this plugin is enabled. Usage: From f55a1b9990f2873ed6111faeee2bfb4be2edaf7c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 18:52:17 -0500 Subject: [PATCH 85/86] autofarm: mention runonce and status in built-in help --- plugins/autofarm.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/autofarm.cpp b/plugins/autofarm.cpp index 991dfd222..54ce3600d 100644 --- a/plugins/autofarm.cpp +++ b/plugins/autofarm.cpp @@ -44,9 +44,11 @@ DFHACK_PLUGIN_IS_ENABLED(enabled); const char *tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; const char *usage = ( - "autofarm enable\n" - "autofarm default 30\n" - "autofarm threshold 150 helmet_plump tail_pig\n" + "``enable autofarm``: Enables the plugin\n" + "``autofarm runonce``: Updates farm plots (one-time only)\n" + "``autofarm status``: Prints status information\n" + "``autofarm default 30``: Sets the default threshold\n" + "``autofarm threshold 150 helmet_plump tail_pig``: Sets thresholds\n" ); class AutoFarm { From bb9bc4e353378524bc70322847c5d16df46a84d9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 18 Jan 2020 18:52:32 -0500 Subject: [PATCH 86/86] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 779de2311..4053321b2 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 779de23111409635b5644daae5338ba0199945e4 +Subproject commit 4053321b202a29f667d64d824ba8339ec1b1df4f diff --git a/scripts b/scripts index 5d0f00eae..61d8e935f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5d0f00eae3bd8eef41c860369550e05f95a1282a +Subproject commit 61d8e935ffd3c705ac13082874e2a3cb0363251b