pax_global_header00006660000000000000000000000064140053702460014513gustar00rootroot0000000000000052 comment=2f26fb76459c587868199160b9d7b5d6d7852e50 Minetest-WorldEdit-1.3/000077500000000000000000000000001400537024600150415ustar00rootroot00000000000000Minetest-WorldEdit-1.3/.gitignore000066400000000000000000000000041400537024600170230ustar00rootroot00000000000000*~ Minetest-WorldEdit-1.3/ChatCommands.md000066400000000000000000000361111400537024600177260ustar00rootroot00000000000000Chat Commands ------------- For more information, see the [README](README.md). Many commands also have shorter names that can be typed faster. For example, if we wanted to use `//move ? 5`, we could instead type `//m ? 5`. All shortened names are listed below: | Short Name | Original Name | |:-----------|:-------------------| | `//i` | `//inspect` | | `//rst` | `//reset` | | `//mk` | `//mark` | | `//umk` | `//unmark` | | `//1` | `//pos1` | | `//2` | `//pos2` | | `//fp` | `//fixedpos` | | `//v` | `//volume` | | `//s` | `//set` | | `//r` | `//replace` | | `//ri` | `//replaceinverse` | | `//hcube` | `//hollowcube` | | `//hspr` | `//hollowsphere` | | `//spr` | `//sphere` | | `//hdo` | `//hollowdome` | | `//do` | `//dome` | | `//hcyl` | `//hollowcylinder` | | `//cyl` | `//cylinder` | | `//hpyr` | `//hollowpyramid` | | `//pyr` | `//pyramid` | ### `//about` Get information about the WorldEdit mod. //about ### `//help [all/]` Get help for WorldEdit commands. `all` shows all WorldEdit commands, `` the help text for the given command. //help //help all //help hollowpyramid ### `//inspect [on/off/1/0/true/false/yes/no/enable/disable]` Enable or disable node inspection. //inspect on //inspect off //inspect ### `//reset` Reset the region so that it is empty. //reset ### `//mark` Show markers at the region positions. //mark ### `//unmark` Hide markers if currently shown. //unmark ### `//pos1` Set WorldEdit region position 1 to the player's location. //pos1 ### `//pos2` Set WorldEdit region position 2 to the player's location. //pos2 ### `//p set/set1/set2/get` Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or print the current WorldEdit region. //p set //p set1 //p set2 //p get ### `//fixedpos set1/set2 ` Set the WorldEdit region position 1 or 2 to the position (``, ``, ``). //fixedpos set1 0 0 0 //fixedpos set1 -30 5 28 //fixedpos set2 1004 -200 432 ### `//volume` Display the volume of the current WorldEdit region. //volume ### `//deleteblocks` Delete the MapBlocks (16x16x16 units) that contain the selected region. This means that mapgen will be invoked for that area. As only whole MapBlocks get removed, the deleted area is usually larger than the selected one. Also, mapgen can trigger mechanisms like mud reflow or cavegen, which affects nodes (up to 112 nodes away) outside the MapBlock, so dont use this near buildings. Note that active entities are not part of a MapBlock and do not get deleted. //deleteblocks ### `//set ` Set the current WorldEdit region to ``. //set air //set cactus //set Blue Lightstone //set dirt with grass ### `//param2 ` Set the param2 value of all nodes in the current WorldEdit region to ``. //param2 8 ### `//mix [count1] [count2] ...` Fill the current WorldEdit region with a random mix of ``, ``, `...`. Weightings can be optionally specified via the `[count1]`, `[count2]`, `...` parameters after a node name. //mix air //mix cactus stone glass sandstone //mix Bronze //mix default:cobble air //mix stone 3 dirt 2 //mix cobblestone 8 stoneblock 2 stonebrick ### `//replace ` Replace all instances of `` with `` in the current WorldEdit region. //replace Cobblestone air //replace lightstone_blue glass //replace dirt Bronze Block //replace mesecons:wire_00000000_off flowers:flower_tulip ### `//replaceinverse ` Replace all nodes other than `` with `` in the current WorldEdit region. //replaceinverse Cobblestone air //replaceinverse flowers:flower_waterlily glass //replaceinverse dirt Bronze Block //replaceinverse mesecons:wire_00000000_off flowers:flower_tulip ### `//hollowcube ` Adds a hollow cube with its ground level centered at WorldEdit position 1 with dimensions `` x `` x ``, composed of ``. //hollowcube 6 5 6 Diamond Block ### `//cube ` Adds a cube with its ground level centered at WorldEdit position 1 with dimensions `` x `` x ``, composed of ``. //cube 6 5 6 Diamond Block //cube 7 2 1 default:cobble ### `//hollowsphere ` Add hollow sphere centered at WorldEdit position 1 with radius ``, composed of ``. //hollowsphere 5 Diamond Block //hollowsphere 12 glass //hollowsphere 17 mesecons:wire_00000000_off ### `//sphere ` Add sphere centered at WorldEdit position 1 with radius ``, composed of ``. //sphere 5 Diamond Block //sphere 12 glass //sphere 17 mesecons:wire_00000000_off ### `//hollowdome ` Add hollow dome centered at WorldEdit position 1 with radius ``, composed of ``. //hollowdome 5 Diamond Block //hollowdome -12 glass //hollowdome 17 mesecons:wire_00000000_off ### `//dome ` Add dome centered at WorldEdit position 1 with radius ``, composed of ``. //dome 5 Diamond Block //dome -12 glass //dome 17 mesecons:wire_00000000_off ### `//hollowcylinder x/y/z/? [radius2] ` Add hollow cylinder at WorldEdit position 1 along the given axis with length ``, base radius `` (and top radius `[radius2]`), composed of ``. Despite its name this command allows you to create cones (`radius2` = 0) as well as any shapes inbetween (0 < `radius2` < `radius1`). Swapping `radius1` and `radius2` will create the same object but upside-down. //hollowcylinder x +5 8 Bronze Block //hollowcylinder y 28 10 glass //hollowcylinder z -12 3 mesecons:wire_00000000_off //hollowcylinder ? 2 4 default:stone //hollowcylinder y 10 10 0 walls:cobble //hollowcylinder x 6 0 5 Dirt //hollowcylinder z 20 10 20 default:desert_stone ### `//cylinder x/y/z/? [radius2] ` Add cylinder at WorldEdit position 1 along the given axis with length ``, base radius `` (and top radius `[radius2]`), composed of ``. Can also create shapes other than cylinders, e.g. cones (see documentation above). //cylinder x +5 8 Bronze Block //cylinder y 28 10 glass //cylinder z -12 3 mesecons:wire_00000000_off //cylinder ? 2 4 default:stone //cylinder y 10 10 0 walls:cobble //cylinder x 6 0 5 Dirt //cylinder z 20 10 20 default:desert_stone ### `//hollowpyramid x/y/z/? ` Add hollow pyramid centered at WorldEdit position 1 along the given axis with height `` composed of ``. //hollowpyramid x 8 Diamond Block //hollowpyramid y -5 glass //hollowpyramid z 2 mesecons:wire_00000000_off //hollowpyramid ? 12 mesecons:wire_00000000_off ### `//pyramid x/y/z/? ` Add pyramid centered at WorldEdit position 1 along the given axis with height `` composed of ``. //pyramid x 8 Diamond Block //pyramid y -5 glass //pyramid z 2 mesecons:wire_00000000_off //pyramid ? 12 mesecons:wire_00000000_off ### `//spiral ` Add spiral centered at WorldEdit position 1 with side length ``, height ``, space between walls ``, composed of ``. //spiral 20 5 3 Diamond Block //spiral 5 2 1 glass //spiral 7 1 5 mesecons:wire_00000000_off ### `//copy x/y/z/? ` Copy the current WorldEdit region along the given axis by `` nodes. //copy x 15 //copy y -7 //copy z +4 //copy ? 8 ### `//move x/y/z/? ` Move the current WorldEdit positions and region along the given axis by `` nodes. //move x 15 //move y -7 //move z +4 //move ? -1 ### `//stack x/y/z/? ` Stack the current WorldEdit region along the given axis `` times. //stack x 3 //stack y -1 //stack z +5 //stack ? 12 ### `//stack2 ` Stack the current WorldEdit region `` times by offset ``, ``, ``. //stack2 5 3 8 2 //stack2 1 -1 -1 -1 ### `//stretch ` Scale the current WorldEdit positions and region by a factor of ``, ``, `` along the X, Y, and Z axes, respectively, with position 1 as the origin. //stretch 2 2 2 //stretch 1 2 1 //stretch 10 20 1 ### `//transpose x/y/z/? x/y/z/?` Transpose the current WorldEdit positions and region along given axes. //transpose x y //transpose y z //transpose ? y ### `//flip x/y/z/?` Flip the current WorldEdit region along the given axis. //flip x //flip ? ### `//rotate x/y/z/? ` Rotate the current WorldEdit positions and region along the given axis by angle `` (90 degree increment). //rotate x 90 //rotate y 180 //rotate z 270 //rotate ? -90 ### `//orient ` Rotate oriented nodes in the current WorldEdit region around the Y axis by angle `` (90 degree increment) //orient 90 //orient 180 //orient 270 //orient -90 ### `//fixlight` Fixes the lighting in the current WorldEdit region. //fixlight ### `//drain` Removes any fluid node within the current WorldEdit region. //drain ### `//clearcut` Removes any plant, tree or foilage-like nodes in the selected region. The idea is to remove anything that isn't part of the terrain, leaving a "natural" empty space ready for building. //clearcut ### `//hide` Hide all nodes in the current WorldEdit region non-destructively. //hide ### `//suppress ` Suppress all `` in the current WorldEdit region non-destructively. //suppress Diamond Block //suppress glass //suppress mesecons:wire_00000000_off ### `//highlight ` Highlight `` in the current WorldEdit region by hiding everything else non-destructively. //highlight Diamond Block //highlight glass //highlight mesecons:wire_00000000_off ### `//restore` Restores nodes hidden with WorldEdit in the current WorldEdit region. //restore ### `//save ` Save the current WorldEdit region to "(world folder)/schems/``.we". //save some random filename //save huge_base ### `//allocate ` Set the region defined by nodes from "(world folder)/schems/``.we" as the current WorldEdit region. //allocate some random filename //allocate huge_base ### `//load ` Load nodes from "(world folder)/schems/``.we" with position 1 of the current WorldEdit region as the origin. //load some random filename //load huge_base ### `//lua ` Executes `` as a Lua chunk in the global namespace. //lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0} //lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90) ### `//luatransform ` Executes `` as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region. //luatransform minetest.swap_node(pos, {name="default:stone"}) //luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end ### `//mtschemcreate ` Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/``.mts". //mtschemcreate some random filename //mtschemcreate huge_base ### `//mtschemplace ` Load nodes from "(world folder)/schems/``.mts" with position 1 of the current WorldEdit region as the origin. //mtschemplace some random filename //mtschemplace huge_base ### `//mtschemprob start/finish/get` After using `//mtschemprob start` all nodes punched will bring up a text field where a probablity can be entered. This mode can be left with `//mtschemprob finish`. `//mtschemprob get` will display the probabilities saved for the nodes. //mtschemprob get ### `//clearobjects` Clears all objects within the WorldEdit region. //clearobjects ### `//shift x/y/z/?/up/down/left/right/front/back [+/-]` Shifts the selection area by `[+|-]` without moving its contents. The shifting axis can be absolute (`x/y/z`) or relative (`up/down/left/right/front/back`). //shift left 5 ### `//expand [+/-]x/y/z/?/up/down/left/right/front/back [reverse amount]` Expands the selection by `` in the selected absolute or relative axis. If specified, the selection can be expanded in the opposite direction over the same axis by `[reverse amount]`. //expand right 7 5 ### `//contract [+/-]x/y/z/?/up/down/left/right/front/back [reverse amount]` Contracts the selection by `` in the selected absolute or relative axis. If specified, the selection can be contracted in the opposite direction over the same axis by `[reverse amount]`. //expand right 7 5 ### `//outset [h/v] ` Expands the selection in all directions by ``. If specified, the selection can be expanded horizontally in the x and z axes using `h` or vertically in the y axis using `v`. //outset v 5 ### `//inset [h/v] ` Contracts the selection in all directions by ``. If specified, the selection can be contracted horizontally in the x and z axes using `h` or vertically in the y axis using `v`. //inset h 5 ### `//brush none/( [parameters])` Assigns the given `` to the currently held brush item, it will be ran with the first pointed solid node (as determined via raycast) as WorldEdit position 1 when using that specific brush item. Passing `none` instead clears the command assigned to the currently held brush item. Note that this functionality requires the `worldedit_brush` mod enabled. //brush cube 8 8 8 Cobblestone //brush spr 12 glass //brush none ### `//cubeapply /( ) [parameters]` Selects a cube with side length of `` around the WorldEdit position 1 and runs the given `` on the newly selected region. If ``, `` and `` are given, they instead specify the length of the cuboid in X, Y, Z direction. This is mostly useful for brushes since it allows commands such as `//replace` to be ran, but it can also be used standalone. //cubeapply 10 replaceinverse air default:water_source //brush cubeapply 15 drain //brush cubeapply 12 3 12 drain //brush cubeapply 1 deleteblocks Minetest-WorldEdit-1.3/LICENSE.txt000066400000000000000000001033301400537024600166640ustar00rootroot00000000000000 GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . Minetest-WorldEdit-1.3/README.md000066400000000000000000000247621400537024600163330ustar00rootroot00000000000000WorldEdit v1.3 ============== The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more. For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) at the Minetest forums. # New users should see the [tutorial](Tutorial.md). ![Screenshot](http://i.imgur.com/lwhodrv.png) Installing ---------- There is a nice installation guide over at the [Minetest Wiki](http://wiki.minetest.com/wiki/Installing_mods). Here is a short summary: 1. Download the mod from the [official releases page](https://github.com/Uberi/Minetest-WorldEdit/releases). The download links are labelled "Source Code". If you are using Windows, you'll want to download the ZIP version. 2. You should have a file named `Minetest-WorldEdit-x.x.zip`. 3. Extract this file using your archiver of choice. If you are using Windows, open the ZIP file and move the folder inside to a safe place outside of the ZIP file. 4. Make sure that you now have a folder with a file named README.md inside it. If you just have another folder inside this folder, use the nested folder instead. 5. Move this folder into the `MINETEST_FOLDER/mods` folder, where `MINETEST_FOLDER` is the folder Minetest is located in. 6. Open Minetest to a world selection screen. 7. Select a world you want to use WorldEdit in by left clicking on it once and press the **Configure** button. 8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button. 9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too. If you are having trouble, try asking for help in the [IRC channel](https://webchat.freenode.net/?channels=#minetest) (faster but may not always have helpers online) or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help). Usage ----- WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "T" key and open the chat console with the "F10" key. WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privilege. This is available by default in singleplayer, but in multiplayer the permission must be explicitly given by someone with the right credentials, using the following chat command: `/grant worldedit`. This privilege can later be removed using the following chat command: `/revoke worldedit`. Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege. This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on. Give this privilege only to people you trust with your computer. For in-game information about these commands, type `//help ` in the chat. For example, to learn more about the `//copy` command, simply type `//help copy` to display information relevant to copying a region. Interface --------- WorldEdit is accessed in-game in two main ways. The GUI adds a screen to each player's inventory that gives access to various WorldEdit functions. The [tutorial](Tutorial.md) may be helpful in learning to use it. The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md). Compatibility ------------- This mod supports Minetest versions 5.0 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported. WorldEdit works quite well with other mods and does not have any known mod conflicts. WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/tree/master/mods/sfinv) (included in minetest_game), [Unified Inventory](https://forum.minetest.net/viewtopic.php?t=12767), [Inventory++](https://forum.minetest.net/viewtopic.php?id=6204) or [Smart Inventory](https://forum.minetest.net/viewtopic.php?t=16597). If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them. WorldEdit API ------------- WorldEdit exposes all significant functionality in a simple Lua interface. Adding WorldEdit as a dependency to your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces and map creation. AGPLv3 compatible mods may further include WorldEdit files in their own mods. This can be useful if a modder wishes to completely avoid any dependency on WorldEdit. Note that it is required to give credit to the authors in this case. This API is documented in the [WorldEdit API Reference](WorldEdit%20API.md). Axes ---- The coordinate system is the same as that used by Minetest; positive Y is upwards, positive X is rightwards, and positive Z is forwards, if a player is facing North (positive Z axis). When an axis is specified in a WorldEdit chat command, it is specified as one of the following values: `x`, `y`, `z`, or `?`. In the GUI, there is a dropdown menu for this purpose. The "Look direction" option has the same effect as `?` does in chat commands. The value `?` represents the axis the player is currently facing. If the player is facing more than one axis, the axis the player face direction is closest to will be used. Nodes ----- Node names are required for many types of commands that identify or modify specific types of nodes. They can be specified in a number of ways. First, by description - the tooltip that appears when hovering over the item in an inventory. This is case insensitive and includes values such as "Cobblestone" and "bronze block". Note that certain commands (namely, `//replace` and `//replaceinverse`) do not support descriptions that contain spaces in the `` field. Second, by name - the node name that is defined by code, but without the mod name prefix. This is case sensitive and includes values such as "piston_normal_off" and "cactus". If there are multiple possible nodes (such as "a:celery" and "b:celery"), one is chosen in no particular order. Finally, by full name - the unambiguous identifier of the node, prefixes and all. This is case sensitive and includes values such as "default:stone" and "mesecons:wire_00000000_off". The node name "air" can be used anywhere a normal node name can and acts as a blank node. This is useful for clearing or removing nodes. For example, `//set air` would remove all the nodes in the current WorldEdit region. Similarly, `//sphere 10 air`, when WorldEdit position 1 underground, would dig a large sphere out of the ground. Regions ------- Most WorldEdit commands operate on regions. Regions are a set of two positions that define a 3D cuboid. They are local to each player and chat commands affect only the region for the player giving the commands. Each positions together define two opposing corners of the cube. With two opposing corners it is possible to determine both the location and dimensions of the region. Regions are not saved between server restarts. They start off as empty regions and cannot be used with most WorldEdit commands until they are set to valid values. Markers ------- Entities are used to mark the location of the WorldEdit regions. They appear as boxes containing the number 1 or 2 and represent the first and second position of the WorldEdit region, respectively. To remove the entities, simply punch them. This does not reset the positions themselves. Schematics ---------- WorldEdit supports two different types of schematics. The first is the WorldEdit Schematic format, with the file extension ".we", and in some older versions, ".wem". There have been several previous versions of the WorldEdit Schematic format, but WorldEdit is capable of loading any past versions, and will always support them - there is no need to worry about schematics becoming obsolete. As of version 5, WorldEdit schematics include a header. The header is seperated from the content by a colon (`:`). It may contain fields seperated by commas (`,`). Currently only one field is used, which contains the version as an ASCII decimal. The current version of the WorldEdit Schematic format is essentially an array of node data tables in Lua 5.1 table syntax preceded by a header. Specifically it looks like this: 5:return { { y = , x = , z = , name = , param1 = , param2 = , meta = , }, <...> } The ordering of the values and minor aspects of the syntax, such as trailing commas or newlines, are not guaranteed to stay the same in future versions. The WorldEdit Schematic format is accessed via the WorldEdit API, or WorldEdit serialization chat commands such as `//serialize` and `//deserialize`. The second is the Minetest Schematic format (MTS). The details of this format may be found in the Minetest documentation and are out of the scope of this document. Access to this format is done via specialized MTS commands such as `//mtschemcreate` and `//mtschemplace`. Authors ------- WorldEdit would not be possible without the contributions of many developers and designers. Below, they are listed alphabetically: Alexander Weber ANAND beyondlimits Carter Kolwey cornernote Cy Daniel Sosa electricface est31 Eugen Wesseloh h3ndrik HybridDog Isidor Zeuner Jean-Patrick Guerrero Joseph Pickard kaeza kilbith KodexKy Kyle MT-Modder Niwla23 Panquesito7 Pedro Gimeno Rui Sebastien Ponce sfan5 ShadowNinja shivajiva101 spillz Starbeamrainbowlabs TalkLounge tenplus1 Uberi/Temperest Wuzzy License ------- Copyright (c) 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote). This mod is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html). Basically, this means everyone is free to use, modify, and distribute the files, as long as these modifications are also licensed the same way. Most importantly, the Affero variant of the GPL requires you to publish your modifications in source form, even if the mod is run only on the server, and not distributed. Minetest-WorldEdit-1.3/Tutorial.md000066400000000000000000000146251400537024600171760ustar00rootroot00000000000000WorldEdit Tutorial ================== This is a step-by-step tutorial outlining the basic usage of WorldEdit. Let's start with a few assumptions: * You have a compatible version of Minetest working, that is 5.0 or later. * You have WorldEdit installed as a mod. * Simply download the file, extract the archive, and move it to the correct mod folder for Minetest. * See the installation instructions in [README](README.md) if you need more details. * You are familiar with the basics of the game. * How to walk, jump, and climb. * How to dig, place, and punch blocks. * One of the following: * How to type into the chat and read text from it. * How to open the inventory screen and press buttons on it. Overview -------- WorldEdit has a "region", which is simply a cuboid area defined by two markers, both of which the player can move around. Every player can have their own region with their own two markers. WorldEdit GUI buttons and chat commands generally work inside the region selected, or around the first marker. If you are using the chat commands, follow the steps under **Chat Commands**. If you are using the WorldEdit GUI, follow the steps under **WorldEdit GUI**. Step 1: Selecting a region -------------------------- ### Chat Commands In the chat prompt, enter `//p set`. In the chat, you are prompted to punch two nodes to set the positions of the two markers. Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1. Walk away from the node you just punched. Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2. ### WorldEdit GUI Open the main WorldEdit GUI from your inventory screen. The icon looks like a globe with a red dot in the center. Press the "Get/Set Positions" button. On the new screen, press the "Set Position 1" button. The inventory screen should close. Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1. Walk away from the node you just punched. Open your inventory again. It should be on the same page as it was before. Press the "Set Position 2" button. The inventory screen should close. Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2. Step 2: Region commands ----------------------- ### Chat Commands In the chat prompt, enter `//set mese`. In the chat, you will see a message showing the number of nodes set after a small delay. Look at the place between the two markers: it is now filled with MESE blocks! The `//set ` command fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only. Now, try a few different variations, such as `//set torch`, `//set cobble`, and `//set water source`. ### WorldEdit GUI Open the main WorldEdit GUI from your inventory screen. Press the "Set Nodes" button. You should see a new screen with various options for setting nodes. Enter "mese" in the "Name" field. Press Search if you would like to see what the node you just entered looks like. Press the "Set Nodes" button on this screen. In the chat, you will see a message showing the number of nodes set after a small delay. Look at the place between the two markers: it is now filled with MESE blocks! The "Set Nodes" function fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only. Now, try a few different variations on the node name, such as "torch", "cobble", and "water source". Step 3: Position commands ------------------------- ### Chat Commands In the chat prompt, enter `//hollowdome 30 glass`. In the chat, you will see a message showing the number of nodes set after a small delay. Look around marker 1: it is now surrounded by a hollow glass dome! The `//hollowdome ` command creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region. ### WorldEdit GUI Open the main WorldEdit GUI from your inventory screen. Press the "Sphere/Dome" button. You should see a new screen with various options for making spheres or domes. Enter "glass" in the "Name" field. Press Search if you would like to see what the node you just entered looks like. Enter "30" in the "Radius" field. Press the "Hollow Dome" button on this screen. In the chat, you will see a message showing the number of nodes added after a small delay. Look around marker 1: it is now surrounded by a hollow glass dome! The "Hollow Dome" function creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region. Step 4: Other commands ---------------------- ### Chat Commands There are many more commands than what is shown here. See the [Chat Commands Reference](ChatCommands.md) for a detailed list of them, along with descriptions and examples for every single one. If you're in-game and forgot how a command works, just use the `/help ` command, without the first forward slash. For example, to see some information about the `//set ` command mentioned earlier, simply use `/help /set`. A very useful command to check out is the `//save ` command, which can save everything inside the WorldEdit region to a file, stored on the computer hosting the server (the player's computer, in single player mode). You can then later use `//load ` to load the data in a file into a world, even another world on another computer. ### WorldEdit GUI This only scratches the surface of what WorldEdit is capable of. Most of the functions in the WorldEdit GUI correspond to chat commands, and so the [Chat Commands Reference](ChatCommands.md) may be useful if you get stuck. It is helpful to explore the various buttons in the interface and check out what they do. Learning the chat command interface is also useful if you use WorldEdit intensively - an experienced chat command user can usually work faster than an experienced WorldEdit GUI user. Minetest-WorldEdit-1.3/WorldEdit API.md000066400000000000000000000224771400537024600176660ustar00rootroot00000000000000WorldEdit API ============= The WorldEdit API is composed of multiple modules, each of which is independent and can be used without the other. Each module is contained within a single file. If needed, individual modules such as visualization.lua can be removed without affecting the rest of the program. The only file that cannot be removed is init.lua, which is necessary for the mod to run. For more information, see the [README](README.md). General ------- ### value = worldedit.version Contains the current version of WorldEdit in a table of the form `{major=MAJOR_INTEGER, minor=MINOR_INTEGER}`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for version checking purposes. ### value = worldedit.version_string Contains the current version of WorldEdit in the form of a string `"MAJOR_INTEGER.MINOR_INTEGER"`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for display purposes. Manipulations ------------- Contained in manipulations.lua, this module allows several node operations to be applied over a region. ### count = worldedit.set(pos1, pos2, node_name) Sets a region defined by positions `pos1` and `pos2` to `node_name`. To clear a region, use "air" as the value of `node_name`. If `node_name` is a list of nodes, each set node is randomly picked from it. Returns the number of nodes set. ### `count = worldedit.set_param2(pos1, pos2, param2)` Sets the param2 values of all nodes in a region defined by positions `pos1` and `pos2` to `param2`. Returns the number of nodes set. ### count = worldedit.replace(pos1, pos2, searchnode, replacenode) Replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`. Returns the number of nodes replaced. ### count = worldedit.replaceinverse(pos1, pos2, searchnode, replacenode) Replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`. Returns the number of nodes replaced. ### count = worldedit.copy(pos1, pos2, axis, amount) Copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes. Returns the number of nodes copied. ### count = worldedit.copy2(pos1, pos2, off) Copies the region defined by positions `pos1` and `pos2` by the offset vector `off`. Note that the offset needs to be big enough that there is no overlap. Returns the number of nodes copied. ### count = worldedit.move(pos1, pos2, axis, amount) Moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes. Returns the number of nodes moved. ### count = worldedit.stack(pos1, pos2, axis, count) Duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times. Returns the number of nodes stacked. ### count = worldedit.stack2(pos1, pos2, direction, amount) Duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`. Note that the offset vector needs to be big enough that there is no overlap. Returns the number of nodes stacked. ### count, newpos1, newpos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz) Stretches the region defined by positions `pos1` and `pos2` by an factor of positive integers `stretchx`, `stretchy`. and `stretchz` along the X, Y, and Z axes, respectively, with `pos1` as the origin. Returns the number of nodes stretched, the new scaled position 1, and the new scaled position 2. ### count, newpos1, newpos2 = worldedit.transpose(pos1, pos2, axis1, axis2) Transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes ("x" or "y" or "z"). Returns the number of nodes transposed, the new transposed position 1, and the new transposed position 2. ### count = worldedit.flip(pos1, pos2, axis) Flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z"). Returns the number of nodes flipped. ### count, newpos2, newpos2 = worldedit.rotate(pos1, pos2, angle) Rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around the y axis (supporting 90 degree increments only). Returns the number of nodes rotated, the new position 1, and the new position 2. ### count = worldedit.orient(pos1, pos2, angle) Rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis. Returns the number of nodes oriented. ### count = worldedit.fixlight(pos1, pos2) Fixes the lighting in a region defined by positions `pos1` and `pos2`. Returns the number of nodes updated. ### count = worldedit.clearobjects(pos1, pos2) Clears all objects in a region defined by the positions `pos1` and `pos2`. Returns the number of objects cleared. Primitives ---------- Contained in primitives.lua, this module allows the creation of several geometric primitives. ### count = worldedit.cube(pos, width, height, length, node_name, hollow) Adds a cube with its ground level centered at `pos`, the dimensions `width` x `height` x `length`, composed of `node_name`. Returns the number of nodes added. ### count = worldedit.sphere(pos, radius, node_name, hollow) Adds a sphere centered at `pos` with radius `radius`, composed of `node_name`. Returns the number of nodes added. ### count = worldedit.dome(pos, radius, node_name, hollow) Adds a dome centered at `pos` with radius `radius`, composed of `node_name`. Returns the number of nodes added. ### count = worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow) Adds a cylinder-like at `pos` along the `axis` axis ("x" or "y" or "z") with length `length`, base radius `radius1` and top radius `radius2`, composed of `node_name`. Returns the number of nodes added. ### count = worldedit.pyramid(pos, axis, height, node_name, hollow) Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`, composed of `node_name`. Returns the number of nodes added. ### count = worldedit.spiral(pos, length, height, spacer, node_name) Adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `node_name`. Returns the number of nodes added. Visualization ------------- Contained in visualization.lua, this module allows nodes to be visualized in different ways. ### volume = worldedit.volume(pos1, pos2) Determines the volume of the region defined by positions `pos1` and `pos2`. Returns the volume. ### count = worldedit.hide(pos1, pos2) Hides all nodes in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes. Returns the number of nodes hidden. ### count = worldedit.suppress(pos1, pos2, node_name) Suppresses all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes. Returns the number of nodes suppressed. ### count = worldedit.highlight(pos1, pos2, node_name) Highlights all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes. Returns the number of nodes found. ### count = worldedit.restore(pos1, pos2) Restores all nodes hidden with WorldEdit functions in a region defined by positions `pos1` and `pos2`. Returns the number of nodes restored. Serialization ------------- Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside Minetest. ### version, extra_fields, content = worldedit.read_header(value) Reads the header from serialized data `value`. Returns the version as a positive integer (nil for unknown versions), extra header fields (nil if not supported), and the content after the header. ### data, count = worldedit.serialize(pos1, pos2) Converts the region defined by positions `pos1` and `pos2` into a single string. Returns the serialized data and the number of nodes serialized, or nil. ### pos1, pos2, count = worldedit.allocate(origin_pos, value) Determines the volume the nodes represented by string `value` would occupy if deserialized at `origin_pos`. Returns the two corner positions and the number of nodes, or nil. ### count = worldedit.deserialize(origin_pos, value) Loads the nodes represented by string `value` at position `origin_pos`. Returns the number of nodes deserialized or nil. Code ---- Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit. ### error = worldedit.lua(code) Executes `code` as a Lua chunk in the global namespace. Returns an error if the code fails or nil otherwise. ### error = worldedit.luatransform(pos1, pos2, code) Executes `code` as a Lua chunk in the global namespace with the variable `pos` available, for each node in a region defined by positions `pos1` and `pos2`. Returns an error if the code fails or nil otherwise. Minetest-WorldEdit-1.3/config.ld000066400000000000000000000003461400537024600166320ustar00rootroot00000000000000project = "WorldEdit" title = "WorldEdit API Documentation" description = "Minetest mod to mass-modify nodes" format = "markdown" file = {"worldedit"} topics = { "README.md", "Tutorial.md", "ChatCommands.md", "LICENSE.txt" } Minetest-WorldEdit-1.3/modpack.conf000066400000000000000000000002261400537024600173260ustar00rootroot00000000000000name = Minetest-WorldEdit description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds. Minetest-WorldEdit-1.3/worldedit/000077500000000000000000000000001400537024600170365ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit/code.lua000066400000000000000000000024601400537024600204550ustar00rootroot00000000000000--- Lua code execution functions. -- @module worldedit.code --- Executes `code` as a Lua chunk in the global namespace. -- @return An error message if the code fails, or nil on success. function worldedit.lua(code) local func, err = loadstring(code) if not func then -- Syntax error return err end local good, err = pcall(func) if not good then -- Runtime error return err end return nil end --- Executes `code` as a Lua chunk in the global namespace with the variable -- pos available, for each node in a region defined by positions `pos1` and -- `pos2`. -- @return An error message if the code fails, or nil on success. function worldedit.luatransform(pos1, pos2, code) pos1, pos2 = worldedit.sort_pos(pos1, pos2) local factory, err = loadstring("return function(pos) " .. code .. " end") if not factory then -- Syntax error return err end local func = factory() worldedit.keep_loaded(pos1, pos2) local pos = {x=pos1.x, y=0, z=0} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local good, err = pcall(func, pos) if not good then -- Runtime error return err end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return nil end Minetest-WorldEdit-1.3/worldedit/common.lua000066400000000000000000000057221400537024600210370ustar00rootroot00000000000000--- Common functions [INTERNAL]. All of these functions are internal! -- @module worldedit.common --- Copies and modifies positions `pos1` and `pos2` so that each component of -- `pos1` is less than or equal to the corresponding component of `pos2`. -- Returns the new positions. function worldedit.sort_pos(pos1, pos2) pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} if pos1.x > pos2.x then pos2.x, pos1.x = pos1.x, pos2.x end if pos1.y > pos2.y then pos2.y, pos1.y = pos1.y, pos2.y end if pos1.z > pos2.z then pos2.z, pos1.z = pos1.z, pos2.z end return pos1, pos2 end --- Determines the volume of the region defined by positions `pos1` and `pos2`. -- @return The volume. function worldedit.volume(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1) end --- Gets other axes given an axis. -- @raise Axis must be x, y, or z! function worldedit.get_axis_others(axis) if axis == "x" then return "y", "z" elseif axis == "y" then return "x", "z" elseif axis == "z" then return "x", "y" else error("Axis must be x, y, or z!") end end function worldedit.keep_loaded(pos1, pos2) -- Create a vmanip and read the area from map, this -- causes all MapBlocks to be loaded into memory. -- This doesn't actually *keep* them loaded, unlike the name implies. local manip = minetest.get_voxel_manip() manip:read_from_map(pos1, pos2) end local mh = {} worldedit.manip_helpers = mh --- Generates an empty VoxelManip data table for an area. -- @return The empty data table. function mh.get_empty_data(area) -- Fill emerged area with ignore so that blocks in the area that are -- only partially modified aren't overwriten. local data = {} local c_ignore = minetest.get_content_id("ignore") for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do data[i] = c_ignore end return data end function mh.init(pos1, pos2) local manip = minetest.get_voxel_manip() local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) return manip, area end function mh.init_radius(pos, radius) local pos1 = vector.subtract(pos, radius) local pos2 = vector.add(pos, radius) return mh.init(pos1, pos2) end function mh.init_axis_radius(base_pos, axis, radius) return mh.init_axis_radius_length(base_pos, axis, radius, radius) end function mh.init_axis_radius_length(base_pos, axis, radius, length) local other1, other2 = worldedit.get_axis_others(axis) local pos1 = { [axis] = base_pos[axis], [other1] = base_pos[other1] - radius, [other2] = base_pos[other2] - radius } local pos2 = { [axis] = base_pos[axis] + length, [other1] = base_pos[other1] + radius, [other2] = base_pos[other2] + radius } return mh.init(pos1, pos2) end function mh.finish(manip, data) -- Update map if data ~= nil then manip:set_data(data) end manip:write_to_map() manip:update_map() end Minetest-WorldEdit-1.3/worldedit/compatibility.lua000066400000000000000000000040731400537024600224160ustar00rootroot00000000000000--- Compatibility functions. -- @module worldedit.compatibility local function deprecated(new_func) local info = debug.getinfo(1, "n") local msg = "worldedit." .. info.name .. "() is deprecated." if new_func then msg = msg .. " Use worldedit." .. new_func .. "() instead." end minetest.log("deprecated", msg) end worldedit.allocate_old = worldedit.allocate worldedit.deserialize_old = worldedit.deserialize function worldedit.metasave(pos1, pos2, filename) deprecated("save") local file, err = io.open(filename, "wb") if err then return 0 end local data, count = worldedit.serialize(pos1, pos2) file:write(data) file:close() return count end function worldedit.metaload(originpos, filename) deprecated("load") filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" local file, err = io.open(filename, "wb") if err then return 0 end local data = file:read("*a") return worldedit.deserialize(originpos, data) end function worldedit.scale(pos1, pos2, factor) deprecated("stretch") return worldedit.stretch(pos1, pos2, factor, factor, factor) end function worldedit.valueversion(value) deprecated("read_header") local version = worldedit.read_header(value) if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then return 0 end return version end function worldedit.replaceinverse(pos1, pos2, search_node, replace_node) deprecated("replace") return worldedit.replace(pos1, pos2, search_node, replace_node, true) end function worldedit.clearobjects(...) deprecated("clear_objects") return worldedit.clear_objects(...) end function worldedit.hollow_sphere(pos, radius, node_name) deprecated("sphere") return worldedit.sphere(pos, radius, node_name, true) end function worldedit.hollow_dome(pos, radius, node_name) deprecated("dome") return worldedit.dome(pos, radius, node_name, true) end function worldedit.hollow_cylinder(pos, axis, length, radius, node_name) deprecated("cylinder") return worldedit.cylinder(pos, axis, length, radius, node_name, true) end Minetest-WorldEdit-1.3/worldedit/cuboid.lua000066400000000000000000000122531400537024600210110ustar00rootroot00000000000000-- Expands or contracts the cuboid in all axes by amount (positive or negative) worldedit.cuboid_volumetric_expand = function(name, amount) local pos1 = worldedit.pos1[name] local pos2 = worldedit.pos2[name] if pos1 == nil or pos2 == nil then return false, "Undefined cuboid" end local delta1 = vector.new() local delta2 = vector.new() local delta_dir1 local delta_dir2 delta1 = vector.add(delta1, amount) delta2 = vector.add(delta2, amount) delta_dir1, delta_dir2 = worldedit.get_expansion_directions(pos1, pos2) delta1 = vector.multiply(delta1, delta_dir1) delta2 = vector.multiply(delta2, delta_dir2) worldedit.pos1[name] = vector.add(pos1, delta1) worldedit.pos2[name] = vector.add(pos2, delta2) return true end -- Expands or contracts the cuboid in a single axis by amount (positive or negative) worldedit.cuboid_linear_expand = function(name, axis, direction, amount) local pos1 = worldedit.pos1[name] local pos2 = worldedit.pos2[name] if pos1 == nil or pos2 == nil then return false, "undefined cuboid" end if direction ~= 1 and direction ~= -1 then return false, "invalid marker" end local marker = worldedit.marker_get_closest_to_axis(name, axis, direction) local deltavect = vector.new() if axis == 'x' then deltavect.x = amount * direction elseif axis == 'y' then deltavect.y = amount * direction elseif axis == 'z' then deltavect.z = amount * direction else return false, "invalid axis" end worldedit.marker_move(name, marker, deltavect) return true end -- Shifts the cuboid by '+-amount' in axis 'axis' worldedit.cuboid_shift = function(name, axis, amount) local pos1 = worldedit.pos1[name] local pos2 = worldedit.pos2[name] if pos1 == nil or pos2 == nil then return false, "undefined cuboid" end if axis == 'x' then worldedit.pos1[name].x = pos1.x + amount worldedit.pos2[name].x = pos2.x + amount elseif axis == 'y' then worldedit.pos1[name].y = pos1.y + amount worldedit.pos2[name].y = pos2.y + amount elseif axis == 'z' then worldedit.pos1[name].z = pos1.z + amount worldedit.pos2[name].z = pos2.z + amount else return false, "invalid axis" end return true end -- Moves the location of a single marker by adding deltavector worldedit.marker_move = function(name, marker, deltavector) if marker ~= 1 and marker ~= 2 then return false end if marker == 1 then local pos = worldedit.pos1[name] worldedit.pos1[name] = vector.add(deltavector, pos) else local pos = worldedit.pos2[name] worldedit.pos2[name] = vector.add(deltavector, pos) end return true end -- Returns two vectors with the directions for volumetric expansion worldedit.get_expansion_directions = function(mark1, mark2) if mark1 == nil or mark2 == nil then return end local dir1 = vector.new() local dir2 = vector.new() if mark1.x < mark2.x then dir1.x = -1 dir2.x = 1 else dir1.x = 1 dir2.x = -1 end if mark1.y < mark2.y then dir1.y = -1 dir2.y = 1 else dir1.y = 1 dir2.y = -1 end if mark1.z < mark2.z then dir1.z = -1 dir2.z = 1 else dir1.z = 1 dir2.z = -1 end return dir1, dir2 end -- Return the marker that is closest to the player worldedit.marker_get_closest_to_player = function(name) local playerpos = minetest.get_player_by_name(name):get_pos() local dist1 = vector.distance(playerpos, worldedit.pos1[name]) local dist2 = vector.distance(playerpos, worldedit.pos2[name]) if dist1 < dist2 then return 1 else return 2 end end -- Returns the closest marker to the specified axis and direction worldedit.marker_get_closest_to_axis = function(name, axis, direction) local pos1 = vector.new() local pos2 = vector.new() if direction ~= 1 and direction ~= -1 then return nil end if axis == 'x' then pos1.x = worldedit.pos1[name].x * direction pos2.x = worldedit.pos2[name].x * direction if pos1.x > pos2.x then return 1 else return 2 end elseif axis == 'y' then pos1.y = worldedit.pos1[name].y * direction pos2.y = worldedit.pos2[name].y * direction if pos1.y > pos2.y then return 1 else return 2 end elseif axis == 'z' then pos1.z = worldedit.pos1[name].z * direction pos2.z = worldedit.pos2[name].z * direction if pos1.z > pos2.z then return 1 else return 2 end else return nil end end -- Translates up, down, left, right, front, back to their corresponding axes and -- directions according to faced direction worldedit.translate_direction = function(name, direction) local axis, dir = worldedit.player_axis(name) local resaxis, resdir if direction == "up" then return 'y', 1 end if direction == "down" then return 'y', -1 end if direction == "front" then if axis == "y" then resaxis = nil resdir = nil else resaxis = axis resdir = dir end end if direction == "back" then if axis == "y" then resaxis = nil resdir = nil else resaxis = axis resdir = -dir end end if direction == "left" then if axis == 'x' then resaxis = 'z' resdir = dir elseif axis == 'z' then resaxis = 'x' resdir = -dir end end if direction == "right" then if axis == 'x' then resaxis = 'z' resdir = -dir elseif axis == 'z' then resaxis = 'x' resdir = dir end end return resaxis, resdir end Minetest-WorldEdit-1.3/worldedit/init.lua000066400000000000000000000021551400537024600205070ustar00rootroot00000000000000--- WorldEdit mod for the Minetest engine -- @module worldedit -- @release 1.3 -- @copyright 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote) -- @license GNU Affero General Public License version 3 (AGPLv3) -- @author sfan5 -- @author Anthony Zang (Uberi/Temperest) -- @author Bret O'Donnel (cornernote) -- @author ShadowNinja worldedit = {} local ver = {major=1, minor=3} worldedit.version = ver worldedit.version_string = string.format("%d.%d", ver.major, ver.minor) local path = minetest.get_modpath(minetest.get_current_modname()) local function load_module(path) local file = io.open(path, "r") if not file then return end file:close() return dofile(path) end dofile(path .. "/common.lua") load_module(path .. "/manipulations.lua") load_module(path .. "/primitives.lua") load_module(path .. "/visualization.lua") load_module(path .. "/serialization.lua") load_module(path .. "/code.lua") load_module(path .. "/compatibility.lua") load_module(path .. "/cuboid.lua") if minetest.settings:get_bool("log_mods") then print("[WorldEdit] Loaded!") end Minetest-WorldEdit-1.3/worldedit/manipulations.lua000066400000000000000000000477211400537024600224370ustar00rootroot00000000000000--- Generic node manipulations. -- @module worldedit.manipulations local mh = worldedit.manip_helpers --- Sets a region to `node_names`. -- @param pos1 -- @param pos2 -- @param node_names Node name or list of node names. -- @return The number of nodes set. function worldedit.set(pos1, pos2, node_names) pos1, pos2 = worldedit.sort_pos(pos1, pos2) local manip, area = mh.init(pos1, pos2) local data = mh.get_empty_data(area) if type(node_names) == "string" then -- Only one type of node local id = minetest.get_content_id(node_names) -- Fill area with node for i in area:iterp(pos1, pos2) do data[i] = id end else -- Several types of nodes specified local node_ids = {} for i, v in ipairs(node_names) do node_ids[i] = minetest.get_content_id(v) end -- Fill area randomly with nodes local id_count, rand = #node_ids, math.random for i in area:iterp(pos1, pos2) do data[i] = node_ids[rand(id_count)] end end mh.finish(manip, data) return worldedit.volume(pos1, pos2) end --- Sets param2 of a region. -- @param pos1 -- @param pos2 -- @param param2 Value of param2 to set -- @return The number of nodes set. function worldedit.set_param2(pos1, pos2, param2) pos1, pos2 = worldedit.sort_pos(pos1, pos2) local manip, area = mh.init(pos1, pos2) local param2_data = manip:get_param2_data() -- Set param2 for every node for i in area:iterp(pos1, pos2) do param2_data[i] = param2 end -- Update map manip:set_param2_data(param2_data) manip:write_to_map() manip:update_map() return worldedit.volume(pos1, pos2) end --- Replaces all instances of `search_node` with `replace_node` in a region. -- When `inverse` is `true`, replaces all instances that are NOT `search_node`. -- @return The number of nodes replaced. function worldedit.replace(pos1, pos2, search_node, replace_node, inverse) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local manip, area = mh.init(pos1, pos2) local data = manip:get_data() local search_id = minetest.get_content_id(search_node) local replace_id = minetest.get_content_id(replace_node) local count = 0 if not inverse then for i in area:iterp(pos1, pos2) do if data[i] == search_id then data[i] = replace_id count = count + 1 end end else for i in area:iterp(pos1, pos2) do if data[i] ~= search_id then data[i] = replace_id count = count + 1 end end end mh.finish(manip, data) return count end local function deferred_execution(next_one, finished) -- Allocate 100% of server step for execution (might lag a little) local allocated_usecs = tonumber(minetest.settings:get("dedicated_server_step")) * 1000000 local function f() local deadline = minetest.get_us_time() + allocated_usecs repeat local is_done = next_one() if is_done then if finished then finished() end return end until minetest.get_us_time() >= deadline minetest.after(0, f) end f() end --- Duplicates a region `amount` times with offset vector `direction`. -- Stacking is spread across server steps. -- @return The number of nodes stacked. function worldedit.stack2(pos1, pos2, direction, amount, finished) -- Protect arguments from external changes during execution pos1 = table.copy(pos1) pos2 = table.copy(pos2) direction = table.copy(direction) local i = 0 local translated = {x=0, y=0, z=0} local function step() translated.x = translated.x + direction.x translated.y = translated.y + direction.y translated.z = translated.z + direction.z worldedit.copy2(pos1, pos2, translated) i = i + 1 return i >= amount end deferred_execution(step, finished) return worldedit.volume(pos1, pos2) * amount end --- Copies a region along `axis` by `amount` nodes. -- @param pos1 -- @param pos2 -- @param axis Axis ("x", "y", or "z") -- @param amount -- @return The number of nodes copied. function worldedit.copy(pos1, pos2, axis, amount) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- Decide if we need to copy stuff backwards (only applies to metadata) local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1) local off = {x=0, y=0, z=0} off[axis] = amount return worldedit.copy2(pos1, pos2, off, backwards) end --- Copies a region by offset vector `off`. -- @param pos1 -- @param pos2 -- @param off -- @param meta_backwards (not officially part of API) -- @return The number of nodes copied. function worldedit.copy2(pos1, pos2, off, meta_backwards) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local src_manip, src_area = mh.init(pos1, pos2) local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride} local src_offset = vector.subtract(pos1, src_area.MinEdge) local dpos1 = vector.add(pos1, off) local dpos2 = vector.add(pos2, off) local dim = vector.add(vector.subtract(pos2, pos1), 1) local dst_manip, dst_area = mh.init(dpos1, dpos2) local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride} local dst_offset = vector.subtract(dpos1, dst_area.MinEdge) local function do_copy(src_data, dst_data) for z = 0, dim.z-1 do local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1 for y = 0, dim.y-1 do local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y -- Copy entire row at once local src_index_x = src_index_y + src_offset.x local dst_index_x = dst_index_y + dst_offset.x for x = 0, dim.x-1 do dst_data[dst_index_x + x] = src_data[src_index_x + x] end end end end -- Copy node data local src_data = src_manip:get_data() local dst_data = dst_manip:get_data() do_copy(src_data, dst_data) dst_manip:set_data(dst_data) -- Copy param1 src_manip:get_light_data(src_data) dst_manip:get_light_data(dst_data) do_copy(src_data, dst_data) dst_manip:set_light_data(dst_data) -- Copy param2 src_manip:get_param2_data(src_data) dst_manip:get_param2_data(dst_data) do_copy(src_data, dst_data) dst_manip:set_param2_data(dst_data) mh.finish(dst_manip) src_data = nil dst_data = nil -- Copy metadata local get_meta = minetest.get_meta if meta_backwards then for z = dim.z-1, 0, -1 do for y = dim.y-1, 0, -1 do for x = dim.x-1, 0, -1 do local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z} local meta = get_meta(pos):to_table() pos = vector.add(pos, off) get_meta(pos):from_table(meta) end end end else for z = 0, dim.z-1 do for y = 0, dim.y-1 do for x = 0, dim.x-1 do local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z} local meta = get_meta(pos):to_table() pos = vector.add(pos, off) get_meta(pos):from_table(meta) end end end end return worldedit.volume(pos1, pos2) end --- Deletes all node metadata in the region -- @param pos1 -- @param pos2 -- @return The number of nodes that had their meta deleted. function worldedit.delete_meta(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local meta_positions = minetest.find_nodes_with_meta(pos1, pos2) local get_meta = minetest.get_meta for _, pos in ipairs(meta_positions) do get_meta(pos):from_table(nil) end return #meta_positions end --- Moves a region along `axis` by `amount` nodes. -- @return The number of nodes moved. function worldedit.move(pos1, pos2, axis, amount) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local dim = vector.add(vector.subtract(pos2, pos1), 1) local overlap = math.abs(amount) < dim[axis] -- Decide if we need to copy metadata backwards local backwards = overlap and amount > 0 local function nuke_area(my_off, my_dim) if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then return end local my_pos1 = vector.add(pos1, my_off) local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1) worldedit.set(my_pos1, my_pos2, "air") worldedit.delete_meta(my_pos1, my_pos2) end -- Copy stuff to new location local off = {x=0, y=0, z=0} off[axis] = amount worldedit.copy2(pos1, pos2, off, backwards) -- Nuke old area if not overlap then nuke_area({x=0, y=0, z=0}, dim) else -- Source and destination region are overlapping, which means we can't -- blindly delete the [pos1, pos2] area local leftover = vector.new(dim) -- size of the leftover slice leftover[axis] = math.abs(amount) if amount > 0 then nuke_area({x=0, y=0, z=0}, leftover) else local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1 top[axis] = dim[axis] - math.abs(amount) nuke_area(top, leftover) end end return worldedit.volume(pos1, pos2) end --- Duplicates a region along `axis` `amount` times. -- Stacking is spread across server steps. -- @param pos1 -- @param pos2 -- @param axis Axis direction, "x", "y", or "z". -- @param count -- @return The number of nodes stacked. function worldedit.stack(pos1, pos2, axis, count, finished) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local length = pos2[axis] - pos1[axis] + 1 if count < 0 then count = -count length = -length end local i, distance = 0, 0 local function step() distance = distance + length worldedit.copy(pos1, pos2, axis, distance) i = i + 1 return i >= count end deferred_execution(step, finished) return worldedit.volume(pos1, pos2) * count end --- Stretches a region by a factor of positive integers along the X, Y, and Z -- axes, respectively, with `pos1` as the origin. -- @param pos1 -- @param pos2 -- @param stretch_x Amount to stretch along X axis. -- @param stretch_y Amount to stretch along Y axis. -- @param stretch_z Amount to stretch along Z axis. -- @return The number of nodes scaled. -- @return The new scaled position 1. -- @return The new scaled position 2. function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- Prepare schematic of large node local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic local placeholder_node = {name="", param1=255, param2=0} local nodes = {} for i = 1, stretch_x * stretch_y * stretch_z do nodes[i] = placeholder_node end local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes} local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1 local new_pos2 = { x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x, y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y, z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z, } worldedit.keep_loaded(pos1, new_pos2) local pos = {x=pos2.x, y=0, z=0} local big_pos = {x=0, y=0, z=0} while pos.x >= pos1.x do pos.y = pos2.y while pos.y >= pos1.y do pos.z = pos2.z while pos.z >= pos1.z do local node = get_node(pos) -- Get current node local meta = get_meta(pos):to_table() -- Get meta of current node -- Calculate far corner of the big node local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z -- Create large node placeholder_node.name = node.name placeholder_node.param2 = node.param2 big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z place_schematic(big_pos, schematic) -- Fill in large node meta if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then -- Node has meta fields for x = 0, size_x do for y = 0, size_y do for z = 0, size_z do big_pos.x = pos_x + x big_pos.y = pos_y + y big_pos.z = pos_z + z -- Set metadata of new node get_meta(big_pos):from_table(meta) end end end end pos.z = pos.z - 1 end pos.y = pos.y - 1 end pos.x = pos.x - 1 end return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2 end --- Transposes a region between two axes. -- @return The number of nodes transposed. -- @return The new transposed position 1. -- @return The new transposed position 2. function worldedit.transpose(pos1, pos2, axis1, axis2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local compare local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2] if extent1 > extent2 then compare = function(extent1, extent2) return extent1 > extent2 end else compare = function(extent1, extent2) return extent1 < extent2 end end -- Calculate the new position 2 after transposition local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} new_pos2[axis1] = pos1[axis1] + extent2 new_pos2[axis2] = pos1[axis2] + extent1 local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z} if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end worldedit.keep_loaded(pos1, upper_bound) local pos = {x=pos1.x, y=0, z=0} local get_node, get_meta, set_node = minetest.get_node, minetest.get_meta, minetest.set_node while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] if compare(extent1, extent2) then -- Transpose only if below the diagonal local node1 = get_node(pos) local meta1 = get_meta(pos):to_table() local value1, value2 = pos[axis1], pos[axis2] -- Save position values pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents local node2 = get_node(pos) local meta2 = get_meta(pos):to_table() set_node(pos, node1) get_meta(pos):from_table(meta1) pos[axis1], pos[axis2] = value1, value2 -- Restore position values set_node(pos, node2) get_meta(pos):from_table(meta2) end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return worldedit.volume(pos1, pos2), pos1, new_pos2 end --- Flips a region along `axis`. -- @return The number of nodes flipped. function worldedit.flip(pos1, pos2, axis) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) --- TODO: Flip the region slice by slice along the flip axis using schematic method. local pos = {x=pos1.x, y=0, z=0} local start = pos1[axis] + pos2[axis] pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) local get_node, get_meta, set_node = minetest.get_node, minetest.get_meta, minetest.set_node while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node1 = get_node(pos) local meta1 = get_meta(pos):to_table() local value = pos[axis] -- Save position pos[axis] = start - value -- Shift position local node2 = get_node(pos) local meta2 = get_meta(pos):to_table() set_node(pos, node1) get_meta(pos):from_table(meta1) pos[axis] = value -- Restore position set_node(pos, node2) get_meta(pos):from_table(meta2) pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return worldedit.volume(pos1, pos2) end --- Rotates a region clockwise around an axis. -- @param pos1 -- @param pos2 -- @param axis Axis ("x", "y", or "z"). -- @param angle Angle in degrees (90 degree increments only). -- @return The number of nodes rotated. -- @return The new first position. -- @return The new second position. function worldedit.rotate(pos1, pos2, axis, angle) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local other1, other2 = worldedit.get_axis_others(axis) angle = angle % 360 local count if angle == 90 then worldedit.flip(pos1, pos2, other1) count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) elseif angle == 180 then worldedit.flip(pos1, pos2, other1) count = worldedit.flip(pos1, pos2, other2) elseif angle == 270 then worldedit.flip(pos1, pos2, other2) count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) else error("Only 90 degree increments are supported!") end return count, pos1, pos2 end --- Rotates all oriented nodes in a region clockwise around the Y axis. -- @param pos1 -- @param pos2 -- @param angle Angle in degrees (90 degree increments only). -- @return The number of nodes oriented. function worldedit.orient(pos1, pos2, angle) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local registered_nodes = minetest.registered_nodes local wallmounted = { [90] = {0, 1, 5, 4, 2, 3, 0, 0}, [180] = {0, 1, 3, 2, 5, 4, 0, 0}, [270] = {0, 1, 4, 5, 3, 2, 0, 0} } local facedir = { [90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16, 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22}, [180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5, 18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21}, [270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14, 7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20} } angle = angle % 360 if angle == 0 then return 0 end if angle % 90 ~= 0 then error("Only 90 degree increments are supported!") end local wallmounted_substitution = wallmounted[angle] local facedir_substitution = facedir[angle] worldedit.keep_loaded(pos1, pos2) local count = 0 local get_node, swap_node = minetest.get_node, minetest.swap_node local pos = {x=pos1.x, y=0, z=0} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = get_node(pos) local def = registered_nodes[node.name] if def then local paramtype2 = def.paramtype2 if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then local orient = node.param2 % 8 node.param2 = node.param2 - orient + wallmounted_substitution[orient + 1] swap_node(pos, node) count = count + 1 elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then local orient = node.param2 % 32 node.param2 = node.param2 - orient + facedir_substitution[orient + 1] swap_node(pos, node) count = count + 1 end end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return count end --- Attempts to fix the lighting in a region. -- @return The number of nodes updated. function worldedit.fixlight(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local vmanip = minetest.get_voxel_manip(pos1, pos2) vmanip:write_to_map() vmanip:update_map() -- this updates the lighting return worldedit.volume(pos1, pos2) end --- Clears all objects in a region. -- @return The number of objects cleared. function worldedit.clear_objects(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) -- Offset positions to include full nodes (positions are in the center of nodes) local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5 local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5 -- Center of region local center = { x = pos1x + ((pos2x - pos1x) / 2), y = pos1y + ((pos2y - pos1y) / 2), z = pos1z + ((pos2z - pos1z) / 2) } -- Bounding sphere radius local radius = math.sqrt( (center.x - pos1x) ^ 2 + (center.y - pos1y) ^ 2 + (center.z - pos1z) ^ 2) local count = 0 for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do local entity = obj:get_luaentity() -- Avoid players and WorldEdit entities if not obj:is_player() and (not entity or not entity.name:find("^worldedit:")) then local pos = obj:get_pos() if pos.x >= pos1x and pos.x <= pos2x and pos.y >= pos1y and pos.y <= pos2y and pos.z >= pos1z and pos.z <= pos2z then -- Inside region obj:remove() count = count + 1 end end end return count end Minetest-WorldEdit-1.3/worldedit/mod.conf000066400000000000000000000001021400537024600204550ustar00rootroot00000000000000name = worldedit description = WorldEdit main functionality & API Minetest-WorldEdit-1.3/worldedit/primitives.lua000066400000000000000000000244601400537024600217420ustar00rootroot00000000000000--- Functions for creating primitive shapes. -- @module worldedit.primitives local mh = worldedit.manip_helpers --- Adds a cube -- @param pos Position of ground level center of cube -- @param width Cube width. (x) -- @param height Cube height. (y) -- @param length Cube length. (z) -- @param node_name Name of node to make cube of. -- @param hollow Whether the cube should be hollow. -- @return The number of nodes added. function worldedit.cube(pos, width, height, length, node_name, hollow) -- Set up voxel manipulator local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)}) local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length})) local data = mh.get_empty_data(area) -- Add cube local node_id = minetest.get_content_id(node_name) local stride = {x=1, y=area.ystride, z=area.zstride} local offset = vector.subtract(basepos, area.MinEdge) local count = 0 for z = 0, length-1 do local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing for y = 0, height-1 do local index_y = index_z + (offset.y + y) * stride.y for x = 0, width-1 do local is_wall = z == 0 or z == length-1 or y == 0 or y == height-1 or x == 0 or x == width-1 if not hollow or is_wall then local i = index_y + (offset.x + x) data[i] = node_id count = count + 1 end end end end mh.finish(manip, data) return count end --- Adds a sphere of `node_name` centered at `pos`. -- @param pos Position to center sphere at. -- @param radius Sphere radius. -- @param node_name Name of node to make shere of. -- @param hollow Whether the sphere should be hollow. -- @return The number of nodes added. function worldedit.sphere(pos, radius, node_name, hollow) local manip, area = mh.init_radius(pos, radius) local data = mh.get_empty_data(area) -- Fill selected area with node local node_id = minetest.get_content_id(node_name) local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z local stride_z, stride_y = area.zstride, area.ystride local count = 0 for z = -radius, radius do -- Offset contributed by z plus 1 to make it 1-indexed local new_z = (z + offset_z) * stride_z + 1 for y = -radius, radius do local new_y = new_z + (y + offset_y) * stride_y for x = -radius, radius do local squared = x * x + y * y + z * z if squared <= max_radius and (not hollow or squared >= min_radius) then -- Position is on surface of sphere local i = new_y + (x + offset_x) data[i] = node_id count = count + 1 end end end end mh.finish(manip, data) return count end --- Adds a dome. -- @param pos Position to center dome at. -- @param radius Dome radius. Negative for concave domes. -- @param node_name Name of node to make dome of. -- @param hollow Whether the dome should be hollow. -- @return The number of nodes added. -- TODO: Add axis option. function worldedit.dome(pos, radius, node_name, hollow) local min_y, max_y = 0, radius if radius < 0 then radius = -radius min_y, max_y = -radius, 0 end local manip, area = mh.init_axis_radius(pos, "y", radius) local data = mh.get_empty_data(area) -- Add dome local node_id = minetest.get_content_id(node_name) local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z local stride_z, stride_y = area.zstride, area.ystride local count = 0 for z = -radius, radius do local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed for y = min_y, max_y do local new_y = new_z + (y + offset_y) * stride_y for x = -radius, radius do local squared = x * x + y * y + z * z if squared <= max_radius and (not hollow or squared >= min_radius) then -- Position is in dome local i = new_y + (x + offset_x) data[i] = node_id count = count + 1 end end end end mh.finish(manip, data) return count end --- Adds a cylinder. -- @param pos Position to center base of cylinder at. -- @param axis Axis ("x", "y", or "z") -- @param length Cylinder length. -- @param radius1 Cylinder base radius. -- @param radius2 Cylinder top radius. -- @param node_name Name of node to make cylinder of. -- @param hollow Whether the cylinder should be hollow. -- @return The number of nodes added. function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow) local other1, other2 = worldedit.get_axis_others(axis) -- Backwards compatibility if type(radius2) == "string" then hollow = node_name node_name = radius2 radius2 = radius1 -- straight cylinder end -- Handle negative lengths local current_pos = {x=pos.x, y=pos.y, z=pos.z} if length < 0 then length = -length current_pos[axis] = current_pos[axis] - length radius1, radius2 = radius2, radius1 end -- Set up voxel manipulator local manip, area = mh.init_axis_radius_length(current_pos, axis, math.max(radius1, radius2), length) local data = mh.get_empty_data(area) -- Add desired shape (anything inbetween cylinder & cone) local node_id = minetest.get_content_id(node_name) local stride = {x=1, y=area.ystride, z=area.zstride} local offset = { x = current_pos.x - area.MinEdge.x, y = current_pos.y - area.MinEdge.y, z = current_pos.z - area.MinEdge.z, } local count = 0 for i = 0, length - 1 do -- Calulate radius for this "height" in the cylinder local radius = radius1 + (radius2 - radius1) * (i + 1) / length radius = math.floor(radius + 0.5) -- round local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) for index2 = -radius, radius do -- Offset contributed by other axis 1 plus 1 to make it 1-indexed local new_index2 = (index2 + offset[other1]) * stride[other1] + 1 for index3 = -radius, radius do local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2] local squared = index2 * index2 + index3 * index3 if squared <= max_radius and (not hollow or squared >= min_radius) then -- Position is in cylinder, add node here local vi = new_index3 + (offset[axis] + i) * stride[axis] data[vi] = node_id count = count + 1 end end end end mh.finish(manip, data) return count end --- Adds a pyramid. -- @param pos Position to center base of pyramid at. -- @param axis Axis ("x", "y", or "z") -- @param height Pyramid height. -- @param node_name Name of node to make pyramid of. -- @param hollow Whether the pyramid should be hollow. -- @return The number of nodes added. function worldedit.pyramid(pos, axis, height, node_name, hollow) local other1, other2 = worldedit.get_axis_others(axis) -- Set up voxel manipulator -- FIXME: passing negative causes mis-sorted pos to be passed -- into mh.init() which is technically not allowed but works local manip, area = mh.init_axis_radius(pos, axis, height) local data = mh.get_empty_data(area) -- Handle inverted pyramids local step if height > 0 then height = height - 1 step = 1 else height = height + 1 step = -1 end -- Add pyramid local node_id = minetest.get_content_id(node_name) local stride = {x=1, y=area.ystride, z=area.zstride} local offset = { x = pos.x - area.MinEdge.x, y = pos.y - area.MinEdge.y, z = pos.z - area.MinEdge.z, } local size = math.abs(height * step) local count = 0 -- For each level of the pyramid for index1 = 0, height, step do -- Offset contributed by axis plus 1 to make it 1-indexed local new_index1 = (index1 + offset[axis]) * stride[axis] + 1 for index2 = -size, size do local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1] for index3 = -size, size do local i = new_index2 + (index3 + offset[other2]) * stride[other2] if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then data[i] = node_id count = count + 1 end end end size = size - 1 end mh.finish(manip, data) return count end --- Adds a spiral. -- @param pos Position to center spiral at. -- @param length Spral length. -- @param height Spiral height. -- @param spacer Space between walls. -- @param node_name Name of node to make spiral of. -- @return Number of nodes added. -- TODO: Add axis option. function worldedit.spiral(pos, length, height, spacer, node_name) local extent = math.ceil(length / 2) local manip, area = mh.init_axis_radius_length(pos, "y", extent, height) local data = mh.get_empty_data(area) -- Set up variables local node_id = minetest.get_content_id(node_name) local stride = {x=1, y=area.ystride, z=area.zstride} local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1 -- Add first column local count = height local column = i for y = 1, height do data[column] = node_id column = column + stride.y end -- Add spiral segments local stride_axis, stride_other = stride.x, stride.z local sign = -1 local segment_length = 0 spacer = spacer + 1 -- Go through each segment except the last for segment = 1, math.floor(length / spacer) * 2 do -- Change sign and length every other turn starting with the first if segment % 2 == 1 then sign = -sign segment_length = segment_length + spacer end -- Fill segment for index = 1, segment_length do -- Move along the direction of the segment i = i + stride_axis * sign local column = i -- Add column for y = 1, height do data[column] = node_id column = column + stride.y end end count = count + segment_length * height stride_axis, stride_other = stride_other, stride_axis -- Swap axes end -- Add shorter final segment sign = -sign for index = 1, segment_length do i = i + stride_axis * sign local column = i -- Add column for y = 1, height do data[column] = node_id column = column + stride.y end end count = count + segment_length * height mh.finish(manip, data) return count end Minetest-WorldEdit-1.3/worldedit/serialization.lua000066400000000000000000000210621400537024600224170ustar00rootroot00000000000000--- Schematic serialization and deserialiation. -- @module worldedit.serialization worldedit.LATEST_SERIALIZATION_VERSION = 5 local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":" --[[ Serialization version history: 1: Original format. Serialized Lua table with a weird linked format... 2: Position and node seperated into sub-tables in fields `1` and `2`. 3: List of nodes, one per line, with fields seperated by spaces. Format: 4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`, `name`, `param1`, `param2`, and `meta` fields. 5: Added header and made `param1`, `param2`, and `meta` fields optional. Header format: ,,...: --]] --- Reads the header of serialized data. -- @param value Serialized WorldEdit data. -- @return The version as a positive natural number, or 0 for unknown versions. -- @return Extra header fields as a list of strings, or nil if not supported. -- @return Content (data after header). function worldedit.read_header(value) if value:find("^[0-9]+[,:]") then local header_end = value:find(":", 1, true) local header = value:sub(1, header_end - 1):split(",") local version = tonumber(header[1]) table.remove(header, 1) local content = value:sub(header_end + 1) return version, header, content end -- Old versions that didn't include a header with a version number if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format return 3, nil, value elseif value:find("^[^\"']+%{%d+%}") then if value:find("%[\"meta\"%]") then -- Meta flat table format return 2, nil, value end return 1, nil, value -- Flat table format elseif value:find("%{") then -- Raw nested table format return 4, nil, value end return nil end --- Converts the region defined by positions `pos1` and `pos2` -- into a single string. -- @return The serialized data. -- @return The number of nodes serialized. function worldedit.serialize(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) local get_node, get_meta, hash_node_position = minetest.get_node, minetest.get_meta, minetest.hash_node_position -- Find the positions which have metadata local has_meta = {} local meta_positions = minetest.find_nodes_with_meta(pos1, pos2) for i = 1, #meta_positions do has_meta[hash_node_position(meta_positions[i])] = true end local pos = {x=pos1.x, y=0, z=0} local count = 0 local result = {} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = get_node(pos) if node.name ~= "air" and node.name ~= "ignore" then count = count + 1 local meta if has_meta[hash_node_position(pos)] then meta = get_meta(pos):to_table() -- Convert metadata item stacks to item strings for _, invlist in pairs(meta.inventory) do for index = 1, #invlist do local itemstack = invlist[index] if itemstack.to_string then invlist[index] = itemstack:to_string() end end end end result[count] = { x = pos.x - pos1.x, y = pos.y - pos1.y, z = pos.z - pos1.z, name = node.name, param1 = node.param1 ~= 0 and node.param1 or nil, param2 = node.param2 ~= 0 and node.param2 or nil, meta = meta, } end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end -- Serialize entries result = minetest.serialize(result) return LATEST_SERIALIZATION_HEADER .. result, count end -- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) -- by ChillCode, available under the MIT license. local function deserialize_workaround(content) local nodes if not jit then nodes = minetest.deserialize(content, true) else -- XXX: This is a filthy hack that works surprisingly well -- in LuaJIT, `minetest.deserialize` will fail due to the register limit nodes = {} content = content:gsub("^%s*return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data -- remove string contents strings while preserving their length local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) local startpos, startpos1 = 1, 1 local endpos while true do -- go through each individual node entry (except the last) startpos, endpos = escaped:find("},%s*{", startpos) if not startpos then break end local current = content:sub(startpos1, startpos) local entry = minetest.deserialize("return " .. current, true) table.insert(nodes, entry) startpos, startpos1 = endpos, endpos end local entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry table.insert(nodes, entry) end return nodes end --- Loads the schematic in `value` into a node list in the latest format. -- @return A node list in the latest format, or nil on failure. local function load_schematic(value) local version, header, content = worldedit.read_header(value) local nodes = {} if version == 1 or version == 2 then -- Original flat table format local tables = minetest.deserialize(content, true) if not tables then return nil end -- Transform the node table into an array of nodes for i = 1, #tables do for j, v in pairs(tables[i]) do if type(v) == "table" then tables[i][j] = tables[v[1]] end end end nodes = tables[1] if version == 1 then --original flat table format for i, entry in ipairs(nodes) do local pos = entry[1] entry.x, entry.y, entry.z = pos.x, pos.y, pos.z entry[1] = nil local node = entry[2] entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2 entry[2] = nil end end elseif version == 3 then -- List format for x, y, z, name, param1, param2 in content:gmatch( "([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" .. "([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do param1, param2 = tonumber(param1), tonumber(param2) table.insert(nodes, { x = tonumber(x), y = tonumber(y), z = tonumber(z), name = name, param1 = param1 ~= 0 and param1 or nil, param2 = param2 ~= 0 and param2 or nil, }) end elseif version == 4 or version == 5 then -- Nested table format nodes = deserialize_workaround(content) else return nil end return nodes end --- Determines the volume the nodes represented by string `value` would occupy -- if deserialized at `origin_pos`. -- @return Low corner position. -- @return High corner position. -- @return The number of nodes. function worldedit.allocate(origin_pos, value) local nodes = load_schematic(value) if not nodes or #nodes == 0 then return nil end return worldedit.allocate_with_nodes(origin_pos, nodes) end -- Internal function worldedit.allocate_with_nodes(origin_pos, nodes) local huge = math.huge local pos1x, pos1y, pos1z = huge, huge, huge local pos2x, pos2y, pos2z = -huge, -huge, -huge local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z for i, entry in ipairs(nodes) do local x, y, z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z if x < pos1x then pos1x = x end if y < pos1y then pos1y = y end if z < pos1z then pos1z = z end if x > pos2x then pos2x = x end if y > pos2y then pos2y = y end if z > pos2z then pos2z = z end end local pos1 = {x=pos1x, y=pos1y, z=pos1z} local pos2 = {x=pos2x, y=pos2y, z=pos2z} return pos1, pos2, #nodes end --- Loads the nodes represented by string `value` at position `origin_pos`. -- @return The number of nodes deserialized. function worldedit.deserialize(origin_pos, value) local nodes = load_schematic(value) if not nodes then return nil end if #nodes == 0 then return #nodes end local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes) worldedit.keep_loaded(pos1, pos2) local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z local count = 0 local add_node, get_meta = minetest.add_node, minetest.get_meta for i, entry in ipairs(nodes) do entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z -- Entry acts as both position and node add_node(entry, entry) if entry.meta then get_meta(entry):from_table(entry.meta) end end return #nodes end Minetest-WorldEdit-1.3/worldedit/textures/000077500000000000000000000000001400537024600207215ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit/textures/worldedit_wand.png000066400000000000000000000006721400537024600244420ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME ;GIDAT8˝1Hqƿwpz-IEa`m9ԖAKDKPcEBVkaC\R j(k#\oz}\~`A# Vu?~0âbs:03*RX Ra؟ p:q7yX}AOHXkb- 6ŊE"п48%^@,!b0 `@'nnzFl /v$Yrt=meLl>SSLg)_BXf.qGmM3 ;je|vyIENDB`Minetest-WorldEdit-1.3/worldedit/visualization.lua000066400000000000000000000077671400537024600224630ustar00rootroot00000000000000--- Functions for visibly hiding nodes -- @module worldedit.visualization minetest.register_node("worldedit:placeholder", { drawtype = "airlike", paramtype = "light", sunlight_propagates = true, diggable = false, pointable = false, walkable = false, groups = {not_in_creative_inventory=1}, }) --- Hides all nodes in a region defined by positions `pos1` and `pos2` by -- non-destructively replacing them with invisible nodes. -- @return The number of nodes hidden. function worldedit.hide(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) local pos = {x=pos1.x, y=0, z=0} local get_node, get_meta, swap_node = minetest.get_node, minetest.get_meta, minetest.swap_node while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = get_node(pos) if node.name ~= "air" and node.name ~= "worldedit:placeholder" then -- Save the node's original name get_meta(pos):set_string("worldedit_placeholder", node.name) -- Swap in placeholder node node.name = "worldedit:placeholder" swap_node(pos, node) end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return worldedit.volume(pos1, pos2) end --- Suppresses all instances of `node_name` in a region defined by positions -- `pos1` and `pos2` by non-destructively replacing them with invisible nodes. -- @return The number of nodes suppressed. function worldedit.suppress(pos1, pos2, node_name) -- Ignore placeholder supression if node_name == "worldedit:placeholder" then return 0 end pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) local nodes = minetest.find_nodes_in_area(pos1, pos2, node_name) local get_node, get_meta, swap_node = minetest.get_node, minetest.get_meta, minetest.swap_node for _, pos in ipairs(nodes) do local node = get_node(pos) -- Save the node's original name get_meta(pos):set_string("worldedit_placeholder", node.name) -- Swap in placeholder node node.name = "worldedit:placeholder" swap_node(pos, node) end return #nodes end --- Highlights all instances of `node_name` in a region defined by positions -- `pos1` and `pos2` by non-destructively hiding all other nodes. -- @return The number of nodes found. function worldedit.highlight(pos1, pos2, node_name) pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) local pos = {x=pos1.x, y=0, z=0} local get_node, get_meta, swap_node = minetest.get_node, minetest.get_meta, minetest.swap_node local count = 0 while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = get_node(pos) if node.name == node_name then -- Node found count = count + 1 elseif node.name ~= "worldedit:placeholder" then -- Hide other nodes -- Save the node's original name get_meta(pos):set_string("worldedit_placeholder", node.name) -- Swap in placeholder node node.name = "worldedit:placeholder" swap_node(pos, node) end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return count end -- Restores all nodes hidden with WorldEdit functions in a region defined -- by positions `pos1` and `pos2`. -- @return The number of nodes restored. function worldedit.restore(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(pos1, pos2) local nodes = minetest.find_nodes_in_area(pos1, pos2, "worldedit:placeholder") local get_node, get_meta, swap_node = minetest.get_node, minetest.get_meta, minetest.swap_node for _, pos in ipairs(nodes) do local node = get_node(pos) local meta = get_meta(pos) local data = meta:to_table() node.name = data.fields.worldedit_placeholder data.fields.worldedit_placeholder = nil meta:from_table(data) swap_node(pos, node) end return #nodes end Minetest-WorldEdit-1.3/worldedit_brush/000077500000000000000000000000001400537024600202415ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_brush/init.lua000066400000000000000000000076431400537024600217210ustar00rootroot00000000000000if minetest.raycast == nil then error( "worldedit_brush requires at least Minetest 5.0" ) end local BRUSH_MAX_DIST = 150 local brush_on_use = function(itemstack, placer) local meta = itemstack:get_meta() local name = placer:get_player_name() local cmd = meta:get_string("command") if cmd == "" then worldedit.player_notify(name, "This brush is not bound, use //brush to bind a command to it.") return false end local cmddef = worldedit.registered_commands[cmd] if cmddef == nil then return false end -- shouldn't happen as //brush checks this local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs) if not has_privs then worldedit.player_notify(name, "Missing privileges: " .. table.concat(missing_privs, ", ")) return false end local raybegin = vector.add(placer:get_pos(), {x=0, y=placer:get_properties().eye_height, z=0}) local rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST)) local ray = minetest.raycast(raybegin, rayend, false, true) local pointed_thing = ray:next() if pointed_thing == nil then worldedit.player_notify(name, "Too far away.") return false end assert(pointed_thing.type == "node") worldedit.pos1[name] = pointed_thing.under worldedit.pos2[name] = nil worldedit.marker_update(name) -- this isn't really clean... local player_notify_old = worldedit.player_notify worldedit.player_notify = function(name, msg) if string.match(msg, "^%d") then return end -- discard "1234 nodes added." return player_notify_old(name, msg) end assert(cmddef.require_pos < 2) local parsed = {cmddef.parse(meta:get_string("params"))} if not table.remove(parsed, 1) then return false end -- shouldn't happen minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s", name, cmd, minetest.pos_to_string(pointed_thing.under))) cmddef.func(name, unpack(parsed)) worldedit.player_notify = player_notify_old return true end minetest.register_tool(":worldedit:brush", { description = "WorldEdit Brush", inventory_image = "worldedit_brush.png", stack_max = 1, -- no need to stack these (metadata prevents this anyway) range = 0, on_use = function(itemstack, placer, pointed_thing) brush_on_use(itemstack, placer) return itemstack -- nothing consumed, nothing changed end, }) worldedit.register_command("brush", { privs = {worldedit=true}, params = "none/ [parameters]", description = "Assign command to WorldEdit brush item", parse = function(param) local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$") if not found then params = "" found, _, cmd = param:find("^(.+)$") end if not found then return false end return true, cmd, params end, func = function(name, cmd, params) local itemstack = minetest.get_player_by_name(name):get_wielded_item() if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then worldedit.player_notify(name, "Not holding brush item.") return end cmd = cmd:lower() local meta = itemstack:get_meta() if cmd == "none" then meta:from_table(nil) worldedit.player_notify(name, "Brush assignment cleared.") else local cmddef = worldedit.registered_commands[cmd] if cmddef == nil or cmddef.require_pos ~= 1 then worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes") return end -- Try parsing command params so we can give the user feedback local ok, err = cmddef.parse(params) if not ok then err = err or "invalid usage" worldedit.player_notify(name, "Error with brush command: " .. err) return end meta:set_string("command", cmd) meta:set_string("params", params) local fullcmd = "//" .. cmd .. " " .. params meta:set_string("description", minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd) worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd) end minetest.get_player_by_name(name):set_wielded_item(itemstack) end, }) Minetest-WorldEdit-1.3/worldedit_brush/mod.conf000066400000000000000000000001351400537024600216660ustar00rootroot00000000000000name = worldedit_brush description = WorldEdit brush depends = worldedit, worldedit_commands Minetest-WorldEdit-1.3/worldedit_brush/textures/000077500000000000000000000000001400537024600221245ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_brush/textures/worldedit_brush.png000066400000000000000000000005211400537024600260300ustar00rootroot00000000000000PNG  IHDR(-SlPLTEhhhzX5|D}J}_ 0) assert(def.privs) def.require_pos = def.require_pos or 0 assert(def.require_pos >= 0 and def.require_pos < 3) if def.params == "" and not def.parse then def.parse = function(param) return true end else assert(def.parse) end assert(def.nodes_needed == nil or type(def.nodes_needed) == "function") assert(def.func) -- for development --[[if def.require_pos == 2 and not def.nodes_needed then minetest.log("warning", "//" .. name .. " might be missing nodes_needed") end--]] minetest.register_chatcommand("/" .. name, { privs = def.privs, params = def.params, description = def.description, func = function(player_name, param) return chatcommand_handler(name, player_name, param) end, }) worldedit.registered_commands[name] = def end dofile(minetest.get_modpath("worldedit_commands") .. "/cuboid.lua") dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua") dofile(minetest.get_modpath("worldedit_commands") .. "/wand.lua") local function check_region(name) return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) end -- Strips any kind of escape codes (translation, colors) from a string -- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777 local function strip_escapes(input) local s = function(idx) return input:sub(idx, idx) end local out = "" local i = 1 while i <= #input do if s(i) == "\027" then -- escape sequence i = i + 1 if s(i) == "(" then -- enclosed i = i + 1 while i <= #input and s(i) ~= ")" do if s(i) == "\\" then i = i + 2 else i = i + 1 end end end else out = out .. s(i) end i = i + 1 end --print(("%q -> %q"):format(input, out)) return out end local function string_endswith(full, part) return full:find(part, 1, true) == #full - #part + 1 end local description_cache = nil -- normalizes node "description" `nodename`, returning a string (or nil) worldedit.normalize_nodename = function(nodename) nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces if nodename == "" then return nil end local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases if minetest.registered_nodes[fullname] or fullname == "air" then -- full name return fullname end nodename = nodename:lower() for key, _ in pairs(minetest.registered_nodes) do if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part) return key end end if description_cache == nil then -- cache stripped descriptions description_cache = {} for key, value in pairs(minetest.registered_nodes) do local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower() if desc ~= "" then description_cache[key] = desc end end end for key, desc in pairs(description_cache) do if desc == nodename then -- matches description return key end end for key, desc in pairs(description_cache) do if desc == nodename .. " block" then -- fuzzy description match (e.g. "Steel" == "Steel Block") return key end end local match = nil for key, value in pairs(description_cache) do if value:find(nodename, 1, true) ~= nil then if match ~= nil then return nil end match = key -- substring description match (only if no ambiguities) end end return match end -- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1) function worldedit.player_axis(name) local dir = minetest.get_player_by_name(name):get_look_dir() local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z) if x > y then if x > z then return "x", dir.x > 0 and 1 or -1 end elseif y > z then return "y", dir.y > 0 and 1 or -1 end return "z", dir.z > 0 and 1 or -1 end local function check_filename(name) return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil end worldedit.register_command("about", { privs = {}, params = "", description = "Get information about the WorldEdit mod", func = function(name) worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string.. " is available on this server. Type //help to get a list of ".. "commands, or get more information at ".. "https://github.com/Uberi/Minetest-WorldEdit") end, }) -- mostly copied from builtin/chatcommands.lua with minor modifications worldedit.register_command("help", { privs = {}, params = "[all/]", description = "Get help for WorldEdit commands", parse = function(param) return true, param end, func = function(name, param) local function format_help_line(cmd, def) local msg = minetest.colorize("#00ffff", "//"..cmd) if def.params and def.params ~= "" then msg = msg .. " " .. def.params end if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end return msg end if not minetest.check_player_privs(name, "worldedit") then return false, "You are not allowed to use any WorldEdit commands." end if param == "" then local msg = "" local cmds = {} for cmd, def in pairs(worldedit.registered_commands) do if minetest.check_player_privs(name, def.privs) then cmds[#cmds + 1] = cmd end end table.sort(cmds) return true, "Available commands: " .. table.concat(cmds, " ") .. "\n" .. "Use '//help ' to get more information," .. " or '//help all' to list everything." elseif param == "all" then local cmds = {} for cmd, def in pairs(worldedit.registered_commands) do if minetest.check_player_privs(name, def.privs) then cmds[#cmds + 1] = format_help_line(cmd, def) end end table.sort(cmds) return true, "Available commands:\n"..table.concat(cmds, "\n") else local def = worldedit.registered_commands[param] if not def then return false, "Command not available: " .. param else return true, format_help_line(param, def) end end end, }) worldedit.register_command("inspect", { params = "[on/off/1/0/true/false/yes/no/enable/disable]", description = "Enable or disable node inspection", privs = {worldedit=true}, parse = function(param) if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then return true, true elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then return true, false end return false end, func = function(name, enable) if enable then worldedit.inspect[name] = true local axis, sign = worldedit.player_axis(name) worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis", name, axis .. (sign > 0 and "+" or "-"))) else worldedit.inspect[name] = nil worldedit.player_notify(name, "inspector: inspection disabled") end end, }) local function get_node_rlight(pos) local vecs = { -- neighboring nodes {x= 1, y= 0, z= 0}, {x=-1, y= 0, z= 0}, {x= 0, y= 1, z= 0}, {x= 0, y=-1, z= 0}, {x= 0, y= 0, z= 1}, {x= 0, y= 0, z=-1}, } local ret = 0 for _, v in ipairs(vecs) do ret = math.max(ret, minetest.get_node_light(vector.add(pos, v))) end return ret end minetest.register_on_punchnode(function(pos, node, puncher) local name = puncher:get_player_name() if worldedit.inspect[name] then local axis, sign = worldedit.player_axis(name) local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis", node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-")) worldedit.player_notify(name, message) end end) worldedit.register_command("reset", { params = "", description = "Reset the region so that it is empty", privs = {worldedit=true}, func = function(name) worldedit.pos1[name] = nil worldedit.pos2[name] = nil worldedit.marker_update(name) worldedit.set_pos[name] = nil --make sure the user does not try to confirm an operation after resetting pos: reset_pending(name) worldedit.player_notify(name, "region reset") end, }) worldedit.register_command("mark", { params = "", description = "Show markers at the region positions", privs = {worldedit=true}, func = function(name) worldedit.marker_update(name) worldedit.player_notify(name, "region marked") end, }) worldedit.register_command("unmark", { params = "", description = "Hide markers if currently shown", privs = {worldedit=true}, func = function(name) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] worldedit.pos1[name] = nil worldedit.pos2[name] = nil worldedit.marker_update(name) worldedit.pos1[name] = pos1 worldedit.pos2[name] = pos2 worldedit.player_notify(name, "region unmarked") end, }) worldedit.register_command("pos1", { params = "", description = "Set WorldEdit region position 1 to the player's location", privs = {worldedit=true}, func = function(name) local pos = minetest.get_player_by_name(name):get_pos() pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) worldedit.pos1[name] = pos worldedit.mark_pos1(name) worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) end, }) worldedit.register_command("pos2", { params = "", description = "Set WorldEdit region position 2 to the player's location", privs = {worldedit=true}, func = function(name) local pos = minetest.get_player_by_name(name):get_pos() pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) worldedit.pos2[name] = pos worldedit.mark_pos2(name) worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) end, }) worldedit.register_command("p", { params = "set/set1/set2/get", description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region", privs = {worldedit=true}, parse = function(param) if param == "set" or param == "set1" or param == "set2" or param == "get" then return true, param end return false, "unknown subcommand: " .. param end, func = function(name, param) if param == "set" then --set both WorldEdit positions worldedit.set_pos[name] = "pos1" worldedit.player_notify(name, "select positions by punching two nodes") elseif param == "set1" then --set WorldEdit position 1 worldedit.set_pos[name] = "pos1only" worldedit.player_notify(name, "select position 1 by punching a node") elseif param == "set2" then --set WorldEdit position 2 worldedit.set_pos[name] = "pos2" worldedit.player_notify(name, "select position 2 by punching a node") elseif param == "get" then --display current WorldEdit positions if worldedit.pos1[name] ~= nil then worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name])) else worldedit.player_notify(name, "position 1 not set") end if worldedit.pos2[name] ~= nil then worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name])) else worldedit.player_notify(name, "position 2 not set") end end end, }) worldedit.register_command("fixedpos", { params = "set1/set2 ", description = "Set a WorldEdit region position to the position at (, , )", privs = {worldedit=true}, parse = function(param) local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$") if found == nil then return false end return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)} end, func = function(name, flag, pos) if flag == "set1" then worldedit.pos1[name] = pos worldedit.mark_pos1(name) worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) else --flag == "set2" worldedit.pos2[name] = pos worldedit.mark_pos2(name) worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) end end, }) minetest.register_on_punchnode(function(pos, node, puncher) local name = puncher:get_player_name() if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position if worldedit.set_pos[name] == "pos1" then --setting position 1 worldedit.pos1[name] = pos worldedit.mark_pos1(name) worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only worldedit.pos1[name] = pos worldedit.mark_pos1(name) worldedit.set_pos[name] = nil --finished setting positions worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) elseif worldedit.set_pos[name] == "pos2" then --setting position 2 worldedit.pos2[name] = pos worldedit.mark_pos2(name) worldedit.set_pos[name] = nil --finished setting positions worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities worldedit.prob_pos[name] = pos minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]") end end end) worldedit.register_command("volume", { params = "", description = "Display the volume of the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, func = function(name) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local volume = worldedit.volume(pos1, pos2) local abs = math.abs worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes (" .. abs(pos2.x - pos1.x) + 1 .. "*" .. abs(pos2.y - pos1.y) + 1 .. "*" .. abs(pos2.z - pos1.z) + 1 .. ")") end, }) worldedit.register_command("deleteblocks", { params = "", description = "remove all MapBlocks (16x16x16) containing the selected area from the map", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local success = minetest.delete_area(pos1, pos2) if success then worldedit.player_notify(name, "Area deleted.") else worldedit.player_notify(name, "There was an error during deletion of the area.") end end, }) worldedit.register_command("set", { params = "", description = "Set the current WorldEdit region to ", privs = {worldedit=true}, require_pos = 2, parse = function(param) local node = worldedit.normalize_nodename(param) if not node then return false, "invalid node name: " .. param end return true, node end, nodes_needed = check_region, func = function(name, node) local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node) worldedit.player_notify(name, count .. " nodes set") end, }) worldedit.register_command("param2", { params = "", description = "Set param2 of all nodes in the current WorldEdit region to ", privs = {worldedit=true}, require_pos = 2, parse = function(param) local param2 = tonumber(param) if not param2 then return false elseif param2 < 0 or param2 > 255 then return false, "Param2 is out of range (must be between 0 and 255 inclusive!)" end return true, param2 end, nodes_needed = check_region, func = function(name, param2) local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2) worldedit.player_notify(name, count .. " nodes altered") end, }) worldedit.register_command("mix", { params = " [count1] [count2] ...", description = "Fill the current WorldEdit region with a random mix of , ...", privs = {worldedit=true}, require_pos = 2, parse = function(param) local nodes = {} for nodename in param:gmatch("[^%s]+") do if tonumber(nodename) ~= nil and #nodes > 0 then local last_node = nodes[#nodes] for i = 1, tonumber(nodename) do nodes[#nodes + 1] = last_node end else local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end nodes[#nodes + 1] = node end end if #nodes == 0 then return false end return true, nodes end, nodes_needed = check_region, func = function(name, nodes) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local count = worldedit.set(pos1, pos2, nodes) worldedit.player_notify(name, count .. " nodes set") end, }) local check_replace = function(param) local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") if found == nil then return false end local newsearchnode = worldedit.normalize_nodename(searchnode) if not newsearchnode then return false, "invalid search node name: " .. searchnode end local newreplacenode = worldedit.normalize_nodename(replacenode) if not newreplacenode then return false, "invalid replace node name: " .. replacenode end return true, newsearchnode, newreplacenode end worldedit.register_command("replace", { params = " ", description = "Replace all instances of with in the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, parse = check_replace, nodes_needed = check_region, func = function(name, search_node, replace_node) local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], search_node, replace_node) worldedit.player_notify(name, count .. " nodes replaced") end, }) worldedit.register_command("replaceinverse", { params = " ", description = "Replace all nodes other than with in the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, parse = check_replace, nodes_needed = check_region, func = function(name, search_node, replace_node) local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], search_node, replace_node, true) worldedit.player_notify(name, count .. " nodes replaced") end, }) local check_cube = function(param) local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, tonumber(w), tonumber(h), tonumber(l), node end worldedit.register_command("hollowcube", { params = " ", description = "Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions x x , composed of .", privs = {worldedit=true}, require_pos = 1, parse = check_cube, nodes_needed = function(name, w, h, l, node) return w * h * l end, func = function(name, w, h, l, node) local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("cube", { params = " ", description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions x x , composed of .", privs = {worldedit=true}, require_pos = 1, parse = check_cube, nodes_needed = function(name, w, h, l, node) return w * h * l end, func = function(name, w, h, l, node) local count = worldedit.cube(worldedit.pos1[name], w, h, l, node) worldedit.player_notify(name, count .. " nodes added") end, }) local check_sphere = function(param) local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, tonumber(radius), node end worldedit.register_command("hollowsphere", { params = " ", description = "Add hollow sphere centered at WorldEdit position 1 with radius , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_sphere, nodes_needed = function(name, radius, node) return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere end, func = function(name, radius, node) local count = worldedit.sphere(worldedit.pos1[name], radius, node, true) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("sphere", { params = " ", description = "Add sphere centered at WorldEdit position 1 with radius , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_sphere, nodes_needed = function(name, radius, node) return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere end, func = function(name, radius, node) local count = worldedit.sphere(worldedit.pos1[name], radius, node) worldedit.player_notify(name, count .. " nodes added") end, }) local check_dome = function(param) local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, tonumber(radius), node end worldedit.register_command("hollowdome", { params = " ", description = "Add hollow dome centered at WorldEdit position 1 with radius , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_dome, nodes_needed = function(name, radius, node) return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome end, func = function(name, radius, node) local count = worldedit.dome(worldedit.pos1[name], radius, node, true) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("dome", { params = " ", description = "Add dome centered at WorldEdit position 1 with radius , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_dome, nodes_needed = function(name, radius, node) return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome end, func = function(name, radius, node) local count = worldedit.dome(worldedit.pos1[name], radius, node) worldedit.player_notify(name, count .. " nodes added") end, }) local check_cylinder = function(param) -- two radii local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$") if found == nil then -- single radius found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") radius2 = radius1 end if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node end worldedit.register_command("hollowcylinder", { params = "x/y/z/? [radius2] ", description = "Add hollow cylinder at WorldEdit position 1 along the given axis with length , base radius (and top radius [radius2]), composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_cylinder, nodes_needed = function(name, axis, length, radius1, radius2, node) local radius = math.max(radius1, radius2) return math.ceil(math.pi * (radius ^ 2) * length) end, func = function(name, axis, length, radius1, radius2, node) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) length = length * sign end local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("cylinder", { params = "x/y/z/? [radius2] ", description = "Add cylinder at WorldEdit position 1 along the given axis with length , base radius (and top radius [radius2]), composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_cylinder, nodes_needed = function(name, axis, length, radius1, radius2, node) local radius = math.max(radius1, radius2) return math.ceil(math.pi * (radius ^ 2) * length) end, func = function(name, axis, length, radius1, radius2, node) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) length = length * sign end local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node) worldedit.player_notify(name, count .. " nodes added") end, }) local check_pyramid = function(param) local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, axis, tonumber(height), node end worldedit.register_command("hollowpyramid", { params = "x/y/z/? ", description = "Add hollow pyramid centered at WorldEdit position 1 along the given axis with height , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_pyramid, nodes_needed = function(name, axis, height, node) return math.ceil(((height * 2 + 1) ^ 2) * height / 3) end, func = function(name, axis, height, node) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) height = height * sign end local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("pyramid", { params = "x/y/z/? ", description = "Add pyramid centered at WorldEdit position 1 along the given axis with height , composed of ", privs = {worldedit=true}, require_pos = 1, parse = check_pyramid, nodes_needed = function(name, axis, height, node) return math.ceil(((height * 2 + 1) ^ 2) * height / 3) end, func = function(name, axis, height, node) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) height = height * sign end local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("spiral", { params = " ", description = "Add spiral centered at WorldEdit position 1 with side length , height , space between walls , composed of ", privs = {worldedit=true}, require_pos = 1, parse = function(param) local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") if found == nil then return false end local node = worldedit.normalize_nodename(nodename) if not node then return false, "invalid node name: " .. nodename end return true, tonumber(length), tonumber(height), tonumber(space), node end, nodes_needed = function(name, length, height, space, node) return (length + space) * height -- TODO: this is not the upper bound end, func = function(name, length, height, space, node) local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node) worldedit.player_notify(name, count .. " nodes added") end, }) worldedit.register_command("copy", { params = "x/y/z/? ", description = "Copy the current WorldEdit region along the given axis by nodes", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") if found == nil then return false end return true, axis, tonumber(amount) end, nodes_needed = function(name, axis, amount) return check_region(name) * 2 end, func = function(name, axis, amount) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) amount = amount * sign end local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount) worldedit.player_notify(name, count .. " nodes copied") end, }) worldedit.register_command("move", { params = "x/y/z/? ", description = "Move the current WorldEdit region along the given axis by nodes", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") if found == nil then return false end return true, axis, tonumber(amount) end, nodes_needed = function(name, axis, amount) return check_region(name) * 2 end, func = function(name, axis, amount) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) amount = amount * sign end local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local count = worldedit.move(pos1, pos2, axis, amount) pos1[axis] = pos1[axis] + amount pos2[axis] = pos2[axis] + amount worldedit.marker_update(name) worldedit.player_notify(name, count .. " nodes moved") end, }) worldedit.register_command("stack", { params = "x/y/z/? ", description = "Stack the current WorldEdit region along the given axis times", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") if found == nil then return false end return true, axis, tonumber(repetitions) end, nodes_needed = function(name, axis, repetitions) return check_region(name) * math.abs(repetitions) end, func = function(name, axis, repetitions) if axis == "?" then local sign axis, sign = worldedit.player_axis(name) repetitions = repetitions * sign end local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local count = worldedit.volume(pos1, pos2) * math.abs(repetitions) worldedit.stack(pos1, pos2, axis, repetitions, function() worldedit.player_notify(name, count .. " nodes stacked") end) end, }) worldedit.register_command("stack2", { params = " ", description = "Stack the current WorldEdit region times by offset , , ", privs = {worldedit=true}, require_pos = 2, parse = function(param) local repetitions, incs = param:match("(%d+)%s*(.+)") if repetitions == nil then return false, "invalid count: " .. param end local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)") if x == nil then return false, "invalid increments: " .. param end return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)} end, nodes_needed = function(name, repetitions, offset) return check_region(name) * repetitions end, func = function(name, repetitions, offset) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local count = worldedit.volume(pos1, pos2) * repetitions worldedit.stack2(pos1, pos2, offset, repetitions, function() worldedit.player_notify(name, count .. " nodes stacked") end) end, }) worldedit.register_command("stretch", { params = " ", description = "Scale the current WorldEdit positions and region by a factor of , , along the X, Y, and Z axes, repectively, with position 1 as the origin", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") if found == nil then return false end stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) if stretchx == 0 or stretchy == 0 or stretchz == 0 then return false, "invalid scaling factors: " .. param end return true, stretchx, stretchy, stretchz end, nodes_needed = function(name, stretchx, stretchy, stretchz) return check_region(name) * stretchx * stretchy * stretchz end, func = function(name, stretchx, stretchy, stretchz) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz) --reset markers to scaled positions worldedit.pos1[name] = pos1 worldedit.pos2[name] = pos2 worldedit.marker_update(name) worldedit.player_notify(name, count .. " nodes stretched") end, }) worldedit.register_command("transpose", { params = "x/y/z/? x/y/z/?", description = "Transpose the current WorldEdit region along the given axes", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") if found == nil then return false elseif axis1 == axis2 then return false, "invalid usage: axes must be different" end return true, axis1, axis2 end, nodes_needed = check_region, func = function(name, axis1, axis2) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] if axis1 == "?" then axis1 = worldedit.player_axis(name) end if axis2 == "?" then axis2 = worldedit.player_axis(name) end local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) --reset markers to transposed positions worldedit.pos1[name] = pos1 worldedit.pos2[name] = pos2 worldedit.marker_update(name) worldedit.player_notify(name, count .. " nodes transposed") end, }) worldedit.register_command("flip", { params = "x/y/z/?", description = "Flip the current WorldEdit region along the given axis", privs = {worldedit=true}, require_pos = 2, parse = function(param) if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then return false end return true, param end, nodes_needed = check_region, func = function(name, param) if param == "?" then param = worldedit.player_axis(name) end local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param) worldedit.player_notify(name, count .. " nodes flipped") end, }) worldedit.register_command("rotate", { params = "x/y/z/? ", description = "Rotate the current WorldEdit region around the given axis by angle (90 degree increment)", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") if found == nil then return false end angle = tonumber(angle) if angle % 90 ~= 0 or angle % 360 == 0 then return false, "invalid usage: angle must be multiple of 90" end return true, axis, angle end, nodes_needed = check_region, func = function(name, axis, angle) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] if axis == "?" then axis = worldedit.player_axis(name) end local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle) --reset markers to rotated positions worldedit.pos1[name] = pos1 worldedit.pos2[name] = pos2 worldedit.marker_update(name) worldedit.player_notify(name, count .. " nodes rotated") end, }) worldedit.register_command("orient", { params = "", description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle (90 degree increment)", privs = {worldedit=true}, require_pos = 2, parse = function(param) local found, _, angle = param:find("^([+-]?%d+)$") if found == nil then return false end angle = tonumber(angle) if angle % 90 ~= 0 then return false, "invalid usage: angle must be multiple of 90" end return true, angle end, nodes_needed = check_region, func = function(name, angle) local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle) worldedit.player_notify(name, count .. " nodes oriented") end, }) worldedit.register_command("fixlight", { params = "", description = "Fix the lighting in the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name]) worldedit.player_notify(name, count .. " nodes updated") end, }) worldedit.register_command("drain", { params = "", description = "Remove any fluid node within the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) -- TODO: make an API function for this local count = 0 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name]) for x = pos1.x, pos2.x do for y = pos1.y, pos2.y do for z = pos1.z, pos2.z do local n = minetest.get_node({x=x, y=y, z=z}).name local d = minetest.registered_nodes[n] if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then minetest.remove_node({x=x, y=y, z=z}) count = count + 1 end end end end worldedit.player_notify(name, count .. " nodes updated") end, }) local clearcut_cache local function clearcut(pos1, pos2) -- decide which nodes we consider plants if clearcut_cache == nil then clearcut_cache = {} for name, def in pairs(minetest.registered_nodes) do local groups = def.groups or {} if ( -- the groups say so groups.flower or groups.grass or groups.flora or groups.plant or groups.leaves or groups.tree or groups.leafdecay or groups.sapling or -- drawtype heuristic (def.is_ground_content and def.buildable_to and (def.sunlight_propagates or not def.walkable) and def.drawtype == "plantlike") or -- if it's flammable, it probably needs to go too (def.is_ground_content and not def.walkable and groups.flammable) ) then clearcut_cache[name] = true end end end local plants = clearcut_cache local count = 0 local prev, any for x = pos1.x, pos2.x do for z = pos1.z, pos2.z do prev = false any = false -- first pass: remove floating nodes that would be left over for y = pos1.y, pos2.y do local n = minetest.get_node({x=x, y=y, z=z}).name if plants[n] then prev = true any = true elseif prev then local def = minetest.registered_nodes[n] or {} local groups = def.groups or {} if groups.attached_node or (def.buildable_to and groups.falling_node) then minetest.remove_node({x=x, y=y, z=z}) count = count + 1 else prev = false end end end -- second pass: remove plants, top-to-bottom to avoid item drops if any then for y = pos2.y, pos1.y, -1 do local n = minetest.get_node({x=x, y=y, z=z}).name if plants[n] then minetest.remove_node({x=x, y=y, z=z}) count = count + 1 end end end end end return count end worldedit.register_command("clearcut", { params = "", description = "Remove any plant, tree or foilage-like nodes in the selected region", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name]) local count = clearcut(pos1, pos2) worldedit.player_notify(name, count .. " nodes removed") end, }) worldedit.register_command("hide", { params = "", description = "Hide all nodes in the current WorldEdit region non-destructively", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name]) worldedit.player_notify(name, count .. " nodes hidden") end, }) worldedit.register_command("suppress", { params = "", description = "Suppress all in the current WorldEdit region non-destructively", privs = {worldedit=true}, require_pos = 2, parse = function(param) local node = worldedit.normalize_nodename(param) if not node then return false, "invalid node name: " .. param end return true, node end, nodes_needed = check_region, func = function(name, node) local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node) worldedit.player_notify(name, count .. " nodes suppressed") end, }) worldedit.register_command("highlight", { params = "", description = "Highlight in the current WorldEdit region by hiding everything else non-destructively", privs = {worldedit=true}, require_pos = 2, parse = function(param) local node = worldedit.normalize_nodename(param) if not node then return false, "invalid node name: " .. param end return true, node end, nodes_needed = check_region, func = function(name, node) local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node) worldedit.player_notify(name, count .. " nodes highlighted") end, }) worldedit.register_command("restore", { params = "", description = "Restores nodes hidden with WorldEdit in the current WorldEdit region", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name]) worldedit.player_notify(name, count .. " nodes restored") end, }) local function detect_misaligned_schematic(name, pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- Check that allocate/save can position the schematic correctly -- The expected behaviour is that the (0,0,0) corner of the schematic stays -- sat pos1, this only works when the minimum position is actually present -- in the schematic. local node = minetest.get_node(pos1) local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore" if not have_node_at_origin then worldedit.player_notify(name, "Warning: The schematic contains excessive free space and WILL be ".. "misaligned when allocated or loaded. To avoid this, shrink your ".. "area to cover exactly the nodes to be saved." ) end end worldedit.register_command("save", { params = "", description = "Save the current WorldEdit region to \"(world folder)/schems/.we\"", privs = {worldedit=true}, require_pos = 2, parse = function(param) if param == "" then return false end if not check_filename(param) then return false, "Disallowed file name: " .. param end return true, param end, nodes_needed = check_region, func = function(name, param) local result, count = worldedit.serialize(worldedit.pos1[name], worldedit.pos2[name]) detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name]) local path = minetest.get_worldpath() .. "/schems" -- Create directory if it does not already exist minetest.mkdir(path) local filename = path .. "/" .. param .. ".we" local file, err = io.open(filename, "wb") if err ~= nil then worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"") return end file:write(result) file:flush() file:close() worldedit.player_notify(name, count .. " nodes saved") end, }) worldedit.register_command("allocate", { params = "", description = "Set the region defined by nodes from \"(world folder)/schems/.we\" as the current WorldEdit region", privs = {worldedit=true}, require_pos = 1, parse = function(param) if param == "" then return false end if not check_filename(param) then return false, "Disallowed file name: " .. param end return true, param end, func = function(name, param) local pos = worldedit.pos1[name] local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we" local file, err = io.open(filename, "rb") if err ~= nil then worldedit.player_notify(name, "could not open file \"" .. filename .. "\"") return end local value = file:read("*a") file:close() local version = worldedit.read_header(value) if version == nil or version == 0 then worldedit.player_notify(name, "File is invalid!") return elseif version > worldedit.LATEST_SERIALIZATION_VERSION then worldedit.player_notify(name, "File was created with newer version of WorldEdit!") return end local nodepos1, nodepos2, count = worldedit.allocate(pos, value) if not nodepos1 then worldedit.player_notify(name, "Schematic empty, nothing allocated") return end worldedit.pos1[name] = nodepos1 worldedit.pos2[name] = nodepos2 worldedit.marker_update(name) worldedit.player_notify(name, count .. " nodes allocated") end, }) worldedit.register_command("load", { params = "", description = "Load nodes from \"(world folder)/schems/[.we[m]]\" with position 1 of the current WorldEdit region as the origin", privs = {worldedit=true}, require_pos = 1, parse = function(param) if param == "" then return false end if not check_filename(param) then return false, "Disallowed file name: " .. param end return true, param end, func = function(name, param) local pos = worldedit.pos1[name] if param == "" then worldedit.player_notify(name, "invalid usage: " .. param) return end if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then worldedit.player_notify(name, "invalid file name: " .. param) return end --find the file in the world path local testpaths = { minetest.get_worldpath() .. "/schems/" .. param, minetest.get_worldpath() .. "/schems/" .. param .. ".we", minetest.get_worldpath() .. "/schems/" .. param .. ".wem", } local file, err for index, path in ipairs(testpaths) do file, err = io.open(path, "rb") if not err then break end end if err then worldedit.player_notify(name, "could not open file \"" .. param .. "\"") return end local value = file:read("*a") file:close() local version = worldedit.read_header(value) if version == nil or version == 0 then worldedit.player_notify(name, "File is invalid!") return elseif version > worldedit.LATEST_SERIALIZATION_VERSION then worldedit.player_notify(name, "File was created with newer version of WorldEdit!") return end local count = worldedit.deserialize(pos, value) worldedit.player_notify(name, count .. " nodes loaded") end, }) worldedit.register_command("lua", { params = "", description = "Executes as a Lua chunk in the global namespace", privs = {worldedit=true, server=true}, parse = function(param) return true, param end, func = function(name, param) local err = worldedit.lua(param) if err then worldedit.player_notify(name, "code error: " .. err) minetest.log("action", name.." tried to execute "..param) else worldedit.player_notify(name, "code successfully executed", false) minetest.log("action", name.." executed "..param) end end, }) worldedit.register_command("luatransform", { params = "", description = "Executes as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region", privs = {worldedit=true, server=true}, require_pos = 2, parse = function(param) return true, param end, nodes_needed = check_region, func = function(name, param) local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param) if err then worldedit.player_notify(name, "code error: " .. err, false) minetest.log("action", name.." tried to execute luatransform "..param) else worldedit.player_notify(name, "code successfully executed", false) minetest.log("action", name.." executed luatransform "..param) end end, }) worldedit.register_command("mtschemcreate", { params = "", description = "Save the current WorldEdit region using the Minetest ".. "Schematic format to \"(world folder)/schems/.mts\"", privs = {worldedit=true}, require_pos = 2, parse = function(param) if param == "" then return false end if not check_filename(param) then return false, "Disallowed file name: " .. param end return true, param end, nodes_needed = check_region, func = function(name, param) local path = minetest.get_worldpath() .. "/schems" -- Create directory if it does not already exist minetest.mkdir(path) local filename = path .. "/" .. param .. ".mts" local ret = minetest.create_schematic(worldedit.pos1[name], worldedit.pos2[name], worldedit.prob_list[name], filename) if ret == nil then worldedit.player_notify(name, "Failed to create Minetest schematic") else worldedit.player_notify(name, "Saved Minetest schematic to " .. param) end worldedit.prob_list[name] = {} end, }) worldedit.register_command("mtschemplace", { params = "", description = "Load nodes from \"(world folder)/schems/.mts\" with position 1 of the current WorldEdit region as the origin", privs = {worldedit=true}, require_pos = 1, parse = function(param) if param == "" then return false end if not check_filename(param) then return false, "Disallowed file name: " .. param end return true, param end, func = function(name, param) local pos = worldedit.pos1[name] local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts" if minetest.place_schematic(pos, path) == nil then worldedit.player_notify(name, "failed to place Minetest schematic") else worldedit.player_notify(name, "placed Minetest schematic " .. param .. " at " .. minetest.pos_to_string(pos)) end end, }) worldedit.register_command("mtschemprob", { params = "start/finish/get", description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry", privs = {worldedit=true}, parse = function(param) if param ~= "start" and param ~= "finish" and param ~= "get" then return false, "unknown subcommand: " .. param end return true, param end, func = function(name, param) if param == "start" then --start probability setting worldedit.set_pos[name] = "prob" worldedit.prob_list[name] = {} worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes") elseif param == "finish" then --finish probability setting worldedit.set_pos[name] = nil worldedit.player_notify(name, "finished Minetest schematic probability selection") elseif param == "get" then --get all nodes that had probabilities set on them local text = "" local problist = worldedit.prob_list[name] if problist == nil then return end for k,v in pairs(problist) do local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | " end worldedit.player_notify(name, "currently set node probabilities:") worldedit.player_notify(name, text) end end, }) minetest.register_on_player_receive_fields(function(player, formname, fields) if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then local name = player:get_player_name() local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)} local index = table.getn(worldedit.prob_list[name]) + 1 worldedit.prob_list[name][index] = prob_entry end end) worldedit.register_command("clearobjects", { params = "", description = "Clears all objects within the WorldEdit region", privs = {worldedit=true}, require_pos = 2, nodes_needed = check_region, func = function(name) local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name]) worldedit.player_notify(name, count .. " objects cleared") end, }) Minetest-WorldEdit-1.3/worldedit_commands/mark.lua000066400000000000000000000144331400537024600223610ustar00rootroot00000000000000worldedit.marker1 = {} worldedit.marker2 = {} worldedit.marker_region = {} local init_sentinel = "new" .. tostring(math.random(99999)) --marks worldedit region position 1 worldedit.mark_pos1 = function(name, region_too) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] if worldedit.marker1[name] ~= nil then --marker already exists worldedit.marker1[name]:remove() --remove marker worldedit.marker1[name] = nil end if pos1 ~= nil then --make area stay loaded local manip = minetest.get_voxel_manip() manip:read_from_map(pos1, pos1) --add marker worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel) if worldedit.marker1[name] ~= nil then worldedit.marker1[name]:get_luaentity().player_name = name end end if region_too == nil or region_too then worldedit.mark_region(name) end end --marks worldedit region position 2 worldedit.mark_pos2 = function(name, region_too) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] if worldedit.marker2[name] ~= nil then --marker already exists worldedit.marker2[name]:remove() --remove marker worldedit.marker2[name] = nil end if pos2 ~= nil then --make area stay loaded local manip = minetest.get_voxel_manip() manip:read_from_map(pos2, pos2) --add marker worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel) if worldedit.marker2[name] ~= nil then worldedit.marker2[name]:get_luaentity().player_name = name end end if region_too == nil or region_too then worldedit.mark_region(name) end end worldedit.mark_region = function(name) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] if worldedit.marker_region[name] ~= nil then --marker already exists for _, entity in ipairs(worldedit.marker_region[name]) do entity:remove() end worldedit.marker_region[name] = nil end if pos1 ~= nil and pos2 ~= nil then local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local vec = vector.subtract(pos2, pos1) local maxside = math.max(vec.x, math.max(vec.y, vec.z)) local limit = tonumber(minetest.settings:get("active_object_send_range_blocks")) * 16 if maxside > limit * 1.5 then -- The client likely won't be able to see the plane markers as intended anyway, -- thus don't place them and also don't load the area into memory return end local thickness = 0.2 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2 --make area stay loaded local manip = minetest.get_voxel_manip() manip:read_from_map(pos1, pos2) local markers = {} --XY plane markers for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z} local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) if marker ~= nil then marker:set_properties({ visual_size={x=sizex * 2, y=sizey * 2}, collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness}, }) marker:get_luaentity().player_name = name table.insert(markers, marker) end end --YZ plane markers for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5} local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) if marker ~= nil then marker:set_properties({ visual_size={x=sizez * 2, y=sizey * 2}, collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez}, }) marker:set_yaw(math.pi / 2) marker:get_luaentity().player_name = name table.insert(markers, marker) end end worldedit.marker_region[name] = markers end end --convenience function that calls everything worldedit.marker_update = function(name) worldedit.mark_pos1(name, false) worldedit.mark_pos2(name, false) worldedit.mark_region(name) end minetest.register_entity(":worldedit:pos1", { initial_properties = { visual = "cube", visual_size = {x=1.1, y=1.1}, textures = {"worldedit_pos1.png", "worldedit_pos1.png", "worldedit_pos1.png", "worldedit_pos1.png", "worldedit_pos1.png", "worldedit_pos1.png"}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, physical = false, static_save = false, }, on_activate = function(self, staticdata, dtime_s) if staticdata ~= init_sentinel then -- we were loaded from before static_save = false was added self.object:remove() end end, on_punch = function(self, hitter) self.object:remove() worldedit.marker1[self.player_name] = nil end, on_blast = function(self, damage) return false, false, {} -- don't damage or knockback end, }) minetest.register_entity(":worldedit:pos2", { initial_properties = { visual = "cube", visual_size = {x=1.1, y=1.1}, textures = {"worldedit_pos2.png", "worldedit_pos2.png", "worldedit_pos2.png", "worldedit_pos2.png", "worldedit_pos2.png", "worldedit_pos2.png"}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, physical = false, static_save = false, }, on_activate = function(self, staticdata, dtime_s) if staticdata ~= init_sentinel then -- we were loaded from before static_save = false was added self.object:remove() end end, on_punch = function(self, hitter) self.object:remove() worldedit.marker2[self.player_name] = nil end, on_blast = function(self, damage) return false, false, {} -- don't damage or knockback end, }) minetest.register_entity(":worldedit:region_cube", { initial_properties = { visual = "upright_sprite", textures = {"worldedit_cube.png"}, visual_size = {x=10, y=10}, physical = false, static_save = false, }, on_activate = function(self, staticdata, dtime_s) if staticdata ~= init_sentinel then -- we were loaded from before static_save = false was added self.object:remove() end end, on_punch = function(self, hitter) local markers = worldedit.marker_region[self.player_name] if not markers then return end for _, entity in ipairs(markers) do entity:remove() end worldedit.marker_region[self.player_name] = nil end, on_blast = function(self, damage) return false, false, {} -- don't damage or knockback end, }) Minetest-WorldEdit-1.3/worldedit_commands/mod.conf000066400000000000000000000001241400537024600223420ustar00rootroot00000000000000name = worldedit_commands description = WorldEdit chat commands depends = worldedit Minetest-WorldEdit-1.3/worldedit_commands/safe.lua000066400000000000000000000022631400537024600223430ustar00rootroot00000000000000local safe_region_callback = {} --`count` is the number of nodes that would possibly be modified --`callback` is a callback to run when the user confirms local function safe_region(name, count, callback) if count < 20000 then return callback() end --save callback to call later safe_region_callback[name] = callback worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel") end local function reset_pending(name) safe_region_callback[name] = nil end minetest.register_chatcommand("/y", { params = "", description = "Confirm a pending operation", func = function(name) local callback = safe_region_callback[name] if not callback then worldedit.player_notify(name, "no operation pending") return end reset_pending(name) callback(name) end, }) minetest.register_chatcommand("/n", { params = "", description = "Abort a pending operation", func = function(name) if not safe_region_callback[name] then worldedit.player_notify(name, "no operation pending") return end reset_pending(name) end, }) return safe_region, reset_pending Minetest-WorldEdit-1.3/worldedit_commands/textures/000077500000000000000000000000001400537024600226025ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_commands/textures/worldedit_cube.png000066400000000000000000000002231400537024600263000ustar00rootroot00000000000000PNG  IHDRabKGD pHYs(JtIME &vK IDAT8c```i0Q`O/,DIENDB`Minetest-WorldEdit-1.3/worldedit_commands/textures/worldedit_pos1.png000066400000000000000000000002161400537024600262460ustar00rootroot00000000000000PNG  IHDRa pHYs(J@IDAT8cd``@!ĀLDkJ\R/0B1a0 `!2#.}\H0`4; k%IENDB`Minetest-WorldEdit-1.3/worldedit_commands/textures/worldedit_pos2.png000066400000000000000000000002351400537024600262500ustar00rootroot00000000000000PNG  IHDRa pHYs(JOIDAT8˵ BL$gyPT* Ø &kA4)b@]طKDO'Z IENDB`Minetest-WorldEdit-1.3/worldedit_commands/wand.lua000066400000000000000000000042041400537024600223530ustar00rootroot00000000000000local function above_or_under(placer, pointed_thing) if placer:get_player_control().sneak then return pointed_thing.above else return pointed_thing.under end end local punched_air_time = {} minetest.register_tool(":worldedit:wand", { description = "WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd", inventory_image = "worldedit_wand.png", stack_max = 1, -- there is no need to have more than one liquids_pointable = true, -- ground with only water on can be selected as well on_use = function(itemstack, placer, pointed_thing) if placer == nil or pointed_thing == nil then return end local name = placer:get_player_name() if pointed_thing.type == "node" then -- set and mark pos1 worldedit.pos1[name] = above_or_under(placer, pointed_thing) worldedit.mark_pos1(name) elseif pointed_thing.type == "nothing" then local now = minetest.get_us_time() if now - (punched_air_time[name] or 0) < 1000 * 1000 then -- reset markers worldedit.registered_commands["reset"].func(name) end punched_air_time[name] = now elseif pointed_thing.type == "object" then local entity = pointed_thing.ref:get_luaentity() if entity and entity.name == "worldedit:pos2" then -- set pos1 = pos2 worldedit.pos1[name] = worldedit.pos2[name] worldedit.mark_pos1(name) end end end, on_place = function(itemstack, placer, pointed_thing) if placer == nil or (pointed_thing or {}).type ~= "node" then return itemstack end local name = placer:get_player_name() -- set and mark pos2 worldedit.pos2[name] = above_or_under(placer, pointed_thing) worldedit.mark_pos2(name) return itemstack -- nothing consumed, nothing changed end, on_secondary_use = function(itemstack, user, pointed_thing) if user == nil or (pointed_thing or {}).type ~= "object" then return itemstack end local name = user:get_player_name() local entity = pointed_thing.ref:get_luaentity() if entity and entity.name == "worldedit:pos1" then -- set pos2 = pos1 worldedit.pos2[name] = worldedit.pos1[name] worldedit.mark_pos2(name) end return itemstack -- nothing consumed, nothing changed end, }) Minetest-WorldEdit-1.3/worldedit_gui/000077500000000000000000000000001400537024600177025ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_gui/functionality.lua000066400000000000000000001134751400537024600233100ustar00rootroot00000000000000--saved state for each player local gui_nodename1 = {} --mapping of player names to node names local gui_nodename2 = {} --mapping of player names to node names local gui_axis1 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) local gui_axis2 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) local gui_distance1 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) local gui_distance2 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) local gui_distance3 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) local gui_count1 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise) local gui_filename = {} --mapping of player names to file names --set default values setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end}) setmetatable(gui_nodename2, {__index = function() return "Stone" end}) setmetatable(gui_axis1, {__index = function() return 4 end}) setmetatable(gui_axis2, {__index = function() return 1 end}) setmetatable(gui_distance1, {__index = function() return "10" end}) setmetatable(gui_distance2, {__index = function() return "5" end}) setmetatable(gui_distance3, {__index = function() return "2" end}) setmetatable(gui_count1, {__index = function() return "3" end}) setmetatable(gui_count2, {__index = function() return "6" end}) setmetatable(gui_count3, {__index = function() return "4" end}) setmetatable(gui_angle, {__index = function() return 90 end}) setmetatable(gui_filename, {__index = function() return "building" end}) local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4} local axis_values = {"x", "y", "z", "?"} setmetatable(axis_indices, {__index = function () return 4 end}) setmetatable(axis_values, {__index = function () return "?" end}) local angle_indices = {["90 degrees"]=1, ["180 degrees"]=2, ["270 degrees"]=3} local angle_values = {90, 180, 270} setmetatable(angle_indices, {__index = function () return 1 end}) setmetatable(angle_values, {__index = function () return 90 end}) -- given multiple sets of privileges, produces a single set of privs that would have the same effect as requiring all of them at the same time local combine_privs = function(...) local result = {} for i, privs in ipairs({...}) do for name, value in pairs(privs) do if result[name] ~= nil and result[name] ~= value then --the priv must be both true and false, which can never happen return {__fake_priv_that_nobody_has__=true} --privilege table that can never be satisfied end result[name] = value end end return result end -- display node (or unknown_node image otherwise) at specified pos in formspec local formspec_node = function(pos, nodename) local ndef = nodename and minetest.registered_nodes[nodename] if nodename and ndef then return string.format("item_image[%s;1,1;%s]", pos, nodename) .. string.format("tooltip[%s;1,1;%s]", pos, minetest.formspec_escape(ndef.description)) else return string.format("image[%s;1,1;worldedit_gui_unknown.png]", pos) end end -- two further priv helpers local function we_privs(command) return worldedit.registered_commands[command].privs end local function combine_we_privs(list) local args = {} for _, t in ipairs(list) do table.insert(args, we_privs(t)) end return combine_privs(unpack(args)) end -- functions that handle value changing & page reshowing (without submitting) local function copy_changes(name, fields, def) for field, into in pairs(def) do if into ~= true and fields[field] then local value = tostring(fields[field]) if into == gui_axis1 or into == gui_axis2 then into[name] = axis_indices[value] elseif into == gui_angle then into[name] = angle_indices[value] else into[name] = value end end end end local function handle_changes(name, identifier, fields, def) local any = false for field, into in pairs(def) do if fields.key_enter_field == field then any = true end -- first condition: buttons (value not saved) -- others: dropdowns which will be sent when their value changes if into == true or into == gui_axis1 or into == gui_axis2 or into == gui_angle then if fields[field] then any = true end end end if not any then return false end any = false for field, into in pairs(def) do if into ~= true and fields[field] then local value = tostring(fields[field]) if into == gui_axis1 or into == gui_axis2 then into[name] = axis_indices[value] elseif into == gui_angle then into[name] = angle_indices[value] else into[name] = value end if into == gui_nodename1 or into == gui_nodename2 then any = true end end end -- Only nodename fields change based on the value, so only re-show the page if necessary if any then worldedit.show_page(name, identifier) end return true end -- This has the same behaviour as the player invoking the chat command local function execute_worldedit_command(command_name, player_name, params) local chatcmd = minetest.registered_chatcommands["/" .. command_name] assert(chatcmd, "unknown command: " .. command_name) local _, msg = chatcmd.func(player_name, params) if msg then worldedit.player_notify(player_name, msg) end end worldedit.register_gui_function("worldedit_gui_about", { name = "About", privs = {interact=true}, on_select = function(name) execute_worldedit_command("about", name, "") end, }) worldedit.register_gui_function("worldedit_gui_inspect", { name = "Toggle Inspect", privs = we_privs("inspect"), on_select = function(name) execute_worldedit_command("inspect", name, worldedit.inspect[name] and "disable" or "enable") end, }) worldedit.register_gui_function("worldedit_gui_region", { name = "Get/Set Region", privs = combine_we_privs({"p", "pos1", "pos2", "reset", "mark", "unmark", "volume", "fixedpos"}), get_formspec = function(name) local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] return "size[9,7]" .. worldedit.get_formspec_header("worldedit_gui_region") .. "button_exit[0,1;3,0.8;worldedit_gui_p_get;Get Positions]" .. "button_exit[3,1;3,0.8;worldedit_gui_p_set1;Choose Position 1]" .. "button_exit[6,1;3,0.8;worldedit_gui_p_set2;Choose Position 2]" .. "button_exit[0,2;3,0.8;worldedit_gui_pos1;Position 1 Here]" .. "button_exit[3,2;3,0.8;worldedit_gui_pos2;Position 2 Here]" .. "button_exit[6,2;3,0.8;worldedit_gui_reset;Reset Region]" .. "button_exit[0,3;3,0.8;worldedit_gui_mark;Mark Region]" .. "button_exit[3,3;3,0.8;worldedit_gui_unmark;Unmark Region]" .. "button_exit[6,3;3,0.8;worldedit_gui_volume;Region Volume]" .. "label[0,4.7;Position 1]" .. string.format("field[2,5;1.5,0.8;worldedit_gui_fixedpos_pos1x;X ;%s]", pos1 and pos1.x or "") .. string.format("field[3.5,5;1.5,0.8;worldedit_gui_fixedpos_pos1y;Y ;%s]", pos1 and pos1.y or "") .. string.format("field[5,5;1.5,0.8;worldedit_gui_fixedpos_pos1z;Z ;%s]", pos1 and pos1.z or "") .. "button_exit[6.5,4.68;2.5,0.8;worldedit_gui_fixedpos_pos1_submit;Set Position 1]" .. "label[0,6.2;Position 2]" .. string.format("field[2,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2x;X ;%s]", pos2 and pos2.x or "") .. string.format("field[3.5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2y;Y ;%s]", pos2 and pos2.y or "") .. string.format("field[5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2z;Z ;%s]", pos2 and pos2.z or "") .. "button_exit[6.5,6.18;2.5,0.8;worldedit_gui_fixedpos_pos2_submit;Set Position 2]" end, }) worldedit.register_gui_handler("worldedit_gui_region", function(name, fields) if fields.worldedit_gui_p_get then execute_worldedit_command("p", name, "get") return true elseif fields.worldedit_gui_p_set1 then execute_worldedit_command("p", name, "set1") return true elseif fields.worldedit_gui_p_set2 then execute_worldedit_command("p", name, "set2") return true elseif fields.worldedit_gui_pos1 then execute_worldedit_command("pos1", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_pos2 then execute_worldedit_command("pos2", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_reset then execute_worldedit_command("reset", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_mark then execute_worldedit_command("mark", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_unmark then execute_worldedit_command("unmark", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_volume then execute_worldedit_command("volume", name, "") worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_fixedpos_pos1_submit then execute_worldedit_command("fixedpos", name, ("set1 %s %s %s"):format( tostring(fields.worldedit_gui_fixedpos_pos1x), tostring(fields.worldedit_gui_fixedpos_pos1y), tostring(fields.worldedit_gui_fixedpos_pos1z))) worldedit.show_page(name, "worldedit_gui_region") return true elseif fields.worldedit_gui_fixedpos_pos2_submit then execute_worldedit_command("fixedpos", name, ("set2 %s %s %s"):format( tostring(fields.worldedit_gui_fixedpos_pos2x), tostring(fields.worldedit_gui_fixedpos_pos2y), tostring(fields.worldedit_gui_fixedpos_pos2z))) worldedit.show_page(name, "worldedit_gui_region") return true end return false end) worldedit.register_gui_function("worldedit_gui_set", { name = "Set Nodes", privs = we_privs("set"), get_formspec = function(name) local node = gui_nodename1[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_set") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_set_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_set_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_set_search;Search]" .. formspec_node("5.5,1.1", nodename) .. "button_exit[0,2.5;3,0.8;worldedit_gui_set_submit;Set Nodes]" end, }) worldedit.register_gui_handler("worldedit_gui_set", function(name, fields) local cg = { worldedit_gui_set_search = true, worldedit_gui_set_node = gui_nodename1, } local ret = handle_changes(name, "worldedit_gui_set", fields, cg) if fields.worldedit_gui_set_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_set") local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command("set", name, n) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_replace", { name = "Replace Nodes", privs = combine_we_privs({"replace", "replaceinverse"}), get_formspec = function(name) local search, replace = gui_nodename1[name], gui_nodename2[name] local search_nodename, replace_nodename = worldedit.normalize_nodename(search), worldedit.normalize_nodename(replace) return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_replace") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_replace_search;Name;%s]", minetest.formspec_escape(search)) .. "field_close_on_enter[worldedit_gui_replace_search;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_replace_search_search;Search]" .. formspec_node("5.5,1.1", search_nodename) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_replace_replace;Name;%s]", minetest.formspec_escape(replace)) .. "field_close_on_enter[worldedit_gui_replace_replace;false]" .. "button[4,2.18;1.5,0.8;worldedit_gui_replace_replace_search;Search]" .. formspec_node("5.5,2.1", replace_nodename) .. "button_exit[0,3.5;3,0.8;worldedit_gui_replace_submit;Replace Nodes]" .. "button_exit[3.5,3.5;3,0.8;worldedit_gui_replace_submit_inverse;Replace Inverse]" end, }) worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields) local cg = { worldedit_gui_replace_search_search = true, worldedit_gui_replace_replace_search = true, worldedit_gui_replace_search = gui_nodename1, worldedit_gui_replace_replace = gui_nodename2, } local ret = handle_changes(name, "worldedit_gui_replace", fields, cg) if fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_replace") local submit = "replace" if fields.worldedit_gui_replace_submit_inverse then submit = "replaceinverse" end local n1 = worldedit.normalize_nodename(gui_nodename1[name]) local n2 = worldedit.normalize_nodename(gui_nodename2[name]) if n1 and n2 then execute_worldedit_command(submit, name, n1 .. " " .. n2) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_sphere_dome", { name = "Sphere/Dome", privs = combine_we_privs({"hollowsphere", "sphere", "hollowdome", "dome"}), get_formspec = function(name) local node, radius = gui_nodename1[name], gui_distance2[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_sphere_dome") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_sphere_dome_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_sphere_dome_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_sphere_dome_search;Search]" .. formspec_node("5.5,1.1", nodename) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_sphere_dome_radius;Radius;%s]", minetest.formspec_escape(radius)) .. "field_close_on_enter[worldedit_gui_sphere_dome_radius;false]" .. "button_exit[0,3.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow;Hollow Sphere]" .. "button_exit[3.5,3.5;3,0.8;worldedit_gui_sphere_dome_submit_solid;Solid Sphere]" .. "button_exit[0,4.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow_dome;Hollow Dome]" .. "button_exit[3.5,4.5;3,0.8;worldedit_gui_sphere_dome_submit_solid_dome;Solid Dome]" end, }) worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields) local cg = { worldedit_gui_sphere_dome_search = true, worldedit_gui_sphere_dome_node = gui_nodename1, worldedit_gui_sphere_dome_radius = gui_distance2, } local ret = handle_changes(name, "worldedit_gui_sphere_dome", fields, cg) if fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid or fields.worldedit_gui_sphere_dome_submit_hollow_dome or fields.worldedit_gui_sphere_dome_submit_solid_dome then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_sphere_dome") local submit = "hollowsphere" if fields.worldedit_gui_sphere_dome_submit_solid then submit = "sphere" elseif fields.worldedit_gui_sphere_dome_submit_hollow_dome then submit = "hollowdome" elseif fields.worldedit_gui_sphere_dome_submit_solid_dome then submit = "dome" end local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command(submit, name, gui_distance2[name] .. " " .. n) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_cylinder", { name = "Cylinder", privs = combine_we_privs({"hollowcylinder", "cylinder"}), get_formspec = function(name) local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name] local radius1, radius2 = gui_distance2[name], gui_distance3[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_cylinder") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_cylinder_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_cylinder_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_cylinder_search;Search]" .. formspec_node("5.5,1.1", nodename) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_cylinder_length;Length;%s]", minetest.formspec_escape(length)) .. string.format("dropdown[4,2.18;2.5;worldedit_gui_cylinder_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. string.format("field[0.5,3.5;2,0.8;worldedit_gui_cylinder_radius1;Base Radius;%s]", minetest.formspec_escape(radius1)) .. string.format("field[2.5,3.5;2,0.8;worldedit_gui_cylinder_radius2;Top Radius;%s]", minetest.formspec_escape(radius2)) .. "field_close_on_enter[worldedit_gui_cylinder_length;false]" .. "field_close_on_enter[worldedit_gui_cylinder_radius1;false]" .. "field_close_on_enter[worldedit_gui_cylinder_radius2;false]" .. "label[0.25,4;Equal base and top radius creates a cylinder,\n".. "zero top radius creates a cone.\nConsult documentation for more information.]".. "button_exit[0,5.5;3,0.8;worldedit_gui_cylinder_submit_hollow;Hollow Cylinder]" .. "button_exit[3.5,5.5;3,0.8;worldedit_gui_cylinder_submit_solid;Solid Cylinder]" end, }) worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields) local cg = { worldedit_gui_cylinder_search = true, worldedit_gui_cylinder_node = gui_nodename1, worldedit_gui_cylinder_axis = gui_axis1, worldedit_gui_cylinder_length = gui_distance1, worldedit_gui_cylinder_radius1 = gui_distance2, worldedit_gui_cylinder_radius2 = gui_distance3, } local ret = handle_changes(name, "worldedit_gui_cylinder", fields, cg) if fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_cylinder") local submit = "hollowcylinder" if fields.worldedit_gui_cylinder_submit_solid then submit = "cylinder" end local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then local args = string.format("%s %s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_distance3[name], n) execute_worldedit_command(submit, name, args) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_pyramid", { name = "Pyramid", privs = we_privs("pyramid"), get_formspec = function(name) local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_pyramid") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_pyramid_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_pyramid_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_pyramid_search;Search]" .. formspec_node("5.5,1.1", nodename) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_pyramid_length;Length;%s]", minetest.formspec_escape(length)) .. string.format("dropdown[4,2.18;2.5;worldedit_gui_pyramid_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. "field_close_on_enter[worldedit_gui_pyramid_length;false]" .. "button_exit[0,3.5;3,0.8;worldedit_gui_pyramid_submit_hollow;Hollow Pyramid]" .. "button_exit[3.5,3.5;3,0.8;worldedit_gui_pyramid_submit_solid;Solid Pyramid]" end, }) worldedit.register_gui_handler("worldedit_gui_pyramid", function(name, fields) local cg = { worldedit_gui_pyramid_search = true, worldedit_gui_pyramid_node = gui_nodename1, worldedit_gui_pyramid_axis = gui_axis1, worldedit_gui_pyramid_length = gui_distance1, } local ret = handle_changes(name, "worldedit_gui_pyramid", fields, cg) if fields.worldedit_gui_pyramid_submit_solid or fields.worldedit_gui_pyramid_submit_hollow then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_pyramid") local submit = "pyramid" if fields.worldedit_gui_pyramid_submit_hollow then submit = "hollowpyramid" end local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command(submit, name, string.format("%s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], n)) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_spiral", { name = "Spiral", privs = we_privs("spiral"), get_formspec = function(name) local node, length, height, space = gui_nodename1[name], gui_distance1[name], gui_distance2[name], gui_distance3[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_spiral") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_spiral_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_spiral_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_spiral_search;Search]" .. formspec_node("5.5,1.1", nodename) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_spiral_length;Side Length;%s]", minetest.formspec_escape(length)) .. string.format("field[0.5,3.5;4,0.8;worldedit_gui_spiral_height;Height;%s]", minetest.formspec_escape(height)) .. string.format("field[0.5,4.5;4,0.8;worldedit_gui_spiral_space;Wall Spacing;%s]", minetest.formspec_escape(space)) .. "field_close_on_enter[worldedit_gui_spiral_length;false]" .. "field_close_on_enter[worldedit_gui_spiral_height;false]" .. "field_close_on_enter[worldedit_gui_spiral_space;false]" .. "button_exit[0,5.5;3,0.8;worldedit_gui_spiral_submit;Spiral]" end, }) worldedit.register_gui_handler("worldedit_gui_spiral", function(name, fields) local cg = { worldedit_gui_spiral_search = true, worldedit_gui_spiral_node = gui_nodename1, worldedit_gui_spiral_length = gui_distance1, worldedit_gui_spiral_height = gui_distance2, worldedit_gui_spiral_space = gui_distance3, } local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg) if fields.worldedit_gui_spiral_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_spiral") local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command("spiral", name, string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n)) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_copy_move", { name = "Copy/Move", privs = combine_we_privs({"copy", "move"}), get_formspec = function(name) local axis = gui_axis1[name] or 4 local amount = gui_distance1[name] or "10" return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_copy_move") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_copy_move_amount;Amount;%s]", minetest.formspec_escape(amount)) .. string.format("dropdown[4,1.18;2.5;worldedit_gui_copy_move_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. "field_close_on_enter[worldedit_gui_copy_move_amount;false]" .. "button_exit[0,2.5;3,0.8;worldedit_gui_copy_move_copy;Copy Region]" .. "button_exit[3.5,2.5;3,0.8;worldedit_gui_copy_move_move;Move Region]" end, }) worldedit.register_gui_handler("worldedit_gui_copy_move", function(name, fields) local cg = { worldedit_gui_copy_move_amount = gui_distance1, worldedit_gui_copy_move_axis = gui_axis1, } local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg) if fields.worldedit_gui_copy_move_copy or fields.worldedit_gui_copy_move_move then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_copy_move") local submit = "copy" if fields.worldedit_gui_copy_move_move then submit = "move" end execute_worldedit_command(submit, name, axis_values[gui_axis1[name]] .. " " .. gui_distance1[name]) return true end return ret end) worldedit.register_gui_function("worldedit_gui_stack", { name = "Stack", privs = we_privs("stack"), get_formspec = function(name) local axis, count = gui_axis1[name], gui_count1[name] return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_stack") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_stack_count;Count;%s]", minetest.formspec_escape(count)) .. string.format("dropdown[4,1.18;2.5;worldedit_gui_stack_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. "field_close_on_enter[worldedit_gui_stack_count;false]" .. "button_exit[0,2.5;3,0.8;worldedit_gui_stack_submit;Stack]" end, }) worldedit.register_gui_handler("worldedit_gui_stack", function(name, fields) local cg = { worldedit_gui_stack_axis = gui_axis1, worldedit_gui_stack_count = gui_count1, } local ret = handle_changes(name, "worldedit_gui_stack", fields, cg) if fields.worldedit_gui_stack_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_stack") execute_worldedit_command("stack", name, axis_values[gui_axis1[name]] .. " " .. gui_count1[name]) return true end return ret end) worldedit.register_gui_function("worldedit_gui_stretch", { name = "Stretch", privs = we_privs("stretch"), get_formspec = function(name) local stretchx, stretchy, stretchz = gui_count1[name], gui_count2[name], gui_count3[name] return "size[5,5]" .. worldedit.get_formspec_header("worldedit_gui_stretch") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_stretch_x;Stretch X;%s]", minetest.formspec_escape(stretchx)) .. string.format("field[0.5,2.5;4,0.8;worldedit_gui_stretch_y;Stretch Y;%s]", minetest.formspec_escape(stretchy)) .. string.format("field[0.5,3.5;4,0.8;worldedit_gui_stretch_z;Stretch Z;%s]", minetest.formspec_escape(stretchz)) .. "field_close_on_enter[worldedit_gui_stretch_x;false]" .. "field_close_on_enter[worldedit_gui_stretch_y;false]" .. "field_close_on_enter[worldedit_gui_stretch_z;false]" .. "button_exit[0,4.5;3,0.8;worldedit_gui_stretch_submit;Stretch]" end, }) worldedit.register_gui_handler("worldedit_gui_stretch", function(name, fields) local cg = { worldedit_gui_stretch_x = gui_count1, worldedit_gui_stretch_y = gui_count2, worldedit_gui_stretch_z = gui_count3, } local ret = handle_changes(name, "worldedit_gui_stretch", fields, cg) if fields.worldedit_gui_stretch_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_stretch") execute_worldedit_command("stretch", name, string.format("%s %s %s", gui_count1[name], gui_count2[name], gui_count3[name])) return true end return ret end) worldedit.register_gui_function("worldedit_gui_transpose", { name = "Transpose", privs = we_privs("transpose"), get_formspec = function(name) local axis1, axis2 = gui_axis1[name], gui_axis2[name] return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_transpose") .. string.format("dropdown[0,1;2.5;worldedit_gui_transpose_axis1;X axis,Y axis,Z axis,Look direction;%d]", axis1) .. string.format("dropdown[3,1;2.5;worldedit_gui_transpose_axis2;X axis,Y axis,Z axis,Look direction;%d]", axis2) .. "button_exit[0,2.5;3,0.8;worldedit_gui_transpose_submit;Transpose]" end, }) worldedit.register_gui_handler("worldedit_gui_transpose", function(name, fields) local cg = { worldedit_gui_transpose_axis1 = gui_axis1, worldedit_gui_transpose_axis2 = gui_axis2, } local ret = handle_changes(name, "worldedit_gui_transpose", fields, cg) if fields.worldedit_gui_transpose_submit then copy_changes(name, fields, cg) execute_worldedit_command("transpose", name, axis_values[gui_axis1[name]] .. " " .. axis_values[gui_axis2[name]]) return true end return ret end) worldedit.register_gui_function("worldedit_gui_flip", { name = "Flip", privs = we_privs("flip"), get_formspec = function(name) local axis = gui_axis1[name] return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_flip") .. string.format("dropdown[0,1;2.5;worldedit_gui_flip_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. "button_exit[0,2.5;3,0.8;worldedit_gui_flip_submit;Flip]" end, }) worldedit.register_gui_handler("worldedit_gui_flip", function(name, fields) local cg = { worldedit_gui_flip_axis = gui_axis1 } local ret = handle_changes(name, "worldedit_gui_flip", fields, cg) if fields.worldedit_gui_flip_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_flip") execute_worldedit_command("flip", name, axis_values[gui_axis1[name]]) return true end return ret end) worldedit.register_gui_function("worldedit_gui_rotate", { name = "Rotate", privs = we_privs("rotate"), get_formspec = function(name) local axis, angle = gui_axis1[name], gui_angle[name] return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_rotate") .. string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. string.format("dropdown[3,1;2.5;worldedit_gui_rotate_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. "button_exit[0,2.5;3,0.8;worldedit_gui_rotate_submit;Rotate]" end, }) worldedit.register_gui_handler("worldedit_gui_rotate", function(name, fields) local cg = { worldedit_gui_rotate_axis = gui_axis1, worldedit_gui_rotate_angle = gui_angle, } local ret = handle_changes(name, "worldedit_gui_rotate", fields, cg) if fields.worldedit_gui_rotate_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_rotate") execute_worldedit_command("rotate", name, axis_values[gui_axis1[name]] .. " " .. angle_values[gui_angle[name]]) return true end return ret end) worldedit.register_gui_function("worldedit_gui_orient", { name = "Orient", privs = we_privs("orient"), get_formspec = function(name) local angle = gui_angle[name] return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_orient") .. string.format("dropdown[0,1;2.5;worldedit_gui_orient_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. "button_exit[0,2.5;3,0.8;worldedit_gui_orient_submit;Orient]" end, }) worldedit.register_gui_handler("worldedit_gui_orient", function(name, fields) local cg = { worldedit_gui_orient_angle = gui_angle, } local ret = handle_changes(name, "worldedit_gui_orient", fields, cg) if fields.worldedit_gui_orient_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_orient") execute_worldedit_command("orient", name, tostring(angle_values[gui_angle[name]])) return true end return ret end) worldedit.register_gui_function("worldedit_gui_fixlight", { name = "Fix Lighting", privs = we_privs("fixlight"), on_select = function(name) execute_worldedit_command("fixlight", name, "") end, }) worldedit.register_gui_function("worldedit_gui_hide", { name = "Hide Region", privs = we_privs("hide"), on_select = function(name) execute_worldedit_command("hide", name, "") end, }) worldedit.register_gui_function("worldedit_gui_suppress", { name = "Suppress Nodes", privs = we_privs("suppress"), get_formspec = function(name) local node = gui_nodename1[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_suppress") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_suppress_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_suppress_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_suppress_search;Search]" .. formspec_node("5.5,1.1", nodename) .. "button_exit[0,2.5;3,0.8;worldedit_gui_suppress_submit;Suppress Nodes]" end, }) worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields) local cg = { worldedit_gui_suppress_search = true, worldedit_gui_suppress_node = gui_nodename1, } local ret = handle_changes(name, "worldedit_gui_suppress", fields, cg) if fields.worldedit_gui_suppress_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_suppress") local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command("suppress", name, n) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_highlight", { name = "Highlight Nodes", privs = we_privs("highlight"), get_formspec = function(name) local node = gui_nodename1[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_highlight") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_highlight_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_highlight_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_highlight_search;Search]" .. formspec_node("5.5,1.1", nodename) .. "button_exit[0,2.5;3,0.8;worldedit_gui_highlight_submit;Highlight Nodes]" end, }) worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields) local cg = { worldedit_gui_highlight_search = true, worldedit_gui_highlight_node = gui_nodename1, } local ret = handle_changes(name, "worldedit_gui_highlight", fields, cg) if fields.worldedit_gui_highlight_submit then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_highlight") local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then execute_worldedit_command("highlight", name, n) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_restore", { name = "Restore Region", privs = we_privs("restore"), on_select = function(name) execute_worldedit_command("restore", name, "") end, }) worldedit.register_gui_function("worldedit_gui_save_load", { name = "Save/Load", privs = combine_we_privs({"save", "allocate", "load"}), get_formspec = function(name) local filename = gui_filename[name] return "size[6,4]" .. worldedit.get_formspec_header("worldedit_gui_save_load") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_save_filename;Filename;%s]", minetest.formspec_escape(filename)) .. "field_close_on_enter[worldedit_gui_save_filename;false]" .. "button_exit[0,2.5;3,0.8;worldedit_gui_save_load_submit_save;Save]" .. "button_exit[3,2.5;3,0.8;worldedit_gui_save_load_submit_allocate;Allocate]" .. "button_exit[0,3.5;3,0.8;worldedit_gui_save_load_submit_load;Load]" end, }) worldedit.register_gui_handler("worldedit_gui_save_load", function(name, fields) if fields.worldedit_gui_save_load_submit_save or fields.worldedit_gui_save_load_submit_allocate or fields.worldedit_gui_save_load_submit_load then gui_filename[name] = tostring(fields.worldedit_gui_save_filename) worldedit.show_page(name, "worldedit_gui_save_load") if fields.worldedit_gui_save_load_submit_save then execute_worldedit_command("save", name, gui_filename[name]) elseif fields.worldedit_gui_save_load_submit_allocate then execute_worldedit_command("allocate", name, gui_filename[name]) else --fields.worldedit_gui_save_load_submit_load execute_worldedit_command("load", name, gui_filename[name]) end return true end return false end) worldedit.register_gui_function("worldedit_gui_cube", { name = "Cube", privs = combine_we_privs({"hollowcube", "cube"}), get_formspec = function(name) local width, height, length = gui_distance1[name], gui_distance2[name], gui_distance3[name] local node = gui_nodename1[name] local nodename = worldedit.normalize_nodename(node) return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_cube") .. string.format("field[0.5,1.5;4,0.8;worldedit_gui_cube_node;Name;%s]", minetest.formspec_escape(node)) .. "field_close_on_enter[worldedit_gui_cube_node;false]" .. "button[4,1.18;1.5,0.8;worldedit_gui_cube_search;Search]" .. formspec_node("5.5,1.1", nodename) .. string.format("field[0.5,2.5;1,0.8;worldedit_gui_cube_width;Width;%s]", minetest.formspec_escape(width)) .. string.format("field[1.5,2.5;1,0.8;worldedit_gui_cube_height;Height;%s]", minetest.formspec_escape(height)) .. string.format("field[2.5,2.5;1,0.8;worldedit_gui_cube_length;Length;%s]", minetest.formspec_escape(length)) .. "field_close_on_enter[worldedit_gui_cube_width;false]" .. "field_close_on_enter[worldedit_gui_cube_height;false]" .. "field_close_on_enter[worldedit_gui_cube_length;false]" .. "button_exit[0,3.5;3,0.8;worldedit_gui_cube_submit_hollow;Hollow Cuboid]" .. "button_exit[3.5,3.5;3,0.8;worldedit_gui_cube_submit_solid;Solid Cuboid]" end, }) worldedit.register_gui_handler("worldedit_gui_cube", function(name, fields) local cg = { worldedit_gui_cube_search = true, worldedit_gui_cube_node = gui_nodename1, worldedit_gui_cube_width = gui_distance1, worldedit_gui_cube_height = gui_distance2, worldedit_gui_cube_length = gui_distance3, } local ret = handle_changes(name, "worldedit_gui_cube", fields, cg) if fields.worldedit_gui_cube_submit_hollow or fields.worldedit_gui_cube_submit_solid then copy_changes(name, fields, cg) worldedit.show_page(name, "worldedit_gui_cube") local submit = "hollowcube" if fields.worldedit_gui_cube_submit_solid then submit = "cube" end local n = worldedit.normalize_nodename(gui_nodename1[name]) if n then local args = string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n) execute_worldedit_command(submit, name, args) end return true end return ret end) worldedit.register_gui_function("worldedit_gui_clearobjects", { name = "Clear Objects", privs = we_privs("clearobjects"), on_select = function(name) execute_worldedit_command("clearobjects", name, "") end, }) Minetest-WorldEdit-1.3/worldedit_gui/init.lua000066400000000000000000000234771400537024600213650ustar00rootroot00000000000000worldedit = worldedit or {} --[[ Example: worldedit.register_gui_function("worldedit_gui_hollow_cylinder", { name = "Make Hollow Cylinder", privs = {worldedit=true}, get_formspec = function(name) return "some formspec here" end, on_select = function(name) print(name .. " clicked the button!") end, }) Use `nil` for the `options` parameter to unregister the function associated with the given identifier. Use `nil` for the `get_formspec` field to denote that the function does not have its own screen. The `privs` field may not be `nil`. If the identifier is already registered to another function, it will be replaced by the new one. The `on_select` function must not call `worldedit.show_page` ]] worldedit.pages = {} --mapping of identifiers to options local identifiers = {} --ordered list of identifiers worldedit.register_gui_function = function(identifier, options) if options.privs == nil or next(options.privs) == nil then error("privs unset") end worldedit.pages[identifier] = options table.insert(identifiers, identifier) end --[[ Example: worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields) print(minetest.serialize(fields)) end) ]] worldedit.register_gui_handler = function(identifier, handler) local enabled = true minetest.register_on_player_receive_fields(function(player, formname, fields) if not enabled then return false end enabled = false minetest.after(0.2, function() enabled = true end) local name = player:get_player_name() --ensure the player has permission to perform the action local entry = worldedit.pages[identifier] if entry and minetest.check_player_privs(name, entry.privs) then return handler(name, fields) end return false end) end worldedit.get_formspec_header = function(identifier) local entry = worldedit.pages[identifier] or {} return "button[0,0;2,0.5;worldedit_gui;Back]" .. string.format("label[2,0;WorldEdit GUI > %s]", entry.name or "") end local get_formspec = function(name, identifier) if worldedit.pages[identifier] then return worldedit.pages[identifier].get_formspec(name) end return worldedit.pages["worldedit_gui"].get_formspec(name) --default to showing main page if an unknown page is given end --implement worldedit.show_page(name, page) in different ways depending on the available APIs if minetest.global_exists("unified_inventory") then -- unified inventory installed local old_func = worldedit.register_gui_function worldedit.register_gui_function = function(identifier, options) old_func(identifier, options) unified_inventory.register_page(identifier, {get_formspec=function(player) return {formspec=options.get_formspec(player:get_player_name())} end}) end unified_inventory.register_button("worldedit_gui", { type = "image", image = "inventory_plus_worldedit_gui.png", condition = function(player) return minetest.check_player_privs(player:get_player_name(), {worldedit=true}) end, }) minetest.register_on_player_receive_fields(function(player, formname, fields) local name = player:get_player_name() if fields.worldedit_gui then --main page worldedit.show_page(name, "worldedit_gui") return true elseif fields.worldedit_gui_exit then --return to original page local player = minetest.get_player_by_name(name) if player then unified_inventory.set_inventory_formspec(player, "craft") end return true end return false end) worldedit.show_page = function(name, page) local player = minetest.get_player_by_name(name) if player then player:set_inventory_formspec(get_formspec(name, page)) end end elseif minetest.global_exists("inventory_plus") then -- inventory++ installed minetest.register_on_joinplayer(function(player) local can_worldedit = minetest.check_player_privs(player:get_player_name(), {worldedit=true}) if can_worldedit then inventory_plus.register_button(player, "worldedit_gui", "WorldEdit") end end) --show the form when the button is pressed and hide it when done local gui_player_formspecs = {} minetest.register_on_player_receive_fields(function(player, formname, fields) local name = player:get_player_name() if fields.worldedit_gui then --main page gui_player_formspecs[name] = player:get_inventory_formspec() worldedit.show_page(name, "worldedit_gui") return true elseif fields.worldedit_gui_exit then --return to original page if gui_player_formspecs[name] then inventory_plus.set_inventory_formspec(player, inventory_plus.get_formspec(player, "main")) end return true end return false end) worldedit.show_page = function(name, page) local player = minetest.get_player_by_name(name) if player then inventory_plus.set_inventory_formspec(player, get_formspec(name, page)) end end elseif minetest.global_exists("smart_inventory") then -- smart_inventory installed -- redefinition: Update the code element on inventory page to show the we-page function worldedit.show_page(name, page) local state = smart_inventory.get_page_state("worldedit_gui", name) if state then state:get("code"):set_we_formspec(page) state.location.rootState:show() -- update inventory page end end -- smart_inventory page callback. Contains just a "custom code" element local function smart_worldedit_gui_callback(state) local codebox = state:element("code", { name = "code", code = "" }) function codebox:set_we_formspec(we_page) local new_formspec = get_formspec(state.location.rootState.location.player, we_page) new_formspec = new_formspec:gsub('button_exit','button') --no inventory closing self.data.code = "container[1,1]".. new_formspec .. "container_end[]" end codebox:set_we_formspec("worldedit_gui") -- process input (the back button) state:onInput(function(state, fields, player) if fields.worldedit_gui then --main page state:get("code"):set_we_formspec("worldedit_gui") elseif fields.worldedit_gui_exit then --return to original page state:get("code"):set_we_formspec("worldedit_gui") state.location.parentState:get("crafting_button"):submit() -- switch to the crafting tab end end) end -- all handler should return false to force inventory UI update local orig_register_gui_handler = worldedit.register_gui_handler worldedit.register_gui_handler = function(identifier, handler) local wrapper = function(...) handler(...) return false end orig_register_gui_handler(identifier, wrapper) end -- register the inventory button smart_inventory.register_page({ name = "worldedit_gui", tooltip = "Edit your World!", icon = "inventory_plus_worldedit_gui.png", smartfs_callback = smart_worldedit_gui_callback, sequence = 99 }) elseif minetest.global_exists("sfinv") then -- sfinv installed assert(sfinv.enabled) local orig_get = sfinv.pages["sfinv:crafting"].get sfinv.override_page("sfinv:crafting", { get = function(self, player, context) local can_worldedit = minetest.check_player_privs(player, {worldedit=true}) local fs = orig_get(self, player, context) return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" or "") end }) --show the form when the button is pressed and hide it when done minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.worldedit_gui then --main page worldedit.show_page(player:get_player_name(), "worldedit_gui") return true elseif fields.worldedit_gui_exit then --return to original page sfinv.set_page(player, "sfinv:crafting") return true end return false end) worldedit.show_page = function(name, page) local player = minetest.get_player_by_name(name) if player then player:set_inventory_formspec(get_formspec(name, page)) end end else error( "worldedit_gui requires a supported gui management mod to be installed.\n".. "To use the it you need to either:\n".. "* use minetest_game or another sfinv-compatible subgame\n".. "* install Unified Inventory, Inventory++ or Smart Inventory\n".. "If you don't want to use worldedit_gui, disable it by editing world.mt or from the main menu." ) end worldedit.register_gui_function("worldedit_gui", { name = "WorldEdit GUI", privs = {interact=true}, get_formspec = function(name) --create a form with all the buttons arranged in a grid local buttons, x, y, index = {}, 0, 1, 0 local width, height = 3, 0.8 local columns = 5 for i, identifier in pairs(identifiers) do if identifier ~= "worldedit_gui" then local entry = worldedit.pages[identifier] table.insert(buttons, string.format((entry.get_formspec and "button" or "button_exit") .. "[%g,%g;%g,%g;%s;%s]", x, y, width, height, identifier, minetest.formspec_escape(entry.name))) index, x = index + 1, x + width if index == columns then --row is full x, y = 0, y + height index = 0 end end end if index == 0 then --empty row y = y - height end return string.format("size[%g,%g]", math.max(columns * width, 5), math.max(y + 0.5, 3)) .. "button[0,0;2,0.5;worldedit_gui_exit;Back]" .. "label[2,0;WorldEdit GUI]" .. table.concat(buttons) end, }) worldedit.register_gui_handler("worldedit_gui", function(name, fields) for identifier, entry in pairs(worldedit.pages) do --check for WorldEdit GUI main formspec button selection if fields[identifier] and identifier ~= "worldedit_gui" then --ensure player has permission to perform action local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs) if not has_privs then worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")") return false end if entry.on_select then entry.on_select(name) end if entry.get_formspec then worldedit.show_page(name, identifier) end return true end end return false end) dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/functionality.lua") Minetest-WorldEdit-1.3/worldedit_gui/mod.conf000066400000000000000000000002601400537024600213260ustar00rootroot00000000000000name = worldedit_gui description = WorldEdit GUI depends = worldedit, worldedit_commands optional_depends = unified_inventory, inventory_plus, sfinv, creative, smart_inventory Minetest-WorldEdit-1.3/worldedit_gui/textures/000077500000000000000000000000001400537024600215655ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_gui/textures/inventory_plus_worldedit_gui.png000066400000000000000000000523341400537024600303230ustar00rootroot00000000000000PNG  IHDR>aTIDATxw]e~Uv=5'9$D)CS 8oS.3Zfzk,HQl P& &$ZO? pE&~ivC=)֚>[o]9:J{LlIKS~K餥rOB* l9iQ6xMc!XR8S /1Skb6H zCH8X,ZJoP?݊%E[LX!Z+>{ƅoU(ApB&&&x ^_ݻkTz}jʕS~dbeW&XU lD,kM_U\gzq)zBdoIƙD1k (M$ ]z}G_J2|(cXc; GHmtb+WWx`ޭ_ot_e֒sV}yQV'?i-[&~vm_?~ʕT`jt/1Tb=!f|Gm-fՏVi)Ko(9| шl.}cuq9zl, f^'X0=YVkkJ\x|@X c,HFa.#9dz4vɱV [ziG-+J[n9S+ě .袋?&Z+vdO$ H-ppO Έ^>Y1X՛R`n?`ZTS+`  6ʥ/D>}>UTfcZb~6ɖi-EOr7t)ji лzU u$ֲF!}&x^x5\w #ֆsУӵTuo;Fޓτ o@ /$$W1Or\@o9a!Uל45Ee~TWDCsK @x#mː>7Yn'X5<>/_^(,"#G1'0rnHԝ&mXR 'kcSӟfRNh<<s`D.^f.Sg#}%'Y T} );iDIhs O >4 J!׷/lpQ!Eq7W`laC'xdه1^1O'|)21kdDŽxi 'ӟ"%P}ܶBJJ) q JGH)@S`N>KW&a 2)%p⸨=Zj7|w;cbGz.49u)fTu<")R-rD ʴ7U^ #M.!dZYvT[늾@[\A>3X !~'+ [=9D1X>,O/\x8=!Ɏ |ށ1 LEخx!Q c9<gvLWǪ**ߵф2)RL >I wV$f'.T㤝j.Alߋ{l𝟵H &G0xs IczhhE| z*>/(^ }&;_ǎ+y#S ᑘ{6G<5PӲ|h9,Cd9,(@ 1 1CɦiE9/#D[k,DAAD3"/j|h X OV^RO(rL]=%|쬲SD [F%on9D!WMMg!$\`U["] 9n? <\*VW``^oi}(-%]o!@f-hVISit%Wv wok1wYV&:*]d3!ߠZE7uc 3s,,n3W>S9r  <4Rm(!K!DK70wMQ /сD 1H!%,( yQ\;,5TUh%LZS(bcj1+/f֤(.OM1DO'+1ƶ_l[@ͼ)%ᗆywGFgPDzN- (=UxCe4{onmU漥%>A^QЗB$hnj9O`|,z66kZXqO9'qQ#&~8,x␃ʂ?P2`1)ΘPwY?#SNݤCmW͌i{ڎn6S.>!Yt8YzЬo9j; >x}ջsSRXd`yJx_,[ǔU7# >/DMKg ZBUL=!](0, :)Y :wLx,{1bƦn95-;-7OhVD?c@F%uXSxBb3D #gF3djEqVb4S dcJ]* ӶnTj]R,=/ xE);~J 8mBNS8\_+c_M=6T#Kl[WtYP{.!(f^}b/|D+֚Z%q[8o.[ N;e) pcЍZΛ[2Fq(x 30&v9ފyb IJ$ĉ1==},4#z+u42 ZJs&/w4SJ0m,-1lX<3UV.cȧqlH$IPIB'h턡lo_+W?&'_! L lBaC3n#@&)l˗V =DӬFmxRᙔ$"OO(y9Z*uqxl]|2]VؙWۏ!§|csiFETӪ}Nܨ7Iѝչ9Z *|ףr_"%\]cX9 9M[t;\w\`b1sfsxS ~ODr|): t&xƚI[kIN N>~{ةaԶ*2h(!#8F87FcA% Z%iBᖫ5FTœu(`m넢io?txviz{Q5kWI޷TRf܁,غuGj )rǃU(RZkYAN>bmS ?6kd}ҿ|V[LOQ>9NcjfefFT5č&I"T7#[gϙXi\){m3#r9d!@|!=! fl~%q=aWnjX8D,*Cqݟ9fRpؿoT{ؽiw,bk#B&oRRԌ5ULBK1kR!FigT՝)̰ 柗|i;1Gz;M%iR_$Dr֝k1ZvaOOYB_𗪉xF ԌvUEH 闡EG3Ǝ{zuhl3A*#U Ŵ້Qc 6G횄 Wnu?Ai$ #DQEvDV8;_0m Ѷ V#۞ <&*թ*ZfFhD-TcTQ:}z2=C-oyG.B9pg"r9+瑞 di/i35Kq6B9gPs2˧ 74{ '0uFL2G {4& ?M\|Q.J?L~Vi0U.Ӝ?K=x=& ړmH.P-|i)sE4 h7̦1P \Ps<?#rh|}s8H^)VdE&ʽ}HsFMH">sL`)K|!r>~9Iea+6{km&5F"} _ofF^BHQ(n眧RX5ILضJ!-soAzKz~|W!)PR>Ogj>|}zo\*!&q̞89̩}r&c Pÿ`W^;v+ p#=K=r`V+4Hn*Xe9wK4O(>M5(s;|lIO޲)/%</ Aop>~jo 3WX< @^^CAଁ'\@jk@J’M_ubd. :)쀝Nl֮Ǔ 4h2 2BA`gB>\ rU ݇*\^[SM;W󕯰cn^|tG몥|?-e C0sH? p |}m.kbH+c[BZBr"XdʳC 6ج}FKkGtYs&ρn1v?sF#a"'`C- m22Bܞw/3'nm y66_ R|?LoL5R+2X$B|k0Jcw_Nu 9IX(A$;(UDsY9b=xVbmc5i5$9_K,C{G+_[Wb =H5v,mZ/sco Oa*Et"?B[\H߇!,KN|/pZ/|uـ*Yőz  Yp) <!=vM3o^W~Tʕr߬*#HҝӾ0758m-Nå@xrGf54LW%q=wXL_or^!WA#|`fCF3OM='[c/PQ (;o|wVIih.sf3{ҬY^. 0 }w.hz^N89|nG|n&,GHazl4Z,V-['BSx>cOXy1+:ϋA.YĤvg%G\%0K BO0TncTv%P4YJ-;5+O.g0J0&E1/+s&-čfzECy`46GYȲP\)yP'_..ʩt}Fӗ FZ\beZۮih zS^x:cai̞m "m~*6lZ)Н#k !"5Usx=%3Ui3ʂC|DT)DJSj}} {c!/)"Z*ފ7#flcǻj14ςL>?4i/d鶋D he3x;v6u"{hiֈFI٢8ߐ|Yw3C￿]̺;.3}ʹϥ3HFĵ1fQ8OmU}Sm~!A7BzӆT`&W=4[Nf@PցLC, ;m&)NNPnfYSD0Pj.T 12N/x^?_CyϦAT X@C5 auJhn@(=Aأ; ~NW^W?݈"N o8油RC}\t !XXzCq)uwM<7q`jgFٴ0tx,sY!#Fvȗh"}:lɋ<w'3@kR;㢣slTҦ&&,XN'fMwˁ祏1v_68um4wo03`H> 8Vs4{OߎR ynePI=Xm~֪v\A:NpfM0Yӊ11SuMD##.>Vå4ig.w ^Z [yi*':0)͟fC1bV`'7@gd5? 1М^+mf7G'M 1YV+Tڇ*N ٹ~/b.ci@u+i>fLݺ֬YRYh?{D{ۇ*.},&q@kz)^*n! f gW n6>iu"e0V%x>iZ./aNb[d;3i37Ϻ @oەg}9@+/;]}ok wK5=njo~6yVy^Ѕe$ngZK d̡+I3cc7ZRtj#Wt$#l{FF#2 EJ֟v&њ|},DI¢Y|z_l?h0٘(祖Yafj(v34WƳ@[kuEtӾo ֠##lAnIZ3cph|Z+Byg'ydOk}_ a[2-XĒes1Ҽ_VQgƒ d@#ҿ_7P rVwlJg(s?I~>,`~cz$6=vVݵt,8M뺒Z!<+}N`= I ρ3J;MhޤRF1sPvݸXhmhA@ܮ9y}dC Fƛl!r ?_Iq͏pOcⅇcj`N-a< K}띴i(EQAE s^0<;߮~&гќg4$ 5.ӮaQ@F hD$ȷ&1jW/ѝx] NXٮپ̈Z`[K8^yBn}tzf-bպ  烟s iInDAfQVZ_\ "Oوc1@1|ffh7m $I,/ :q>*n!o>i6I/Bʗrڤ˛>l Sg7hgy͈7_k~1Vo?cŇW/>ȵξ$"WAq܅4Z$| VvIҶ{E2E5[[g&i$z,^PFk̸V]_ Z@1K aS68b^:]htz]}]m-6퓟sϹ粽^ ņ'iXƚ O2X ͚;we҂<1VRGlyTkzJat 𘞘9|㎕+?`=t|E 4mGX=_><ʢN*uz}qvJ:7YS/Ӌc||K=2>N{F`)E敖ieg)>_'sbn|8hTer>U h6Cg&"m1(4g Vr)rHzplUCi/:퓍6MZ&5?w ZDVZR4W ^ JB-r]6Zʕw.S%R)=`O&,#iA%(W 'ݶ Ͻٷ=50s!|uG3)Hm&.{HbRy]+]:i%1yz/B2S, #)۷$vW6rq;G'`0M[~w;6VX',rAm~- kF7LwPSx X{Ӊؽ~=ۤ;C?W_3.YgɦN\~iR9{红?FanEC,PM*. k5uccY 1n] N>a6J#*~O!n5ƙ2)Ek Ӌ'%Bv_೯@]A:kحE SNG_jbۃp%3. uoA*|κV~]%X;oz PncǕhj1YReggBw.Baz:ʷ'wL˷H sÌ2Oܨi$* 46Ɲҹl {{Ym*_75 /_>~S?i{lc6<~Khy˹g7+}!n]ԇ|=NOu޶*NivMζdYV?JȪUyᵽG,&]Ѧk֫Y6M;*2-fe)5Pf%Rud՘:RA˳0ҭX~'tT2ٽ`N}ͿqqS?њÆWSL7+ 2ԕyI$iJ 0j&ٓmO'Y= K XȰx Oww Ϊf+B;& תֽNFaM%#iR׵x?[kT5O Є?nWEM\p؇A߆In}wɏW^h6ǾE1}Od =|;~Y2JԮ3^*'Ge~4}B̥Mh3ЭME2 /s-0HRg;Z-oKqBP7yT@FоOOh?o ț(/ҬmEst5%'=OЈ~^ʨ..6liac}VugNE܈sWꑌ~v4'C3_*^w~NgGޥ6‹: $+ϣ֛ҧvH#S'dEgjrpv㍯ O֪壛* K^tϧ_XGo|C®& Y4M}7jϷ*~Z;ozczesӠ]¯fQ;H\Dͭ R֑`J|/~" XCL@X56N#BkB wW[FѫBSӦy{2r CbPd)q0  ⁲2M>Y^T Pw4o"{i2o:Q:" U0LlYB 5l"N-'$Vu=:6Z٥UkФA` _| X%۹(VI]8N*pFޠxkMÞ:P/%|A\4>uD gOV裮L -7GNc\מ'TL-Nɱ sv IЪ9#kW@ :L-5p4?T/}ȆLO/w=l;$ujr:>UX2¨i萖aV41ٗ^%HGR97 JXV H=qY0~{iSڄv𛄴ӈ(A/aH &|ߟ j ʫwi,_ESn i$~?_g7Z;K+.-d KT:E;ShҧMy7n[9˽KLpSI@1 D "#>3=ǎ/൯}1N, IFQZ#) (Cz33s`CO5W_}_*yJֺѐ; 1go$ YZM} ҦNn }=ʻ;@IAbwt6Ac** }D$FE<8,-1w98-4$C@PZŤ{LfSc<o"c!JbEFLT_#dBSZ,x[^TJ>vgO`}<潏QP^t>n\ULc}C:9+_ [1w Ǧ]Z1;#AEW6;<~*~I_?p;&V)Th&LXg-%cJA׿z'Z{KyZ& `|wbC5nni.}f$O["+ؿP \M ,{^x?]ON9_lGg`mɖMTk㝒05`R*4SL m}4E(yc[} Bp8UgsԿ݊[v"D`vjYQbL߂Sv +vo kQw 9g-upe oe oKxkx "Wv_N<}R 0&ʀ(TG~45V&MK'lE)q'mNHz{w7q&"jO'T.UȢ3!vloz5yd;R:>[҄ u3 {,#@1P!jJ@oSXY<UgΩߢ)s-r'vZF[}:QH)UsRX_@f:T}AV@#86SILcc= `v ӏ]:v3;LS7 VTH= |'o=Ю&]Аϱ%:\r, d 1ZwkY=^NGE":v ?ec - +6_ף7kq&ϯ~ -khڮ/Szp‡ffwa  gn;pa.<[\:L1`t`PPAB1{$$EhnYUA*%RyU2qI4dJ|9a2ʟjMTUb ʨp܁ B SeŌM-wϕS^L~"d$uҮ~<;sSO*GKK}Z7 `Yd,2+a9y񣇾|V}TUJueePel'8P>[Nl63|*2*E/HSN> RT\`E* ^< SFo"RD-AKDh cf4+>)uZ7$[0Ts*pq؉GJ1aHJ dRA'~7ʔAEi{\8ٓ^+!J#!(H`@s$3@+ē#n5É5}grM(@ٔh5BG@FڅĴi*ݢ v^GYc!&@V8DT $~UAm/`Yғ/ ֕۱>+qmفuE6&<pʉLqs @9 PgPdH(ExJ<=u@/LlFDI(ޘ-22*Rʦc !cetrgL{:@n¦ܬ69RQr)V;r%*rʕpP )27bDD0¢ZT3 {"{yFA,rlتrLl)Qr6,J;q{|F &OstngNt4,dd)gF g )àLD2qᕑ>DPWő*nC8*(!PH\(d-+(84iɻms; \Zx{:gXU6Xcɍ̋ϴ g ʰaFhj^J@jA(&ģB"08ˍ(+(O(8E)xH;Mڹsf¸bi]:a? $cŬ頓 &Y8M``$,D Dka-)h i@TPP^JLe.94O0Y[E37V ;B'"rbd bp8Ā V(.Eg"bM6KRA+/c9}ݰ{ϴ3f.Ej* LSR^ dR KLԆZn@A @b Ьn8oIz̩|Vy]]]]ϊ(i>IENDB`Minetest-WorldEdit-1.3/worldedit_gui/textures/worldedit_gui_unknown.png000066400000000000000000000011251400537024600267120ustar00rootroot00000000000000PNG  IHDR&& pHYs  tIME PLTE ###&&&'''222888:::GGG[[[^^^bbbccciiilllnnneD>tRNSǣbKGD?>c0uIDAT8˽0E 5{ @$`dzHGlɣi;$g,$$(o:Zb|Awtgw5l4 lLC_κŀu9UmJm K[:ғ+y5 Gx/.UrKS`".Y'#)AjfaF &,l/ہk؛mPS&BW}DaIENDB`Minetest-WorldEdit-1.3/worldedit_shortcommands/000077500000000000000000000000001400537024600217775ustar00rootroot00000000000000Minetest-WorldEdit-1.3/worldedit_shortcommands/init.lua000066400000000000000000000041471400537024600234530ustar00rootroot00000000000000--provides shorter names for the commands in `worldedit_commands` worldedit.alias_command = function(alias, original) if not worldedit.registered_commands[original] then minetest.log("error", "worldedit_shortcommands: original " .. original .. " does not exist") return end if minetest.chatcommands["/" .. alias] then minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists") return end minetest.register_chatcommand("/" .. alias, minetest.chatcommands["/" .. original]) worldedit.registered_commands[alias] = worldedit.registered_commands[original] end worldedit.alias_command("i", "inspect") worldedit.alias_command("rst", "reset") worldedit.alias_command("mk", "mark") worldedit.alias_command("umk", "unmark") worldedit.alias_command("1", "pos1") worldedit.alias_command("2", "pos2") worldedit.alias_command("fp", "fixedpos") worldedit.alias_command("v", "volume") worldedit.alias_command("s", "set") worldedit.alias_command("r", "replace") worldedit.alias_command("ri", "replaceinverse") worldedit.alias_command("hcube", "hollowcube") worldedit.alias_command("hspr", "hollowsphere") worldedit.alias_command("spr", "sphere") worldedit.alias_command("hdo", "hollowdome") worldedit.alias_command("do", "dome") worldedit.alias_command("hcyl", "hollowcylinder") worldedit.alias_command("cyl", "cylinder") worldedit.alias_command("hpyr", "hollowpyramid") worldedit.alias_command("pyr", "pyramid") worldedit.alias_command("spl", "spiral") worldedit.alias_command("m", "move") worldedit.alias_command("c", "copy") worldedit.alias_command("stk", "stack") worldedit.alias_command("sch", "stretch") worldedit.alias_command("tps", "transpose") worldedit.alias_command("fl", "flip") worldedit.alias_command("rot", "rotate") worldedit.alias_command("ort", "orient") worldedit.alias_command("hi", "hide") worldedit.alias_command("sup", "suppress") worldedit.alias_command("hlt", "highlight") worldedit.alias_command("rsr", "restore") worldedit.alias_command("l", "lua") worldedit.alias_command("lt", "luatransform") worldedit.alias_command("clro", "clearobjects") Minetest-WorldEdit-1.3/worldedit_shortcommands/mod.conf000066400000000000000000000001521400537024600234230ustar00rootroot00000000000000name = worldedit_shortcommands description = WorldEdit command short aliases depends = worldedit_commands