From 1d0ce0e73e226a07d8c846726a5248909e91e7ee Mon Sep 17 00:00:00 2001 From: Oversword Date: Fri, 25 Jun 2021 06:43:37 +0100 Subject: [PATCH 1/4] Reconcile BlS gravelsieve updates with original techpack upgrades --- gravelsieve/CREDITS.txt | 41 ++ gravelsieve/LICENSE.txt | 662 +++++++++++++++++++++++++ gravelsieve/README.md | 22 - gravelsieve/api.lua | 393 +++++++++++++++ gravelsieve/compat.lua | 2 + gravelsieve/crafts.lua | 40 ++ gravelsieve/default_output.lua | 17 + gravelsieve/depends.txt | 3 +- gravelsieve/hammer.lua | 83 ++-- gravelsieve/init.lua | 772 ++--------------------------- gravelsieve/interop/hopper.lua | 22 + gravelsieve/interop/moreblocks.lua | 12 + gravelsieve/interop/tubelib.lua | 107 ++++ gravelsieve/mod.conf | 4 +- gravelsieve/nodes.lua | 15 + gravelsieve/probability_api.lua | 118 +++++ gravelsieve/settings.lua | 29 ++ gravelsieve/settingtypes.txt | 11 +- gravelsieve/sieve.lua | 335 +++++++++++++ gravelsieve/test.lua | 507 +++++++++++++++++++ 20 files changed, 2368 insertions(+), 827 deletions(-) create mode 100644 gravelsieve/CREDITS.txt create mode 100644 gravelsieve/LICENSE.txt create mode 100644 gravelsieve/api.lua create mode 100644 gravelsieve/compat.lua create mode 100644 gravelsieve/crafts.lua create mode 100644 gravelsieve/default_output.lua create mode 100644 gravelsieve/interop/hopper.lua create mode 100644 gravelsieve/interop/moreblocks.lua create mode 100644 gravelsieve/interop/tubelib.lua create mode 100644 gravelsieve/nodes.lua create mode 100644 gravelsieve/probability_api.lua create mode 100644 gravelsieve/settings.lua create mode 100644 gravelsieve/sieve.lua create mode 100644 gravelsieve/test.lua diff --git a/gravelsieve/CREDITS.txt b/gravelsieve/CREDITS.txt new file mode 100644 index 0000000..524ee26 --- /dev/null +++ b/gravelsieve/CREDITS.txt @@ -0,0 +1,41 @@ +--[[ + Gravel Sieve Mod + ================ + + v20210214.0 by flux + forked from v1.09 by JoSt + Derived from the work of celeron55, Perttu Ahola (furnace) + Pipeworks support added by FiftySix + + Copyright (C) 2021 flux, Oversword, & Blocky Survival server + Copyright (C) 2017-2021 Joachim Stolberg + Copyright (C) 2011-2016 celeron55, Perttu Ahola + Copyright (C) 2011-2016 Various Minetest developers and contributors + + AGPL v3 + See LICENSE.txt for more information + + History: + 2017-06-14 v0.01 First version + 2017-06-15 v0.02 Manually use of the sieve added + 2017-06-17 v0.03 * Settings bug fixed + * Drop bug fixed + * Compressed Gravel block added (Inspired by Modern Hippie) + * Recipes for Compressed Gravel added + 2017-06-17 v0.04 * Support for manual and automatic gravel sieve + * Rarity now configurable + * Output is 50% gravel and 50% sieved gravel + 2017-06-20 v0.05 * Hammer sound bugfix + 2017-06-24 v1.00 * Released version w/o any changes + 2017-07-08 V1.01 * extended for moreores + 2017-07-09 V1.02 * Cobblestone bugfix (NathanSalapat) + * ore_probability is now global accessable (bell07) + 2017-08-29 V1.03 * Fix syntax listring (Jat15) + 2017-09-08 V1.04 * Adaption to Tubelib + 2017-11-03 V1.05 * Adaption to Tubelib v0.06 + 2018-01-01 V1.06 * Hopper support added + 2018-01-02 V1.07 * changed to registered ores + 2018-02-09 V1.08 * Pipeworks support added, bugfix for issue #7 + 2018-12-28 V1.09 * Ore probability calculation changed (thanks to obl3pplifp) + tubelib aging added +]]-- diff --git a/gravelsieve/LICENSE.txt b/gravelsieve/LICENSE.txt new file mode 100644 index 0000000..2beb9e1 --- /dev/null +++ b/gravelsieve/LICENSE.txt @@ -0,0 +1,662 @@ + 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 +. + diff --git a/gravelsieve/README.md b/gravelsieve/README.md index 36cee3a..a5e5f05 100644 --- a/gravelsieve/README.md +++ b/gravelsieve/README.md @@ -1,33 +1,11 @@ # Gravel Sieve Mod A Mod for those who do not want to spend half their lives underground... -Inspired from a Minecr**t Video on YT. - This mod simplifies the extraction of ores by the fact that ores can be obtained simply by sieving gravel. This mod includes three new tools: - a hammer to produce gravel from Cobblestone - two gravel sieves to find ores (a manual sieve and a automatic sieve) -The sieved gravel can be crafted to Compressed Gravel (inspired by Modern Hippie) and "cooked" in the furnace to get Cobblestone again. - -Recipe for the Gravel Sieve: - - Wood, -----------, Wood - Wood, Steel Ingot, Wood - Wood, -----------, Wood - - -Recipe for the Automatic Gravel Sieve: - - Gravel Sieve, Mese Crystal, Mese Crystal - - -Recipe for Compressed Gravel: - - Sieved Gravel, Sieved Gravel, - Sieved Gravel, Sieved Gravel, - - ### Dependencies default, optional moreores, hopper, and pipeworks diff --git a/gravelsieve/api.lua b/gravelsieve/api.lua new file mode 100644 index 0000000..4097f72 --- /dev/null +++ b/gravelsieve/api.lua @@ -0,0 +1,393 @@ +--[[ +TODO: binary instead of linear search +but, the current method shouldn't be a problem unless there's a whole lot of possible outputs +--]] + +gravelsieve.api = {} + +local processes = {} +local process_totals = {} + +local output_types = {"fixed","relative","dynamic"} +local output_types_reversed = {fixed=1,relative=2,dynamic=3} +local default_output_type = "relative" +local function output_type_has_total(output_type) + return output_type ~= "dynamic" +end + +local ore_frequencies = {} +local ores_calculated = false +local after_ores_calculated_callbacks = {} +function gravelsieve.api.after_ores_calculated(callback) + + if type(callback) ~= 'function' then + error("Gravelsieve after_ores_calculated callbacks must be functions.") + end + + if ores_calculated then + callback(table.copy(ore_frequencies)) + else + table.insert(after_ores_calculated_callbacks, callback) + end +end + +minetest.register_on_mods_loaded(function() + ore_frequencies = gravelsieve.api.get_ore_frequencies() + gravelsieve.api.report_probabilities(ore_frequencies) + ores_calculated = true + for _,callback in ipairs(after_ores_calculated_callbacks) do + callback(table.copy(ore_frequencies)) + end + after_ores_calculated_callbacks = nil +end) + +function gravelsieve.api.reset_config() + processes = {} + process_totals = {} +end + +local unified_inventory_enabled = minetest.global_exists("unified_inventory") +local function clear_unified_inventory_craft(input_name, output_name) + local crafts = unified_inventory.crafts_for.recipe[ItemStack(output_name):get_name()] + if crafts then + for i, craft in ipairs(crafts) do + if craft.type == "sieving" + and craft.output == output_name + and #craft.items == 1 + and craft.items[1] == input_name + then + table.remove(crafts, i) + break + end + end + end +end + +--[[ +e.g. +gravelsieve.api.register_input("default:gravel", { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 +}) +--]] +function gravelsieve.api.register_input(input_name, outputs) + + if gravelsieve.api.can_process(input_name) then + error(("re-registering input \"%s\""):format(input_name)) + end + + if not minetest.registered_items[input_name] then + error(("attempt to register unknown node \"%s\""):format(input_name)) + end + + if not outputs then + outputs = {} + end + + if type(outputs) == 'string' then + outputs = { [outputs] = 1 } + end + + if type(outputs) ~= 'table' then + error("Gravelsieve outputs must be a table or a string.") + end + + local outputs_by_type = outputs + local contains_output_type = false + for _,output_type in ipairs(output_types) do + if outputs[output_type] then + contains_output_type = true + break + end + end + if not contains_output_type then + outputs_by_type = { [default_output_type] = outputs } + end + + processes[input_name] = {} + process_totals[input_name] = {} + for _,output_type in ipairs(output_types) do + processes[input_name][output_type] = {} + if output_type_has_total(output_type) then + process_totals[input_name][output_type] = 0 + end + end + + for output_type, type_outputs in pairs(outputs_by_type) do + for output_name, output_probability in pairs(type_outputs) do + gravelsieve.api.register_output(input_name, output_name, output_probability, output_type) + end + end +end + +function gravelsieve.api.override_input(input_name, outputs) + gravelsieve.api.remove_input(input_name) + return gravelsieve.api.register_input(input_name, outputs) +end + +function gravelsieve.api.remove_input(input_name) + + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("error", "Cannot remove an input (%s) that does not exist.", input_name) + return + end + + local output = processes[input_name] + if unified_inventory_enabled then + for output_type, type_outputs in pairs(output) do + for output_name,_ in pairs(type_outputs) do + clear_unified_inventory_craft(input_name, output_name) + end + end + end + processes[input_name] = nil + process_totals[input_name] = nil + return output +end + +function gravelsieve.api.swap_input(input_name, new_input_name) + local old_output = gravelsieve.api.remove_input(input_name) + return gravelsieve.api.register_input(new_input_name, old_output) +end + +function gravelsieve.api.get_outputs(input_name, output_type) + + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("error", "Cannot get outputs for an input (%s) that does not exist.", input_name) + return + end + + local process = processes[input_name] + if output_type then + if not process[output_type] then + gravelsieve.log("error", "Cannot get outputs for an output type (%s) that does not exist.", output_type) + return + end + + return table.copy(process[output_type]) + end + + return table.copy(process) +end + +function gravelsieve.api.get_output(input_name, output_name) + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("error", "Cannot get outputs for an input (%s) that does not exist.", input_name) + return + end + + local process = processes[input_name] + for _,output_type in ipairs(output_types) do + local val = process[output_type][output_name] + if val then + return val, output_type + end + end + +end + +--[[ +e.g. +gravelsieve.api.register_output("default:gravel", "default:iron_lump", 0.01) +--]] +function gravelsieve.api.register_output(input_name, output_name, probability, output_type) + + if not output_type then + output_type = default_output_type + end + + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("error", "You must register the input (%s) before registering the output (%s).", input_name, output_name) + return + end + + local existing_value, existing_type = gravelsieve.api.get_output(input_name, output_name) + + if existing_value then + local message = ("re-registering %s output \"%s\" for \"%s\""):format(output_type, input_name, output_name) + if existing_type ~= output_type then + message = message .. (" - already registered as \"%s\" output"):format(existing_type) + end + error(message) + end + + local stack = ItemStack(output_name) + if not minetest.registered_items[stack:get_name()] then + error(("attempt to register unknown node \"%s\""):format(stack:get_name())) + end + + if not output_types_reversed[output_type] then + error(("attempt to register output with an unknown output type \"%s\""):format(output_type)) + end + + processes[input_name][output_type][output_name] = probability + if output_type_has_total(output_type) then + process_totals[input_name][output_type] = process_totals[input_name][output_type] + probability + end + + if unified_inventory_enabled then + unified_inventory.register_craft({ + items = {input_name}, + output = output_name, + type = "sieving" + }) + end +end + +function gravelsieve.api.register_relative_output(input_name, output_name, probability) + return gravelsieve.api.register_output(input_name, output_name, probability, "relative") +end + +function gravelsieve.api.register_dynamic_output(input_name, output_name, probability) + return gravelsieve.api.register_output(input_name, output_name, probability, "dynmaic") +end + +function gravelsieve.api.register_fixed_output(input_name, output_name, probability) + return gravelsieve.api.register_output(input_name, output_name, probability, "fixed") +end + +function gravelsieve.api.override_output(input_name, output_name, probability, output_type) + gravelsieve.api.remove_output(input_name, output_name) + return gravelsieve.api.register_output(input_name, output_name, probability, output_type) +end + +function gravelsieve.api.remove_output(input_name, output_name) + + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("error", "Cannot remove an output for an input (%s) that does not exist.", input_name) + return + end + + local existing_value, existing_type = gravelsieve.api.get_output(input_name, output_name) + + if not existing_value then + gravelsieve.log("error", "Cannot remove an output (%s) that does not exist.", output_name) + return + end + + processes[input_name][existing_type][output_name] = nil + if output_type_has_total(output_type) then + process_totals[input_name][existing_type] = process_totals[input_name][existing_type] - existing_value + end + + if unified_inventory_enabled then + clear_unified_inventory_craft(input_name, output_name) + end + + return existing_value, existing_type +end + +function gravelsieve.api.swap_output(input_name, output_name, new_output_name) + local old_probability, old_output_type = gravelsieve.api.remove_output(input_name, output_name) + return gravelsieve.api.register_output(input_name, new_output_name, old_probability, old_output_type) +end + +function gravelsieve.api.can_process(input_name) + return processes[input_name] ~= nil +end + +local function get_random_output(probabilities, random_value) + local running_total = 0 + local last_name = "" + for output_name, value in pairs(probabilities) do + running_total = running_total + value + if running_total >= random_value then + return output_name + end + last_name = output_name + end + -- This returns the last seen value if floating point errors + -- result in the probabilities not adding up to the recorded total + -- This should not affect probabilities significantly + return last_name +end + +local function get_random_dynamic_output(probabilities, random_value, dynamic_args_generator, args) + local running_total = 0 + local dynamic_args + for output_name, dynamic_value_generator in pairs(probabilities) do + if not dynamic_args then + dynamic_args = dynamic_args_generator(args) + end + local value = dynamic_value_generator(dynamic_args, output_name, running_total) + running_total = running_total + value + if running_total >= random_value then + return output_name + end + end +end + +function gravelsieve.api.get_random_output(input_name, dynamic_args_generator, args) + + if not gravelsieve.api.can_process(input_name) then + gravelsieve.log("warning", "can't get random output for unregistered input \"%s\"", input_name) + return + end + + local random_value = math.random() + local process = processes[input_name] + + local fixed_total = process_totals[input_name]["fixed"] + if fixed_total > 0 and fixed_total >= random_value then + return get_random_output(process["fixed"], random_value) + end + + local dynamic_output = get_random_dynamic_output(process["dynamic"], random_value-fixed_total, dynamic_args_generator, args) + if dynamic_output then + return dynamic_output + end + + return get_random_output(process["relative"], process_totals[input_name]["relative"] * math.random()) +end + + +local function get_pos_list(player) + return minetest.deserialize(player:get_attribute("techpack_gravelsieves")) or {} +end + +local function set_pos_list(player, lPos) + player:set_attribute("techpack_gravelsieves", minetest.serialize(lPos)) +end + +local function find_in_list(list, member) + for key, val in ipairs(list) do + if vector.equals(val, member) then + return key + end + end +end + +local function remove_list_elem(list, member) + local key = find_in_list(list, member) + if key then + table.remove(list, key) + end + return list +end + +local function add_pos(pos, player) + local lPos = get_pos_list(player) + if not find_in_list(lPos, pos) then + lPos[#lPos+1] = pos + set_pos_list(player, lPos) + return true + end + return false +end + +local function del_pos(pos, player) + local lPos = get_pos_list(player) + lPos = remove_list_elem(lPos, pos) + set_pos_list(player, lPos) +end + +local function get_count(player) + return #get_pos_list(player) +end + +gravelsieve.api.count = { + add = add_pos, + del = del_pos, + get = get_count +} \ No newline at end of file diff --git a/gravelsieve/compat.lua b/gravelsieve/compat.lua new file mode 100644 index 0000000..08d0e0c --- /dev/null +++ b/gravelsieve/compat.lua @@ -0,0 +1,2 @@ +minetest.register_alias("gravelsieve:sieve", "gravelsieve:sieve3") +minetest.register_alias("gravelsieve:auto_sieve", "gravelsieve:auto_sieve3") diff --git a/gravelsieve/crafts.lua b/gravelsieve/crafts.lua new file mode 100644 index 0000000..96f4b5c --- /dev/null +++ b/gravelsieve/crafts.lua @@ -0,0 +1,40 @@ +minetest.register_craft({ + output = "gravelsieve:hammer", + recipe = { + { "", "default:steel_ingot", "" }, + { "", "group:stick", "default:steel_ingot" }, + { "group:stick", "", "" }, + } +}) + +minetest.register_craft({ + output = "gravelsieve:sieve", + recipe = { + { "group:wood", "", "group:wood" }, + { "group:wood", "default:steel_ingot", "group:wood" }, + { "group:wood", "", "group:wood" }, + }, +}) + +minetest.register_craft({ + output = "gravelsieve:auto_sieve", + type = "shapeless", + recipe = { + "gravelsieve:sieve", "default:mese_crystal", "default:mese_crystal", + }, +}) + +minetest.register_craft({ + output = "gravelsieve:compressed_gravel", + recipe = { + { "gravelsieve:sieved_gravel", "gravelsieve:sieved_gravel" }, + { "gravelsieve:sieved_gravel", "gravelsieve:sieved_gravel" }, + }, +}) + +minetest.register_craft({ + type = "cooking", + output = "default:cobble", + recipe = "gravelsieve:compressed_gravel", + cooktime = 10, +}) diff --git a/gravelsieve/default_output.lua b/gravelsieve/default_output.lua new file mode 100644 index 0000000..1c45739 --- /dev/null +++ b/gravelsieve/default_output.lua @@ -0,0 +1,17 @@ +local api = gravelsieve.api + + +api.register_input("gravelsieve:sieved_gravel", "gravelsieve:sieved_gravel") + +api.after_ores_calculated(function (ore_probabilities) + local ore_rates = api.sum_probabilities(ore_probabilities) + api.register_input("default:gravel", + api.merge_probabilities( + gravelsieve.ore_probabilities, + api.scale_probabilities_to_fill({ + ["default:gravel"] = 1, + ["gravelsieve:sieved_gravel"] = 1 + }, 1-ore_rates) + ) + ) +end) \ No newline at end of file diff --git a/gravelsieve/depends.txt b/gravelsieve/depends.txt index f18b1c5..af96744 100644 --- a/gravelsieve/depends.txt +++ b/gravelsieve/depends.txt @@ -3,4 +3,5 @@ moreblocks? tubelib? hopper? pipeworks? - +test? +unified_inventory? diff --git a/gravelsieve/hammer.lua b/gravelsieve/hammer.lua index caecb34..f998198 100644 --- a/gravelsieve/hammer.lua +++ b/gravelsieve/hammer.lua @@ -1,60 +1,37 @@ ---[[ - - Gravel Sieve Mod - ================ - -]]-- - --- Load support for I18n local S = gravelsieve.S -gravelsieve.disallow = function(pos, node, user, mode, new_param2) - return false -end - -gravelsieve.handler = function(itemstack, user, pointed_thing) - if pointed_thing.type ~= "node" then - return - end - - local pos = pointed_thing.under - - if minetest.is_protected(pos, user:get_player_name()) then - minetest.record_protection_violation(pos, user:get_player_name()) - return - end - - local node = minetest.get_node(pos) - if node.name == "default:cobble" or node.name == "default:mossycobble" - or node.name == "default:desert_cobble" then - node.name = "default:gravel" - minetest.swap_node(pos, node) - minetest.sound_play({ - name="default_dig_crumbly"},{ - gain=1, - pos=pos, - max_hear_distance=6, - loop=false}) - end - - itemstack:add_wear(65535 / (500 - 1)) - return itemstack +local function on_use(itemstack, user, pointed_thing) + if pointed_thing.type ~= "node" then + return + end + + local pos = pointed_thing.under + + if minetest.is_protected(pos, user:get_player_name()) then + minetest.record_protection_violation(pos, user:get_player_name()) + return + end + + local node = minetest.get_node(pos) + if node.name == "default:cobble" or node.name == "default:mossycobble" + or node.name == "default:desert_cobble" then + node.name = "default:gravel" + minetest.swap_node(pos, node) + minetest.sound_play({ + name = "default_dig_crumbly" }, { + gain = 1, + pos = pos, + max_hear_distance = 6, + loop = false }) + end + + itemstack:add_wear(65535 / (500 - 1)) + return itemstack end minetest.register_tool("gravelsieve:hammer", { - description = S("Hammer converts Cobblestone into Gravel"), - inventory_image = "gravelsieve_hammer.png", - on_use = function(itemstack, user, pointed_thing) - return gravelsieve.handler(itemstack, user, pointed_thing) - end, -}) - -minetest.register_craft({ - output = "gravelsieve:hammer", - recipe = { - {"", "default:steel_ingot", ""}, - {"", "group:stick", "default:steel_ingot"}, - {"group:stick", "", ""}, - } + description = S("Hammer converts Cobblestone into Gravel"), + inventory_image = "gravelsieve_hammer.png", + on_use = on_use, }) diff --git a/gravelsieve/init.lua b/gravelsieve/init.lua index 732b2a6..a6a1205 100644 --- a/gravelsieve/init.lua +++ b/gravelsieve/init.lua @@ -1,758 +1,46 @@ ---[[ - - Gravel Sieve Mod - ================ - - v1.09 by JoSt - Derived from the work of celeron55, Perttu Ahola (furnace) - Pipeworks support added by FiftySix - - Copyright (C) 2017-2020 Joachim Stolberg - Copyright (C) 2011-2016 celeron55, Perttu Ahola - Copyright (C) 2011-2016 Various Minetest developers and contributors - - AGPL v3 - See LICENSE.txt for more information - - History: - 2017-06-14 v0.01 First version - 2017-06-15 v0.02 Manually use of the sieve added - 2017-06-17 v0.03 * Settings bug fixed - * Drop bug fixed - * Compressed Gravel block added (Inspired by Modern Hippie) - * Recipes for Compressed Gravel added - 2017-06-17 v0.04 * Support for manual and automatic gravel sieve - * Rarity now configurable - * Output is 50% gravel and 50% sieved gravel - 2017-06-20 v0.05 * Hammer sound bugfix - 2017-06-24 v1.00 * Released version w/o any changes - 2017-07-08 V1.01 * extended for moreores - 2017-07-09 V1.02 * Cobblestone bugfix (NathanSalapat) - * ore_probability is now global accessable (bell07) - 2017-08-29 V1.03 * Fix syntax listring (Jat15) - 2017-09-08 V1.04 * Adaption to Tubelib - 2017-11-03 V1.05 * Adaption to Tubelib v0.06 - 2018-01-01 V1.06 * Hopper support added - 2018-01-02 V1.07 * changed to registered ores - 2018-02-09 V1.08 * Pipeworks support added, bugfix for issue #7 - 2018-12-28 V1.09 * Ore probability calculation changed (thanks to obl3pplifp) - tubelib aging added -]]-- - gravelsieve = { -} - --- Load support for I18n -gravelsieve.S = minetest.get_translator("gravelsieve") -local S = gravelsieve.S - -dofile(minetest.get_modpath("gravelsieve") .. "/hammer.lua") - -local settings_get -if minetest.setting_get then - settings_get = minetest.setting_get -else - settings_get = function(...) return minetest.settings:get(...) end -end -gravelsieve.ore_rarity = tonumber(settings_get("gravelsieve_ore_rarity")) or 1.16 -gravelsieve.ore_max_elevation = tonumber(settings_get("gravelsieve_ore_max_elevation")) or 0 -gravelsieve.ore_min_elevation = tonumber(settings_get("gravelsieve_ore_min_elevation")) or -30912 -local y_spread = math.max(1 + gravelsieve.ore_max_elevation - gravelsieve.ore_min_elevation, 1) - --- Increase the probability over the natural occurrence -local PROBABILITY_FACTOR = tonumber(settings_get("gravelsieve_probability_factor")) or 3 - -local STEP_DELAY = tonumber(settings_get("gravelsieve_step_delay")) or 1.0 - --- tubelib aging feature -local AGING_LEVEL1 = nil -local AGING_LEVEL2 = nil -if minetest.get_modpath("tubelib") and tubelib ~= nil then - AGING_LEVEL1 = 15 * tubelib.machine_aging_value - AGING_LEVEL2 = 60 * tubelib.machine_aging_value -end - --- Ore probability table (1/n) -gravelsieve.ore_probability = { -} - -gravelsieve.process_probabilities = {} + -- Load support for I18n + S = minetest.get_translator("gravelsieve"), + version = "20210214.0", --- Pipeworks support -local pipeworks_after_dig = nil -local pipeworks_after_place = function(pos, placer) end - -if minetest.get_modpath("pipeworks") and pipeworks ~= nil then - pipeworks_after_dig = pipeworks.after_dig - pipeworks_after_place = pipeworks.after_place -end - -local function calculate_probability(item) - local ymax = math.min(item.y_max, gravelsieve.ore_max_elevation) - local ymin = math.max(item.y_min, gravelsieve.ore_min_elevation) - return (gravelsieve.ore_rarity / PROBABILITY_FACTOR) * - item.clust_scarcity / (item.clust_num_ores * ((ymax - ymin) / y_spread)) -end + modname = minetest.get_current_modname(), + modpath = minetest.get_modpath(minetest.get_current_modname()), -local function pairs_by_values(t, f) - if not f then - f = function(a, b) return a > b end - end - local s = {} - for k, v in pairs(t) do - table.insert(s, {k, v}) + log = function(level, message, ...) + minetest.log(level, ("[%s] %s"):format(gravelsieve.modname, message:format(...))) end - table.sort(s, function(a, b) - return f(a[2], b[2]) - end) - local i = 0 - return function() - i = i + 1 - local v = s[i] - if v then - return unpack(v) - else - return nil - end - end -end - -local function parse_drop(drop) - local d, count = drop:match("^%s*(%S+)%s+(%d+)%s*$") - if d and count then - return d, count - end - d, count = drop:match("%s*craft%s+\"?([^%s\"]+)\"?%s+(%d+)%s*") - if d and count then - return d, count - end - return drop, 1 -end - --- collect all registered ores and calculate the probability -local function add_ores() - for _,item in pairs(minetest.registered_ores) do - if minetest.registered_nodes[item.ore] then - local drop = minetest.registered_nodes[item.ore].drop - if type(drop) == "string" - and drop ~= item.ore - and drop ~= "" - and item.ore_type == "scatter" - and item.wherein == "default:stone" - and item.clust_scarcity ~= nil and item.clust_scarcity > 0 - and item.clust_num_ores ~= nil and item.clust_num_ores > 0 - and item.y_max ~= nil and item.y_min ~= nil then - local count - drop, count = parse_drop(drop) - - local probability = calculate_probability(item) - if probability > 0 then - local probabilityFraction = count / probability - local cur_probability = gravelsieve.ore_probability[drop] - if cur_probability then - gravelsieve.ore_probability[drop] = cur_probability+probabilityFraction - else - gravelsieve.ore_probability[drop] = probabilityFraction - end - end - end - end - end - minetest.log("action", "[gravelsieve] ore probabilties:") - local overall_probability = 0.0 - for name,probability in pairs_by_values(gravelsieve.ore_probability) do - minetest.log("action", ("[gravelsieve] %-32s: 1 / %.02f"):format(name, 1.0/probability)) - overall_probability = overall_probability + probability - end - minetest.log("action", ("[gravelsieve] Overall probability %f"):format(overall_probability)) -end - -local function default_configuration() - local normal_gravel = "default:gravel" - local sieved_gravel = "gravelsieve:sieved_gravel" - local gravel_probabilities = table.copy(gravelsieve.ore_probability) - local overall_probability = 0 - for _,v in pairs(gravel_probabilities) do - overall_probability = overall_probability+v - end - local remainder_probability = 0 - if overall_probability < 1 then - remainder_probability = 1-overall_probability - end - gravel_probabilities[normal_gravel] = remainder_probability/2.0 - gravel_probabilities[sieved_gravel] = remainder_probability/2.0 - - return { - [normal_gravel] = gravel_probabilities, - [sieved_gravel] = { - [sieved_gravel] = 1 - } - } -end - -local function normalize_probabilities(conf) - local total = 0 - for _,val in pairs(conf) do - if val >= 0 then - total = total + val - end - end - local normalized = {} - for key,val in pairs(conf) do - if val >= 0 then - normalized[key] = val/total - end - end - return normalized -end - -local function normalize_config(current_config) - local normalized_config = {} - -- Normalize all inputs so their output probabilities always add up to 1 - for input, output_probabilities in pairs(current_config) do - if output_probabilities then - normalized_config[input] = normalize_probabilities(output_probabilities) - end - end - return normalized_config -end - -local function merge_config(def_conf, new_conf) - local result_conf = table.copy(def_conf) - for key,val in pairs(new_conf) do - if type(val) == 'table' and type(result_conf[key]) == 'table' then - result_conf[key] = merge_config(result_conf[key], val) - else - result_conf[key] = val - end - end - return result_conf -end - -local function configure_probabilities_step(current_config, funct_or_table) - local var_type = type(funct_or_table) - local conf - if var_type == 'function' then - conf = funct_or_table() - elseif var_type == 'table' then - conf = funct_or_table - end - if conf then - return merge_config(current_config, conf) - end - return current_config -end - -local configured = false -local set_probabilities = {default_configuration} - -function gravelsieve.set_probabilities(funct_or_table) - if configured then - -- This is here so you can do hard overrides after everything has loaded if you need to - -- Otherwise the order mods are loaded may cause them to override your configs - local current_config = gravelsieve.process_probabilities - current_config = configure_probabilities_step(current_config, funct_or_table) - gravelsieve.process_probabilities = normalize_config(current_config) - else - -- Build up a list of callbacks to be run after all mods are loaded - table.insert(set_probabilities, funct_or_table) - end -end - -local function configure_probabilities() - configured = true - add_ores() - local current_config = {} - - -- Run through all configs in order and merge them - for _,funct_or_table in ipairs(set_probabilities) do - current_config = configure_probabilities_step(current_config, funct_or_table) - end - gravelsieve.process_probabilities = normalize_config(current_config) -end - -minetest.after(1, configure_probabilities) - -local sieve_formspec = - "size[8,8]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "list[context;src;1,1.5;1,1;]".. - "image[3,1.5;1,1;gui_furnace_arrow_bg.png^[transformR270]".. - "list[context;dst;4,0;4,4;]".. - "list[current_player;main;0,4.2;8,4;]".. - "listring[context;dst]".. - "listring[current_player;main]".. - "listring[context;src]".. - "listring[current_player;main]" - - -local function allow_metadata_inventory_put(pos, listname, index, stack, player) - if minetest.is_protected(pos, player:get_player_name()) then - return 0 - end - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if listname == "src" then - return stack:get_count() - elseif listname == "dst" then - return 0 - end -end - -local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local stack = inv:get_stack(from_list, from_index) - return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) -end - -local function allow_metadata_inventory_take(pos, listname, index, stack, player) - if minetest.is_protected(pos, player:get_player_name()) then - return 0 - end - return stack:get_count() -end - -local function aging(pos, meta) - if AGING_LEVEL1 then - local cnt = meta:get_int("tubelib_aging") + 1 - meta:set_int("tubelib_aging", cnt) - if cnt > AGING_LEVEL1 and math.random(AGING_LEVEL2) == 1 then - minetest.get_node_timer(pos):stop() - minetest.swap_node(pos, {name = "gravelsieve:sieve_defect"}) - end - end -end - --- handle the sieve animation -local function swap_node(pos, meta, start) - local node = minetest.get_node(pos) - local idx = meta:get_int("idx") - if start then - if idx == 3 then - idx = 0 - end - else - idx = (idx + 1) % 4 - end - meta:set_int("idx", idx) - node.name = meta:get_string("node_name")..idx - minetest.swap_node(pos, node) - return idx == 3 -end - --- place ores to dst according to the calculated probability -local function move_random_ore(inv, item) - local running_total = 0 - local probabilities = gravelsieve.process_probabilities[item] - local chosen = math.random() - for ore, probability in pairs(probabilities) do - running_total = running_total + probability - if chosen < running_total then - local ore_item = ItemStack(ore) - if not inv:room_for_item("dst", ore_item) then - return false - end - inv:add_item("dst", ore_item) - return true - end - end - return false -- Failure, this shouldn't really happen but might due to floating point errors -end - --- move gravel and ores to dst -local function move_src2dst(meta, pos, inv, item, dst) - local src = ItemStack(item) - if inv:room_for_item("dst", dst) and inv:contains_item("src", src) then - local res = swap_node(pos, meta, false) - if res then -- time to move one item? - local processed = move_random_ore(inv, item) - if processed then - inv:remove_item("src", src) - end - end - return true -- process finished - end - return false -- process still running -end - --- timer callback, alternatively called by on_punch -local function sieve_node_timer(pos, elapsed) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - - for item,probabilities in pairs(gravelsieve.process_probabilities) do - if probabilities and move_src2dst(meta, pos, inv, item) then - aging(pos, meta) - return true - end - end - - minetest.get_node_timer(pos):stop() - return false -end - - -for automatic = 0,1 do -for idx = 0,4 do - local nodebox_data = { - { -8/16, -8/16, -8/16, 8/16, 4/16, -6/16 }, - { -8/16, -8/16, 6/16, 8/16, 4/16, 8/16 }, - { -8/16, -8/16, -8/16, -6/16, 4/16, 8/16 }, - { 6/16, -8/16, -8/16, 8/16, 4/16, 8/16 }, - { -6/16, -2/16, -6/16, 6/16, 8/16, 6/16 }, - } - nodebox_data[5][5] = (8 - 2*idx) / 16 - - local node_name - local description - local tiles_data - local tube_info - if automatic == 0 then - node_name = "gravelsieve:sieve" - description = S("Gravel Sieve") - tiles_data = { - -- up, down, right, left, back, front - "gravelsieve_gravel.png", - "gravelsieve_gravel.png", - "gravelsieve_sieve.png", - "gravelsieve_sieve.png", - "gravelsieve_sieve.png", - "gravelsieve_sieve.png", - } - else - node_name = "gravelsieve:auto_sieve" - description = S("Automatic Gravel Sieve") - tiles_data = { - -- up, down, right, left, back, front - "gravelsieve_gravel.png", - "gravelsieve_gravel.png", - "gravelsieve_auto_sieve.png", - "gravelsieve_auto_sieve.png", - "gravelsieve_auto_sieve.png", - "gravelsieve_auto_sieve.png", - } - - -- Pipeworks support - tube_info = { - insert_object = function(pos, node, stack, direction) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if automatic == 0 then - local meta = minetest.get_meta(pos) - swap_node(pos, meta, true) - else - minetest.get_node_timer(pos):start(STEP_DELAY) - end - return inv:add_item("src", stack) - end, - can_insert = function(pos, node, stack, direction) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - return inv:room_for_item("src", stack) - end, - input_inventory = "dst", - connect_sides = {left = 1, right = 1, front = 1, back = 1, bottom = 1, top = 1} - } - end - - local not_in_creative_inventory - if idx == 3 then - tiles_data[1] = "gravelsieve_top.png" - not_in_creative_inventory = 0 - else - not_in_creative_inventory = 1 - end - - - minetest.register_node(node_name..idx, { - description = description, - tiles = tiles_data, - drawtype = "nodebox", - drop = node_name, - - tube = tube_info, -- NEW - - node_box = { - type = "fixed", - fixed = nodebox_data, - }, - selection_box = { - type = "fixed", - fixed = { -8/16, -8/16, -8/16, 8/16, 4/16, 8/16 }, - }, - - on_timer = sieve_node_timer, - - on_construct = function(pos) - local meta = minetest.get_meta(pos) - meta:set_int("idx", idx) -- for the 4 sieve phases - meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel - meta:set_string("node_name", node_name) - meta:set_string("formspec", sieve_formspec) - local inv = meta:get_inventory() - inv:set_size('src', 1) - inv:set_size('dst', 16) - end, - - -- Pipeworks support - after_dig_node = pipeworks_after_dig, - - after_place_node = function(pos, placer) - local meta = minetest.get_meta(pos) - meta:set_string("infotext", "Gravel Sieve") - - -- Pipeworks support - pipeworks_after_place(pos, placer) - end, - - on_metadata_inventory_move = function(pos) - if automatic == 0 then - local meta = minetest.get_meta(pos) - swap_node(pos, meta, true) - else - minetest.get_node_timer(pos):start(STEP_DELAY) - end - end, - - on_metadata_inventory_take = function(pos) - if automatic == 0 then - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if inv:is_empty("src") then - -- sieve should be empty - meta:set_int("idx", 2) - swap_node(pos, meta, false) - meta:set_int("gravel_cnt", 0) - end - else - minetest.get_node_timer(pos):start(STEP_DELAY) - end - end, - - on_metadata_inventory_put = function(pos) - if automatic == 0 then - local meta = minetest.get_meta(pos) - swap_node(pos, meta, true) - else - minetest.get_node_timer(pos):start(STEP_DELAY) - end - end, - - on_punch = function(pos, node, puncher, pointed_thing) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if inv:is_empty("dst") and inv:is_empty("src") then - minetest.node_punch(pos, node, puncher, pointed_thing) - else - sieve_node_timer(pos, 0) - end - end, - - on_dig = function(pos, node, puncher, pointed_thing) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if inv:is_empty("dst") and inv:is_empty("src") then - minetest.node_dig(pos, node, puncher, pointed_thing) - end - end, +} - allow_metadata_inventory_put = allow_metadata_inventory_put, - allow_metadata_inventory_move = allow_metadata_inventory_move, - allow_metadata_inventory_take = allow_metadata_inventory_take, +gravelsieve.log("info", "loading gravelsieve mod...") - paramtype = "light", - sounds = default.node_sound_wood_defaults(), - paramtype2 = "facedir", - sunlight_propagates = true, - is_ground_content = false, - groups = {choppy=2, cracky=1, not_in_creative_inventory=not_in_creative_inventory, tubedevice = 1, tubedevice_receiver = 1}, - drop = node_name.."3", - }) -end +local function gs_dofile(filename) + dofile(("%s/%s"):format(gravelsieve.modpath, filename)) end - ------------------------------------------------------------------------- --- Optional adaption to tubelib ------------------------------------------------------------------------- -if minetest.global_exists("tubelib") then - minetest.register_node("gravelsieve:sieve_defect", { - tiles = { - -- up, down, right, left, back, front - "gravelsieve_top.png", - "gravelsieve_gravel.png", - "gravelsieve_auto_sieve.png^tubelib_defect.png", - }, - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { - { -8/16, -8/16, -8/16, 8/16, 4/16, -6/16 }, - { -8/16, -8/16, 6/16, 8/16, 4/16, 8/16 }, - { -8/16, -8/16, -8/16, -6/16, 4/16, 8/16 }, - { 6/16, -8/16, -8/16, 8/16, 4/16, 8/16 }, - { -6/16, -2/16, -6/16, 6/16, 2/16, 6/16 }, - }, - }, - selection_box = { - type = "fixed", - fixed = { -8/16, -8/16, -8/16, 8/16, 4/16, 8/16 }, - }, - - on_construct = function(pos) - local meta = minetest.get_meta(pos) - meta:set_int("idx", 0) -- for the 4 sieve phases - meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel - meta:set_string("node_name", "gravelsieve:auto_sieve") - meta:set_string("formspec", sieve_formspec) - local inv = meta:get_inventory() - inv:set_size('src', 1) - inv:set_size('dst', 16) - end, - - after_place_node = function(pos, placer) - local meta = minetest.get_meta(pos) - meta:set_string("infotext", S("Gravel Sieve")) - end, - - on_dig = function(pos, node, puncher, pointed_thing) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - if inv:is_empty("dst") and inv:is_empty("src") then - minetest.node_dig(pos, node, puncher, pointed_thing) - end - end, - - paramtype = "light", - sounds = default.node_sound_wood_defaults(), - paramtype2 = "facedir", - sunlight_propagates = true, - is_ground_content = false, - groups = {choppy=2, cracky=1, not_in_creative_inventory=1}, - }) - - tubelib.register_node("gravelsieve:auto_sieve3", - { - "gravelsieve:auto_sieve0", - "gravelsieve:auto_sieve1", - "gravelsieve:auto_sieve2", - "gravelsieve:sieve_defect", - }, - { - on_pull_item = function(pos, side) - local meta = minetest.get_meta(pos) - return tubelib.get_item(meta, "dst") - end, - on_push_item = function(pos, side, item) - minetest.get_node_timer(pos):start(STEP_DELAY) - local meta = minetest.get_meta(pos) - return tubelib.put_item(meta, "src", item) - end, - on_unpull_item = function(pos, side, item) - local meta = minetest.get_meta(pos) - return tubelib.put_item(meta, "dst", item) - end, - on_node_load = function(pos) - minetest.get_node_timer(pos):start(STEP_DELAY) - end, - on_node_repair = function(pos) - local meta = minetest.get_meta(pos) - meta:set_int("tubelib_aging", 0) - meta:set_int("idx", 2) - meta:set_string("node_name", "gravelsieve:auto_sieve") - local inv = meta:get_inventory() - inv:set_size('src', 1) - inv:set_size('dst', 16) - swap_node(pos, meta, false) - minetest.get_node_timer(pos):start(STEP_DELAY) - return true - end, +if minetest.global_exists("unified_inventory") then + unified_inventory.register_craft_type("sieving", { + description = S("Sieving (by chance)"), + icon = "gravelsieve_sieve.png", + width = 1, + height = 1, }) end -minetest.register_node("gravelsieve:sieved_gravel", { - description = S("Sieved Gravel"), - tiles = {"default_gravel.png"}, - groups = {crumbly=2, falling_node=1, not_in_creative_inventory=1}, - sounds = default.node_sound_gravel_defaults(), -}) - -minetest.register_node("gravelsieve:compressed_gravel", { - description = S("Compressed Gravel"), - tiles = {"gravelsieve_compressed_gravel.png"}, - groups = {cracky=2, crumbly = 2, cracky = 2}, - sounds = default.node_sound_gravel_defaults(), -}) +gs_dofile("settings.lua") +gs_dofile("api.lua") +gs_dofile("probability_api.lua") -minetest.register_craft({ - output = "gravelsieve:sieve", - recipe = { - {"group:wood", "", "group:wood"}, - {"group:wood", "default:steel_ingot", "group:wood"}, - {"group:wood", "", "group:wood"}, - }, -}) +gs_dofile("test.lua") -minetest.register_craft({ - output = "gravelsieve:auto_sieve", - type = "shapeless", - recipe = { - "gravelsieve:sieve", "default:mese_crystal", "default:mese_crystal", - }, -}) - -minetest.register_craft({ - output = "gravelsieve:compressed_gravel", - recipe = { - {"gravelsieve:sieved_gravel", "gravelsieve:sieved_gravel"}, - {"gravelsieve:sieved_gravel", "gravelsieve:sieved_gravel"}, - }, -}) - -minetest.register_craft({ - type = "cooking", - output = "default:cobble", - recipe = "gravelsieve:compressed_gravel", - cooktime = 10, -}) - -minetest.register_alias("gravelsieve:sieve", "gravelsieve:sieve3") -minetest.register_alias("gravelsieve:auto_sieve", "gravelsieve:auto_sieve3") - --- adaption to hopper -if minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil then - hopper:add_container({ - {"bottom", "gravelsieve:auto_sieve0", "src"}, - {"top", "gravelsieve:auto_sieve0", "dst"}, - {"side", "gravelsieve:auto_sieve0", "src"}, - - {"bottom", "gravelsieve:auto_sieve1", "src"}, - {"top", "gravelsieve:auto_sieve1", "dst"}, - {"side", "gravelsieve:auto_sieve1", "src"}, - - {"bottom", "gravelsieve:auto_sieve2", "src"}, - {"top", "gravelsieve:auto_sieve2", "dst"}, - {"side", "gravelsieve:auto_sieve2", "src"}, - - {"bottom", "gravelsieve:auto_sieve3", "src"}, - {"top", "gravelsieve:auto_sieve3", "dst"}, - {"side", "gravelsieve:auto_sieve3", "src"}, - }) -end - --- adaption to Circular Saw -if minetest.get_modpath("moreblocks") then - - stairsplus:register_all("gravelsieve", "compressed_gravel", "gravelsieve:compressed_gravel", { - description=S("Compressed Gravel"), - groups={cracky=2, crumbly=2, choppy=2, not_in_creative_inventory=1}, - tiles = {"gravelsieve_compressed_gravel.png"}, - sounds = default.node_sound_stone_defaults(), - }) -end +gs_dofile("sieve.lua") +gs_dofile("nodes.lua") +gs_dofile("hammer.lua") +gs_dofile("crafts.lua") +gs_dofile("default_output.lua") +gs_dofile("compat.lua") +gs_dofile("interop/hopper.lua") +gs_dofile("interop/moreblocks.lua") +gs_dofile("interop/tubelib.lua") diff --git a/gravelsieve/interop/hopper.lua b/gravelsieve/interop/hopper.lua new file mode 100644 index 0000000..ad0e47e --- /dev/null +++ b/gravelsieve/interop/hopper.lua @@ -0,0 +1,22 @@ +-- support for hopper +if not minetest.get_modpath("hopper") or not hopper or not hopper.add_container then + return +end + +hopper:add_container({ + { "bottom", "gravelsieve:auto_sieve0", "src" }, + { "top", "gravelsieve:auto_sieve0", "dst" }, + { "side", "gravelsieve:auto_sieve0", "src" }, + + { "bottom", "gravelsieve:auto_sieve1", "src" }, + { "top", "gravelsieve:auto_sieve1", "dst" }, + { "side", "gravelsieve:auto_sieve1", "src" }, + + { "bottom", "gravelsieve:auto_sieve2", "src" }, + { "top", "gravelsieve:auto_sieve2", "dst" }, + { "side", "gravelsieve:auto_sieve2", "src" }, + + { "bottom", "gravelsieve:auto_sieve3", "src" }, + { "top", "gravelsieve:auto_sieve3", "dst" }, + { "side", "gravelsieve:auto_sieve3", "src" }, +}) diff --git a/gravelsieve/interop/moreblocks.lua b/gravelsieve/interop/moreblocks.lua new file mode 100644 index 0000000..777508d --- /dev/null +++ b/gravelsieve/interop/moreblocks.lua @@ -0,0 +1,12 @@ +-- support for moreblock's circular saw +if not minetest.get_modpath("moreblocks") then + return +end + +local S = gravelsieve.S +stairsplus:register_all("gravelsieve", "compressed_gravel", "gravelsieve:compressed_gravel", { + description = S("Compressed Gravel"), + groups = { cracky = 2, crumbly = 2, choppy = 2, not_in_creative_inventory = 1 }, + tiles = { "gravelsieve_compressed_gravel.png" }, + sounds = default.node_sound_stone_defaults(), +}) diff --git a/gravelsieve/interop/tubelib.lua b/gravelsieve/interop/tubelib.lua new file mode 100644 index 0000000..35c98ad --- /dev/null +++ b/gravelsieve/interop/tubelib.lua @@ -0,0 +1,107 @@ +------------------------------------------------------------------------ +-- Optional adaption to tubelib +------------------------------------------------------------------------ +if not minetest.global_exists("tubelib") then + return +end + +local settings = gravelsieve.settings + +minetest.register_node("gravelsieve:sieve_defect", { + tiles = { + -- up, down, right, left, back, front + "gravelsieve_top.png", + "gravelsieve_gravel.png", + "gravelsieve_auto_sieve.png^tubelib_defect.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -8 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, -6 / 16 }, + { -8 / 16, -8 / 16, 6 / 16, 8 / 16, 4 / 16, 8 / 16 }, + { -8 / 16, -8 / 16, -8 / 16, -6 / 16, 4 / 16, 8 / 16 }, + { 6 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, 8 / 16 }, + { -6 / 16, -2 / 16, -6 / 16, 6 / 16, 2 / 16, 6 / 16 }, + }, + }, + selection_box = { + type = "fixed", + fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, 8 / 16 }, + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_int("idx", 0) -- for the 4 sieve phases + meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel + meta:set_string("node_name", "gravelsieve:auto_sieve") + meta:set_string("formspec", sieve_formspec) + local inv = meta:get_inventory() + inv:set_size('src', 1) + inv:set_size('dst', 16) + end, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", S("Gravel Sieve")) + meta:set_string("player_name", placer:get_player_name()) + gravelsieve.api.count.add(pos, placer) + end, + + on_dig = function(pos, node, puncher, pointed_thing) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if inv:is_empty("dst") and inv:is_empty("src") then + minetest.node_dig(pos, node, puncher, pointed_thing) + end + end, + after_dig_node = function (pos, oldnode, oldmetadata, digger) + gravelsieve.api.count.del(pos, digger) + end, + + paramtype = "light", + sounds = default.node_sound_wood_defaults(), + paramtype2 = "facedir", + sunlight_propagates = true, + is_ground_content = false, + groups = { choppy = 2, cracky = 1, not_in_creative_inventory = 1 }, +}) + +tubelib.register_node("gravelsieve:auto_sieve3", + { + "gravelsieve:auto_sieve0", + "gravelsieve:auto_sieve1", + "gravelsieve:auto_sieve2", + "gravelsieve:sieve_defect", + }, + { + on_pull_item = function(pos, side) + local meta = minetest.get_meta(pos) + return tubelib.get_item(meta, "dst") + end, + on_push_item = function(pos, side, item) + minetest.get_node_timer(pos):start(settings.step_delay) + local meta = minetest.get_meta(pos) + return tubelib.put_item(meta, "src", item) + end, + on_unpull_item = function(pos, side, item) + local meta = minetest.get_meta(pos) + return tubelib.put_item(meta, "dst", item) + end, + on_node_load = function(pos) + minetest.get_node_timer(pos):start(settings.step_delay) + end, + on_node_repair = function(pos) + local meta = minetest.get_meta(pos) + meta:set_int("tubelib_aging", 0) + meta:set_int("idx", 2) + meta:set_string("node_name", "gravelsieve:auto_sieve") + local inv = meta:get_inventory() + inv:set_size('src', 1) + inv:set_size('dst', 16) + gravelsieve.sieve.step_node(pos, meta, false) + minetest.get_node_timer(pos):start(settings.step_delay) + return true + end, + } +) diff --git a/gravelsieve/mod.conf b/gravelsieve/mod.conf index 3280ed6..a778bf7 100644 --- a/gravelsieve/mod.conf +++ b/gravelsieve/mod.conf @@ -1,4 +1,4 @@ name=gravelsieve -description=This mod includes a hammer to produce gravel from Cobblestone and a sieve to sift gravel to find ores. +description=Sieve gravel to find ores! depends=default -optional_depends=moreblocks,tubelib,hopper,pipeworks +optional_depends=moreblocks,tubelib,hopper,pipeworks,test,unified_inventory diff --git a/gravelsieve/nodes.lua b/gravelsieve/nodes.lua new file mode 100644 index 0000000..eaa7618 --- /dev/null +++ b/gravelsieve/nodes.lua @@ -0,0 +1,15 @@ +local S = gravelsieve.S + +minetest.register_node("gravelsieve:sieved_gravel", { + description = S("Sieved Gravel"), + tiles = { "default_gravel.png" }, + groups = { crumbly = 2, falling_node = 1, not_in_creative_inventory = 1 }, + sounds = default.node_sound_gravel_defaults(), +}) + +minetest.register_node("gravelsieve:compressed_gravel", { + description = S("Compressed Gravel"), + tiles = { "gravelsieve_compressed_gravel.png" }, + groups = { cracky = 2, crumbly = 2, cracky = 2 }, + sounds = default.node_sound_gravel_defaults(), +}) diff --git a/gravelsieve/probability_api.lua b/gravelsieve/probability_api.lua new file mode 100644 index 0000000..8fa2991 --- /dev/null +++ b/gravelsieve/probability_api.lua @@ -0,0 +1,118 @@ + +local y_spread = math.max(1 + gravelsieve.settings.ore_max_elevation - gravelsieve.settings.ore_min_elevation, 1) +local function calculate_probability(item) + local ymax = math.min(item.y_max, gravelsieve.settings.ore_max_elevation) + local ymin = math.max(item.y_min, gravelsieve.settings.ore_min_elevation) + return item.clust_scarcity / (item.clust_num_ores * ((ymax - ymin) / y_spread)) +end + +local function parse_drop(drop) + local d, count = drop:match("^%s*(%S+)%s+(%d+)%s*$") + if d and count then + return d, count + end + d, count = drop:match("%s*craft%s+\"?([^%s\"]+)\"?%s+(%d+)%s*") + if d and count then + return d, count + end + return drop, 1 +end + +-- collect all registered ores and calculate the probability +function gravelsieve.api.get_ore_frequencies() + local ore_frequencies = {} + for _,item in pairs(minetest.registered_ores) do + if minetest.registered_nodes[item.ore] then + local drop = minetest.registered_nodes[item.ore].drop + if type(drop) == "string" + and drop ~= item.ore + and drop ~= "" + and item.ore_type == "scatter" + and item.wherein == "default:stone" + and item.clust_scarcity ~= nil and item.clust_scarcity > 0 + and item.clust_num_ores ~= nil and item.clust_num_ores > 0 + and item.y_max ~= nil and item.y_min ~= nil then + local count + drop, count = parse_drop(drop) + + local probability = calculate_probability(item) + if probability > 0 then + local probabilityFraction = count / probability + local cur_probability = ore_frequencies[drop] + if cur_probability then + ore_frequencies[drop] = cur_probability+probabilityFraction + else + ore_frequencies[drop] = probabilityFraction + end + end + end + end + end + return ore_frequencies +end + +local function pairs_by_values(t, f) + if not f then + f = function(a, b) return a > b end + end + local s = {} + for k, v in pairs(t) do + table.insert(s, {k, v}) + end + table.sort(s, function(a, b) + return f(a[2], b[2]) + end) + local i = 0 + return function() + i = i + 1 + local v = s[i] + if v then + return unpack(v) + else + return nil + end + end +end + +function gravelsieve.api.report_probabilities(probabilities) + gravelsieve.log("action", "ore probabilities:") + local overall_probability = 0.0 + for name,probability in pairs_by_values(probabilities) do + gravelsieve.log("action", "%-32s: 1 / %.02f", name, 1.0/probability) + overall_probability = overall_probability + probability + end + gravelsieve.log("action", "Overall probability %f", overall_probability) +end + +-- The following functions actually work for any table of numbers... perhaps this could be more generic? +function gravelsieve.api.sum_probabilities(probabilities) + local sum = 0.0 + for _,probability in pairs(probabilities) do + sum = sum + probability + end + return sum +end + +function gravelsieve.api.scale_probabilities_to_fill(probabilities, fill_to) + local sum = gravelsieve.api.sum_probabilities(probabilities) + local scale_factor = fill_to / sum + return gravelsieve.api.scale_probabilities(probabilities, scale_factor) +end + +function gravelsieve.api.scale_probabilities(probabilities, scale_factor) + local scaled_probabilities = {} + for name,probability in pairs(probabilities) do + scaled_probabilities[name] = probability * scale_factor + end + return scaled_probabilities +end + +function gravelsieve.api.merge_probabilities( ... ) + local merged_probabilities = {} + for _,probabilities in pairs({...}) do + for name,probability in pairs(probabilities) do + merged_probabilities[name] = (merged_probabilities[name] or 0) + probability + end + end + return merged_probabilities +end \ No newline at end of file diff --git a/gravelsieve/settings.lua b/gravelsieve/settings.lua new file mode 100644 index 0000000..1c6e3ba --- /dev/null +++ b/gravelsieve/settings.lua @@ -0,0 +1,29 @@ +gravelsieve.settings = {} + +local settings_get +if minetest.setting_get then + settings_get = minetest.setting_get +else + settings_get = function(...) return minetest.settings:get(...) end +end + +gravelsieve.settings.step_delay = tonumber(settings_get("gravelsieve.step_delay")) or 1.0 +gravelsieve.settings.ore_max_elevation = tonumber(settings_get("gravelsieve.ore_max_elevation")) or tonumber(settings_get("gravelsieve_ore_max_elevation")) or 0 +gravelsieve.settings.ore_min_elevation = tonumber(settings_get("gravelsieve.ore_min_elevation")) or tonumber(settings_get("gravelsieve_ore_min_elevation")) or -30912 + +-- Need to do weird logic to account for both factors being combined in legacy version +local ore_rarity = settings_get("gravelsieve.ore_rarity") +local legacy_ore_rarity = settings_get("gravelsieve_ore_rarity") +gravelsieve.settings.ore_rarity = 1.16 / 3.0 +if ore_rarity then + gravelsieve.settings.ore_rarity = tonumber(ore_rarity) +elseif legacy_ore_rarity then + gravelsieve.settings.ore_rarity = tonumber(legacy_ore_rarity) / 3.0 +end + +-- tubelib aging feature +if minetest.get_modpath("tubelib") and tubelib ~= nil then + gravelsieve.settings.aging_level1 = 15 * tubelib.machine_aging_value + gravelsieve.settings.aging_level2 = 60 * tubelib.machine_aging_value +end + diff --git a/gravelsieve/settingtypes.txt b/gravelsieve/settingtypes.txt index 411d5b2..2178615 100644 --- a/gravelsieve/settingtypes.txt +++ b/gravelsieve/settingtypes.txt @@ -1,7 +1,4 @@ -# Rarity factor to find ores when sieving with the Gravel Sieve -# 1.0 is according to the mapgen generator -# 2.0 means half as many ores as result -# 0.5 means twice as many ores as result -gravelsieve_ore_rarity (Rarity factor to find ores) float 1.16 -gravelsieve_ore_max_elevation (Maximum elevation considered when calculating ore distribution) int 0 -30912 30927 -gravelsieve_ore_min_elevation (Minimum elevation considered when calculating ore distribution) int -30912 -30912 30927 +gravelsieve.step_delay (Delay between sieving steps) float 1.0 +gravelsieve.ore_rarity (Multiplier to make ores more or less common than they appear in the world) float 0.387 +gravelsieve.ore_max_elevation (The maximum world height ores will be searched for) int 0 +gravelsieve.ore_min_elevation (The minimum world height ores will be searched for) int -30912 diff --git a/gravelsieve/sieve.lua b/gravelsieve/sieve.lua new file mode 100644 index 0000000..4e16331 --- /dev/null +++ b/gravelsieve/sieve.lua @@ -0,0 +1,335 @@ +gravelsieve.sieve = {} +local S = gravelsieve.S +local settings = gravelsieve.settings +local api = gravelsieve.api + +-- Pipeworks support +local pipeworks_after_dig +local pipeworks_after_place + +if minetest.get_modpath("pipeworks") and pipeworks ~= nil then + pipeworks_after_dig = pipeworks.after_dig + pipeworks_after_place = pipeworks.after_place +end + +local sieve_formspec = "size[8,8]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "list[context;src;1,1.5;1,1;]" .. + "image[3,1.5;1,1;gui_furnace_arrow_bg.png^[transformR270]" .. + "list[context;dst;4,0;4,4;]" .. + "list[current_player;main;0,4.2;8,4;]" .. + "listring[context;dst]" .. + "listring[current_player;main]" .. + "listring[context;src]" .. + "listring[current_player;main]" + +local function allow_metadata_inventory_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + + if listname == "src" then + return stack:get_count() + end + + return 0 +end + +local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local stack = inv:get_stack(from_list, from_index) + return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) +end + +local function allow_metadata_inventory_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + return stack:get_count() +end + +local function aging(pos, meta) + if settings.aging_level1 then + local cnt = meta:get_int("tubelib_aging") + 1 + meta:set_int("tubelib_aging", cnt) + if cnt > settings.aging_level1 and math.random(settings.aging_level2) == 1 then + minetest.get_node_timer(pos):stop() + minetest.swap_node(pos, { name = "gravelsieve:sieve_defect" }) + end + end +end + +-- handle the sieve animation +function gravelsieve.sieve.step_node(pos, meta, start) + local node = minetest.get_node(pos) + local idx = meta:get_int("idx") + if start then + if idx == 3 then + idx = 0 + end + else + idx = (idx + 1) % 4 + end + meta:set_int("idx", idx) + node.name = meta:get_string("node_name") .. idx + minetest.swap_node(pos, node) + return idx == 3 +end + + +local function dynamic_args_generator(args) + local dynamic_args = {} + local player_name = args.meta:get_string("player_name") + if player_name then + local player = minetest.get_player_by_name(player_name) + if not player then return {} end -- player is not logged in, this shouldn't happen + dynamic_args = {player_name=player_name,player=player,sieve_count=api.count.get(player)} + end + return dynamic_args +end + +-- place ores to dst according to the calculated probability +local function generate_output(inv, input_name, args) + local output = api.get_random_output(input_name, dynamic_args_generator, args) + local output_item = ItemStack(output) + if inv:room_for_item("dst", output_item) then + inv:add_item("dst", output_item) + return true + end + return false +end + +-- move gravel and ores to dst +local function process_input(meta, pos, inv, input_name) + local input_stack = ItemStack(input_name) + if inv:contains_item("src", input_stack) then + local is_done = gravelsieve.sieve.step_node(pos, meta, false) + if is_done then + -- time to move one item? + if generate_output(inv, input_name, {meta=meta}) then + inv:remove_item("src", input_stack) + end + end + return true -- process finished + end + return false -- process still running +end + +local function choose_input_item(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + for i = 1, inv:get_size("src") do + local input_stack = inv:get_stack("src", i) + local input_name = input_stack:get_name() + if api.can_process(input_name) then + return input_name + end + end +end + +-- timer callback, alternatively called by on_punch +local function sieve_node_timer(pos, elapsed) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local input_name = choose_input_item(pos) + + if input_name then + if process_input(meta, pos, inv, input_name) then + aging(pos, meta) + return true + end + end + + minetest.get_node_timer(pos):stop() + return false +end + +for automatic = 0, 1 do + for idx = 0, 4 do + local nodebox_data = { + { -8 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, -6 / 16 }, + { -8 / 16, -8 / 16, 6 / 16, 8 / 16, 4 / 16, 8 / 16 }, + { -8 / 16, -8 / 16, -8 / 16, -6 / 16, 4 / 16, 8 / 16 }, + { 6 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, 8 / 16 }, + { -6 / 16, -2 / 16, -6 / 16, 6 / 16, 8 / 16, 6 / 16 }, + } + nodebox_data[5][5] = (8 - 2 * idx) / 16 + + local node_name + local description + local tiles_data + local tube_info + if automatic == 0 then + node_name = "gravelsieve:sieve" + description = S("Gravel Sieve") + tiles_data = { + -- up, down, right, left, back, front + "gravelsieve_gravel.png", + "gravelsieve_gravel.png", + "gravelsieve_sieve.png", + "gravelsieve_sieve.png", + "gravelsieve_sieve.png", + "gravelsieve_sieve.png", + } + else + node_name = "gravelsieve:auto_sieve" + description = S("Automatic Gravel Sieve") + tiles_data = { + -- up, down, right, left, back, front + "gravelsieve_gravel.png", + "gravelsieve_gravel.png", + "gravelsieve_auto_sieve.png", + "gravelsieve_auto_sieve.png", + "gravelsieve_auto_sieve.png", + "gravelsieve_auto_sieve.png", + } + + -- Pipeworks support + tube_info = { + insert_object = function(pos, node, stack, direction) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if automatic == 0 then + local meta = minetest.get_meta(pos) + gravelsieve.sieve.step_node(pos, meta, true) + else + minetest.get_node_timer(pos):start(settings.step_delay) + end + return inv:add_item("src", stack) + end, + can_insert = function(pos, node, stack, direction) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:room_for_item("src", stack) + end, + input_inventory = "dst", + connect_sides = { left = 1, right = 1, front = 1, back = 1, bottom = 1, top = 1 } + } + end + + local not_in_creative_inventory + if idx == 3 then + tiles_data[1] = "gravelsieve_top.png" + not_in_creative_inventory = 0 + else + not_in_creative_inventory = 1 + end + + minetest.register_node(node_name .. idx, { + description = description, + tiles = tiles_data, + drawtype = "nodebox", + drop = node_name, + + tube = tube_info, -- NEW + + node_box = { + type = "fixed", + fixed = nodebox_data, + }, + selection_box = { + type = "fixed", + fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, 4 / 16, 8 / 16 }, + }, + + on_timer = sieve_node_timer, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_int("idx", idx) -- for the 4 sieve phases + meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel + meta:set_string("node_name", node_name) + meta:set_string("formspec", sieve_formspec) + local inv = meta:get_inventory() + inv:set_size('src', 1) + inv:set_size('dst', 16) + end, + + after_dig_node = function (pos, oldnode, oldmetadata, digger) + api.count.del(pos, digger) + -- Pipeworks support + if pipeworks_after_dig then + pipeworks_after_dig(pos, oldnode, oldmetadata, digger) + end + end, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Gravel Sieve") + meta:set_string("player_name", placer:get_player_name()) + api.count.add(pos, placer) + + -- Pipeworks support + if pipeworks_after_place then + pipeworks_after_place(pos, placer) + end + end, + + on_metadata_inventory_move = function(pos) + if automatic == 0 then + local meta = minetest.get_meta(pos) + gravelsieve.sieve.step_node(pos, meta, true) + else + minetest.get_node_timer(pos):start(settings.step_delay) + end + end, + + on_metadata_inventory_take = function(pos) + if automatic == 0 then + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if inv:is_empty("src") then + -- sieve should be empty + meta:set_int("idx", 2) + gravelsieve.sieve.step_node(pos, meta, false) + meta:set_int("gravel_cnt", 0) + end + else + minetest.get_node_timer(pos):start(settings.step_delay) + end + end, + + on_metadata_inventory_put = function(pos) + if automatic == 0 then + local meta = minetest.get_meta(pos) + gravelsieve.sieve.step_node(pos, meta, true) + else + minetest.get_node_timer(pos):start(settings.step_delay) + end + end, + + on_punch = function(pos, node, puncher, pointed_thing) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if inv:is_empty("dst") and inv:is_empty("src") then + minetest.node_punch(pos, node, puncher, pointed_thing) + else + sieve_node_timer(pos, 0) + end + end, + + on_dig = function(pos, node, puncher, pointed_thing) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if inv:is_empty("dst") and inv:is_empty("src") then + minetest.node_dig(pos, node, puncher, pointed_thing) + end + end, + + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_move = allow_metadata_inventory_move, + allow_metadata_inventory_take = allow_metadata_inventory_take, + + paramtype = "light", + sounds = default.node_sound_wood_defaults(), + paramtype2 = "facedir", + sunlight_propagates = true, + is_ground_content = false, + groups = { choppy = 2, cracky = 1, not_in_creative_inventory = not_in_creative_inventory, tubedevice = 1, tubedevice_receiver = 1 }, + drop = node_name .. "3", + }) + end +end diff --git a/gravelsieve/test.lua b/gravelsieve/test.lua new file mode 100644 index 0000000..24aee4c --- /dev/null +++ b/gravelsieve/test.lua @@ -0,0 +1,507 @@ +-- Automatic...ish tests for api methods +if not minetest.global_exists('test') then return end + +local describe = test.describe +local it = test.it +local stub = test.stub +local before_each = test.before_each +local after_all = test.after_all +local assert_equal = test.assert.equal +local assert_not_equal = test.assert.not_equal +local expect_error = test.expect.error + +local api = gravelsieve.api + +describe("gravelsieve", function () + local log_stub = stub() + local original_log = gravelsieve.log + gravelsieve.log = log_stub.call + local log_called_times = log_stub.called_times + local log_called_with = log_stub.called_with + + after_all(function () + gravelsieve.log = original_log + end) + + describe("probability api", function () + + describe("report_probabilities", function () + it("prints out correct values", function () + api.report_probabilities({ + test1 = 1, + test2 = 2, + test3 = 4 + }) + log_called_times(5) + log_called_with("action", "ore probabilities:") + log_called_with("action", "%-32s: 1 / %.02f", "test1", 1) + log_called_with("action", "%-32s: 1 / %.02f", "test2", 0.5) + log_called_with("action", "%-32s: 1 / %.02f", "test3", 0.25) + log_called_with("action", "Overall probability %f", 7) + end) + end) + + describe("sum_probabilities", function () + it("properly adds up all values in table", function () + + local sum_result = api.sum_probabilities({ + test1 = 1, + test2 = 2, + test3 = 4 + }) + + assert_equal(sum_result, 7, "Sum should equal 7") + end) + end) + + describe("scale_probabilities", function () + it("properly scales up probabilities", function () + + local doubled_probabilities = api.scale_probabilities({ + test1 = 1, + test2 = 2, + test3 = 4 + }, 2) + + assert_equal(api.sum_probabilities(doubled_probabilities), 14, "Scaled up probabilities should sum to total") + assert_equal(doubled_probabilities, { + test1 = 2, + test2 = 4, + test3 = 8 + }, "Probabilities should be properly scaled up") + end) + end) + + describe("scale_probabilities_to_fill", function () + it("properly scales up probabilities", function () + + local doubled_probabilities = api.scale_probabilities_to_fill({ + test1 = 1, + test2 = 2, + test3 = 4 + }, 14) + + assert_equal(api.sum_probabilities(doubled_probabilities), 14, "Scaled up probabilities should sum to total") + assert_equal(doubled_probabilities, { + test1 = 2, + test2 = 4, + test3 = 8 + }, "Probabilities should be properly scaled up") + end) + + it("properly scales down probabilities", function () + + local normalized_probabilities = api.scale_probabilities_to_fill({ + test1 = 1, + test2 = 2, + test3 = 4 + }, 1) + + assert_equal(api.sum_probabilities(normalized_probabilities), 1, "Scaled down probabilities should sum to total") + assert_equal(normalized_probabilities, { + test1 = 1/7, + test2 = 2/7, + test3 = 4/7 + }, "Probabilities should be properly scaled down") + end) + end) + + describe("merge_probabilities", function () + + it("merges unique tables", function () + + local input1 = { + test1 = 1, + test2 = 2, + test3 = 4 + } + local input2 = { + test4 = 1, + test5 = 2, + test6 = 4 + } + local output = { + test1 = 1, + test2 = 2, + test3 = 4, + test4 = 1, + test5 = 2, + test6 = 4 + } + + local result = api.merge_probabilities(input1, input2) + + assert_equal(result, output, "Probabilities should be properly merged") + end) + + it("will add up similar values in tables", function () + + local input1 = { + test1 = 1, + test2 = 2, + test3 = 4 + } + local input2 = { + test2 = 1, + test3 = 2, + test4 = 4 + } + local output = { + test1 = 1, + test2 = 3, + test3 = 6, + test4 = 4 + } + + local result = api.merge_probabilities(input1, input2) + + assert_equal(result, output, "Probabilities should be properly merged") + end) + + it("can merge several tables", function () + local input1 = { + test1 = 1, + test2 = 2, + test3 = 4 + } + local input2 = { + test2 = 1, + test3 = 2, + test4 = 4 + } + local input3 = { + test4 = 1, + test5 = 2, + test6 = 4 + } + local output = { + test1 = 1, + test2 = 3, + test3 = 6, + test4 = 5, + test5 = 2, + test6 = 4 + } + + local result = api.merge_probabilities(input1, input2, input3) + + assert_equal(result, output, "Probabilities should be properly merged") + end) + + end) + + end) + + describe("config api", function () + + before_each(function () + api.reset_config() + end) + + after_all(function () + api.reset_config() + end) + + describe("can_process", function () + + it("returns true if an input exists", function () + api.register_input("default:gravel") + local registered = api.can_process("default:gravel") + + assert_equal(registered, true, "Should return correctly") + end) + + it("returns false if an input does not exist", function () + local registered = api.can_process("default:gravel") + + assert_equal(registered, false, "Should return correctly") + end) + end) + + describe("get_outputs", function () + + it("returns the outputs of an input", function () + + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + api.register_input("default:gravel", output) + + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, output, "Output should be properly retrieved") + end) + + it("does not allow modification of interal variable", function () + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + + api.register_input("default:gravel", output) + + local registered_output1 = api.get_outputs("default:gravel", "relative") + registered_output1["default:gravel"] = 100 + local registered_output2 = api.get_outputs("default:gravel", "relative") + + assert_not_equal(registered_output1, registered_output2, "Output should not be modified") + assert_equal(registered_output2, output, "Original output should remain the same when modified") + end) + end) + + describe("register_input", function () + it("registers an input with no output", function () + api.register_input("default:gravel") + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, {}, "Should be registered properly") + end) + + it("registers an input with a string output", function () + api.register_input("default:gravel", "default:sand") + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, {["default:sand"] = 1}, "Should be registered properly") + end) + + it("registers an input with a table output", function () + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + api.register_input("default:gravel", output) + + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, output, "Should be registered properly") + end) + + it("does not allow a previously registered input to be registered again", function () + expect_error("re-registering input \"default:gravel\"") + + api.register_input("default:gravel") + api.register_input("default:gravel") + end) + + -- it("does allow a previously registered input to be registered again if allow_override is switched on", function () + -- api.register_input("default:gravel") + -- api.register_input("default:gravel", {}, true) + -- end) + + it("does allow a previously registered input to be registered again", function () + api.register_input("default:gravel") + api.override_input("default:gravel") + end) + + it("does not allow an invalid input to be registered", function () + expect_error("attempt to register unknown node \"garbage_nonsense\"") + + api.register_input("garbage_nonsense") + end) + + it("does not allow an invalid output to be registered", function () + expect_error("attempt to register unknown node \"garbage_nonsense\"") + + api.register_input("default:gravel", "garbage_nonsense") + end) + + it("does not allow an invalid output type to be used", function () + expect_error("Gravelsieve outputs must be a table or a string") + + api.register_input("default:gravel", 28465) + end) + end) + + describe("remove_input", function () + it("removes an input from the config", function () + api.register_input("default:gravel", "default:sand") + api.remove_input("default:gravel") + + assert_equal(api.can_process("default:gravel"), false, "Should be properly removed") + end) + + it("informs you if an unregistered input is removed", function () + api.remove_input("default:gravel") + + log_called_times(1) + log_called_with("error", "Cannot remove an input (%s) that does not exist.", "default:gravel") + end) + + it("returns the registered output of the input", function () + local output = {["default:sand"]=1} + api.register_input("default:gravel", output) + local registered_output = api.remove_input("default:gravel") + + assert_equal(registered_output, {relative=output,dynamic={},fixed={}}, "Should return proper result") + end) + end) + + describe("swap_input", function () + + it("deletes the old input", function () + api.register_input("default:gravel") + api.swap_input("default:gravel", "default:sand") + + assert_equal(api.can_process("default:gravel"), false, "Should be properly deleted") + end) + + it("creates the new input with the same output", function () + local output = {["default:sand"]=1} + api.register_input("default:gravel", output) + api.swap_input("default:gravel", "default:sand") + local registered_output = api.get_outputs("default:sand", "relative") + + assert_equal(registered_output, output, "Should be properly swapped") + end) + end) + + + describe("register_output", function () + it("registers an output to an input", function () + api.register_input("default:gravel") + api.register_output("default:gravel", "default:sand", 0.1) + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, {["default:sand"]=0.1}, "Output should be registered properly") + end) + + it("informs you if you register an output to a non existent input", function () + api.register_output("default:gravel", "default:sand", 0.1) + + log_called_times(1) + log_called_with("error", "You must register the input (%s) before registering the output (%s).", "default:gravel", "default:sand") + end) + + it("does not allow a previously registered output to be registered again", function () + expect_error("re-registering relative output \"default:gravel\" for \"default:sand\"") + + api.register_input("default:gravel", "default:sand") + api.register_output("default:gravel", "default:sand", 1) + end) + + -- it("does allow a previously registered output to be registered again if allow_override is switched on", function () + -- api.register_input("default:gravel", "default:sand") + -- api.register_output("default:gravel", "default:sand", 0.1, true) + -- local registered_output = api.get_outputs("default:gravel", "relative") + + -- assert_equal(registered_output, {["default:sand"]=0.1}, "Output should be overridden properly") + -- end) + + it("does allow a previously registered output to be registered again", function () + api.register_input("default:gravel", "default:sand") + api.override_output("default:gravel", "default:sand", 0.1) + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, {["default:sand"]=0.1}, "Output should be overridden properly") + end) + + it("does not allow an invalid output to be registered", function () + expect_error("attempt to register unknown node \"garbage_nonsense\"") + + api.register_input("default:gravel") + api.register_output("default:gravel", "garbage_nonsense", 1) + end) + + end) + + describe("remove_output", function () + it("removes a single output from an input", function () + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + local expected_output = { + ["default:gravel"] = 1, + ["default:sand"] = 1 + } + api.register_input("default:gravel", output) + + api.remove_output("default:gravel", "default:coal_lump") + + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output, expected_output, "Should be removed properly") + + end) + + it("informs you if you try to remove an output from an input that doesn't exist", function () + api.remove_output("default:gravel", "default:coal_lump") + + log_called_times(1) + log_called_with("error", "Cannot remove an output for an input (%s) that does not exist.", "default:gravel") + end) + end) + + describe("swap_output", function () + + it("removes the old output", function () + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + api.register_input("default:gravel", output) + api.swap_output("default:gravel", "default:coal_lump", "default:iron_lump") + + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output["default:coal_lump"], nil, "Old output should be properly removed") + end) + it("adds in the new output with the old value", function () + local output = { + ["default:gravel"] = 1, + ["default:sand"] = 1, + ["default:coal_lump"] = 0.1 + } + api.register_input("default:gravel", output) + api.swap_output("default:gravel", "default:coal_lump", "default:iron_lump") + + local registered_output = api.get_outputs("default:gravel", "relative") + + assert_equal(registered_output["default:iron_lump"], 0.1, "New output should be properly added") + end) + end) + + + describe("get_random_output", function () + + it("returns outputs (roughly) in the expected distributions", function () + + local runs = 1000000 + local probabilities = { + ["default:gravel"] = 0.5, + ["default:sand"] = 0.5, + ["default:coal_lump"] = 1 / 57.63, + ["default:iron_lump"] = 1 / 59.87, + ["default:copper_lump"] = 1 / 146.31, + ["default:tin_lump"] = 1 / 200.69, + ["default:gold_lump"] = 1 / 445.36, + ["default:mese_crystal"] = 1 / 564.89, + ["default:diamond"] = 1 / 882.17, + } + api.register_input("default:gravel", probabilities) + local normalized_probabilities = api.scale_probabilities_to_fill(probabilities, runs) + local results = {} + for i=1,runs,1 do + local output = api.get_random_output("default:gravel") + results[output] = (results[output] or 0) + 1 + end + + for name,value in pairs(normalized_probabilities) do + local diff = value-(results[name] or 0) + local relative = math.abs(diff) / value + if relative > 0.1 then + fail_test("Random distribution not accurate") + end + end + end) + end) + end) +end) + +test.execute() \ No newline at end of file From b5599776e71f79a0a062ab33ec33116a4c1ee2c2 Mon Sep 17 00:00:00 2001 From: Oversword Date: Fri, 25 Jun 2021 06:48:42 +0100 Subject: [PATCH 2/4] Undo readme deletion --- gravelsieve/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gravelsieve/README.md b/gravelsieve/README.md index a5e5f05..36cee3a 100644 --- a/gravelsieve/README.md +++ b/gravelsieve/README.md @@ -1,11 +1,33 @@ # Gravel Sieve Mod A Mod for those who do not want to spend half their lives underground... +Inspired from a Minecr**t Video on YT. + This mod simplifies the extraction of ores by the fact that ores can be obtained simply by sieving gravel. This mod includes three new tools: - a hammer to produce gravel from Cobblestone - two gravel sieves to find ores (a manual sieve and a automatic sieve) +The sieved gravel can be crafted to Compressed Gravel (inspired by Modern Hippie) and "cooked" in the furnace to get Cobblestone again. + +Recipe for the Gravel Sieve: + + Wood, -----------, Wood + Wood, Steel Ingot, Wood + Wood, -----------, Wood + + +Recipe for the Automatic Gravel Sieve: + + Gravel Sieve, Mese Crystal, Mese Crystal + + +Recipe for Compressed Gravel: + + Sieved Gravel, Sieved Gravel, + Sieved Gravel, Sieved Gravel, + + ### Dependencies default, optional moreores, hopper, and pipeworks From db99b2a3d109ade4843e872fb3da4ced4f04151b Mon Sep 17 00:00:00 2001 From: Oversword Date: Sun, 27 Jun 2021 04:43:43 +0100 Subject: [PATCH 3/4] Reconcile with some changes in the original --- gravelsieve/hammer.lua | 83 +++++++++++++++++++++++------------- gravelsieve/mod.conf | 2 +- gravelsieve/settingtypes.txt | 10 +++-- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/gravelsieve/hammer.lua b/gravelsieve/hammer.lua index f998198..caecb34 100644 --- a/gravelsieve/hammer.lua +++ b/gravelsieve/hammer.lua @@ -1,37 +1,60 @@ +--[[ + + Gravel Sieve Mod + ================ + +]]-- + +-- Load support for I18n local S = gravelsieve.S -local function on_use(itemstack, user, pointed_thing) - if pointed_thing.type ~= "node" then - return - end - - local pos = pointed_thing.under - - if minetest.is_protected(pos, user:get_player_name()) then - minetest.record_protection_violation(pos, user:get_player_name()) - return - end - - local node = minetest.get_node(pos) - if node.name == "default:cobble" or node.name == "default:mossycobble" - or node.name == "default:desert_cobble" then - node.name = "default:gravel" - minetest.swap_node(pos, node) - minetest.sound_play({ - name = "default_dig_crumbly" }, { - gain = 1, - pos = pos, - max_hear_distance = 6, - loop = false }) - end - - itemstack:add_wear(65535 / (500 - 1)) - return itemstack +gravelsieve.disallow = function(pos, node, user, mode, new_param2) + return false +end + +gravelsieve.handler = function(itemstack, user, pointed_thing) + if pointed_thing.type ~= "node" then + return + end + + local pos = pointed_thing.under + + if minetest.is_protected(pos, user:get_player_name()) then + minetest.record_protection_violation(pos, user:get_player_name()) + return + end + + local node = minetest.get_node(pos) + if node.name == "default:cobble" or node.name == "default:mossycobble" + or node.name == "default:desert_cobble" then + node.name = "default:gravel" + minetest.swap_node(pos, node) + minetest.sound_play({ + name="default_dig_crumbly"},{ + gain=1, + pos=pos, + max_hear_distance=6, + loop=false}) + end + + itemstack:add_wear(65535 / (500 - 1)) + return itemstack end minetest.register_tool("gravelsieve:hammer", { - description = S("Hammer converts Cobblestone into Gravel"), - inventory_image = "gravelsieve_hammer.png", - on_use = on_use, + description = S("Hammer converts Cobblestone into Gravel"), + inventory_image = "gravelsieve_hammer.png", + on_use = function(itemstack, user, pointed_thing) + return gravelsieve.handler(itemstack, user, pointed_thing) + end, +}) + +minetest.register_craft({ + output = "gravelsieve:hammer", + recipe = { + {"", "default:steel_ingot", ""}, + {"", "group:stick", "default:steel_ingot"}, + {"group:stick", "", ""}, + } }) diff --git a/gravelsieve/mod.conf b/gravelsieve/mod.conf index a778bf7..f699355 100644 --- a/gravelsieve/mod.conf +++ b/gravelsieve/mod.conf @@ -1,4 +1,4 @@ name=gravelsieve -description=Sieve gravel to find ores! +description=This mod includes a hammer to produce gravel from Cobblestone and a sieve to sift gravel to find ores. depends=default optional_depends=moreblocks,tubelib,hopper,pipeworks,test,unified_inventory diff --git a/gravelsieve/settingtypes.txt b/gravelsieve/settingtypes.txt index 2178615..78868c7 100644 --- a/gravelsieve/settingtypes.txt +++ b/gravelsieve/settingtypes.txt @@ -1,4 +1,8 @@ -gravelsieve.step_delay (Delay between sieving steps) float 1.0 +# Rarity factor to find ores when sieving with the Gravel Sieve +# 1.0 is according to the mapgen generator +# 0.5 means half as many ores as result +# 2.0 means twice as many ores as result gravelsieve.ore_rarity (Multiplier to make ores more or less common than they appear in the world) float 0.387 -gravelsieve.ore_max_elevation (The maximum world height ores will be searched for) int 0 -gravelsieve.ore_min_elevation (The minimum world height ores will be searched for) int -30912 +gravelsieve.ore_max_elevation (The maximum world height ores will be searched for) int 0 -30912 30927 +gravelsieve.ore_min_elevation (The minimum world height ores will be searched for) int -30912 -30912 30927 +gravelsieve.step_delay (Delay between sieving steps) float 1.0 \ No newline at end of file From 330c7a47848fbbc99ea7f274416c136215afa885 Mon Sep 17 00:00:00 2001 From: Oversword Date: Thu, 15 Jul 2021 14:22:10 +0100 Subject: [PATCH 4/4] Remove gravel_cnt and account for blocked sieves --- gravelsieve/interop/tubelib.lua | 1 - gravelsieve/sieve.lua | 29 ++++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/gravelsieve/interop/tubelib.lua b/gravelsieve/interop/tubelib.lua index 9082794..29e0cf8 100644 --- a/gravelsieve/interop/tubelib.lua +++ b/gravelsieve/interop/tubelib.lua @@ -33,7 +33,6 @@ minetest.register_node("gravelsieve:sieve_defect", { on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_int("idx", 0) -- for the 4 sieve phases - meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel meta:set_string("node_name", "gravelsieve:auto_sieve") meta:set_string("formspec", sieve_formspec) local inv = meta:get_inventory() diff --git a/gravelsieve/sieve.lua b/gravelsieve/sieve.lua index c699608..7a1e355 100644 --- a/gravelsieve/sieve.lua +++ b/gravelsieve/sieve.lua @@ -91,33 +91,35 @@ local function dynamic_args_generator(args) return dynamic_args end --- place ores to dst according to the calculated probability -local function generate_output(inv, input_name, args) - local output = api.get_random_output(input_name, dynamic_args_generator, args) +-- move gravel and ores to dst +local function process_output(meta, pos, inv, output) local output_item = ItemStack(output) if inv:room_for_item("dst", output_item) then inv:add_item("dst", output_item) + aging(pos, meta) return true end + meta:set_string("blocked_item", output) return false end --- move gravel and ores to dst +-- take gravel from input local function process_input(meta, pos, inv, input_name) local input_stack = ItemStack(input_name) if inv:contains_item("src", input_stack) then local is_done = gravelsieve.sieve.step_node(pos, meta, false) if is_done then -- time to move one item? - if generate_output(inv, input_name, {meta=meta}) then - inv:remove_item("src", input_stack) - end + inv:remove_item("src", input_stack) + local output = api.get_random_output(input_name, dynamic_args_generator, {meta=meta}) + process_output(meta, pos, inv, output) end return true -- process finished end return false -- process still running end + local function choose_input_item(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() @@ -134,11 +136,18 @@ end local function sieve_node_timer(pos, elapsed) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() + local blocked_output = meta:get_string("blocked_item") + if blocked_output and blocked_output ~= "" then + local item_output = process_output(meta, pos, inv, blocked_output) + if not item_output then + return true + else + meta:set_string("blocked_item", "") + end + end local input_name = choose_input_item(pos) - if input_name then if process_input(meta, pos, inv, input_name) then - aging(pos, meta) return true end end @@ -240,7 +249,6 @@ for automatic = 0, 1 do on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_int("idx", idx) -- for the 4 sieve phases - meta:set_int("gravel_cnt", 0) -- counter to switch between gravel and sieved gravel meta:set_string("node_name", node_name) meta:set_string("formspec", sieve_formspec) local inv = meta:get_inventory() @@ -285,7 +293,6 @@ for automatic = 0, 1 do -- sieve should be empty meta:set_int("idx", 2) gravelsieve.sieve.step_node(pos, meta, false) - meta:set_int("gravel_cnt", 0) end else minetest.get_node_timer(pos):start(settings.step_delay)