Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -64,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)
Expand Down
154 changes: 154 additions & 0 deletions docs/plugins/plant.rst
Original file line number Diff line number Diff line change
@@ -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 <plant_id> [<pos>] [<options>]

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 <value>``, ``--age <value>``
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 [<pos> [<pos>]] [<options>]

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 <value>``, ``--age <value>``
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 <list>``, ``--filter <list>``
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 <list>``, ``--exclude <list>``
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 [<pos> [<pos>]] [<options>]

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
<https://dwarffortressbugtracker.com/view.php?id=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 <list>``, ``--filter <list>``
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 <list>``, ``--exclude <list>``
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.
33 changes: 0 additions & 33 deletions docs/plugins/plants.rst

This file was deleted.

9 changes: 5 additions & 4 deletions docs/plugins/regrass.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ Options
specified.
``-p <grass_id>``, ``--plant <grass_id>``
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``
Expand All @@ -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
--------
Expand Down
2 changes: 1 addition & 1 deletion plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
127 changes: 127 additions & 0 deletions plugins/lua/plant.lua
Original file line number Diff line number Diff line change
@@ -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
Loading