From 78d1462c600b9e3a27cb214711e869ad35aaefa1 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Thu, 23 May 2024 17:15:40 -0700 Subject: [PATCH 01/20] Update changelog.txt --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1093319637..5ff96b06eb 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ Template for new versions: ## New Features - `tweak`: ``named-codices``: display book titles instead of a material description in the stocks/trade screens +- `plant` (formerly ``plants``): can now ``remove`` shrubs and saplings; ``grow`` can make mature trees older; many new command options ## Fixes - `suspendmanager`: stop suspending single tile stair constructions From 11760d375b886a93104c3ff9aeb933549798563d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Thu, 23 May 2024 17:18:00 -0700 Subject: [PATCH 02/20] Improve plant plugin * Update regrass.cpp * Update regrass.lua * Update regrass.rst * Rename plants.cpp to plant.cpp * Rename plants.rst to plant.rst * Update plugins/CMakeLists.txt * Update plant.cpp * Create plant.lua * Update plant.rst --- docs/plugins/plant.rst | 154 ++++++++++ docs/plugins/plants.rst | 33 --- docs/plugins/regrass.rst | 9 +- plugins/CMakeLists.txt | 2 +- plugins/lua/plant.lua | 127 ++++++++ plugins/lua/regrass.lua | 16 +- plugins/plant.cpp | 627 +++++++++++++++++++++++++++++++++++++++ plugins/plants.cpp | 208 ------------- plugins/regrass.cpp | 83 +++--- 9 files changed, 971 insertions(+), 288 deletions(-) create mode 100644 docs/plugins/plant.rst delete mode 100644 docs/plugins/plants.rst create mode 100644 plugins/lua/plant.lua create mode 100644 plugins/plant.cpp delete mode 100644 plugins/plants.cpp diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst new file mode 100644 index 0000000000..306d09b65a --- /dev/null +++ b/docs/plugins/plant.rst @@ -0,0 +1,154 @@ +plant +===== + +.. dfhack-tool:: + :summary: Grow and remove shrubs or trees. + :tags: adventure fort armok map plants + +Grow and remove shrubs or trees. Modes are ``create``, ``grow``, and ``remove``. +``create`` allows the creation of new shrubs and saplings. ``grow`` adjusts the +age of saplings and trees, allowing them to grow instantly. ``remove`` can +remove existing shrubs and saplings. + +Usage +----- + +Provide a mode (including a ``plant_id`` for ``create``) followed by optional +``pos`` arguments and options. The ``pos`` arguments can limit operation of +``grow`` or ``remove`` to a single tile or a cuboid. ``pos`` should normally be +in the form ``0,0,0``, without spaces. The string ``here`` can be used in place +of numeric coordinates to use the position of the keyboard cursor, if active. + +create +====== + +:: + + plant create [] [] + +Creates a new plant of the specified type at ``pos`` or the cursor position. +The target tile must be a dirt or grass floor. ``plant_id`` is not +case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded +shrub or sapling IDs have spaces.) A numerical ID can also be used. Providing +an empty string with "" will print all available IDs and skip plant creation. + +Options +------- + +``-a ``, ``--age `` + Set the created plant to a specific age (in years.) ``value`` can be a + non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) + ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at + 1250. Defaults to 0 if option is unused. Only a few tree types grow wider + than 1x1, but many may grow taller. (Going directly to higher years will + stunt height. It may be more desirable to instead use ``plant grow`` in + stages, or just spawn full trees using `gui/sandbox`.) + +grow +==== + +:: + + plant grow [ []] [] + +Grows saplings (including dead ones) into trees. Will default to all saplings +on the map if no ``pos`` arguments are used. Saplings will die and fail to grow +if they are blocked by another tree. + +Options +------- + +``-a ``, ``--age `` + Define the age (in years) to set saplings to. ``value`` can be a + non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) + ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at + 1250. Defaults to 3 if option is unused. If a ``value`` larger than 3 is + used, it will make sure even fully-grown trees have an age of at least the + given value, allowing them to grow larger. (Going directly to higher years + will stunt tree height. It may be more desirable to grow in stages rather + than all at once. Trees grow taller every 10 years.) +``-f ``, ``--filter `` + Define a filter list of plant IDs to target, ignoring all other tree types. + ``list`` should be a comma-separated list of strings and/or non-negative + integers with no spaces in between them. Spaces are acceptable within + strings as long as they are enclosed in quotes. +``-e ``, ``--exclude `` + Same as ``--filter``, but target everything except these. Cannot be used + with ``--filter``. +``-z``, ``--zlevel`` + Operate on a range of z-levels instead of default targeting. Will do all + z-levels between ``pos`` arguments if both are given (instead of cuboid,) + z-level of first ``pos`` if one is given (instead of single tile,) else + z-level of current view if no ``pos`` is given (instead of entire map.) +``-n``, ``--dryrun`` + Don't actually grow plants. Just print the total number of plants that + would be grown. + +remove +====== + +:: + + plant remove [ []] [] + +Remove plants from the map (or area defined by ``pos`` arguments.) By default, +only removes invalid plants that exist on non-plant tiles (due to `Bug 12868 +`_.) The ``--shrubs`` +and ``--saplings`` options allow normal plants to be targeted instead. Removal +of fully-grown trees isn't currently supported. + +Options +------- + +``-s``, ``--shrubs`` + Target shrubs for removal. +``-p``, ``--saplings`` + Target saplings for removal. +``-d``, ``--dead`` + Only target dead plants for removal. Can't be used without ``--shrubs`` or + ``--saplings``. +``-f ``, ``--filter `` + Define a filter list of plant IDs to target, ignoring all other plant types. + This applies after ``--shrubs`` and ``--saplings`` are targeted, and can't + be used without one of those options. ``list`` should be a comma-separated + list of strings and/or non-negative integers with no spaces in between them. + Spaces are acceptable within strings as long as they are enclosed in quotes. +``-e ``, ``--exclude `` + Same as ``--filter``, but target everything except these. Cannot be used + with ``--filter``. +``-z``, ``--zlevel`` + Operate on a range of z-levels instead of default targeting. Will do all + z-levels between ``pos`` arguments if both are given (instead of cuboid,) + z-level of first ``pos`` if one is given (instead of single tile,) else + z-level of current view if no ``pos`` is given (instead of entire map.) +``-n``, ``--dryrun`` + Don't actually remove plants. Just print the total number of plants that + would be removed. + +Examples +======== + +``plant create tower_cap`` + Create a Tower Cap sapling at the cursor. +``plant create ""`` + List all valid shrub and sapling IDs. +``plant create 198 -a tree`` + Create an Oak sapling at the cursor, ready to mature into a tree. +``plant create single-grain_wheat 70,70,140`` + Create a Single-grain Wheat shrub at (70, 70, 140.) +``plant grow`` + Attempt to grow all saplings on the map into trees. +``plant grow -z -f maple,200,sand_pear`` + Attempt to grow all Maple, Acacia, and Sand Pear saplings on the current + z-level into trees. +``plant grow 0,0,100 19,19,119 -a 10`` + Set the age of all saplings and trees (with their original sapling tile) + in the defined 20x20x20 cube to at least 10 years. +``plant remove`` + Remove all invalid plants from the map. +``plant remove here -sp`` + Remove the shrub or sapling at the cursor. +``plant remove -spd`` + Remove all dead shrubs and saplings from the map. +``plant remove 0,0,49 0,0,51 -pz -e nether_cap`` + Remove all saplings on z-levels 49 to 51, excluding Nether Cap. diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst deleted file mode 100644 index d35579e1d4..0000000000 --- a/docs/plugins/plants.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _plant: - -plants -====== - -.. dfhack-tool:: - :summary: Provides commands that interact with plants. - :tags: unavailable - :no-command: - -.. dfhack-command:: plant - :summary: Create a plant or make an existing plant grow up. - -Usage ------ - -``plant create `` - Creates a new plant of the specified type at the active cursor position. - The cursor must be on a dirt or grass floor tile. -``plant grow`` - Grows saplings into trees. If the cursor is active, it only affects the - sapling under the cursor. If no cursor is active, it affect all saplings - on the map. - -To see the full list of plant ids, run the following command:: - - devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 - -Example -------- - -``plant create TOWER_CAP`` - Create a Tower Cap sapling at the cursor position. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 2bafc82bcf..f3cb019dce 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -46,8 +46,9 @@ Options specified. ``-p ``, ``--plant `` Specify a grass type for the ``--force`` option. ``grass_id`` is not - case-sensitive, but must be enclosed in quotes if spaces exist. Providing - an empty string with "" will print all available IDs and skip regrass. + case-sensitive, but must be enclosed in quotes if spaces exist. A numerical + ID can also be used. Providing an empty string with "" will print all + available IDs and skip regrass. ``-a``, ``--ashes`` Regrass tiles that've been burnt to ash. ``-d``, ``--buildings`` @@ -65,8 +66,8 @@ Options `devel/block-borders` can be used to visualize map blocks. ``-z``, ``--zlevel`` Regrass entire z-levels. Will do all z-levels between ``pos`` arguments if - both given, z-level of first ``pos`` if one given, else z-level of - viewscreen if no ``pos`` given. + both are given, z-level of first ``pos`` if one is given, else z-level of + current view if no ``pos`` is given. Examples -------- diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ccd8ff09e8..8b9a066036 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -141,7 +141,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(pet-uncapper pet-uncapper.cpp) - #dfhack_plugin(plants plants.cpp) + dfhack_plugin(plant plant.cpp LINK_LIBRARIES lua) dfhack_plugin(preserve-tombs preserve-tombs.cpp) dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua) dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua new file mode 100644 index 0000000000..25e91c2fa2 --- /dev/null +++ b/plugins/lua/plant.lua @@ -0,0 +1,127 @@ +local _ENV = mkmodule('plugins.plant') + +local argparse = require('argparse') +local utils = require('utils') + +local function search_str(s) + return dfhack.upperCp437(dfhack.toSearchNormalized(s)) +end + +local function find_plant_idx(s) --find plant raw index by id string + local id_str = search_str(s) + for k, v in ipairs(df.global.world.raws.plants.all) do + if search_str(v.id) == id_str then + return k + end + end + + qerror('Plant raw not found: "'..s..'"') +end + +local function find_plant(s) --accept index string or match id string + if s == '' then + return -2 --will print all non-grass ids (for create) + elseif tonumber(s) then + return argparse.nonnegativeInt(s, 'plant_id') + else + return find_plant_idx(s) + end +end + +local function build_filter(vec, s) + if #vec > 0 then + qerror('Filter already defined!') + end + + local set = {} + for _,id in ipairs(argparse.stringList(s, 'list')) do + if id ~= '' then + set[find_plant(id)] = true + end + end + + for idx,_ in pairs(set) do + vec:insert('#', idx) --add plant raw indices to vector + end +end + +local year_table = +{ + tree = 3, --sapling_to_tree_threshold + ["1x1"] = 3, + ["2x2"] = 201, --kapok, ginkgo, highwood + ["3x3"] = 401, --highwood +} + +local function plant_age(s) --tree stage or numerical value + local n + if tonumber(s) then + n = argparse.nonnegativeInt(s, 'age') + else + n = year_table[s:lower()] + end + + if n then + n = (n > 1250) and 1250 or n + if n > 0 then + return 40320*n-1 --years to tens of ticks - 1 + else + return 0 --don't subtract 1 + end + end + + qerror('Invalid age: "'..s..'"') +end + +function parse_commandline(opts, pos_1, pos_2, filter_vec, args) + local positionals = argparse.processArgsGetopt(args, + { + {'s', 'shrubs', handler=function() opts.shrubs = true end}, + {'p', 'saplings', handler=function() opts.saplings = true end}, + {'t', 'trees', handler=function() opts.trees = true end}, + {'d', 'dead', handler=function() opts.dead = true end}, + {'a', 'age', hasArg=true, handler=function(optarg) + opts.age = plant_age(optarg) end}, + {'f', 'filter', hasArg=true, handler=function(optarg) + build_filter(filter_vec, optarg) end}, + {'e', 'exclude', hasArg=true, handler=function(optarg) + opts.filter_ex = true + build_filter(filter_vec, optarg) end}, + {'z', 'zlevel', handler=function() opts.zlevel = true end}, + {'n', 'dryrun', handler=function() opts.dry_run = true end}, + }) + + if #positionals > 3 then + qerror('Too many positionals!') + end + + local p1 = positionals[1] + if not p1 then + qerror('Specify mode: create, grow, or remove!') + elseif p1 == 'create' then + opts.create = true + + if positionals[2] then + opts.plant_idx = find_plant(positionals[2]) + else + qerror('Must specify plant_id for create!') + end + elseif p1 == 'grow' then + opts.grow = true + elseif p1 == 'remove' then + opts.del = true + else + qerror('Invalid mode: "'..p1..'"! Must be create, grow, or remove!') + end + + local n = opts.create and 3 or 2 + if positionals[n] then + utils.assign(pos_1, argparse.coords(positionals[n], 'pos_1', true)) + end + + if not opts.create and positionals[3] then + utils.assign(pos_2, argparse.coords(positionals[3], 'pos_2', true)) + end +end + +return _ENV diff --git a/plugins/lua/regrass.lua b/plugins/lua/regrass.lua index 63205b5a33..7213198848 100644 --- a/plugins/lua/regrass.lua +++ b/plugins/lua/regrass.lua @@ -7,7 +7,7 @@ local function search_str(s) return dfhack.upperCp437(dfhack.toSearchNormalized(s)) end -local function find_grass_idx(s) +local function find_grass_idx(s) --find plant raw index by id string local id_str = search_str(s) for _,grass in ipairs(df.global.world.raws.plants.grasses) do if search_str(grass.id) == id_str then @@ -15,7 +15,15 @@ local function find_grass_idx(s) end end - return -1 + qerror('Plant raw not found: "'..s..'"') +end + +local function find_grass(s) --accept index string or match id string + if tonumber(s) then + return argparse.nonnegativeInt(s, 'grass_id') + else + return find_grass_idx(s) + end end function parse_commandline(opts, pos_1, pos_2, args) @@ -34,12 +42,12 @@ function parse_commandline(opts, pos_1, pos_2, args) }) if plant_str then - if plant_str == '' then --will print all ids + if plant_str == '' then --will print all grass ids opts.forced_plant = -2 elseif not opts.force then qerror('Use of --plant without --force!') else - opts.forced_plant = find_grass_idx(plant_str) + opts.forced_plant = find_grass(plant_str) end elseif opts.force then local grasses = df.global.world.raws.plants.grasses diff --git a/plugins/plant.cpp b/plugins/plant.cpp new file mode 100644 index 0000000000..c554535eb1 --- /dev/null +++ b/plugins/plant.cpp @@ -0,0 +1,627 @@ +// Grow and remove shrubs or trees. + +#include +#include +#include +#include +#include +#include + +#include "Debug.h" +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "TileTypes.h" + +#include "modules/Gui.h" +#include "modules/MapCache.h" +#include "modules/Maps.h" + +#include "df/block_square_event_grassst.h" +#include "df/map_block.h" +#include "df/map_block_column.h" +#include "df/plant.h" +#include "df/plant_raw.h" +#include "df/world.h" + +using std::vector; +using std::string; +using namespace DFHack; + +DFHACK_PLUGIN("plant"); +REQUIRE_GLOBAL(world); + +namespace DFHack +{ + DBG_DECLARE(plant, log, DebugCategory::LINFO); +} + +struct cuboid +{ + int16_t x_min = -1; + int16_t x_max = -1; + int16_t y_min = -1; + int16_t y_max = -1; + int16_t z_min = -1; + int16_t z_max = -1; + + bool isValid() const + { // False if any bound is < 0 + return x_min >= 0 && x_max >= 0 && + y_min >= 0 && y_max >= 0 && + z_min >= 0 && z_max >= 0; + } + + bool addPos(int16_t x, int16_t y, int16_t z) + { // Expand cuboid to include point. Return true if bounds changed + if (x < 0 || y < 0 || z < 0 || (isValid() && testPos(x, y, z))) + return false; + + x_min = (x_min < 0 || x < x_min) ? x : x_min; + x_max = (x_max < 0 || x > x_max) ? x : x_max; + + y_min = (y_min < 0 || y < y_min) ? y : y_min; + y_max = (y_max < 0 || y > y_max) ? y : y_max; + + z_min = (z_min < 0 || z < z_min) ? z : z_min; + z_max = (z_max < 0 || z > z_max) ? z : z_max; + + return true; + } + inline bool addPos(df::coord pos) { return addPos(pos.x, pos.y, pos.z); } + + bool testPos(int16_t x, int16_t y, int16_t z) const + { // Return true if point inside cuboid. Make sure cuboid is valid first! + return x >= x_min && x <= x_max && + y >= y_min && y <= y_max && + z >= z_min && z <= z_max; + } + inline bool testPos(df::coord pos) const { return testPos(pos.x, pos.y, pos.z); } +}; + +struct plant_options +{ + bool create = false; // Create a plant + bool grow = false; // Grow saplings into trees + bool del = false; // Remove plants + bool shrubs = false; // Remove shrubs + bool saplings = false; // Remove saplings + bool trees = false; // Remove grown trees + bool dead = false; // Remove only dead plants + bool filter_ex = false; // Filter vector excludes if true, else requires its plants + bool zlevel = false; // Operate on entire z-levels + bool dry_run = false; // Don't actually grow or remove anything + + int32_t plant_idx = -1; // Plant raw index of plant to create; -2 means print all non-grass ids + int32_t age = -1; // Set plant to this age for grow/create; -1 for default + + static struct_identity _identity; +}; +static const struct_field_info plant_options_fields[] = +{ + { struct_field_info::PRIMITIVE, "create", offsetof(plant_options, create), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "grow", offsetof(plant_options, grow), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "del", offsetof(plant_options, del), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "shrubs", offsetof(plant_options, shrubs), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "saplings", offsetof(plant_options, saplings), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "trees", offsetof(plant_options, trees), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "dead", offsetof(plant_options, dead), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "filter_ex", offsetof(plant_options, filter_ex), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "zlevel", offsetof(plant_options, zlevel), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "dry_run", offsetof(plant_options, dry_run), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "plant_idx", offsetof(plant_options, plant_idx), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "age", offsetof(plant_options, age), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity plant_options::_identity(sizeof(plant_options), &df::allocator_fn, NULL, "plant_options", NULL, plant_options_fields); + +const int32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3 - 1; // 3 years minus 1; let the game handle the actual growing-up + +command_result df_createplant(color_ostream &out, df::coord pos, const plant_options &options) +{ + auto col = Maps::getBlockColumn((pos.x / 48)*3, (pos.y / 48)*3); + if (!Maps::getTileBlock(pos) || !col) + { + out.printerr("No map block at pos!\n"); + return CR_FAILURE; + } + + auto tt = Maps::getTileType(pos); + if (!tt || tileShape(*tt) != tiletype_shape::FLOOR) + { + out.printerr("Plants can only be placed on floors!\n"); + return CR_FAILURE; + } + else // Check tile mat and building occ + { + auto mat = tileMaterial(*tt); + if (mat != tiletype_material::SOIL && + mat != tiletype_material::GRASS_DARK && + mat != tiletype_material::GRASS_LIGHT) + { + out.printerr("Plants can only be placed on dirt or grass!\n"); + return CR_FAILURE; + } + + auto occ = Maps::getTileOccupancy(pos); + if (occ && occ->bits.building > tile_building_occ::None) + { + out.printerr("Building present at pos!\n"); + return CR_FAILURE; + } + } + + auto p_raw = vector_get(world->raws.plants.all, options.plant_idx); + if (!p_raw) + { + out.printerr("Plant raw not found!\n"); + return CR_FAILURE; + } + + auto plant = df::allocate(); + if (p_raw->flags.is_set(plant_raw_flags::TREE)) + plant->hitpoints = 400000; + else + { + plant->hitpoints = 100000; + plant->flags.bits.is_shrub = true; + } + + // This is correct except for RICE, DATE_PALM, and underground plants + // near pool/river/brook. These have both WET and DRY flags. + // Should more properly detect if near surface water feature + if (!p_raw->flags.is_set(plant_raw_flags::DRY)) + plant->flags.bits.watery = true; + + plant->material = options.plant_idx; + plant->pos = pos; + plant->grow_counter = options.age < 0 ? 0 : options.age; + plant->update_order = rand() % 10; + + world->plants.all.push_back(plant); + switch (plant->flags.whole & 3) // watery, is_shrub + { + case 0: world->plants.tree_dry.push_back(plant); break; + case 1: world->plants.tree_wet.push_back(plant); break; + case 2: world->plants.shrub_dry.push_back(plant); break; + case 3: world->plants.shrub_wet.push_back(plant); break; + } + + col->plants.push_back(plant); + if (plant->flags.bits.is_shrub) + *tt = tiletype::Shrub; + else + *tt = tiletype::Sapling; + + return CR_OK; +} + +command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_options &options, vector *filter = nullptr) +{ + if (!bounds.isValid()) + { + out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); + return CR_FAILURE; + } + + bool do_filter = filter && !filter->empty(); + if (do_filter) // Sort filter vector + std::sort(filter->begin(), filter->end()); + + int32_t age = options.age < 0 ? sapling_to_tree_threshold : options.age; // Enforce default + bool do_trees = age > sapling_to_tree_threshold; + + int grown = 0, grown_trees = 0; + for (auto plant : world->plants.all) + { + if (plant->flags.bits.is_shrub) + continue; // Shrub + else if (!bounds.testPos(plant->pos)) + continue; // Outside cuboid + else if (do_filter && (vector_contains(*filter, (int32_t)plant->material) == options.filter_ex)) + continue; // Filtered out + else if (plant->tree_info) + { // Tree + if (do_trees && !plant->damage_flags.bits.dead && plant->grow_counter < age) + { + if (!options.dry_run) + plant->grow_counter = age; + grown_trees++; + } + continue; // Next plant + } + + auto tt = Maps::getTileType(plant->pos); + if (!tt || tileShape(*tt) != tiletype_shape::SAPLING) + { + out.printerr("Invalid sapling tiletype at (%d, %d, %d): %s!\n", + plant->pos.x, plant->pos.y, plant->pos.z, + tt ? ENUM_KEY_STR(tiletype, *tt).c_str() : "No map block!"); + continue; // Bad tiletype + } + else if (*tt == tiletype::SaplingDead) + *tt = tiletype::Sapling; // Revive sapling + + if (!options.dry_run) + { + plant->damage_flags.bits.dead = false; + plant->grow_counter = age; + } + grown++; + } + + if (do_trees) + out.print("%d saplings and %d trees%s set to grow.\n", grown, grown_trees, options.dry_run ? " would be" : ""); + else + out.print("%d saplings%s set to grow.\n", grown, options.dry_run ? " would be" : ""); + + return CR_OK; +} + +static bool uncat_plant(df::plant *plant) +{ // Remove plant from extra vectors + vector *vec = NULL; + switch (plant->flags.whole & 3) // watery, is_shrub + { + case 0: vec = &world->plants.tree_dry; break; + case 1: vec = &world->plants.tree_wet; break; + case 2: vec = &world->plants.shrub_dry; break; + case 3: vec = &world->plants.shrub_wet; break; + } + + for (size_t i = vec->size(); i-- > 0;) + { // Not sorted, but more likely near end + if ((*vec)[i] == plant) + { + vec->erase(vec->begin() + i); + break; + } + } + + auto col = Maps::getBlockColumn((plant->pos.x / 48)*3, (plant->pos.y / 48)*3); + if (!col) + return false; + + vec = &col->plants; + for (size_t i = vec->size(); i-- > 0;) + { // Not sorted, but more likely near end + if ((*vec)[i] == plant) + { + vec->erase(vec->begin() + i); + break; + } + } + + return true; +} + +static bool has_grass(df::map_block *block, int tx, int ty) +{ // Block tile has grass + for (auto blev : block->block_events) + { + if (blev->getType() != block_square_event_type::grass) + continue; + + auto &g_ev = *(df::block_square_event_grassst *)blev; + if (g_ev.amount[tx][ty] > 0) + return true; + } + + return false; +} + +static void set_tt(df::coord pos) +{ // Set tiletype to grass or soil floor + auto block = Maps::getTileBlock(pos); + if (!block) + return; + + int tx = pos.x & 15, ty = pos.y & 15; + if (has_grass(block, tx, ty)) + block->tiletype[tx][ty] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); + else + block->tiletype[tx][ty] = findRandomVariant(tiletype::SoilFloor1); +} + +command_result df_removeplant(color_ostream &out, const cuboid &bounds, const plant_options &options, vector *filter = nullptr) +{ + if (!bounds.isValid()) + { + out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); + return CR_FAILURE; + } + + bool by_type = options.shrubs || options.saplings || options.trees; + bool do_filter = by_type && filter && !filter->empty(); + if (do_filter) // Sort filter vector + std::sort(filter->begin(), filter->end()); + + + int count = 0, count_bad = 0; + auto &vec = world->plants.all; + for (size_t i = vec.size(); i-- > 0;) + { + auto &plant = *vec[i]; + auto tt = Maps::getTileType(plant.pos); + + if (plant.tree_info) // TODO: handle trees + continue; // Not implemented + else if (by_type) + { + if (options.dead && !plant.damage_flags.bits.dead && tt && tileSpecial(*tt) != tiletype_special::DEAD) + continue; // Not removing living + /*else if (plant->tree_info && !options.trees) + continue; // Not removing trees*/ + else if (plant.flags.bits.is_shrub) + { + if (!options.shrubs) + continue; // Not removing shrubs + } + else if (!options.saplings) + continue; // Not removing saplings + } + + if (!bounds.testPos(plant.pos)) + continue; // Outside cuboid + else if (do_filter && (vector_contains(*filter, (int32_t)plant.material) == options.filter_ex)) + continue; // Filtered out + + bool bad_tt = false; + if (tt) + { + if (plant.flags.bits.is_shrub) + { + if (tileShape(*tt) != tiletype_shape::SHRUB) + { + out.printerr("Bad shrub tiletype at (%d, %d, %d): %s\n", + plant.pos.x, plant.pos.y, plant.pos.z, + ENUM_KEY_STR(tiletype, *tt).c_str()); + bad_tt = true; + } + } + else if (!plant.tree_info) + { + if (tileShape(*tt) != tiletype_shape::SAPLING) + { + out.printerr("Bad sapling tiletype at (%d, %d, %d): %s\n", + plant.pos.x, plant.pos.y, plant.pos.z, + ENUM_KEY_STR(tiletype, *tt).c_str()); + bad_tt = true; + } + } + // TODO: trees + } + else + { + out.printerr("Bad plant tiletype at (%d, %d, %d): No map block!\n", + plant.pos.x, plant.pos.y, plant.pos.z); + bad_tt = true; + } + + if (!by_type && !bad_tt) + continue; // Only remove bad plants + + count++; + if (bad_tt) + count_bad++; + + if (!options.dry_run) + { + if (!uncat_plant(&plant)) + out.printerr("Remove plant: No block column at (%d, %d)!\n", plant.pos.x, plant.pos.y); + + if (!bad_tt) // TODO: trees + set_tt(plant.pos); + + vec.erase(vec.begin() + i); + delete &plant; + } + } + + out.print("Plants%s removed: %d (%d bad)\n", options.dry_run ? " that would be" : "", count, count_bad); + return CR_OK; +} + +command_result df_plant(color_ostream &out, vector ¶meters) +{ + plant_options options; + cuboid bounds; + df::coord pos_1, pos_2; + vector filter; // Unsorted + + CoreSuspender suspend; + + if (!Lua::CallLuaModuleFunction(out, "plugins.plant", "parse_commandline", + std::make_tuple(&options, &pos_1, &pos_2, &filter, parameters))) + { + return CR_WRONG_USAGE; + } + + bool by_type = options.shrubs || options.saplings || options.trees; // Remove invalid plants otherwise + if (!options.del && (by_type || options.dead)) + { // Don't use remove options outside remove + out.printerr("Can't use remove's options without remove!\n"); + return CR_WRONG_USAGE; + } + else if (!by_type && options.dead) + { // Don't target dead plants while fixing invalid + out.printerr("Can't use --dead without targeting shrubs/saplings!\n"); // TODO: trees + return CR_WRONG_USAGE; + } + else if (options.del && options.age >= 0) + { // Can't set age with remove + out.printerr("Can't use --age with remove!\n"); + return CR_WRONG_USAGE; + } + else if (options.trees) + { // TODO: implement + out.printerr("--trees not implemented!\n"); + return CR_FAILURE; + } + else if (options.plant_idx == -2) + { // Print all non-grass raw ids + out.print("--- Shrubs ---\n"); + for (auto p_raw : world->raws.plants.bushes) + out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + + out.print("\n--- Saplings ---\n"); + for (auto p_raw : world->raws.plants.trees) + out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + + return CR_OK; + } + + DEBUG(log, out).print("pos_1 = (%d, %d, %d)\npos_2 = (%d, %d, %d)\n", + pos_1.x, pos_1.y, pos_1.z, pos_2.x, pos_2.y, pos_2.z); + + if (!Core::getInstance().isMapLoaded()) + { + out.printerr("Map not loaded!\n"); + return CR_FAILURE; + } + + if (options.create) + { // Check improper options and plant raw + if (options.zlevel || options.dry_run) + { + out.printerr("Cannot use --zlevel or --dryrun with create!\n"); + return CR_FAILURE; + } + else if (!filter.empty()) + { + out.printerr("Cannot use filter/exclude with create!\n"); + return CR_FAILURE; + } + + if (!pos_1.isValid()) + { // Attempt to use cursor for pos if active + Gui::getCursorCoords(pos_1); + DEBUG(log, out).print("Try to use cursor (%d, %d, %d) for pos_1.\n", + pos_1.x, pos_1.y, pos_1.z); + + if (!pos_1.isValid()) + { + out.printerr("Invalid pos for create! Make sure keyboard cursor is active if not entering pos manually!\n"); + return CR_WRONG_USAGE; + } + } + + DEBUG(log, out).print("plant_idx = %d\n", options.plant_idx); + auto p_raw = vector_get(world->raws.plants.all, options.plant_idx); + if (p_raw) + { + DEBUG(log, out).print("Plant raw: %s\n", p_raw->id.c_str()); + if (p_raw->flags.is_set(plant_raw_flags::GRASS)) + { + out.printerr("Plant raw was grass: %d (%s)\n", options.plant_idx, p_raw->id.c_str()); + return CR_FAILURE; + } + } + else + { + out.printerr("Plant raw not found for create: %d\n", options.plant_idx); + return CR_FAILURE; + } + } + else // options.grow || options.remove + { // Check filter and setup cuboid + if (!filter.empty()) + { // Validate filter plant raws + if (!by_type && options.del) + { + out.printerr("Filter/exclude set, but not targeting shrubs/saplings!\n"); // TODO: trees + return CR_WRONG_USAGE; + } + + for (auto idx : filter) + { + DEBUG(log, out).print("Filter/exclude test idx: %d\n", idx); + auto p_raw = vector_get(world->raws.plants.all, idx); + if (p_raw) + { + DEBUG(log, out).print("Filter/exclude raw: %s\n", p_raw->id.c_str()); + if (p_raw->flags.is_set(plant_raw_flags::GRASS)) + { + out.printerr("Filter/exclude plant raw was grass: %d (%s)\n", idx, p_raw->id.c_str()); + return CR_FAILURE; + } + else if (options.grow && !p_raw->flags.is_set(plant_raw_flags::TREE)) + { // User might copy-paste filters between grow and remove, so just log this + DEBUG(log, out).print("Filter/exclude shrub with grow: %d (%s)\n", idx, p_raw->id.c_str()); + } + } + else + { + out.printerr("Plant raw not found for filter/exclude: %d\n", idx); + return CR_FAILURE; + } + } + } + + if (options.zlevel) + { // Adjusted cuboid + if (!pos_1.isValid()) + { + DEBUG(log, out).print("pos_1 invalid and --zlevel. Using viewport.\n"); + pos_1.z = Gui::getViewportPos().z; + } + + if (!pos_2.isValid()) + { + DEBUG(log, out).print("pos_2 invalid and --zlevel. Using pos_1.\n"); + pos_2.z = pos_1.z; + } + + bounds.addPos(0, world->map.y_count-1, pos_1.z); + bounds.addPos(world->map.x_count-1, 0, pos_2.z); + } + else if (pos_1.isValid()) + { // Cuboid or single point + bounds.addPos(pos_1); + bounds.addPos(pos_2); // Point if invalid + } + else // Entire map + { + bounds.addPos(0, 0, world->map.z_count-1); + bounds.addPos(world->map.x_count-1, world->map.y_count-1, 0); + } + + DEBUG(log, out).print("bounds = (%d:%d, %d:%d, %d:%d)\n", + bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); + + if (!bounds.isValid()) + { + out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); + return CR_FAILURE; + } + } + + if (options.create) + return df_createplant(out, pos_1, options); + else if (options.grow) + return df_grow(out, bounds, options, &filter); + else if (options.del) + return df_removeplant(out, bounds, options, &filter); + + return CR_WRONG_USAGE; +} + +DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) +{ + commands.push_back(PluginCommand( + "plant", + "Grow and remove shrubs or trees.", + df_plant)); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + return CR_OK; +} diff --git a/plugins/plants.cpp b/plugins/plants.cpp deleted file mode 100644 index 2325c01e3e..0000000000 --- a/plugins/plants.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "modules/Maps.h" -#include "modules/Gui.h" -#include "TileTypes.h" -#include "modules/MapCache.h" - -#include "df/plant.h" -#include "df/world.h" - -using std::vector; -using std::string; -using namespace DFHack; - -DFHACK_PLUGIN("plants"); -REQUIRE_GLOBAL(world); - -const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3 - 1; // 3 years minus 1 - let the game handle the actual growing-up - -command_result df_grow (color_ostream &out, vector & parameters) -{ - for(size_t i = 0; i < parameters.size();i++) - { - if(parameters[i] == "help" || parameters[i] == "?") - { - return CR_WRONG_USAGE; - } - } - - CoreSuspender suspend; - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } - MapExtras::MapCache map; - int32_t x,y,z; - int grown = 0; - if(Gui::getCursorCoords(x,y,z)) - { - for(size_t i = 0; i < world->plants.all.size(); i++) - { - df::plant * tree = world->plants.all[i]; - if(tree->pos.x == x && tree->pos.y == y && tree->pos.z == z) - { - if(tileShape(map.tiletypeAt(DFCoord(x,y,z))) == tiletype_shape::SAPLING && - tileSpecial(map.tiletypeAt(DFCoord(x,y,z))) != tiletype_special::DEAD) - { - tree->grow_counter = sapling_to_tree_threshold; - grown++; - } - break; - } - } - } - else - { - for(size_t i = 0 ; i < world->plants.all.size(); i++) - { - df::plant *p = world->plants.all[i]; - df::tiletype ttype = map.tiletypeAt(df::coord(p->pos.x,p->pos.y,p->pos.z)); - if(!p->flags.bits.is_shrub && tileShape(ttype) == tiletype_shape::SAPLING && tileSpecial(ttype) != tiletype_special::DEAD) - { - p->grow_counter = sapling_to_tree_threshold; - grown++; - } - } - } - if (grown) - out.print("%i plants grown.\n", grown); - else - out.printerr("No plant(s) found!\n"); - - return CR_OK; -} - -command_result df_createplant (color_ostream &out, vector & parameters) -{ - if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?")) - { - return CR_WRONG_USAGE; - } - - CoreSuspender suspend; - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } - - int32_t x,y,z; - if(!Gui::getCursorCoords(x,y,z)) - { - out.printerr("No cursor detected - please place the cursor over the location in which you wish to create a new plant.\n"); - return CR_FAILURE; - } - df::map_block *map = Maps::getTileBlock(x, y, z); - df::map_block_column *col = Maps::getBlockColumn((x / 48) * 3, (y / 48) * 3); - if (!map || !col) - { - out.printerr("Invalid location selected!\n"); - return CR_FAILURE; - } - int tx = x & 15, ty = y & 15; - int mat = tileMaterial(map->tiletype[tx][ty]); - if ((tileShape(map->tiletype[tx][ty]) != tiletype_shape::FLOOR) || ((mat != tiletype_material::SOIL) && (mat != tiletype_material::GRASS_DARK) && (mat != tiletype_material::GRASS_LIGHT))) - { - out.printerr("Plants can only be placed on dirt or grass floors!\n"); - return CR_FAILURE; - } - - int plant_id = -1; - df::plant_raw *plant_raw = NULL; - for (size_t i = 0; i < world->raws.plants.all.size(); i++) - { - plant_raw = world->raws.plants.all[i]; - if (plant_raw->id == parameters[0]) - { - plant_id = i; - break; - } - } - if (plant_id == -1) - { - out.printerr("Invalid plant ID specified!\n"); - return CR_FAILURE; - } - if (plant_raw->flags.is_set(plant_raw_flags::GRASS)) - { - out.printerr("You cannot plant grass using this command.\n"); - return CR_FAILURE; - } - - df::plant *plant = df::allocate(); - if (plant_raw->flags.is_set(plant_raw_flags::TREE)) - plant->hitpoints = 400000; - else - { - plant->hitpoints = 100000; - plant->flags.bits.is_shrub = 1; - } - // for now, always set "watery" for WET-permitted plants, even if they're spawned away from water - // the proper method would be to actually look for nearby water features, but it's not clear exactly how that works - if (plant_raw->flags.is_set(plant_raw_flags::WET)) - plant->flags.bits.watery = 1; - plant->material = plant_id; - plant->pos.x = x; - plant->pos.y = y; - plant->pos.z = z; - plant->update_order = rand() % 10; - - world->plants.all.push_back(plant); - switch (plant->flags.whole & 3) - { - case 0: world->plants.tree_dry.push_back(plant); break; - case 1: world->plants.tree_wet.push_back(plant); break; - case 2: world->plants.shrub_dry.push_back(plant); break; - case 3: world->plants.shrub_wet.push_back(plant); break; - } - col->plants.push_back(plant); - if (plant->flags.bits.is_shrub) - map->tiletype[tx][ty] = tiletype::Shrub; - else - map->tiletype[tx][ty] = tiletype::Sapling; - - return CR_OK; -} - -command_result df_plant (color_ostream &out, vector & parameters) -{ - if (parameters.size() >= 1) - { - if (parameters[0] == "grow") { - parameters.erase(parameters.begin()); - return df_grow(out, parameters); - } else if (parameters[0] == "create") { - parameters.erase(parameters.begin()); - return df_createplant(out, parameters); - } - } - return CR_WRONG_USAGE; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "plant", - "Grow shrubs or trees.", - df_plant)); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index 23ffb3f406..a3b04caefd 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -64,10 +64,10 @@ struct_identity regrass_options::_identity(sizeof(regrass_options), &df::allocat command_result df_regrass(color_ostream &out, vector ¶meters); -static bool valid_tile(color_ostream &out, regrass_options options, df::map_block *block, int x, int y) +static bool valid_tile(color_ostream &out, regrass_options options, df::map_block *block, int tx, int ty) { // Is valid tile for regrass - auto des = block->designation[x][y]; - auto tt = block->tiletype[x][y]; + auto des = block->designation[tx][ty]; + auto tt = block->tiletype[tx][ty]; auto shape = tileShape(tt); auto mat = tileMaterial(tt); auto spec = tileSpecial(tt); @@ -82,7 +82,7 @@ static bool valid_tile(color_ostream &out, regrass_options options, df::map_bloc else if (tt == tiletype::TreeTrunkPillar || tt == tiletype::TreeTrunkInterior || (tt >= tiletype::TreeTrunkThickN && tt <= tiletype::TreeTrunkThickSE)) { // Trees can have grass for ground level tiles - auto p = df::coord(block->map_pos.x + x, block->map_pos.y + y, block->map_pos.z); + auto p = df::coord(block->map_pos.x + tx, block->map_pos.y + ty, block->map_pos.z); auto plant = Maps::getPlantAtTile(p); if (plant && plant->pos.z == p.z) { @@ -107,14 +107,14 @@ static bool valid_tile(color_ostream &out, regrass_options options, df::map_bloc DEBUG(log, out).print("Invalid tile: Shape\n"); return false; } - else if (block->occupancy[x][y].bits.building > + else if (block->occupancy[tx][ty].bits.building > (options.buildings ? tile_building_occ::Passable : tile_building_occ::None)) { // Avoid stockpiles and planned/passable buildings unless enabled DEBUG(log, out).print("Invalid tile: Building (%s)\n", - ENUM_KEY_STR(tile_building_occ, block->occupancy[x][y].bits.building).c_str()); + ENUM_KEY_STR(tile_building_occ, block->occupancy[tx][ty].bits.building).c_str()); return false; } - else if (!options.force && block->occupancy[x][y].bits.no_grow) + else if (!options.force && block->occupancy[tx][ty].bits.no_grow) { DEBUG(log, out).print("Invalid tile: no_grow\n"); return false; @@ -158,7 +158,7 @@ static bool valid_tile(color_ostream &out, regrass_options options, df::map_bloc auto &ms_ev = *(df::block_square_event_material_spatterst *)blev; if (ms_ev.mat_type == builtin_mats::MUD) { - if (ms_ev.amount[x][y] > 0) + if (ms_ev.amount[tx][ty] > 0) { DEBUG(log, out).print("Valid tile: Muddy stone\n"); return true; @@ -176,18 +176,18 @@ static bool valid_tile(color_ostream &out, regrass_options options, df::map_bloc return false; } -static vector grasses_for_tile(color_ostream &out, df::map_block *block, int x, int y) +static vector grasses_for_tile(color_ostream &out, df::map_block *block, int tx, int ty) { // Return sorted vector of valid grass ids vector grasses; - if (block->occupancy[x][y].bits.no_grow) + if (block->occupancy[tx][ty].bits.no_grow) { DEBUG(log, out).print("Skipping grass collection: no_grow\n"); return grasses; } DEBUG(log, out).print("Collecting grasses...\n"); - if (block->designation[x][y].bits.subterranean) + if (block->designation[tx][ty].bits.subterranean) { for (auto p_raw : world->raws.plants.grasses) { // Sorted by df::plant_raw::index @@ -200,7 +200,7 @@ static vector grasses_for_tile(color_ostream &out, df::map_block *block } else // Above ground { - auto rgn_pos = Maps::getBlockTileBiomeRgn(block, df::coord2d(x, y)); // x&15 is okay + auto rgn_pos = Maps::getBlockTileBiomeRgn(block, df::coord2d(tx, ty)); if (!rgn_pos.isValid()) { // No biome (happens in sky) @@ -233,9 +233,9 @@ static vector grasses_for_tile(color_ostream &out, df::map_block *block return grasses; } -static bool regrass_events(color_ostream &out, const regrass_options &options, df::map_block *block, int x, int y) +static bool regrass_events(color_ostream &out, const regrass_options &options, df::map_block *block, int tx, int ty) { // Modify grass block events - if (!valid_tile(out, options, block, x, y)) + if (!valid_tile(out, options, block, tx, ty)) return false; bool success = false; @@ -248,23 +248,23 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d if (options.max_grass) { // Refill all - gr_ev.amount[x][y] = 100; + gr_ev.amount[tx][ty] = 100; success = true; } - else if (gr_ev.amount[x][y] > 0) + else if (gr_ev.amount[tx][ty] > 0) { // Refill first non-zero grass - gr_ev.amount[x][y] = 100; + gr_ev.amount[tx][ty] = 100; DEBUG(log, out).print("Refilled existing grass.\n"); return true; } } - auto valid_grasses = grasses_for_tile(out, block, x, y); + auto valid_grasses = grasses_for_tile(out, block, tx, ty); if (options.force && valid_grasses.empty()) { DEBUG(log, out).print("Forcing grass.\n"); valid_grasses.push_back(options.forced_plant); - block->occupancy[x][y].bits.no_grow = false; + block->occupancy[tx][ty].bits.no_grow = false; } if (options.force || (options.new_grass && !valid_grasses.empty())) @@ -286,7 +286,7 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d if (options.max_grass) { // Initialize tile as full - gr_ev->amount[x][y] = 100; + gr_ev->amount[tx][ty] = 100; success = true; } } @@ -313,7 +313,7 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d auto gr_ev = vector_get_random(temp); if (gr_ev) { - gr_ev->amount[x][y] = 100; + gr_ev->amount[tx][ty] = 100; DEBUG(log, out).print("Random regrass plant index %d\n", gr_ev->plant_index); return true; } @@ -322,20 +322,20 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d return false; } -int regrass_tile(color_ostream &out, const regrass_options &options, df::map_block *block, int x, int y) +int regrass_tile(color_ostream &out, const regrass_options &options, df::map_block *block, int tx, int ty) { // Regrass single tile. Return 1 if tile success, else 0 CHECK_NULL_POINTER(block); - if (!is_valid_tile_coord(df::coord2d(x, y))) + if (!is_valid_tile_coord(df::coord2d(tx, ty))) { - out.printerr("(%d, %d) not in range 0-15!\n", x, y); + out.printerr("(%d, %d) not in range 0-15!\n", tx, ty); return 0; } - DEBUG(log, out).print("Regrass tile (%d, %d, %d)\n", block->map_pos.x + x, block->map_pos.y + y, block->map_pos.z); - if (!regrass_events(out, options, block, x, y)) + DEBUG(log, out).print("Regrass tile (%d, %d, %d)\n", block->map_pos.x + tx, block->map_pos.y + ty, block->map_pos.z); + if (!regrass_events(out, options, block, tx, ty)) return 0; - auto tt = block->tiletype[x][y]; + auto tt = block->tiletype[tx][ty]; auto mat = tileMaterial(tt); auto shape = tileShape(tt); @@ -359,7 +359,7 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo auto &ms_ev = *(df::block_square_event_material_spatterst *)blev; if (ms_ev.mat_type == builtin_mats::MUD) { - ms_ev.amount[x][y] = 0; + ms_ev.amount[tx][ty] = 0; DEBUG(log, out).print("Removed tile mud.\n"); break; } @@ -369,14 +369,14 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo if (shape == tiletype_shape::FLOOR) { // Handle random variant, ashes DEBUG(log, out).print("Tiletype to random grass floor.\n"); - block->tiletype[x][y] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); + block->tiletype[tx][ty] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); } else { auto new_mat = (rand() & 1) ? tiletype_material::GRASS_LIGHT : tiletype_material::GRASS_DARK; auto new_tt = findTileType(shape, new_mat, tiletype_variant::NONE, tiletype_special::NONE, nullptr); DEBUG(log, out).print("Tiletype to %s.\n", ENUM_KEY_STR(tiletype, new_tt).c_str()); - block->tiletype[x][y] = new_tt; + block->tiletype[tx][ty] = new_tt; } return 1; @@ -387,10 +387,10 @@ int regrass_block(color_ostream &out, const regrass_options &options, df::map_bl CHECK_NULL_POINTER(block); int count = 0; - for (int x = 0; x < 16; x++) + for (int tx = 0; tx < 16; tx++) { - for (int y = 0; y < 16; y++) - count += regrass_tile(out, options, block, x, y); + for (int ty = 0; ty < 16; ty++) + count += regrass_tile(out, options, block, tx, ty); } return count; @@ -493,7 +493,7 @@ command_result df_regrass(color_ostream &out, vector ¶meters) else if (options.forced_plant == -2) { // Print all grass raw ids for (auto p_raw : world->raws.plants.grasses) - out.print("%s\n", p_raw->id.c_str()); + out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); return CR_OK; } @@ -503,12 +503,12 @@ command_result df_regrass(color_ostream &out, vector ¶meters) if (options.block && options.zlevel) { - out.printerr("Choose only block or zlevel!\n"); + out.printerr("Choose only --block or --zlevel!\n"); return CR_WRONG_USAGE; } else if (options.block && (!pos_1.isValid() || pos_2.isValid())) { - out.printerr("Attempt to regrass block with inappropriate pos!\n"); + out.printerr("Invalid pos for --block (or used more than one!)\n"); return CR_WRONG_USAGE; } else if (!Core::getInstance().isMapLoaded()) @@ -522,10 +522,17 @@ command_result df_regrass(color_ostream &out, vector ¶meters) DEBUG(log, out).print("forced_plant = %d\n", options.forced_plant); auto p_raw = vector_get(world->raws.plants.all, options.forced_plant); if (p_raw) - DEBUG(log, out).print("Forced plant_raw = %s\n", p_raw->id.c_str()); + { + DEBUG(log, out).print("Forced plant raw: %s\n", p_raw->id.c_str()); + if (!p_raw->flags.is_set(plant_raw_flags::GRASS)) + { + out.printerr("Plant raw wasn't grass: %d (%s)\n", options.forced_plant, p_raw->id.c_str()); + return CR_FAILURE; + } + } else { - out.printerr("Plant raw not found for force regrass!\n"); + out.printerr("Plant raw not found for --force: %d\n", options.forced_plant); return CR_FAILURE; } } From 33498939a5756c0717b632ff940ee34877926009 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Thu, 23 May 2024 17:23:13 -0700 Subject: [PATCH 03/20] Update changelog.txt --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 5ff96b06eb..6e37728c34 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -65,6 +65,7 @@ Template for new versions: - `blueprint`: capture track carving designations in addition to already-carved tracks - `changevein`: affect connected veins even outside of the selected map block - `logistics`: new ability to automatically forbid or claim items brought to a stockpile (or a dump within a stockpile) +- `regrass`: now accepts numerical IDs for grass raws ## Documentation - `installing`: add instructions for how to use Steam DFHack with non-Steam DF (for DFHack auto-updates and cloud backups) From d6e4b667040b8de7bfce41fcbbc75de8390eb70e Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 16:18:03 -0700 Subject: [PATCH 04/20] Update plant, regrass * Add option to force plant create on no_grow; allow more valid tiles * Update plant.cpp: remove extra includes, use if/else for plant vectors * Update regrass.cpp: don't remove mud * Update changelog.txt * Update Removed.rst * Update regrass.rst * Update plant.rst --- docs/about/Removed.rst | 6 ++ docs/changelog.txt | 5 +- docs/plugins/plant.rst | 126 +++++++++++++++--------------- docs/plugins/regrass.rst | 64 +++++++-------- plugins/lua/plant.lua | 1 + plugins/plant.cpp | 163 ++++++++++++++++++++++++++------------- plugins/regrass.cpp | 4 +- 7 files changed, 215 insertions(+), 154 deletions(-) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 005484cabf..2c68915bd4 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -241,6 +241,12 @@ petcapRemover ============= Renamed to `pet-uncapper`. +.. _plants: + +plants +====== +Renamed to `plant`. + .. _resume: resume diff --git a/docs/changelog.txt b/docs/changelog.txt index 6e37728c34..fe446a7674 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,14 +52,16 @@ Template for new versions: # Future ## New Tools +- `plant` (formerly `plants`): (reinstated) tool for creating/growing/removing plants ## New Features - `tweak`: ``named-codices``: display book titles instead of a material description in the stocks/trade screens -- `plant` (formerly ``plants``): can now ``remove`` shrubs and saplings; ``grow`` can make mature trees older; many new command options +- `plant` (formerly `plants`): can now ``remove`` shrubs and saplings; ``grow`` can make mature trees older; many new command options ## Fixes - `suspendmanager`: stop suspending single tile stair constructions - ``Gui::makeAnnouncement``, ``Gui::autoDFAnnouncement``: fix skipping index 0 when iterating reports vector +- `regrass`: don't remove mud on regrass, consistent with vanilla behavior ## Misc Improvements - `blueprint`: capture track carving designations in addition to already-carved tracks @@ -82,6 +84,7 @@ Template for new versions: - ``safe_index``: will now return nil when attempting to index into a non-indexable object ## Removed +- `plants`: renamed to `plant` # 50.13-r2 diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 306d09b65a..d97f6bd5cd 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -19,22 +19,70 @@ Provide a mode (including a ``plant_id`` for ``create``) followed by optional in the form ``0,0,0``, without spaces. The string ``here`` can be used in place of numeric coordinates to use the position of the keyboard cursor, if active. -create -====== - :: plant create [] [] Creates a new plant of the specified type at ``pos`` or the cursor position. -The target tile must be a dirt or grass floor. ``plant_id`` is not -case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded -shrub or sapling IDs have spaces.) A numerical ID can also be used. Providing -an empty string with "" will print all available IDs and skip plant creation. +The target must be a floor tile, consisting of soil, grass, ashes, or +non-smooth muddy stone. ``plant_id`` is not case-sensitive, but must be +enclosed in quotes if spaces exist. (No unmodded shrub or sapling IDs have +spaces.) A numerical ID can also be used. Providing an empty string with "" +will print all available IDs and skip plant creation. + +:: + + plant grow [ []] [] + +Grows saplings (including dead ones) into trees. Will default to all saplings +on the map if no ``pos`` arguments are used. Saplings will die and fail to grow +if they are blocked by another tree. + +:: + + plant remove [ []] [] + +Remove plants from the map (or area defined by ``pos`` arguments.) By default, +only removes invalid plants that exist on non-plant tiles (due to `Bug 12868 +`_.) The ``--shrubs`` +and ``--saplings`` options allow normal plants to be targeted instead. Removal +of fully-grown trees isn't currently supported. -Options -------- +Examples +-------- +``plant create tower_cap`` + Create a Tower Cap sapling at the cursor. +``plant create ""`` + List all valid shrub and sapling IDs. +``plant create 198 -a tree`` + Create an Oak sapling at the cursor, ready to mature into a tree. +``plant create single-grain_wheat 70,70,140`` + Create a Single-grain Wheat shrub at (70, 70, 140.) +``plant grow`` + Attempt to grow all saplings on the map into trees. +``plant grow -z -f maple,200,sand_pear`` + Attempt to grow all Maple, Acacia, and Sand Pear saplings on the current + z-level into trees. +``plant grow 0,0,100 19,19,119 -a 10`` + Set the age of all saplings and trees (with their original sapling tile) + in the defined 20x20x20 cube to at least 10 years. +``plant remove`` + Remove all invalid plants from the map. +``plant remove here -sp`` + Remove the shrub or sapling at the cursor. +``plant remove -spd`` + Remove all dead shrubs and saplings from the map. +``plant remove 0,0,49 0,0,51 -pz -e nether_cap`` + Remove all saplings on z-levels 49 to 51, excluding Nether Cap. + +Create Options +-------------- + +``-c``, ``--force`` + Create plant even on tiles flagged ``no_grow`` and unset the flag. This + flag is set on tiles that were originally boulders or pebbles, as well + as on some tiles found in deserts, etc. ``-a ``, ``--age `` Set the created plant to a specific age (in years.) ``value`` can be a non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) @@ -44,19 +92,8 @@ Options stunt height. It may be more desirable to instead use ``plant grow`` in stages, or just spawn full trees using `gui/sandbox`.) -grow -==== - -:: - - plant grow [ []] [] - -Grows saplings (including dead ones) into trees. Will default to all saplings -on the map if no ``pos`` arguments are used. Saplings will die and fail to grow -if they are blocked by another tree. - -Options -------- +Grow Options +------------ ``-a ``, ``--age `` Define the age (in years) to set saplings to. ``value`` can be a @@ -84,21 +121,8 @@ Options Don't actually grow plants. Just print the total number of plants that would be grown. -remove -====== - -:: - - plant remove [ []] [] - -Remove plants from the map (or area defined by ``pos`` arguments.) By default, -only removes invalid plants that exist on non-plant tiles (due to `Bug 12868 -`_.) The ``--shrubs`` -and ``--saplings`` options allow normal plants to be targeted instead. Removal -of fully-grown trees isn't currently supported. - -Options -------- +Remove Options +-------------- ``-s``, ``--shrubs`` Target shrubs for removal. @@ -124,31 +148,3 @@ Options ``-n``, ``--dryrun`` Don't actually remove plants. Just print the total number of plants that would be removed. - -Examples -======== - -``plant create tower_cap`` - Create a Tower Cap sapling at the cursor. -``plant create ""`` - List all valid shrub and sapling IDs. -``plant create 198 -a tree`` - Create an Oak sapling at the cursor, ready to mature into a tree. -``plant create single-grain_wheat 70,70,140`` - Create a Single-grain Wheat shrub at (70, 70, 140.) -``plant grow`` - Attempt to grow all saplings on the map into trees. -``plant grow -z -f maple,200,sand_pear`` - Attempt to grow all Maple, Acacia, and Sand Pear saplings on the current - z-level into trees. -``plant grow 0,0,100 19,19,119 -a 10`` - Set the age of all saplings and trees (with their original sapling tile) - in the defined 20x20x20 cube to at least 10 years. -``plant remove`` - Remove all invalid plants from the map. -``plant remove here -sp`` - Remove the shrub or sapling at the cursor. -``plant remove -spd`` - Remove all dead shrubs and saplings from the map. -``plant remove 0,0,49 0,0,51 -pz -e nether_cap`` - Remove all saplings on z-levels 49 to 51, excluding Nether Cap. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index f3cb019dce..1a1b4efa5b 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -24,6 +24,38 @@ can be used in place of numeric coordinates to use the position of the keyboard cursor, if active. The ``--block`` and ``--zlevel`` options use the ``pos`` values differently. +Examples +-------- + +``regrass`` + Regrass the entire map, refilling existing and depleted grass. +``regrass here`` + Regrass the selected tile, refilling existing and depleted grass. +``regrass here 0,0,90 --zlevel`` + Regrass all z-levels including the selected tile's z-level through z-level + 90, refilling existing and depleted grass. +``regrass 0,0,100 19,19,119 --ashes --mud`` + Regrass tiles in the 20x20x20 cube defined by the coords, refilling + existing and depleted grass, and converting ashes and muddy stone (if + respective blocks ever had grass.) +``regrass 10,10,100 -baudnm`` + Regrass the block that contains the given coord; converting ashes, muddy + stone, and tiles under buildings; adding all compatible grass types, and + filling each grass type to max. +``regrass -f`` + Regrass the entire map, refilling existing and depleted grass, else filling + with a randomly selected grass type if non-existent. +``regrass -p ""`` + Print all valid grass raw ids. Don't regrass. +``regrass -zf -p underlichen`` + Regrass the current z-level, refilling existing and depleted grass, else + filling with ``underlichen`` if non-existent. +``regrass here -bnf -p "dog's tooth grass"`` + Regrass the selected block, adding all compatible grass types to block data, + ``dog's tooth grass`` if no compatible types exist. Refill existing grass + on each tile, else select one of the block's types if depleted or + previously non-existent. + Options ------- @@ -69,38 +101,6 @@ Options both are given, z-level of first ``pos`` if one is given, else z-level of current view if no ``pos`` is given. -Examples --------- - -``regrass`` - Regrass the entire map, refilling existing and depleted grass. -``regrass here`` - Regrass the selected tile, refilling existing and depleted grass. -``regrass here 0,0,90 --zlevel`` - Regrass all z-levels including the selected tile's z-level through z-level - 90, refilling existing and depleted grass. -``regrass 0,0,100 19,19,119 --ashes --mud`` - Regrass tiles in the 20x20x20 cube defined by the coords, refilling - existing and depleted grass, and converting ashes and muddy stone (if - respective blocks ever had grass.) -``regrass 10,10,100 -baudnm`` - Regrass the block that contains the given coord; converting ashes, muddy - stone, and tiles under buildings; adding all compatible grass types, and - filling each grass type to max. -``regrass -f`` - Regrass the entire map, refilling existing and depleted grass, else filling - with a randomly selected grass type if non-existent. -``regrass -p ""`` - Print all valid grass raw ids. Don't regrass. -``regrass -zf -p underlichen`` - Regrass the current z-level, refilling existing and depleted grass, else - filling with ``underlichen`` if non-existent. -``regrass here -bnf -p "dog's tooth grass"`` - Regrass the selected block, adding all compatible grass types to block data, - ``dog's tooth grass`` if no compatible types exist. Refill existing grass - on each tile, else select one of the block's types if depleted or - previously non-existent. - Troubleshooting --------------- diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index 25e91c2fa2..29e2ab9462 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -76,6 +76,7 @@ end function parse_commandline(opts, pos_1, pos_2, filter_vec, args) local positionals = argparse.processArgsGetopt(args, { + {'c', 'force', handler=function() opts.force = true end}, {'s', 'shrubs', handler=function() opts.shrubs = true end}, {'p', 'saplings', handler=function() opts.saplings = true end}, {'t', 'trees', handler=function() opts.trees = true end}, diff --git a/plugins/plant.cpp b/plugins/plant.cpp index c554535eb1..2a4ece4d06 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -1,25 +1,16 @@ // Grow and remove shrubs or trees. -#include -#include -#include -#include -#include -#include - #include "Debug.h" -#include "Core.h" -#include "Console.h" -#include "Export.h" #include "LuaTools.h" #include "PluginManager.h" #include "TileTypes.h" #include "modules/Gui.h" -#include "modules/MapCache.h" #include "modules/Maps.h" #include "df/block_square_event_grassst.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/builtin_mats.h" #include "df/map_block.h" #include "df/map_block_column.h" #include "df/plant.h" @@ -48,15 +39,14 @@ struct cuboid int16_t z_max = -1; bool isValid() const - { // False if any bound is < 0 - return x_min >= 0 && x_max >= 0 && - y_min >= 0 && y_max >= 0 && - z_min >= 0 && z_max >= 0; + { // True if all max >= min >= 0 + return (x_min >= 0 && y_min >= 0 && z_min >= 0 && + x_max >= x_min && y_max >= y_min && z_max >= z_min); } bool addPos(int16_t x, int16_t y, int16_t z) { // Expand cuboid to include point. Return true if bounds changed - if (x < 0 || y < 0 || z < 0 || (isValid() && testPos(x, y, z))) + if (x < 0 || y < 0 || z < 0 || (isValid() && containsPos(x, y, z))) return false; x_min = (x_min < 0 || x < x_min) ? x : x_min; @@ -70,15 +60,15 @@ struct cuboid return true; } - inline bool addPos(df::coord pos) { return addPos(pos.x, pos.y, pos.z); } + inline bool addPos(const df::coord &pos) { return addPos(pos.x, pos.y, pos.z); } - bool testPos(int16_t x, int16_t y, int16_t z) const + bool containsPos(int16_t x, int16_t y, int16_t z) const { // Return true if point inside cuboid. Make sure cuboid is valid first! return x >= x_min && x <= x_max && y >= y_min && y <= y_max && z >= z_min && z <= z_max; } - inline bool testPos(df::coord pos) const { return testPos(pos.x, pos.y, pos.z); } + inline bool containsPos(const df::coord &pos) const { return containsPos(pos.x, pos.y, pos.z); } }; struct plant_options @@ -86,6 +76,7 @@ struct plant_options bool create = false; // Create a plant bool grow = false; // Grow saplings into trees bool del = false; // Remove plants + bool force = false; // Create plants on no_grow bool shrubs = false; // Remove shrubs bool saplings = false; // Remove saplings bool trees = false; // Remove grown trees @@ -104,6 +95,7 @@ static const struct_field_info plant_options_fields[] = { struct_field_info::PRIMITIVE, "create", offsetof(plant_options, create), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "grow", offsetof(plant_options, grow), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "del", offsetof(plant_options, del), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "force", offsetof(plant_options, force), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "shrubs", offsetof(plant_options, shrubs), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "saplings", offsetof(plant_options, saplings), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "trees", offsetof(plant_options, trees), &df::identity_traits::identity, 0, 0 }, @@ -119,38 +111,89 @@ struct_identity plant_options::_identity(sizeof(plant_options), &df::allocator_f const int32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3 - 1; // 3 years minus 1; let the game handle the actual growing-up -command_result df_createplant(color_ostream &out, df::coord pos, const plant_options &options) +static bool tile_muddy(const df::coord &pos) +{ // True if tile has mud spatter + auto block = Maps::getTileBlock(pos); + if (!block) + return false; + + for (auto blev : block->block_events) + { + if (blev->getType() != block_square_event_type::material_spatter) + continue; + + auto &ms_ev = *(df::block_square_event_material_spatterst *)blev; + if (ms_ev.mat_type == builtin_mats::MUD) + return ms_ev.amount[pos.x&15][pos.y&15] > 0; + } + + return false; +} + +command_result df_createplant(color_ostream &out, const df::coord &pos, const plant_options &options) { auto col = Maps::getBlockColumn((pos.x / 48)*3, (pos.y / 48)*3); - if (!Maps::getTileBlock(pos) || !col) + auto tt = Maps::getTileType(pos); + if (!tt || !col) { out.printerr("No map block at pos!\n"); return CR_FAILURE; } - auto tt = Maps::getTileType(pos); - if (!tt || tileShape(*tt) != tiletype_shape::FLOOR) + if (tileShape(*tt) != tiletype_shape::FLOOR) { - out.printerr("Plants can only be placed on floors!\n"); + out.printerr("Can't create plant: Not floor!\n"); return CR_FAILURE; } - else // Check tile mat and building occ + + auto occ = Maps::getTileOccupancy(pos); + CHECK_NULL_POINTER(occ); + if (occ->bits.building > tile_building_occ::None) { - auto mat = tileMaterial(*tt); - if (mat != tiletype_material::SOIL && - mat != tiletype_material::GRASS_DARK && - mat != tiletype_material::GRASS_LIGHT) - { - out.printerr("Plants can only be placed on dirt or grass!\n"); - return CR_FAILURE; - } + out.printerr("Can't create plant: Building present!\n"); + return CR_FAILURE; + } + else if (!options.force && occ->bits.no_grow) + { + out.printerr("Can't create plant: Tile flagged no_grow and not --force!\n"); + return CR_FAILURE; + } - auto occ = Maps::getTileOccupancy(pos); - if (occ && occ->bits.building > tile_building_occ::None) - { - out.printerr("Building present at pos!\n"); + auto des = Maps::getTileDesignation(pos); + CHECK_NULL_POINTER(des); + if (des->bits.flow_size > (des->bits.liquid_type == tile_liquid::Magma ? 0 : 3)) + { + out.printerr("Can't create plant: Too much liquid!\n"); + return CR_FAILURE; + } + + auto spec = tileSpecial(*tt); + auto mat = tileMaterial(*tt); + switch (mat) + { + case tiletype_material::SOIL: + case tiletype_material::GRASS_LIGHT: + case tiletype_material::GRASS_DARK: + case tiletype_material::ASHES: + break; + case tiletype_material::STONE: + case tiletype_material::LAVA_STONE: + case tiletype_material::MINERAL: + if (spec == tiletype_special::SMOOTH || spec == tiletype_special::TRACK) + { + out.printerr("Can't create plant: Smooth stone!\n"); + return CR_FAILURE; + } + else if (!tile_muddy(pos)) + { + out.printerr("Can't create plant: Non-muddy stone!\n"); + return CR_FAILURE; + } + + break; + default: + out.printerr("Can't create plant: Wrong tile material!\n"); return CR_FAILURE; - } } auto p_raw = vector_get(world->raws.plants.all, options.plant_idx); @@ -171,7 +214,7 @@ command_result df_createplant(color_ostream &out, df::coord pos, const plant_opt // This is correct except for RICE, DATE_PALM, and underground plants // near pool/river/brook. These have both WET and DRY flags. - // Should more properly detect if near surface water feature + // Should more properly detect if near surface water feature. if (!p_raw->flags.is_set(plant_raw_flags::DRY)) plant->flags.bits.watery = true; @@ -181,12 +224,19 @@ command_result df_createplant(color_ostream &out, df::coord pos, const plant_opt plant->update_order = rand() % 10; world->plants.all.push_back(plant); - switch (plant->flags.whole & 3) // watery, is_shrub + if (plant->flags.bits.is_shrub) { - case 0: world->plants.tree_dry.push_back(plant); break; - case 1: world->plants.tree_wet.push_back(plant); break; - case 2: world->plants.shrub_dry.push_back(plant); break; - case 3: world->plants.shrub_wet.push_back(plant); break; + if (plant->flags.bits.watery) + world->plants.shrub_wet.push_back(plant); + else + world->plants.shrub_dry.push_back(plant); + } + else + { + if (plant->flags.bits.watery) + world->plants.tree_wet.push_back(plant); + else + world->plants.tree_dry.push_back(plant); } col->plants.push_back(plant); @@ -195,6 +245,8 @@ command_result df_createplant(color_ostream &out, df::coord pos, const plant_opt else *tt = tiletype::Sapling; + occ->bits.no_grow = false; + return CR_OK; } @@ -219,7 +271,7 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt { if (plant->flags.bits.is_shrub) continue; // Shrub - else if (!bounds.testPos(plant->pos)) + else if (!bounds.containsPos(plant->pos)) continue; // Outside cuboid else if (do_filter && (vector_contains(*filter, (int32_t)plant->material) == options.filter_ex)) continue; // Filtered out @@ -264,13 +316,11 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt static bool uncat_plant(df::plant *plant) { // Remove plant from extra vectors vector *vec = NULL; - switch (plant->flags.whole & 3) // watery, is_shrub - { - case 0: vec = &world->plants.tree_dry; break; - case 1: vec = &world->plants.tree_wet; break; - case 2: vec = &world->plants.shrub_dry; break; - case 3: vec = &world->plants.shrub_wet; break; - } + + if (plant->flags.bits.is_shrub) + vec = plant->flags.bits.watery ? &world->plants.shrub_wet : &world->plants.shrub_dry; + else + vec = plant->flags.bits.watery ? &world->plants.tree_wet : &world->plants.tree_dry; for (size_t i = vec->size(); i-- > 0;) { // Not sorted, but more likely near end @@ -313,7 +363,7 @@ static bool has_grass(df::map_block *block, int tx, int ty) return false; } -static void set_tt(df::coord pos) +static void set_tt(const df::coord &pos) { // Set tiletype to grass or soil floor auto block = Maps::getTileBlock(pos); if (!block) @@ -365,7 +415,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl continue; // Not removing saplings } - if (!bounds.testPos(plant.pos)) + if (!bounds.containsPos(plant.pos)) continue; // Outside cuboid else if (do_filter && (vector_contains(*filter, (int32_t)plant.material) == options.filter_ex)) continue; // Filtered out @@ -440,6 +490,11 @@ command_result df_plant(color_ostream &out, vector ¶meters) { return CR_WRONG_USAGE; } + else if (options.force && !options.create) + { + out.printerr("Can't use --force without create!\n"); + return CR_WRONG_USAGE; + } bool by_type = options.shrubs || options.saplings || options.trees; // Remove invalid plants otherwise if (!options.del && (by_type || options.dead)) diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index a3b04caefd..a9e39a83b7 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -347,7 +347,7 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo DEBUG(log, out).print("Tiletype no change.\n"); return 1; } - else if (mat == tiletype_material::STONE || + /*else if (mat == tiletype_material::STONE || // DF doesn't seem to remove mud mat == tiletype_material::LAVA_STONE || mat == tiletype_material::MINERAL) { // Muddy non-feature stone @@ -364,7 +364,7 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo break; } } - } + }*/ if (shape == tiletype_shape::FLOOR) { // Handle random variant, ashes From 6b98f50a3d63ff1f02fec1a91ea6875bc8f724c8 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 16:37:52 -0700 Subject: [PATCH 05/20] Update plant.lua --- plugins/lua/plant.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index 29e2ab9462..ca5ebb5bbb 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -62,7 +62,7 @@ local function plant_age(s) --tree stage or numerical value end if n then - n = (n > 1250) and 1250 or n + n = math.min(n, 1250) if n > 0 then return 40320*n-1 --years to tens of ticks - 1 else From aa5cd348082798fc90fdd26a09e1c81931dce829 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 23:50:47 -0700 Subject: [PATCH 06/20] Update plant.rst --- docs/plugins/plant.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index d97f6bd5cd..90434b1882 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -104,12 +104,12 @@ Grow Options given value, allowing them to grow larger. (Going directly to higher years will stunt tree height. It may be more desirable to grow in stages rather than all at once. Trees grow taller every 10 years.) -``-f ``, ``--filter `` +``-f [,...]``, ``--filter [,...]`` Define a filter list of plant IDs to target, ignoring all other tree types. - ``list`` should be a comma-separated list of strings and/or non-negative - integers with no spaces in between them. Spaces are acceptable within - strings as long as they are enclosed in quotes. -``-e ``, ``--exclude `` + ``plant_id`` is not case-sensitive, but must be enclosed in quotes if + spaces exist. (No unmodded tree IDs have spaces.) A numerical ID can also + be used. +``-e [,...]``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. ``-z``, ``--zlevel`` @@ -131,13 +131,13 @@ Remove Options ``-d``, ``--dead`` Only target dead plants for removal. Can't be used without ``--shrubs`` or ``--saplings``. -``-f ``, ``--filter `` +``-f [,...]``, ``--filter [,...]`` Define a filter list of plant IDs to target, ignoring all other plant types. This applies after ``--shrubs`` and ``--saplings`` are targeted, and can't - be used without one of those options. ``list`` should be a comma-separated - list of strings and/or non-negative integers with no spaces in between them. - Spaces are acceptable within strings as long as they are enclosed in quotes. -``-e ``, ``--exclude `` + be used without one of those options. ``plant_id`` is not case-sensitive, + but must be enclosed in quotes if spaces exist. (No unmodded shrub or + sapling IDs have spaces.) A numerical ID can also be used. +``-e [,...]``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. ``-z``, ``--zlevel`` From 69fd250908f5565bba099314a2cc4f712975812d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 23:54:23 -0700 Subject: [PATCH 07/20] Update plant.rst --- docs/plugins/plant.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 90434b1882..e711c54561 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -108,7 +108,7 @@ Grow Options Define a filter list of plant IDs to target, ignoring all other tree types. ``plant_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded tree IDs have spaces.) A numerical ID can also - be used. + be used. Use ``plant create ""`` for a list of valid IDs. ``-e [,...]``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. @@ -136,7 +136,8 @@ Remove Options This applies after ``--shrubs`` and ``--saplings`` are targeted, and can't be used without one of those options. ``plant_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded shrub or - sapling IDs have spaces.) A numerical ID can also be used. + sapling IDs have spaces.) A numerical ID can also be used. Use + ``plant create ""`` for a list of valid IDs. ``-e [,...]``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. From 5fd2033b97bf5b140210645e513e0aed70d760ca Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 23:56:38 -0700 Subject: [PATCH 08/20] Update plant.rst --- docs/plugins/plant.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index e711c54561..4812884d7e 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -108,7 +108,8 @@ Grow Options Define a filter list of plant IDs to target, ignoring all other tree types. ``plant_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded tree IDs have spaces.) A numerical ID can also - be used. Use ``plant create ""`` for a list of valid IDs. + be used. Use ``plant create ""`` and check under ``Saplings`` for a list + of valid IDs. ``-e [,...]``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. From a4cd675c8917b262c29926aaee959f1024644194 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 23:58:35 -0700 Subject: [PATCH 09/20] Update plant.rst --- docs/plugins/plant.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 4812884d7e..b0e5910760 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -95,7 +95,7 @@ Create Options Grow Options ------------ -``-a ``, ``--age `` +``-a``, ``--age `` Define the age (in years) to set saplings to. ``value`` can be a non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at @@ -104,13 +104,13 @@ Grow Options given value, allowing them to grow larger. (Going directly to higher years will stunt tree height. It may be more desirable to grow in stages rather than all at once. Trees grow taller every 10 years.) -``-f [,...]``, ``--filter [,...]`` +``-f``, ``--filter [,...]`` Define a filter list of plant IDs to target, ignoring all other tree types. ``plant_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded tree IDs have spaces.) A numerical ID can also be used. Use ``plant create ""`` and check under ``Saplings`` for a list of valid IDs. -``-e [,...]``, ``--exclude [,...]`` +``-e``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. ``-z``, ``--zlevel`` @@ -132,14 +132,14 @@ Remove Options ``-d``, ``--dead`` Only target dead plants for removal. Can't be used without ``--shrubs`` or ``--saplings``. -``-f [,...]``, ``--filter [,...]`` +``-f``, ``--filter [,...]`` Define a filter list of plant IDs to target, ignoring all other plant types. This applies after ``--shrubs`` and ``--saplings`` are targeted, and can't be used without one of those options. ``plant_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded shrub or sapling IDs have spaces.) A numerical ID can also be used. Use ``plant create ""`` for a list of valid IDs. -``-e [,...]``, ``--exclude [,...]`` +``-e``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. ``-z``, ``--zlevel`` From 1c6625704338e7cb4ddb10ac39c53e6e873ea889 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 25 May 2024 23:59:08 -0700 Subject: [PATCH 10/20] Update regrass.rst --- docs/plugins/regrass.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 1a1b4efa5b..926d87f568 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -76,7 +76,7 @@ Options forced instead. By default, a single random grass type is selected from the world's raws. The ``--plant`` option allows a specific grass type to be specified. -``-p ``, ``--plant `` +``-p``, ``--plant `` Specify a grass type for the ``--force`` option. ``grass_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. A numerical ID can also be used. Providing an empty string with "" will print all From 70f3403432640d916e0bb440aea1613f5261b5da Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 00:03:07 -0700 Subject: [PATCH 11/20] Update plant.rst --- docs/plugins/plant.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index b0e5910760..7e6423eedd 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -83,7 +83,7 @@ Create Options Create plant even on tiles flagged ``no_grow`` and unset the flag. This flag is set on tiles that were originally boulders or pebbles, as well as on some tiles found in deserts, etc. -``-a ``, ``--age `` +``-a``, ``--age `` Set the created plant to a specific age (in years.) ``value`` can be a non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at From 01daf561e638a324ce93f703ed0230d92c296408 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 00:08:02 -0700 Subject: [PATCH 12/20] Update regrass.rst --- docs/plugins/regrass.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 926d87f568..bfd39bba6c 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -35,7 +35,7 @@ Examples Regrass all z-levels including the selected tile's z-level through z-level 90, refilling existing and depleted grass. ``regrass 0,0,100 19,19,119 --ashes --mud`` - Regrass tiles in the 20x20x20 cube defined by the coords, refilling + Regrass tiles in the 20 x 20 x 20 cube defined by the coords, refilling existing and depleted grass, and converting ashes and muddy stone (if respective blocks ever had grass.) ``regrass 10,10,100 -baudnm`` From a6c62890e3e0b2930254bb9a711888e678c5b3f2 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 00:08:19 -0700 Subject: [PATCH 13/20] Update plant.rst --- docs/plugins/plant.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 7e6423eedd..e5ebaad425 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -66,7 +66,7 @@ Examples z-level into trees. ``plant grow 0,0,100 19,19,119 -a 10`` Set the age of all saplings and trees (with their original sapling tile) - in the defined 20x20x20 cube to at least 10 years. + in the defined 20 x 20 x 20 cube to at least 10 years. ``plant remove`` Remove all invalid plants from the map. ``plant remove here -sp`` From b7f49a80f79984f45621ccf8050deadf10a1c6f9 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 14:39:29 -0700 Subject: [PATCH 14/20] Update logic for wet/dry plants * Update plant.cpp * Update plant.rst --- docs/plugins/plant.rst | 10 ++++++--- plugins/plant.cpp | 51 +++++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index e5ebaad425..81f6a069d3 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -55,8 +55,9 @@ Examples Create a Tower Cap sapling at the cursor. ``plant create ""`` List all valid shrub and sapling IDs. -``plant create 198 -a tree`` - Create an Oak sapling at the cursor, ready to mature into a tree. +``plant create 203 -c -a tree`` + Create a Willow sapling at the cursor, even away from water features, + ready to mature into a tree. ``plant create single-grain_wheat 70,70,140`` Create a Single-grain Wheat shrub at (70, 70, 140.) ``plant grow`` @@ -82,7 +83,10 @@ Create Options ``-c``, ``--force`` Create plant even on tiles flagged ``no_grow`` and unset the flag. This flag is set on tiles that were originally boulders or pebbles, as well - as on some tiles found in deserts, etc. + as on some tiles found in deserts, etc. Also allow non-``[DRY]`` plants + (e.g., willow) to grow away (3+ tiles) from water features (i.e., pools, + brooks, and rivers,) and non-``[WET]`` plants (e.g., prickle berry) to + grow near them. ``-a``, ``--age `` Set the created plant to a specific age (in years.) ``value`` can be a non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) diff --git a/plugins/plant.cpp b/plugins/plant.cpp index 2a4ece4d06..9fb0f379b6 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -76,7 +76,7 @@ struct plant_options bool create = false; // Create a plant bool grow = false; // Grow saplings into trees bool del = false; // Remove plants - bool force = false; // Create plants on no_grow + bool force = false; // Create plants on no_grow or incompatible wet/dry bool shrubs = false; // Remove shrubs bool saplings = false; // Remove saplings bool trees = false; // Remove grown trees @@ -130,6 +130,34 @@ static bool tile_muddy(const df::coord &pos) return false; } +static bool tile_watery(const df::coord &pos) +{ // Determines if plant should be in wet or dry vector + int32_t x = pos.x, y = pos.y, z = pos.z - 1; + + for (int32_t dx = -2; dx <= 2; dx++) + { // Check 5x5 area under tile, skipping corners + for (int32_t dy = -2; dy <= 2; dy++) + { + if (abs(dx) == 2 && abs(dy) == 2) + continue; // Skip corners + + auto tt = Maps::getTileType(x+dx, y+dy, z); + if (!tt) + continue; // Invalid tile + + auto mat = tileMaterial(*tt); + if (mat == tiletype_material::POOL || + mat == tiletype_material::RIVER || + tileShape(*tt) == tiletype_shape::BROOK_BED) + { + return true; + } + } + } + + return false; +} + command_result df_createplant(color_ostream &out, const df::coord &pos, const plant_options &options) { auto col = Maps::getBlockColumn((pos.x / 48)*3, (pos.y / 48)*3); @@ -203,21 +231,28 @@ command_result df_createplant(color_ostream &out, const df::coord &pos, const pl return CR_FAILURE; } + bool is_watery = tile_watery(pos); + if (!options.force) + { // Check if plant compatible with wet/dry + if (is_watery && !p_raw->flags.is_set(plant_raw_flags::WET) || + !is_watery && !p_raw->flags.is_set(plant_raw_flags::DRY)) + { + out.printerr("Can't create plant: Plant type can't grow this %s water feature!\n" + "Override with --force\n", is_watery ? "close to" : "far from"); + return CR_FAILURE; + } + } + auto plant = df::allocate(); if (p_raw->flags.is_set(plant_raw_flags::TREE)) plant->hitpoints = 400000; else { - plant->hitpoints = 100000; plant->flags.bits.is_shrub = true; + plant->hitpoints = 100000; } - // This is correct except for RICE, DATE_PALM, and underground plants - // near pool/river/brook. These have both WET and DRY flags. - // Should more properly detect if near surface water feature. - if (!p_raw->flags.is_set(plant_raw_flags::DRY)) - plant->flags.bits.watery = true; - + plant->flags.bits.watery = is_watery; plant->material = options.plant_idx; plant->pos = pos; plant->grow_counter = options.age < 0 ? 0 : options.age; From eff599dbdb7a0bd4c2c9801884e3dc27ef4758b5 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 21:27:38 -0700 Subject: [PATCH 15/20] Add "list" command to plant and regrass * Add "plant list" and "regrass --list" to replace clunky commands * Update Tags.rst: Add grass to "plants" tag description --- docs/Tags.rst | 2 +- docs/changelog.txt | 4 ++-- docs/plugins/plant.rst | 52 +++++++++++++++++++++++----------------- docs/plugins/regrass.rst | 18 ++++++++------ plugins/lua/plant.lua | 12 +++++----- plugins/lua/regrass.lua | 11 +++++---- plugins/plant.cpp | 28 +++++++++++----------- 7 files changed, 70 insertions(+), 57 deletions(-) diff --git a/docs/Tags.rst b/docs/Tags.rst index 0856bb88ed..55bfde977b 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -42,7 +42,7 @@ for the tag assignment spreadsheet. - `labors `: Tools that deal with labor assignment. - `map `: Tools that interact with the game map. - `military `: Tools that interact with the military. -- `plants `: Tools that interact with trees, shrubs, and crops. +- `plants `: Tools that interact with grass, trees, shrubs, and crops. - `stockpiles `: Tools that interact with stockpiles. - `units `: Tools that interact with units. - `workorders `: Tools that interact with workorders. diff --git a/docs/changelog.txt b/docs/changelog.txt index efb4a35fd0..2d51088b67 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -56,7 +56,7 @@ Template for new versions: ## New Features - `tweak`: ``named-codices``: display book titles instead of a material description in the stocks/trade screens -- `plant` (formerly `plants`): can now ``remove`` shrubs and saplings; ``grow`` can make mature trees older; many new command options +- `plant` (formerly `plants`): can now ``remove`` shrubs and saplings; ``list`` all valid shrub/sapling raw IDs; ``grow`` can make mature trees older; many new command options ## Fixes - `suspendmanager`: stop suspending single tile stair constructions @@ -67,7 +67,7 @@ Template for new versions: - `blueprint`: capture track carving designations in addition to already-carved tracks - `changevein`: affect connected veins even outside of the selected map block - `logistics`: new ability to automatically forbid or claim items brought to a stockpile (or a dump within a stockpile) -- `regrass`: now accepts numerical IDs for grass raws +- `regrass`: now accepts numerical IDs for grass raws; ``regrass --list`` replaces ``regrass --plant ""`` ## Documentation - `installing`: add instructions for how to use Steam DFHack with non-Steam DF (for DFHack auto-updates and cloud backups) diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 81f6a069d3..3cfbb662a6 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -5,9 +5,10 @@ plant :summary: Grow and remove shrubs or trees. :tags: adventure fort armok map plants -Grow and remove shrubs or trees. Modes are ``create``, ``grow``, and ``remove``. -``create`` allows the creation of new shrubs and saplings. ``grow`` adjusts the -age of saplings and trees, allowing them to grow instantly. ``remove`` can +Grow and remove shrubs or trees. Modes are ``list``, ``create``, ``grow``, +and ``remove``. ``list`` prints a list of all valid shrub and sapling raw IDs. +``create`` allows the creation of new shrubs and saplings. ``grow`` adjusts +the age of saplings and trees, allowing them to grow instantly. ``remove`` can remove existing shrubs and saplings. Usage @@ -19,16 +20,23 @@ Provide a mode (including a ``plant_id`` for ``create``) followed by optional in the form ``0,0,0``, without spaces. The string ``here`` can be used in place of numeric coordinates to use the position of the keyboard cursor, if active. +:: + + plant list + +Prints a list of all shrub and sapling raw IDs for use with the other modes. +Both numerical and string IDs are provided. + :: plant create [] [] Creates a new plant of the specified type at ``pos`` or the cursor position. The target must be a floor tile, consisting of soil, grass, ashes, or -non-smooth muddy stone. ``plant_id`` is not case-sensitive, but must be -enclosed in quotes if spaces exist. (No unmodded shrub or sapling IDs have -spaces.) A numerical ID can also be used. Providing an empty string with "" -will print all available IDs and skip plant creation. +non-smooth muddy (layer, obsidian, or ore) stone. ``plant_id`` is not +case-sensitive, but must be enclosed in quotes if spaces exist. (No unmodded +shrub or sapling raw IDs have spaces.) A numerical ID can be used in place of a +string. Use ``plant list`` for a list of valid IDs. :: @@ -51,10 +59,10 @@ of fully-grown trees isn't currently supported. Examples -------- +``plant list`` + List all valid shrub and sapling raw IDs. ``plant create tower_cap`` Create a Tower Cap sapling at the cursor. -``plant create ""`` - List all valid shrub and sapling IDs. ``plant create 203 -c -a tree`` Create a Willow sapling at the cursor, even away from water features, ready to mature into a tree. @@ -109,11 +117,11 @@ Grow Options will stunt tree height. It may be more desirable to grow in stages rather than all at once. Trees grow taller every 10 years.) ``-f``, ``--filter [,...]`` - Define a filter list of plant IDs to target, ignoring all other tree types. - ``plant_id`` is not case-sensitive, but must be enclosed in quotes if - spaces exist. (No unmodded tree IDs have spaces.) A numerical ID can also - be used. Use ``plant create ""`` and check under ``Saplings`` for a list - of valid IDs. + Define a filter list of plant raw IDs to target, ignoring all other tree + types. ``plant_id`` is not case-sensitive, but must be enclosed in quotes + if spaces exist. (No unmodded tree raw IDs have spaces.) A numerical ID + can be used in place of a string. Use ``plant list`` and check under + ``Saplings`` for a list of valid IDs. ``-e``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. @@ -134,15 +142,15 @@ Remove Options ``-p``, ``--saplings`` Target saplings for removal. ``-d``, ``--dead`` - Only target dead plants for removal. Can't be used without ``--shrubs`` or - ``--saplings``. + Only target dead plants for removal. Can't be used without ``--shrubs`` + or ``--saplings``. ``-f``, ``--filter [,...]`` - Define a filter list of plant IDs to target, ignoring all other plant types. - This applies after ``--shrubs`` and ``--saplings`` are targeted, and can't - be used without one of those options. ``plant_id`` is not case-sensitive, - but must be enclosed in quotes if spaces exist. (No unmodded shrub or - sapling IDs have spaces.) A numerical ID can also be used. Use - ``plant create ""`` for a list of valid IDs. + Define a filter list of plant raw IDs to target, ignoring all other plant + types. This applies after ``--shrubs`` and ``--saplings`` are targeted, + and can't be used without one of those options. ``plant_id`` is not + case-sensitive, but must be enclosed in quotes if spaces exist. (No + unmodded shrub or sapling raw IDs have spaces.) A numerical ID can be + used in place of a string. Use ``plant list`` for a list of valid IDs. ``-e``, ``--exclude [,...]`` Same as ``--filter``, but target everything except these. Cannot be used with ``--filter``. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index bfd39bba6c..66924c3c7b 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -3,7 +3,7 @@ regrass .. dfhack-tool:: :summary: Regrow surface grass and cavern moss. - :tags: adventure fort armok animals map + :tags: adventure fort armok animals map plants This command can refresh the grass (and subterranean moss) growing on your map. Operates on floors, stairs, and ramps. Also works underneath shrubs, saplings, @@ -45,9 +45,10 @@ Examples ``regrass -f`` Regrass the entire map, refilling existing and depleted grass, else filling with a randomly selected grass type if non-existent. -``regrass -p ""`` - Print all valid grass raw ids. Don't regrass. -``regrass -zf -p underlichen`` +``regrass -l`` + Print all valid grass raw IDs for use with ``--plant``. Both numerical and + string IDs are provided. This ignores all other options and skips regrass. +``regrass -zf -p 128`` Regrass the current z-level, refilling existing and depleted grass, else filling with ``underlichen`` if non-existent. ``regrass here -bnf -p "dog's tooth grass"`` @@ -59,6 +60,9 @@ Examples Options ------- +``-l``, ``--list`` + Print all available grass raw IDs and skip regrass. For use with + ``--plant``. ``-m``, ``--max`` Maxes out every grass type in the tile, giving extra grazing time. Not normal DF behavior. Tile will appear to be the first type of grass @@ -79,10 +83,10 @@ Options ``-p``, ``--plant `` Specify a grass type for the ``--force`` option. ``grass_id`` is not case-sensitive, but must be enclosed in quotes if spaces exist. A numerical - ID can also be used. Providing an empty string with "" will print all - available IDs and skip regrass. + ID can also be used. ``-a``, ``--ashes`` - Regrass tiles that've been burnt to ash. + Regrass tiles that've been burnt to ash. Usually ash must revert to soil + first before grass can grow. ``-d``, ``--buildings`` Regrass tiles under certain passable building tiles including stockpiles, planned buildings, workshops, and farms. (Farms will convert grass tiles to diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index ca5ebb5bbb..e32d50706e 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -19,9 +19,7 @@ local function find_plant_idx(s) --find plant raw index by id string end local function find_plant(s) --accept index string or match id string - if s == '' then - return -2 --will print all non-grass ids (for create) - elseif tonumber(s) then + if tonumber(s) then return argparse.nonnegativeInt(s, 'plant_id') else return find_plant_idx(s) @@ -50,7 +48,7 @@ local year_table = tree = 3, --sapling_to_tree_threshold ["1x1"] = 3, ["2x2"] = 201, --kapok, ginkgo, highwood - ["3x3"] = 401, --highwood + ["3x3"] = 401, --highwood (tower-cap is bugged) } local function plant_age(s) --tree stage or numerical value @@ -98,7 +96,9 @@ function parse_commandline(opts, pos_1, pos_2, filter_vec, args) local p1 = positionals[1] if not p1 then - qerror('Specify mode: create, grow, or remove!') + qerror('Specify mode: list, create, grow, or remove!') + elseif p1 == 'list' then + opts.plant_idx = -2 --will print all non-grass IDs elseif p1 == 'create' then opts.create = true @@ -112,7 +112,7 @@ function parse_commandline(opts, pos_1, pos_2, filter_vec, args) elseif p1 == 'remove' then opts.del = true else - qerror('Invalid mode: "'..p1..'"! Must be create, grow, or remove!') + qerror('Invalid mode: "'..p1..'"! Must be list, create, grow, or remove!') end local n = opts.create and 3 or 2 diff --git a/plugins/lua/regrass.lua b/plugins/lua/regrass.lua index 7213198848..38fd2ddacb 100644 --- a/plugins/lua/regrass.lua +++ b/plugins/lua/regrass.lua @@ -27,9 +27,10 @@ local function find_grass(s) --accept index string or match id string end function parse_commandline(opts, pos_1, pos_2, args) - local plant_str + local plant_str, do_list local positionals = argparse.processArgsGetopt(args, { + {'l', 'list', handler=function() do_list = true end}, {'m', 'max', handler=function() opts.max_grass = true end}, {'n', 'new', handler=function() opts.new_grass = true end}, {'a', 'ashes', handler=function() opts.ashes = true end}, @@ -41,10 +42,10 @@ function parse_commandline(opts, pos_1, pos_2, args) {'p', 'plant', hasArg=true, handler=function(optarg) plant_str = optarg end}, }) - if plant_str then - if plant_str == '' then --will print all grass ids - opts.forced_plant = -2 - elseif not opts.force then + if do_list then + opts.forced_plant = -2 --will print all grass IDs + elseif plant_str then + if not opts.force then qerror('Use of --plant without --force!') else opts.forced_plant = find_grass(plant_str) diff --git a/plugins/plant.cpp b/plugins/plant.cpp index 9fb0f379b6..d4dcc39339 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -85,7 +85,7 @@ struct plant_options bool zlevel = false; // Operate on entire z-levels bool dry_run = false; // Don't actually grow or remove anything - int32_t plant_idx = -1; // Plant raw index of plant to create; -2 means print all non-grass ids + int32_t plant_idx = -1; // Plant raw index of plant to create; -2 means print all non-grass IDs int32_t age = -1; // Set plant to this age for grow/create; -1 for default static struct_identity _identity; @@ -227,7 +227,7 @@ command_result df_createplant(color_ostream &out, const df::coord &pos, const pl auto p_raw = vector_get(world->raws.plants.all, options.plant_idx); if (!p_raw) { - out.printerr("Plant raw not found!\n"); + out.printerr("Can't create plant: Plant raw not found!\n"); return CR_FAILURE; } @@ -525,6 +525,18 @@ command_result df_plant(color_ostream &out, vector ¶meters) { return CR_WRONG_USAGE; } + else if (options.plant_idx == -2) + { // Print all non-grass raw IDs ("plant list") + out.print("--- Shrubs ---\n"); + for (auto p_raw : world->raws.plants.bushes) + out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + + out.print("\n--- Saplings ---\n"); + for (auto p_raw : world->raws.plants.trees) + out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + + return CR_OK; + } else if (options.force && !options.create) { out.printerr("Can't use --force without create!\n"); @@ -552,18 +564,6 @@ command_result df_plant(color_ostream &out, vector ¶meters) out.printerr("--trees not implemented!\n"); return CR_FAILURE; } - else if (options.plant_idx == -2) - { // Print all non-grass raw ids - out.print("--- Shrubs ---\n"); - for (auto p_raw : world->raws.plants.bushes) - out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); - - out.print("\n--- Saplings ---\n"); - for (auto p_raw : world->raws.plants.trees) - out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); - - return CR_OK; - } DEBUG(log, out).print("pos_1 = (%d, %d, %d)\npos_2 = (%d, %d, %d)\n", pos_1.x, pos_1.y, pos_1.z, pos_2.x, pos_2.y, pos_2.z); From f249a4e5eb3685ad7feec42421e2be92de64b5bc Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 21:54:41 -0700 Subject: [PATCH 16/20] Update plant.lua --- plugins/lua/plant.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index e32d50706e..ce1df5c265 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -61,11 +61,7 @@ local function plant_age(s) --tree stage or numerical value if n then n = math.min(n, 1250) - if n > 0 then - return 40320*n-1 --years to tens of ticks - 1 - else - return 0 --don't subtract 1 - end + return math.max(0, 40320*n-1) --years to tens of ticks - 1; correct for n = 0 end qerror('Invalid age: "'..s..'"') From ee8c85a25671e33586da3b9991e5ea9fb963ac4a Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 26 May 2024 21:55:27 -0700 Subject: [PATCH 17/20] Update plant.lua --- plugins/lua/plant.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index ce1df5c265..379e5bb159 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -60,7 +60,7 @@ local function plant_age(s) --tree stage or numerical value end if n then - n = math.min(n, 1250) + n = math.min(n, 1250) --grow_counter stops at 1250 years return math.max(0, 40320*n-1) --years to tens of ticks - 1; correct for n = 0 end From 2d38adce0cf186f446ac8aef03d3c7e7fea182e0 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 27 May 2024 14:18:45 -0700 Subject: [PATCH 18/20] Changes to docs, hyphenate dry-run * Update regrass.rst * Update plant.rst * Update plant.lua * Update plant.cpp * Update changelog.txt --- docs/changelog.txt | 4 ++-- docs/plugins/plant.rst | 37 ++++++++++++++++++------------------- docs/plugins/regrass.rst | 6 +++--- plugins/lua/plant.lua | 2 +- plugins/plant.cpp | 2 +- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2d51088b67..d34d7416a3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,11 +52,11 @@ Template for new versions: # Future ## New Tools -- `plant` (formerly `plants`): (reinstated) tool for creating/growing/removing plants +- `plant`: (reinstated) tool for creating/growing/removing plants ## New Features - `tweak`: ``named-codices``: display book titles instead of a material description in the stocks/trade screens -- `plant` (formerly `plants`): can now ``remove`` shrubs and saplings; ``list`` all valid shrub/sapling raw IDs; ``grow`` can make mature trees older; many new command options +- `plant`: can now ``remove`` shrubs and saplings; ``list`` all valid shrub/sapling raw IDs; ``grow`` can make mature trees older; many new command options ## Fixes - `suspendmanager`: stop suspending single tile stair constructions diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst index 3cfbb662a6..260524ada4 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plant.rst @@ -50,11 +50,10 @@ if they are blocked by another tree. plant remove [ []] [] -Remove plants from the map (or area defined by ``pos`` arguments.) By default, -only removes invalid plants that exist on non-plant tiles (due to `Bug 12868 -`_.) The ``--shrubs`` -and ``--saplings`` options allow normal plants to be targeted instead. Removal -of fully-grown trees isn't currently supported. +Remove plants from the map (or area defined by ``pos`` arguments). By default, +it only removes invalid plants that exist on non-plant tiles (due to +:bug:`12868`). The ``--shrubs`` and ``--saplings`` options allow normal plants +to be targeted instead. Removal of fully-grown trees isn't currently supported. Examples -------- @@ -67,7 +66,7 @@ Examples Create a Willow sapling at the cursor, even away from water features, ready to mature into a tree. ``plant create single-grain_wheat 70,70,140`` - Create a Single-grain Wheat shrub at (70, 70, 140.) + Create a Single-grain Wheat shrub at (70, 70, 140). ``plant grow`` Attempt to grow all saplings on the map into trees. ``plant grow -z -f maple,200,sand_pear`` @@ -93,12 +92,12 @@ Create Options flag is set on tiles that were originally boulders or pebbles, as well as on some tiles found in deserts, etc. Also allow non-``[DRY]`` plants (e.g., willow) to grow away (3+ tiles) from water features (i.e., pools, - brooks, and rivers,) and non-``[WET]`` plants (e.g., prickle berry) to + brooks, and rivers), and non-``[WET]`` plants (e.g., prickle berry) to grow near them. ``-a``, ``--age `` - Set the created plant to a specific age (in years.) ``value`` can be a - non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) - ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at + Set the created plant to a specific age (in years). ``value`` can be a + non-negative integer, or one of the strings ``tree``/``1x1`` (3 years), + ``2x2`` (201 years), or ``3x3`` (401 years). ``value`` will be capped at 1250. Defaults to 0 if option is unused. Only a few tree types grow wider than 1x1, but many may grow taller. (Going directly to higher years will stunt height. It may be more desirable to instead use ``plant grow`` in @@ -109,8 +108,8 @@ Grow Options ``-a``, ``--age `` Define the age (in years) to set saplings to. ``value`` can be a - non-negative integer, or one of the strings ``tree``/``1x1`` (3 years,) - ``2x2`` (201 years,) or ``3x3`` (401 years.) ``value`` will be capped at + non-negative integer, or one of the strings ``tree``/``1x1`` (3 years), + ``2x2`` (201 years), or ``3x3`` (401 years). ``value`` will be capped at 1250. Defaults to 3 if option is unused. If a ``value`` larger than 3 is used, it will make sure even fully-grown trees have an age of at least the given value, allowing them to grow larger. (Going directly to higher years @@ -127,10 +126,10 @@ Grow Options with ``--filter``. ``-z``, ``--zlevel`` Operate on a range of z-levels instead of default targeting. Will do all - z-levels between ``pos`` arguments if both are given (instead of cuboid,) - z-level of first ``pos`` if one is given (instead of single tile,) else - z-level of current view if no ``pos`` is given (instead of entire map.) -``-n``, ``--dryrun`` + z-levels between ``pos`` arguments if both are given (instead of cuboid), + z-level of first ``pos`` if one is given (instead of single tile), else + z-level of current view if no ``pos`` is given (instead of entire map). +``-n``, ``--dry-run`` Don't actually grow plants. Just print the total number of plants that would be grown. @@ -156,9 +155,9 @@ Remove Options with ``--filter``. ``-z``, ``--zlevel`` Operate on a range of z-levels instead of default targeting. Will do all - z-levels between ``pos`` arguments if both are given (instead of cuboid,) - z-level of first ``pos`` if one is given (instead of single tile,) else - z-level of current view if no ``pos`` is given (instead of entire map.) + z-levels between ``pos`` arguments if both are given (instead of cuboid), + z-level of first ``pos`` if one is given (instead of single tile), else + z-level of current view if no ``pos`` is given (instead of entire map). ``-n``, ``--dryrun`` Don't actually remove plants. Just print the total number of plants that would be removed. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 66924c3c7b..525a20c6a1 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -37,7 +37,7 @@ Examples ``regrass 0,0,100 19,19,119 --ashes --mud`` Regrass tiles in the 20 x 20 x 20 cube defined by the coords, refilling existing and depleted grass, and converting ashes and muddy stone (if - respective blocks ever had grass.) + respective blocks ever had grass). ``regrass 10,10,100 -baudnm`` Regrass the block that contains the given coord; converting ashes, muddy stone, and tiles under buildings; adding all compatible grass types, and @@ -61,8 +61,8 @@ Options ------- ``-l``, ``--list`` - Print all available grass raw IDs and skip regrass. For use with - ``--plant``. + List all available grass raw IDs that you can later pass to the ``--plant`` + option. The map will not be affected when running with this option. ``-m``, ``--max`` Maxes out every grass type in the tile, giving extra grazing time. Not normal DF behavior. Tile will appear to be the first type of grass diff --git a/plugins/lua/plant.lua b/plugins/lua/plant.lua index 379e5bb159..2002e787f8 100644 --- a/plugins/lua/plant.lua +++ b/plugins/lua/plant.lua @@ -83,7 +83,7 @@ function parse_commandline(opts, pos_1, pos_2, filter_vec, args) opts.filter_ex = true build_filter(filter_vec, optarg) end}, {'z', 'zlevel', handler=function() opts.zlevel = true end}, - {'n', 'dryrun', handler=function() opts.dry_run = true end}, + {'n', 'dry-run', handler=function() opts.dry_run = true end}, }) if #positionals > 3 then diff --git a/plugins/plant.cpp b/plugins/plant.cpp index d4dcc39339..5514a0b710 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -578,7 +578,7 @@ command_result df_plant(color_ostream &out, vector ¶meters) { // Check improper options and plant raw if (options.zlevel || options.dry_run) { - out.printerr("Cannot use --zlevel or --dryrun with create!\n"); + out.printerr("Cannot use --zlevel or --dry-run with create!\n"); return CR_FAILURE; } else if (!filter.empty()) From 5ced7103e020757e189fc5ac231d9ef1a0dde8f5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 6 Jun 2024 15:06:57 -0700 Subject: [PATCH 19/20] adapt to structure canonicalization --- plugins/plant.cpp | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/plugins/plant.cpp b/plugins/plant.cpp index 5514a0b710..93ea4ee3e8 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -158,6 +158,16 @@ static bool tile_watery(const df::coord &pos) return false; } +static bool plant_shrub(const df::plant &plant) +{ + return plant.type == df::plant_type::DRY_PLANT || plant.type == df::plant_type::WET_PLANT; +} + +static bool plant_shrub(const df::plant *plant) +{ + return plant_shrub(*plant); +} + command_result df_createplant(color_ostream &out, const df::coord &pos, const plant_options &options) { auto col = Maps::getBlockColumn((pos.x / 48)*3, (pos.y / 48)*3); @@ -244,38 +254,42 @@ command_result df_createplant(color_ostream &out, const df::coord &pos, const pl } auto plant = df::allocate(); + bool is_shrub = true; if (p_raw->flags.is_set(plant_raw_flags::TREE)) + { + plant->type = is_watery ? df::plant_type::WET_TREE : df::plant_type::DRY_TREE; plant->hitpoints = 400000; + is_shrub = false; + } else { - plant->flags.bits.is_shrub = true; + plant->type = is_watery ? df::plant_type::WET_PLANT : df::plant_type::DRY_PLANT; plant->hitpoints = 100000; } - plant->flags.bits.watery = is_watery; plant->material = options.plant_idx; plant->pos = pos; plant->grow_counter = options.age < 0 ? 0 : options.age; plant->update_order = rand() % 10; world->plants.all.push_back(plant); - if (plant->flags.bits.is_shrub) + if (is_shrub) { - if (plant->flags.bits.watery) + if (is_watery) world->plants.shrub_wet.push_back(plant); else world->plants.shrub_dry.push_back(plant); } else { - if (plant->flags.bits.watery) + if (is_watery) world->plants.tree_wet.push_back(plant); else world->plants.tree_dry.push_back(plant); } col->plants.push_back(plant); - if (plant->flags.bits.is_shrub) + if (is_shrub) *tt = tiletype::Shrub; else *tt = tiletype::Sapling; @@ -304,7 +318,7 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt int grown = 0, grown_trees = 0; for (auto plant : world->plants.all) { - if (plant->flags.bits.is_shrub) + if (plant_shrub(plant)) continue; // Shrub else if (!bounds.containsPos(plant->pos)) continue; // Outside cuboid @@ -352,10 +366,13 @@ static bool uncat_plant(df::plant *plant) { // Remove plant from extra vectors vector *vec = NULL; - if (plant->flags.bits.is_shrub) - vec = plant->flags.bits.watery ? &world->plants.shrub_wet : &world->plants.shrub_dry; - else - vec = plant->flags.bits.watery ? &world->plants.tree_wet : &world->plants.tree_dry; + switch (plant->type) + { + case df::plant_type::DRY_PLANT: vec = &world->plants.shrub_dry; break; + case df::plant_type::WET_PLANT: vec = &world->plants.shrub_wet; break; + case df::plant_type::DRY_TREE: vec = &world->plants.tree_dry; break; + case df::plant_type::WET_TREE: vec = &world->plants.tree_wet; break; + } for (size_t i = vec->size(); i-- > 0;) { // Not sorted, but more likely near end @@ -441,7 +458,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl continue; // Not removing living /*else if (plant->tree_info && !options.trees) continue; // Not removing trees*/ - else if (plant.flags.bits.is_shrub) + else if (plant_shrub(plant)) { if (!options.shrubs) continue; // Not removing shrubs @@ -458,7 +475,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl bool bad_tt = false; if (tt) { - if (plant.flags.bits.is_shrub) + if (plant_shrub(plant)) { if (tileShape(*tt) != tiletype_shape::SHRUB) { From ef9ee5cb4d39dfbe1f55ea14997095239085c1bb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 6 Jun 2024 15:14:46 -0700 Subject: [PATCH 20/20] fix gcc syntax warning --- plugins/plant.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/plant.cpp b/plugins/plant.cpp index 93ea4ee3e8..741ff00892 100644 --- a/plugins/plant.cpp +++ b/plugins/plant.cpp @@ -244,8 +244,8 @@ command_result df_createplant(color_ostream &out, const df::coord &pos, const pl bool is_watery = tile_watery(pos); if (!options.force) { // Check if plant compatible with wet/dry - if (is_watery && !p_raw->flags.is_set(plant_raw_flags::WET) || - !is_watery && !p_raw->flags.is_set(plant_raw_flags::DRY)) + if ((is_watery && !p_raw->flags.is_set(plant_raw_flags::WET)) || + (!is_watery && !p_raw->flags.is_set(plant_raw_flags::DRY))) { out.printerr("Can't create plant: Plant type can't grow this %s water feature!\n" "Override with --force\n", is_watery ? "close to" : "far from");