Merge remote-tracking branch 'PatrikLundell/getplants' into develop

Merges #1484

Conflicts:
	docs/changelog.txt
develop
lethosor 2020-04-25 02:39:05 -04:00
commit 4dce9f2010
3 changed files with 185 additions and 39 deletions

@ -2298,16 +2298,19 @@ by spaces.
Options: Options:
:``-t``: Select trees only (exclude shrubs) :``-t``: Tree: Select trees only (exclude shrubs)
:``-s``: Select shrubs only (exclude trees) :``-s``: Shrub: Select shrubs only (exclude trees)
:``-c``: Clear designations instead of setting them :``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s
:``-x``: Apply selected action to all plants except those specified (invert :``-c``: Clear: Clear designations instead of setting them
:``-x``: eXcept: Apply selected action to all plants except those specified (invert
selection) selection)
:``-a``: Select every type of plant (obeys ``-t``/``-s``) :``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``)
:``-v``: Lists the number of (un)designations per plant :``-v``: Verbose: Lists the number of (un)designations per plant
:``-n *``: Number: Designate up to * (an integer number) plants of each species
Specifying both ``-t`` and ``-s`` will have no effect. If no plant IDs are specified, Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are
all valid plant IDs will be listed. specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f``
restricting the list to trees, shrubs, and farmable shrubs, respectively.
.. note:: .. note::
@ -2318,6 +2321,12 @@ all valid plant IDs will be listed.
plant gatherer to walk there and do nothing (except clearing the plant gatherer to walk there and do nothing (except clearing the
designation). See :issue:`1479` for details. designation). See :issue:`1479` for details.
The implementation another known deficiency: it's incapable of detecting that
RAW definitions that specify a seed extraction reaction for the structural part
but has no other use for it cannot actually yield any seeds, as the part is
never used (parts of bug tracker report 6940, e.g. Red Spinach), even though DF
collects it, unless there's a workshop reaction to do it (which there isn't
in vanilla).
.. _infiniteSky: .. _infiniteSky:

@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements ## Misc Improvements
- ``dfhack.init-example``: enabled `autodump` - ``dfhack.init-example``: enabled `autodump`
- `getplants`: added switches for designations for farming seeds and for max number designated per plant
- `manipulator`: added intrigue to displayed skills - `manipulator`: added intrigue to displayed skills
- `search`: added support for the fortress mode justice screen - `search`: added support for the fortress mode justice screen

@ -38,29 +38,81 @@ enum class selectability {
Unselected Unselected
}; };
//selectability selectablePlant(color_ostream &out, const df::plant_raw *plant) // Determination of whether seeds can be collected is somewhat messy:
selectability selectablePlant(const df::plant_raw *plant) // - Growths of type SEEDS are collected only if they are edible either raw or cooked.
// - Growths of type PLANT_GROWTH are collected provided the STOCKPILE_PLANT_GROWTH
// flag is set.
// The two points above were determined through examination of the DF code, while the ones
// below were determined through examination of the behavior of bugged, working, and
// RAW manipulated shrubs on embarks.
// - If seeds are defined as explicit growths, they are the source of seeds, overriding
// the default STRUCTURAL part as the source.
// - If a growth has the reaction that extracts seeds as a side effect of other
// processing (brewing, eating raw, etc.), this overrides the STRUCTURAL part as the
// source of seeds. However, for some reason it does not produce seeds from eating
// raw growths unless the structural part is collected (at least for shrubs: other
// processing was not examined).
// - If a growth has a (non vanilla) reaction that produces seeds, seeds are produced,
// provided there is something (such as a workshop order) that triggers it.
// The code below is satisfied with detection of a seed producing reaction, and does not
// detect the bugged case where a seed extraction process is defined but doesn't get
// triggered. Such a process can be triggered either as a side effect of other
// processing, or as a workshop reaction, and it would be overkill for this code to
// try to determine if a workshop reaction exists and has been permitted for the played
// race.
// There are two bugged cases of this in the current vanilla RAWs:
// Both Red Spinach and Elephant-Head Amaranth have the seed extraction reaction
// explicitly specified for the structural part, but no other use for it. This causes
// these parts to be collected (a valid reaction is defined), but remain unusable. This
// is one ofthe issues in bug 9640 on the bug tracker (the others cases are detected and
// result in the plants not being usable for farming or even collectable at all).
//selectability selectablePlant(color_ostream &out, const df::plant_raw *plant, bool farming)
selectability selectablePlant(const df::plant_raw *plant, bool farming)
{ {
const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant->material_defs.type_basic_mat, plant->material_defs.idx_basic_mat); const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant->material_defs.type_basic_mat, plant->material_defs.idx_basic_mat);
bool outOfSeason = false; bool outOfSeason = false;
selectability result = selectability::Nonselectable;
if (plant->flags.is_set(plant_raw_flags::TREE)) if (plant->flags.is_set(plant_raw_flags::TREE))
{ {
// out.print("%s is a selectable tree\n", plant->id.c_str()); // out.print("%s is a selectable tree\n", plant->id.c_str());
if (farming)
{
return selectability::Nonselectable;
}
else
{
return selectability::Selectable; return selectability::Selectable;
} }
}
else if (plant->flags.is_set(plant_raw_flags::GRASS)) else if (plant->flags.is_set(plant_raw_flags::GRASS))
{ {
// out.print("%s is a non selectable Grass\n", plant->id.c_str()); // out.print("%s is a non selectable Grass\n", plant->id.c_str());
return selectability::Grass; return selectability::Grass;
} }
if (farming && plant->material_defs.type_seed == -1)
{
return selectability::Nonselectable;
}
if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) ||
basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED))
{ {
// out.print("%s is edible\n", plant->id.c_str()); // out.print("%s is edible\n", plant->id.c_str());
if (farming)
{
if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW))
{
result = selectability::Selectable;
}
}
else
{
return selectability::Selectable; return selectability::Selectable;
} }
}
if (plant->flags.is_set(plant_raw_flags::THREAD) || if (plant->flags.is_set(plant_raw_flags::THREAD) ||
plant->flags.is_set(plant_raw_flags::MILL) || plant->flags.is_set(plant_raw_flags::MILL) ||
@ -69,15 +121,29 @@ selectability selectablePlant(const df::plant_raw *plant)
plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL))
{ {
// out.print("%s is thread/mill/extract\n", plant->id.c_str()); // out.print("%s is thread/mill/extract\n", plant->id.c_str());
if (farming)
{
result = selectability::Selectable;
}
else
{
return selectability::Selectable; return selectability::Selectable;
} }
}
if (basic_mat.material->reaction_product.id.size() > 0 || if (basic_mat.material->reaction_product.id.size() > 0 ||
basic_mat.material->reaction_class.size() > 0) basic_mat.material->reaction_class.size() > 0)
{ {
// out.print("%s has a reaction\n", plant->id.c_str()); // out.print("%s has a reaction\n", plant->id.c_str());
if (farming)
{
result = selectability::Selectable;
}
else
{
return selectability::Selectable; return selectability::Selectable;
} }
}
for (size_t i = 0; i < plant->growths.size(); i++) for (size_t i = 0; i < plant->growths.size(); i++)
{ {
@ -91,19 +157,40 @@ selectability selectablePlant(const df::plant_raw *plant)
(plant->growths[i]->item_type == df::item_type::PLANT_GROWTH && (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH &&
growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now...
{ {
bool seedSource = plant->growths[i]->item_type == df::item_type::SEEDS;
if (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH)
{
for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++)
{
if (growth_mat.material->reaction_product.material.mat_type[k] == plant->material_defs.type_seed &&
growth_mat.material->reaction_product.material.mat_index[k] == plant->material_defs.idx_seed)
{
seedSource = true;
break;
}
}
}
if (*cur_year_tick >= plant->growths[i]->timing_1 && if (*cur_year_tick >= plant->growths[i]->timing_1 &&
(plant->growths[i]->timing_2 == -1 || (plant->growths[i]->timing_2 == -1 ||
*cur_year_tick <= plant->growths[i]->timing_2)) *cur_year_tick <= plant->growths[i]->timing_2))
{ {
// out.print("%s has an edible seed or a stockpile growth\n", plant->id.c_str()); // out.print("%s has an edible seed or a stockpile growth\n", plant->id.c_str());
if (!farming || seedSource)
{
return selectability::Selectable; return selectability::Selectable;
} }
}
else else
{
if (!farming || seedSource)
{ {
outOfSeason = true; 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. /* 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); const DFHack::MaterialInfo seed_mat = DFHack::MaterialInfo(plant->material_defs.type_seed, plant->material_defs.idx_seed);
@ -133,7 +220,7 @@ selectability selectablePlant(const df::plant_raw *plant)
else else
{ {
// out.printerr("%s cannot be gathered\n", plant->id.c_str()); // out.printerr("%s cannot be gathered\n", plant->id.c_str());
return selectability::Nonselectable; return result;
} }
} }
@ -143,8 +230,8 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
std::vector<selectability> plantSelections; std::vector<selectability> plantSelections;
std::vector<size_t> collectionCount; std::vector<size_t> collectionCount;
set<string> plantNames; set<string> plantNames;
bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false; bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false, farming = false;
size_t maxCount = 999999;
int count = 0; int count = 0;
plantSelections.resize(world->raws.plants.all.size()); plantSelections.resize(world->raws.plants.all.size());
@ -160,20 +247,43 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < parameters.size(); i++) for (size_t i = 0; i < parameters.size(); i++)
{ {
if(parameters[i] == "help" || parameters[i] == "?") if (parameters[i] == "help" || parameters[i] == "?")
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
else if(parameters[i] == "-t") else if (parameters[i] == "-t")
treesonly = true; treesonly = true;
else if(parameters[i] == "-s") else if (parameters[i] == "-s")
shrubsonly = true; shrubsonly = true;
else if(parameters[i] == "-c") else if (parameters[i] == "-c")
deselect = true; deselect = true;
else if(parameters[i] == "-x") else if (parameters[i] == "-x")
exclude = true; exclude = true;
else if(parameters[i] == "-a") else if (parameters[i] == "-a")
all = true; all = true;
else if(parameters[i] == "-v") else if (parameters[i] == "-v")
verbose = true; verbose = true;
else if (parameters[i] == "-f")
farming = true;
else if (parameters[i] == "-n")
{
if (parameters.size() > i + 1)
{
maxCount = atoi(parameters[i + 1].c_str());
if (maxCount >= 1)
{
i++; // We've consumed the next parameter, so we need to progress the iterator.
}
else
{
out.printerr("-n requires a positive integer parameter!\n");
return CR_WRONG_USAGE;
}
}
else
{
out.printerr("-n requires a positive integer parameter!\n");
return CR_WRONG_USAGE;
}
}
else else
plantNames.insert(parameters[i]); plantNames.insert(parameters[i]);
} }
@ -182,6 +292,11 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
out.printerr("Cannot specify both -t and -s at the same time!\n"); out.printerr("Cannot specify both -t and -s at the same time!\n");
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
if (treesonly && farming)
{
out.printerr("Cannot specify both -t and -f at the same time!\n");
return CR_WRONG_USAGE;
}
if (all && exclude) if (all && exclude)
{ {
out.printerr("Cannot specify both -a and -x at the same time!\n"); out.printerr("Cannot specify both -a and -x at the same time!\n");
@ -200,14 +315,14 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
df::plant_raw *plant = world->raws.plants.all[i]; df::plant_raw *plant = world->raws.plants.all[i];
if (all) if (all)
{ {
// plantSelections[i] = selectablePlant(out, plant); // plantSelections[i] = selectablePlant(out, plant, farming);
plantSelections[i] = selectablePlant(plant); plantSelections[i] = selectablePlant(plant, farming);
} }
else if (plantNames.find(plant->id) != plantNames.end()) else if (plantNames.find(plant->id) != plantNames.end())
{ {
plantNames.erase(plant->id); plantNames.erase(plant->id);
// plantSelections[i] = selectablePlant(out, plant); // plantSelections[i] = selectablePlant(out, plant, farming);
plantSelections[i] = selectablePlant(plant); plantSelections[i] = selectablePlant(plant, farming);
switch (plantSelections[i]) switch (plantSelections[i])
{ {
case selectability::Grass: case selectability::Grass:
@ -215,7 +330,14 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
break; break;
case selectability::Nonselectable: case selectability::Nonselectable:
if (farming)
{
out.printerr("%s does not have any parts that can be gathered for seeds for farming\n", plant->id.c_str());
}
else
{
out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str()); out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str());
}
break; break;
case selectability::OutOfSeason: case selectability::OutOfSeason:
@ -255,22 +377,30 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < world->raws.plants.all.size(); i++) for (size_t i = 0; i < world->raws.plants.all.size(); i++)
{ {
df::plant_raw *plant = world->raws.plants.all[i]; df::plant_raw *plant = world->raws.plants.all[i];
// switch (selectablePlant(out, plant)) // switch (selectablePlant(out, plant, farming))
switch (selectablePlant(plant)) switch (selectablePlant(plant, farming))
{ {
case selectability::Grass: case selectability::Grass:
case selectability::Nonselectable: case selectability::Nonselectable:
continue; continue;
case selectability::OutOfSeason: case selectability::OutOfSeason:
{
if (!treesonly)
{ {
out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str()); out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str());
}
break; break;
} }
case selectability::Selectable: case selectability::Selectable:
{
if ((treesonly && plant->flags.is_set(plant_raw_flags::TREE)) ||
(shrubsonly && !plant->flags.is_set(plant_raw_flags::TREE)) ||
(!treesonly && !shrubsonly)) // 'farming' weeds out trees when determining selectability, so no need to test that explicitly
{ {
out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str()); 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; break;
} }
@ -311,6 +441,8 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
continue; continue;
if (cur->designation[x][y].bits.hidden) if (cur->designation[x][y].bits.hidden)
continue; continue;
if (collectionCount[plant->material] >= maxCount)
continue;
if (deselect && Designations::unmarkPlant(plant)) if (deselect && Designations::unmarkPlant(plant))
{ {
collectionCount[plant->material]++; collectionCount[plant->material]++;
@ -350,12 +482,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
"Options:\n" "Options:\n"
" -t - Tree: Select trees only (exclude shrubs)\n" " -t - Tree: Select trees only (exclude shrubs)\n"
" -s - Shrub: Select shrubs only (exclude trees)\n" " -s - Shrub: Select shrubs only (exclude trees)\n"
" -f - Farming: Designate only shrubs that yield seeds for farming. Implies -s\n"
" -c - Clear: Clear designations instead of setting them\n" " -c - Clear: Clear designations instead of setting them\n"
" -x - eXcept: Apply selected action to all plants except those specified\n" " -x - eXcept: Apply selected action to all plants except those specified\n"
" -a - All: Select every type of plant (obeys -t/-s)\n" " -a - All: Select every type of plant (obeys -t/-s/-f)\n"
" -v - Verbose: lists the number of (un)designations per plant\n" " -v - Verbose: List the number of (un)designations per plant\n"
"Specifying both -t and -s will have no effect.\n" " -n * - Number: Designate up to * (an integer number) plants of each species\n"
"If no plant IDs are specified, all valid plant IDs will be listed.\n" "Specifying both -t and -s or -f will have no effect.\n"
"If no plant IDs are specified, and the -a switch isn't given, all valid plant\n"
"IDs will be listed with -t, -s, and -f restricting the list to trees, shrubs,\n"
"and farmable shrubs, respectively.\n"
)); ));
return CR_OK; return CR_OK;
} }