Initial commit
28
COPYING.txt
Normal file
@ -0,0 +1,28 @@
|
||||
The TechPack Modpack for Minetest is
|
||||
|
||||
Copyright (C) 2017-2018 Joachim Stolberg
|
||||
|
||||
License of source code
|
||||
----------------------
|
||||
|
||||
This program is free software; you can redistribute and/or
|
||||
modify it under the terms of the GNU Lesser General Public License version 2.1 or later
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the
|
||||
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
License of media (textures, sounds and documentation)
|
||||
-----------------------------------------------------
|
||||
|
||||
All textures, sounds and documentation files are licensed under the
|
||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
502
LICENSE.txt
Normal file
@ -0,0 +1,502 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
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 this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser 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 Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "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
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY 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
|
||||
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
33
gravelsieve/README.md
Normal file
@ -0,0 +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 and tubelib
|
6
gravelsieve/depends.txt
Normal file
@ -0,0 +1,6 @@
|
||||
default
|
||||
moreblocks?
|
||||
tubelib?
|
||||
hopper?
|
||||
pipeworks?
|
||||
|
1
gravelsieve/description.txt
Normal file
@ -0,0 +1 @@
|
||||
This mod includes a hammer to produce gravel from Cobblestone and a sieve to sift gravel to find ores.
|
68
gravelsieve/hammer.lua
Normal file
@ -0,0 +1,68 @@
|
||||
--[[
|
||||
|
||||
Gravel Sieve Mod
|
||||
================
|
||||
|
||||
v0.01 by JoSt
|
||||
Derived from the work of RealBadAngel, Maciej Kasatkin (screwdriver)
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
Copyright (C) 2013-2016 RealBadAngel, Maciej Kasatkin
|
||||
Copyright (C) 2013-2016 Various Minetest developers and contributors
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
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
|
||||
|
||||
return itemstack
|
||||
end
|
||||
|
||||
minetest.register_tool("gravelsieve:hammer", {
|
||||
description = "Hammer converts Cobblestone into Gravel",
|
||||
inventory_image = "gravelsieve_hammer.png",
|
||||
on_use = function(itemstack, user, pointed_thing)
|
||||
gravelsieve.handler(itemstack, user, pointed_thing)
|
||||
return itemstack
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "gravelsieve:hammer",
|
||||
recipe = {
|
||||
{"", "default:steel_ingot", ""},
|
||||
{"", "group:stick", "default:steel_ingot"},
|
||||
{"group:stick", "", ""},
|
||||
}
|
||||
})
|
||||
|
517
gravelsieve/init.lua
Normal file
@ -0,0 +1,517 @@
|
||||
--[[
|
||||
|
||||
Gravel Sieve Mod
|
||||
================
|
||||
|
||||
v1.07 by JoSt
|
||||
Derived from the work of celeron55, Perttu Ahola (furnace)
|
||||
Pipeworks support added by FiftySix
|
||||
|
||||
Copyright (C) 2017-2018 Joachim Stolberg
|
||||
Copyright (C) 2011-2016 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
Copyright (C) 2011-2016 Various Minetest developers and contributors
|
||||
|
||||
LGPLv2.1+
|
||||
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
|
||||
]]--
|
||||
|
||||
gravelsieve = {
|
||||
}
|
||||
|
||||
dofile(minetest.get_modpath("gravelsieve") .. "/hammer.lua")
|
||||
|
||||
gravelsieve.ore_rarity = tonumber(minetest.setting_get("gravelsieve_ore_rarity")) or 1
|
||||
|
||||
-- Increase the probability over the natural occurrence
|
||||
local PROBABILITY_FACTOR = 3
|
||||
|
||||
-- Ore probability table (1/n)
|
||||
gravelsieve.ore_probability = {
|
||||
}
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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.clust_scarcity ~= nil and item.clust_scarcity > 0
|
||||
and item.clust_size ~= nil and item.clust_size > 0 then
|
||||
local probability = item.clust_scarcity / item.clust_size /
|
||||
PROBABILITY_FACTOR * gravelsieve.ore_rarity
|
||||
probability = math.floor(probability)
|
||||
if probability > 20 then
|
||||
if gravelsieve.ore_probability[drop] == nil then
|
||||
gravelsieve.ore_probability[drop] = probability
|
||||
else
|
||||
gravelsieve.ore_probability[drop] =
|
||||
math.min(gravelsieve.ore_probability[drop], probability)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local overall_probability = 0.0
|
||||
for name,probability in pairs(gravelsieve.ore_probability) do
|
||||
print(string.format("[gravelsieve] %-32s %u", name, probability))
|
||||
overall_probability = overall_probability + 1.0/probability
|
||||
end
|
||||
print(string.format("[gravelsieve] Overall probability %g", overall_probability))
|
||||
end
|
||||
|
||||
minetest.after(1, add_ores)
|
||||
|
||||
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
|
||||
|
||||
-- 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 random_ore(inv, src)
|
||||
local num
|
||||
for ore, probability in pairs(gravelsieve.ore_probability) do
|
||||
if math.random(probability) == 1 then
|
||||
local item = ItemStack(ore)
|
||||
if inv:room_for_item("dst", item) then
|
||||
inv:add_item("dst", item)
|
||||
return true -- ore placed
|
||||
end
|
||||
end
|
||||
end
|
||||
return false -- gravel has to be moved
|
||||
end
|
||||
|
||||
|
||||
local function add_gravel_to_dst(meta, inv)
|
||||
-- maintain a counter for gravel kind selection
|
||||
local gravel_cnt = meta:get_int("gravel_cnt") + 1
|
||||
meta:set_int("gravel_cnt", gravel_cnt)
|
||||
|
||||
if (gravel_cnt % 2) == 0 then -- gravel or sieved gravel?
|
||||
inv:add_item("dst", ItemStack("default:gravel")) -- add to dest
|
||||
else
|
||||
inv:add_item("dst", ItemStack("gravelsieve:sieved_gravel")) -- add to dest
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- move gravel and ores to dst
|
||||
local function move_src2dst(meta, pos, inv, src, dst)
|
||||
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?
|
||||
if src:get_name() == "default:gravel" then -- will we find ore?
|
||||
if not random_ore(inv, src) then -- no ore found?
|
||||
add_gravel_to_dst(meta, inv)
|
||||
end
|
||||
else
|
||||
inv:add_item("dst", ItemStack("gravelsieve:sieved_gravel")) -- add to dest
|
||||
end
|
||||
inv:remove_item("src", src)
|
||||
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()
|
||||
local gravel = ItemStack("default:gravel")
|
||||
local gravel_sieved = ItemStack("gravelsieve:sieved_gravel")
|
||||
|
||||
if move_src2dst(meta, pos, inv, gravel) then
|
||||
return true
|
||||
elseif move_src2dst(meta, pos, inv, gravel_sieved) then
|
||||
return true
|
||||
else
|
||||
minetest.get_node_timer(pos):stop()
|
||||
return false
|
||||
end
|
||||
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 = "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 = "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(1.0)
|
||||
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
|
||||
|
||||
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(1.0)
|
||||
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(1.0)
|
||||
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(1.0)
|
||||
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
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Optional adaption to tubelib
|
||||
------------------------------------------------------------------------
|
||||
if minetest.global_exists("tubelib") then
|
||||
tubelib.register_node("gravelsieve:auto_sieve3",
|
||||
{
|
||||
"gravelsieve:auto_sieve0",
|
||||
"gravelsieve:auto_sieve1",
|
||||
"gravelsieve:auto_sieve2",
|
||||
},
|
||||
{
|
||||
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(1.0)
|
||||
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_recv_message = function(pos, topic, payload)
|
||||
if topic == "on" then
|
||||
start_the_machine(pos)
|
||||
elseif topic == "off" then
|
||||
stop_the_machine(pos)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_node("gravelsieve:sieved_gravel", {
|
||||
description = "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 = "Compressed Gravel",
|
||||
tiles = {"gravelsieve_compressed_gravel.png"},
|
||||
groups = {cracky=2, crumbly = 2, cracky = 2},
|
||||
sounds = default.node_sound_gravel_defaults(),
|
||||
})
|
||||
|
||||
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",
|
||||
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="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
|
||||
|
||||
|
1
gravelsieve/mod.conf
Normal file
@ -0,0 +1 @@
|
||||
name=gravelsieve
|
5
gravelsieve/settingtypes.txt
Normal file
@ -0,0 +1,5 @@
|
||||
# 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
|
BIN
gravelsieve/textures/gravelsieve_auto_sieve.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
gravelsieve/textures/gravelsieve_compressed_gravel.png
Normal file
After Width: | Height: | Size: 753 B |
BIN
gravelsieve/textures/gravelsieve_gravel.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
gravelsieve/textures/gravelsieve_hammer.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
gravelsieve/textures/gravelsieve_sieve.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
gravelsieve/textures/gravelsieve_top.png
Normal file
After Width: | Height: | Size: 934 B |
1
modpack.txt
Normal file
@ -0,0 +1 @@
|
||||
The presence of this file indicates that the current folder is a modpack.
|
41
smartline/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# SmartLine \[smartline\]
|
||||
|
||||
This tubelib extension provides small and smart sensors, actors and controllers.
|
||||
|
||||
![SmartLine](https://github.com/joe7575/techpack/blob/master/smartline/screenshot.png)
|
||||
|
||||
The most important and smart node of SmartLine is the SmartLine Controller, a 'computer' to control and monitor Tubelib based machines.
|
||||
You don't need any programming skills, it is more like a configuration according to the "IF this THEN that" concept:
|
||||
|
||||
IF <cond1> OR <cond2> THEN <action>
|
||||
IF <cond1> AND <cond2> THEN <action>
|
||||
|
||||
Examples for conditions are:
|
||||
- the Player Detector detects a player
|
||||
- a button is pressed
|
||||
- a node state is fault, blocked, standby,...
|
||||
- a timer is expired
|
||||
|
||||
Examples for actions are:
|
||||
- switch on/off tubelib nodes, like lamps, door blocks, machines
|
||||
- send mail/chat messages to the owner
|
||||
- output a text message to the display
|
||||
- set timer variables
|
||||
- set/reset flag variables
|
||||
|
||||
The mod comes with several new nodes, all in a smart and small housing:
|
||||
- a Player Detector, sending on/off commands to connected nodes
|
||||
- a Smart Button, sending on/off commands to connected nodes
|
||||
- a Display for text outputs of the controller
|
||||
- a Signal Tower, with green, amber, red lights to signal error/fault states
|
||||
- a Timer (derived from Tubelib Addons2), for daytime based actions
|
||||
- a Sequencer (derived from Tubelib Addons2), for time triggered actions (time in seconds)
|
||||
|
||||
|
||||
API Reference: ![api.md](https://github.com/joe7575/techpack/blob/master/smartline/api.md)
|
||||
|
||||
|
||||
## Dependencies
|
||||
tubelib, default, doors
|
||||
optional: display_lib, font_lib, mail
|
||||
|
121
smartline/api.md
Normal file
@ -0,0 +1,121 @@
|
||||
# SmartLine Controller API
|
||||
|
||||
|
||||
The SmartLine Controller provides the following API to register additional condition and action commands from other mods:
|
||||
|
||||
* smartline.register_condition(name, condition definition)
|
||||
* smartline.register_action(name, action definition)
|
||||
|
||||
The controller executes up to 10 rules every second. Each rule consists of:
|
||||
- two condition functions
|
||||
- one logical operator (and/or)
|
||||
- one action function
|
||||
|
||||
Depending on the operator, one or both condition functions have to return true, so that the action function is called.
|
||||
If the action is called, the action related flag (a1..a10) is automatically set and can be used in
|
||||
subsequent rules to trigger further actions without the need to evaluate both conditions again.
|
||||
|
||||
The controller executes all rules once per second. Independent how long the input condition stays 'true',
|
||||
the corresponding action will be triggered only once. The condition has to become false and then true again, to
|
||||
re-trigger/execute the action again.
|
||||
|
||||
The Controller supports the following variables:
|
||||
- binary flags (f1..f8) to store states (true/false)
|
||||
- timer variables (t1..t8), decremented each second. Used to execute actions time based
|
||||
- input values (referenced via node number) to evaluate received commands (on/off) from other tubelib nodes
|
||||
- action flags (a1..a10) )to store the action state from previous executed rules
|
||||
|
||||
All variables are stored non volatile (as long as the controller is running).
|
||||
|
||||
Each registered condition and action has the function `on_execute` which is called for each rule (every second).
|
||||
* The condition function has to return true or false, depending if the condition is true or not.
|
||||
* The action function has to execute the defined action.
|
||||
|
||||
In addition each registered condition and action has the function `button_label`, which determines the button label
|
||||
for the Controller main formspec. Please note that the maximum number of visible characters for the button label is
|
||||
something about 15.
|
||||
|
||||
See `commands.lua` as reference. All predefined SmartLine Controller commands are registered via `commands.lua`.
|
||||
|
||||
## Prototypes
|
||||
|
||||
```LUA
|
||||
smartline.register_condition("mymod;mycond", {
|
||||
title = "my condition",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
-- data: table with the formspec data
|
||||
-- flag: table with the flag values
|
||||
-- timers: table with the timer values
|
||||
-- inputs: table with the input values
|
||||
-- actions: table with the action values
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "button label"
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
```LUA
|
||||
smartline.register_action(name, {
|
||||
title = "my action",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs)
|
||||
-- data: table with the formspec data
|
||||
-- flag: table with the flag values
|
||||
-- timers: table with the timer values
|
||||
-- inputs: table with the input values
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "button label"
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
The 'title' is used in the main menu for the condition and action selection dialog.
|
||||
The 'formspec' table defines the condition/action related form for additional user parameters.
|
||||
It supports the following subset of the minetest formspec elements:
|
||||
|
||||
- textlist
|
||||
- field
|
||||
- label
|
||||
|
||||
Please note that size and position is automatically determined.
|
||||
All other attributes are according to the original formspec.
|
||||
Example:
|
||||
|
||||
```LUA
|
||||
formspec = {
|
||||
{
|
||||
type = "field", -- formspec element
|
||||
name = "number", -- reference key for the table 'data'
|
||||
label = "input from node with number", -- label shown above of the element
|
||||
default = "", -- default value
|
||||
},
|
||||
{
|
||||
type = "textlist", -- formspec element
|
||||
name = "value", -- reference key for the table 'data'
|
||||
label = "is", -- label shown above of the element
|
||||
choices = "on,off", -- list elements
|
||||
default = 1, -- first list element as default value
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl", -- not really used, but internally needed
|
||||
label = "Hint: Connect the input nodes with the controller",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The table 'data' includes the condition/action related 'formspec' data.
|
||||
For the above 'formspec' example, it is:
|
||||
|
||||
```LUA
|
||||
data = {
|
||||
number = <string>, -- the entered value of the "field",
|
||||
value = <number>, -- the number of the selected element of the "textlist"
|
||||
value_text = <string>, -- in addition the text of the selected element of the "textlist"
|
||||
}
|
||||
```
|
||||
|
189
smartline/button.lua
Normal file
@ -0,0 +1,189 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
button.lua:
|
||||
Derived from Tubelib button
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
local function switch_on(pos, node)
|
||||
node.name = "smartline:button_active"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.sound_play("button", {
|
||||
pos = pos,
|
||||
gain = 0.5,
|
||||
max_hear_distance = 5,
|
||||
})
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = meta:get_string("own_num")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local cycle_time = meta:get_int("cycle_time")
|
||||
if cycle_time > 0 then -- button mode?
|
||||
minetest.get_node_timer(pos):start(cycle_time)
|
||||
end
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
local clicker_name = nil
|
||||
if meta:get_string("public") == "false" then
|
||||
clicker_name = meta:get_string("clicker_name")
|
||||
end
|
||||
tubelib.send_message(numbers, placer_name, clicker_name, "on", own_num)
|
||||
end
|
||||
|
||||
local function switch_off(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "smartline:button"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):stop()
|
||||
minetest.sound_play("button", {
|
||||
pos = pos,
|
||||
gain = 0.5,
|
||||
max_hear_distance = 5,
|
||||
})
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = meta:get_string("own_num")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
tubelib.send_message(numbers, placer_name, nil, "off", own_num)
|
||||
end
|
||||
|
||||
|
||||
minetest.register_node("smartline:button", {
|
||||
description = "SmartLine Button/Switch",
|
||||
inventory_image = "smartline_button_inventory.png",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_button_off.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = tubelib.add_node(pos, "smartline:button")
|
||||
meta:set_string("own_num", own_num)
|
||||
meta:set_string("formspec", "size[5,6]"..
|
||||
"dropdown[0.2,0;3;type;switch,button 2s,button 4s,button 8s,button 16s;1]"..
|
||||
"field[0.5,2;3,1;numbers;Insert destination block number(s);]" ..
|
||||
"checkbox[1,3;public;public;false]"..
|
||||
"button_exit[1,4;2,1;exit;Save]")
|
||||
meta:set_string("placer_name", placer:get_player_name())
|
||||
meta:set_string("public", "false")
|
||||
meta:set_int("cycle_time", 0)
|
||||
meta:set_string("infotext", "SmartLine Button "..own_num)
|
||||
end,
|
||||
|
||||
on_receive_fields = function(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if tubelib.check_numbers(fields.numbers) then
|
||||
meta:set_string("numbers", fields.numbers)
|
||||
local own_num = meta:get_string("own_num")
|
||||
meta:set_string("infotext", "SmartLine Button "..own_num..", connected with block "..fields.numbers)
|
||||
else
|
||||
return
|
||||
end
|
||||
if fields.public then
|
||||
meta:set_string("public", fields.public)
|
||||
end
|
||||
local cycle_time = nil
|
||||
if fields.type == "switch" then
|
||||
cycle_time = 0
|
||||
elseif fields.type == "button 2s" then
|
||||
cycle_time = 2
|
||||
elseif fields.type == "button 4s" then
|
||||
cycle_time = 4
|
||||
elseif fields.type == "button 8s" then
|
||||
cycle_time = 8
|
||||
elseif fields.type == "button 16s" then
|
||||
cycle_time = 16
|
||||
end
|
||||
if cycle_time ~= nil then
|
||||
meta:set_int("cycle_time", cycle_time)
|
||||
end
|
||||
if fields.exit then
|
||||
meta:set_string("formspec", nil)
|
||||
end
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if meta:get_string("numbers") then
|
||||
meta:set_string("clicker_name", clicker:get_player_name())
|
||||
switch_on(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_node("smartline:button_active", {
|
||||
description = "SmartLine Button/Switch",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_button_on.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("clicker_name", clicker:get_player_name())
|
||||
if meta:get_int("cycle_time") == nil or meta:get_int("cycle_time") == 0 then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
on_timer = switch_off,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2, not_in_creative_inventory=1},
|
||||
drop = "smartline:button",
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:button",
|
||||
recipe = {
|
||||
{"", "", ""},
|
||||
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
|
||||
{"", "", ""},
|
||||
},
|
||||
})
|
611
smartline/commands.lua
Normal file
@ -0,0 +1,611 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
command.lua:
|
||||
|
||||
Register all basic controller commands
|
||||
|
||||
]]--
|
||||
|
||||
smartline.register_condition("default", {
|
||||
title = "",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs, actions) end,
|
||||
button_label = function(data) return "" end,
|
||||
})
|
||||
|
||||
smartline.register_action("default", {
|
||||
title = "",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs) end,
|
||||
button_label = function(data) return "" end,
|
||||
})
|
||||
|
||||
|
||||
smartline.register_condition("true", {
|
||||
title = "true",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return true
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "true"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("false", {
|
||||
title = "false",
|
||||
formspec = {},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return false
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "false"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("flag", {
|
||||
title = "flag test",
|
||||
formspec = {
|
||||
{
|
||||
type = "textlist",
|
||||
name = "flag",
|
||||
label = "flag",
|
||||
choices = "f1,f2,f3,f4,f5,f6,f7,f8",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "is",
|
||||
choices = "true,false",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Don't forget to reset the flag again.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return flags[data.flag] == data.value_text
|
||||
end,
|
||||
button_label = function(data)
|
||||
return data.flag_text.."=="..data.value_text
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("flag", {
|
||||
title = "flag set",
|
||||
formspec = {
|
||||
{
|
||||
type = "textlist",
|
||||
name = "flag",
|
||||
label = "set flag",
|
||||
choices = "f1,f2,f3,f4,f5,f6,f7,f8",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "to value",
|
||||
choices = "true,false",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Flags are stored permanently and can be used by other rules.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
flags[data.flag] = data.value_text
|
||||
end,
|
||||
button_label = function(data)
|
||||
return data.flag_text.."="..data.value_text
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("input", {
|
||||
title = "check input",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "input from node with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "is",
|
||||
choices = "on,off",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: An input is only available,\nif the sending node is connected with the controller.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return inputs[data.number] == data.value_text
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "i("..data.number..")=="..data.value_text
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
smartline.register_condition("timer", {
|
||||
title = "timer expired",
|
||||
formspec = {
|
||||
{
|
||||
type = "textlist",
|
||||
name = "timer",
|
||||
label = "timer expired",
|
||||
choices = "t1,t2,t3,t4,t5,t6,t7,t8",
|
||||
default = 1,
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return timers[data.timer] == 0
|
||||
end,
|
||||
button_label = function(data)
|
||||
return data.timer_text.." expired"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("timer", {
|
||||
title = "timer start",
|
||||
formspec = {
|
||||
{
|
||||
type = "textlist",
|
||||
name = "timer",
|
||||
label = "start timer",
|
||||
choices = "t1,t2,t3,t4,t5,t6,t7,t8",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
name = "value",
|
||||
label = "value in sec.",
|
||||
default = "",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
timers[data.timer] = tonumber(data.value) or 0
|
||||
end,
|
||||
button_label = function(data)
|
||||
return data.timer_text.."="..data.value
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("pusher", {
|
||||
title = "Pusher state",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "state from Pusher with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "is",
|
||||
choices = "stopped,running,standby,blocked,fault",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint:\n - standby means 'nothing to do'\n - blocked means 'inventory is full'",
|
||||
},
|
||||
},
|
||||
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return tubelib.send_request(data.number, "state", "") == data.value_text
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "st("..data.number..")=="..string.sub(data.value_text or "???", 1, 4).."."
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("fuel", {
|
||||
title = "fuel state",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "fuel state from node with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "is",
|
||||
choices = "full,empty,not full,not empty",
|
||||
default = 1,
|
||||
},
|
||||
},
|
||||
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
if data.value > 2 then
|
||||
return tubelib.send_request(data.number, "fuel", nil) ~= string.sub(data.value_text or "???", 5)
|
||||
else
|
||||
return tubelib.send_request(data.number, "fuel", nil) == data.value_text
|
||||
end
|
||||
end,
|
||||
button_label = function(data)
|
||||
if data.value > 2 then
|
||||
return "st("..data.number..")<>"..string.sub(data.value_text or "???", 5)
|
||||
else
|
||||
return "st("..data.number..")=="..data.value_text
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("signaltower", {
|
||||
title = "Signal Tower state",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "state from Signal Tower with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "is",
|
||||
choices = "off,green,amber,red,not off,not green,not amber,not red",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Works also for Signal Towers in unloaded areas.",
|
||||
},
|
||||
},
|
||||
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
if data.value > 4 then
|
||||
return tubelib.send_request(data.number, "state", nil) ~= string.sub(data.value_text or "???", 5)
|
||||
else
|
||||
return tubelib.send_request(data.number, "state", nil) == data.value_text
|
||||
end
|
||||
end,
|
||||
button_label = function(data)
|
||||
if data.value > 4 then
|
||||
return "sig("..data.number..")<>"..string.sub(data.value_text or "???", 5)
|
||||
else
|
||||
return "sig("..data.number..")=="..data.value_text
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("signaltower", {
|
||||
title = "Signal Tower command",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "set Signal Tower with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "to color",
|
||||
choices = "off,green,amber,red",
|
||||
default = 1,
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
tubelib.send_message(data.number, data.owner, nil, data.value_text, number)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "sig("..data.number..","..data.value_text..")"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("switch", {
|
||||
title = "switch nodes on/off",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "set node with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "value",
|
||||
label = "to state",
|
||||
choices = "on,off",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Used for pushers, lamps, machines, gates,...",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
tubelib.send_message(data.number, data.owner, nil, data.value_text, number)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "cmnd("..data.number..","..data.value_text..")"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("display1", {
|
||||
title = "Display: add one line",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "output to Display with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
name = "text",
|
||||
label = "the following text",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Works also for Displays in unloaded areas.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
tubelib.send_message(data.number, data.owner, nil, "text", data.text)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "display("..data.number..")"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("display2", {
|
||||
title = "Display: overwrite one line",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "output to Display with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "row",
|
||||
label = "Display line",
|
||||
choices = "1,2,3,4,5,6,7,8,9",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
name = "text",
|
||||
label = "the following text",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: Works also for Displays in unloaded areas.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
local payload = {row = data.row, str = data.text}
|
||||
tubelib.send_message(data.number, data.owner, nil, "row", payload)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "display("..data.number..")"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("display3", {
|
||||
title = "Display: player name",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "output to Display with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
name = "text",
|
||||
label = "the following text",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: use a '*' character as reference to the player name",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
local text = string.gsub(data.text, "*", flags.name or "<unknown>")
|
||||
tubelib.send_message(data.number, data.owner, nil, "text", text)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "display(<name>)"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_action("display4", {
|
||||
title = "Display: Clear screen",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "Display number",
|
||||
default = "",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
tubelib.send_message(data.number, data.owner, nil, "clear", "")
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "Clear screen"
|
||||
end,
|
||||
})
|
||||
|
||||
if minetest.get_modpath("mail") and mail ~= nil then
|
||||
smartline.register_action("mail", {
|
||||
title = "mail",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "text",
|
||||
label = "send the message",
|
||||
default = "",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
mail.send("Server", data.owner, "[SmartLine Controller]", data.text)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "mail(...)"
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
smartline.register_action("chat", {
|
||||
title = "chat",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "text",
|
||||
label = "send the message",
|
||||
default = "",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
minetest.chat_send_player(data.owner, "[SmartLine Controller] "..data.text)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "chat(...)"
|
||||
end,
|
||||
})
|
||||
|
||||
local function door_toggle(pos, owner, state)
|
||||
pos = minetest.string_to_pos("("..pos..")")
|
||||
if pos then
|
||||
local door = doors.get(pos)
|
||||
if door then
|
||||
local player = {
|
||||
get_player_name = function() return owner end,
|
||||
}
|
||||
if state == "open" then
|
||||
door:open(player)
|
||||
elseif state == "close" then
|
||||
door:close(player)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
smartline.register_action("door", {
|
||||
title = "doors open/close",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "pos",
|
||||
label = "door position like: 123,7,-1200",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "textlist",
|
||||
name = "state",
|
||||
label = "set",
|
||||
choices = "open,close",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl1",
|
||||
label = "For standard doors like the Steel Door",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl2",
|
||||
label = "Hint: use a marker stick to determine the door position",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, number)
|
||||
door_toggle(data.pos, data.owner, data.state_text)
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "door("..data.state_text..")"
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("playerdetector", {
|
||||
title = "detected player name",
|
||||
formspec = {
|
||||
{
|
||||
type = "field",
|
||||
name = "number",
|
||||
label = "name from player detector with number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "field",
|
||||
name = "name",
|
||||
label = "is",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: use a '*' character for all player names",
|
||||
},
|
||||
},
|
||||
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
flags.name = tubelib.send_request(data.number, "name", nil)
|
||||
return (data.name == "*" and flags.name ~= "") or flags.name == data.name
|
||||
end,
|
||||
button_label = function(data)
|
||||
if string.len(data.name) > 6 then
|
||||
return "name=="..string.sub(data.name or "???", 1, 6).."."
|
||||
end
|
||||
return "name=="..data.name
|
||||
end,
|
||||
})
|
||||
|
||||
smartline.register_condition("action", {
|
||||
title = "action",
|
||||
formspec = {
|
||||
{
|
||||
type = "textlist",
|
||||
name = "action",
|
||||
label = "action is executed",
|
||||
choices = "a1,a2,a3,a4,a5,a6,a7,a8,a9,a10",
|
||||
default = 1,
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Hint: The corresponding flag is set for each\nexecute action. Useful to execute\nmore than one action with one condition.",
|
||||
},
|
||||
},
|
||||
on_execute = function(data, flags, timers, inputs, actions)
|
||||
return actions[data.action] == true
|
||||
end,
|
||||
button_label = function(data)
|
||||
return "action"..data.action
|
||||
end,
|
||||
})
|
||||
|
937
smartline/controller.lua
Normal file
@ -0,0 +1,937 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
controller.lua:
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
local NUM_RULES = 10
|
||||
|
||||
local mail_exists = minetest.get_modpath("mail") and mail ~= nil
|
||||
|
||||
local sHELP = [[SmartLine Controller Help
|
||||
|
||||
Control other nodes by means of rules, according to:
|
||||
IF <cond1> and/or <cond2> THEN <action>
|
||||
|
||||
These rules allow to execute actions based on conditions.
|
||||
Examples for conditions are:
|
||||
- the Player Detector detects a player
|
||||
- a button is pressed
|
||||
- a node state is fault, blocked, standby,...
|
||||
- a timer is expired
|
||||
|
||||
Actions are:
|
||||
- switch on/off tubelib nodes, like lamps, door blocks, machines
|
||||
- send mail/chat messages to the owner
|
||||
- output a text message to the display
|
||||
- set timer variables
|
||||
- set/reset flag variables
|
||||
|
||||
Variables and timers:
|
||||
- 8 flags (set/reset) can be used to store conditions
|
||||
for later use.
|
||||
- Action flags (one flag for each rule, set when action is executed)
|
||||
The flag can be used as condition for subsequent rules.
|
||||
- 8 timers (resolution in seconds) can be use
|
||||
for delayed actions.
|
||||
|
||||
The controller executes all rules once per second.
|
||||
Independent how long the input condition stays 'true',
|
||||
the corresponding action will be triggered only once.
|
||||
The condition has to become false and then true again, to
|
||||
re-trigger/execute the action again.
|
||||
|
||||
The 'label' has no function. It is only used
|
||||
to give rules a name.
|
||||
|
||||
Edit command examples:
|
||||
- 'x 1 8' exchange rows 1 with row 8
|
||||
- 'c 1 2' copy row 1 to 2
|
||||
- 'd 3' delete row 3
|
||||
|
||||
The state view shows the current state of all rules.
|
||||
The colors show, if conditions are true or false and
|
||||
if actions were already executed or not.
|
||||
It has a 'update' button to update the view.
|
||||
|
||||
For more information, see: goo.gl/wZ5GUR
|
||||
]]
|
||||
|
||||
local sOUTPUT = "Press 'help' for edit commands"
|
||||
|
||||
--
|
||||
-- Helper functions
|
||||
--
|
||||
local function create_kv_list(elem)
|
||||
local a = {}
|
||||
for i,v in ipairs(elem) do
|
||||
a[v] = i
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
local function output(label, prefix, postfix, kvTbl)
|
||||
local tbl = {label..": "}
|
||||
for k,v in pairs(kvTbl) do
|
||||
tbl[#tbl+1] = prefix..k..postfix.."="..v..", "
|
||||
end
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Conditions
|
||||
--
|
||||
|
||||
-- tables with all data from condition/action registrations
|
||||
local kvRegisteredCond = {}
|
||||
local kvRegisteredActn = {}
|
||||
|
||||
-- list of keys for conditions/actions
|
||||
local aCondTypes = {}
|
||||
local aActnTypes = {}
|
||||
|
||||
-- list of titles for conditions/actions
|
||||
local aCondTitles = {}
|
||||
local aActnTitles = {}
|
||||
|
||||
-- table with runtime functions
|
||||
local CondRunTimeHandlers = {}
|
||||
local ActnRunTimeHandlers = {}
|
||||
|
||||
|
||||
local function eval_cond(data, flags, timers, inputs, actions)
|
||||
return CondRunTimeHandlers[data.__idx__](data, flags, timers, inputs, actions) and 1 or 0
|
||||
end
|
||||
|
||||
local function exec_action(data, flags, timers, number)
|
||||
ActnRunTimeHandlers[data.__idx__](data, flags, timers, number)
|
||||
end
|
||||
|
||||
smartline = {}
|
||||
|
||||
--
|
||||
-- API functions for condition/action registrations
|
||||
--
|
||||
function smartline.register_condition(key, tData)
|
||||
table.insert(CondRunTimeHandlers, tData.on_execute)
|
||||
table.insert(aCondTypes, key)
|
||||
table.insert(aCondTitles, tData.title)
|
||||
tData.__idx__ = #aCondTypes
|
||||
if kvRegisteredCond[key] ~= nil then
|
||||
print("[SmartLine] Condition registration error "..key)
|
||||
return
|
||||
end
|
||||
kvRegisteredCond[key] = tData
|
||||
for _,item in ipairs(tData.formspec) do
|
||||
if item.type == "textlist" then
|
||||
item.tChoices = string.split(item.choices, ",")
|
||||
item.num_choices = #item.tChoices
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function smartline.register_action(key, tData)
|
||||
table.insert(ActnRunTimeHandlers, tData.on_execute)
|
||||
table.insert(aActnTypes, key)
|
||||
table.insert(aActnTitles, tData.title)
|
||||
tData.__idx__ = #aActnTypes
|
||||
if kvRegisteredActn[key] ~= nil then
|
||||
print("[SmartLine] Action registration error "..key)
|
||||
return
|
||||
end
|
||||
kvRegisteredActn[key] = tData
|
||||
for _,item in ipairs(tData.formspec) do
|
||||
if item.type == "textlist" then
|
||||
item.tChoices = string.split(item.choices, ",")
|
||||
item.num_choices = #item.tChoices
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Formspec
|
||||
--
|
||||
|
||||
-- Determine the selected submenu and return the corresponding
|
||||
-- formspec definition.
|
||||
-- postfix: row/culumn info like "11" or "a2"
|
||||
-- type: "cond" or "actn"
|
||||
-- fs_data: formspec data
|
||||
local function get_active_subm_definition(postfix, type, fs_data)
|
||||
local idx = 1
|
||||
local fs_definition = {}
|
||||
if type == "cond" then
|
||||
idx = fs_data["subm"..postfix.."_cond"] or 1
|
||||
local key = aCondTypes[idx]
|
||||
fs_definition = kvRegisteredCond[key]
|
||||
elseif type == "actn" then
|
||||
idx = fs_data["subm"..postfix.."_actn"] or 1
|
||||
local key = aActnTypes[idx]
|
||||
fs_definition = kvRegisteredActn[key]
|
||||
end
|
||||
return idx, fs_definition
|
||||
end
|
||||
|
||||
-- Extract runtime relevant data from the given submenu
|
||||
-- postfix: row/culum info like "11" or "a2"
|
||||
-- fs_definition: submenu formspec definition
|
||||
-- fs_data: formspec data
|
||||
local function get_subm_data(postfix, fs_definition, fs_data)
|
||||
local data = {}
|
||||
for idx,elem in ipairs(fs_definition.formspec) do
|
||||
if elem.type == "field" then
|
||||
data[elem.name] = fs_data["subm"..postfix.."_"..elem.name] or "?"
|
||||
elseif elem.type == "textlist" then
|
||||
local num = tonumber(fs_data["subm"..postfix.."_"..elem.name]) or 1
|
||||
num = math.min(num, elem.num_choices)
|
||||
data[elem.name] = num
|
||||
data[elem.name.."_text"] = elem.tChoices[num]
|
||||
end
|
||||
end
|
||||
-- type of the condition/action
|
||||
data.__idx__ = fs_definition.__idx__
|
||||
return data
|
||||
end
|
||||
|
||||
-- Copy field/formspec data to the table fs_data
|
||||
-- fs_definition: submenu formspec definituion
|
||||
-- fields: formspec input
|
||||
-- fs_data: formspec data
|
||||
local function field2fs_data(fs_definition, fields, fs_data)
|
||||
for idx,elem in ipairs(fs_definition.formspec) do
|
||||
local key = "subm"..fields._postfix_.."_"..elem.name
|
||||
if elem.type == "field" then
|
||||
if fields[elem.name] then
|
||||
fs_data[key] = fields[elem.name]
|
||||
end
|
||||
elseif elem.type == "textlist" then
|
||||
local evt = minetest.explode_textlist_event(fields[elem.name])
|
||||
if evt.type == "CHG" then
|
||||
fs_data[key] = evt.index
|
||||
end
|
||||
end
|
||||
if fs_data[key] == nil then
|
||||
fs_data[key] = elem.default
|
||||
end
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function add_controls_to_table(tbl, postfix, fs_data, fs_definition)
|
||||
local val = ""
|
||||
local offs = 2.4
|
||||
for idx,elem in ipairs(fs_definition.formspec) do
|
||||
if elem.label then
|
||||
tbl[#tbl+1] = "label[0,"..offs..";"..elem.label.."]"
|
||||
offs = offs + 0.5
|
||||
end
|
||||
if elem.type == "field" then
|
||||
val = fs_data["subm"..postfix.."_"..elem.name] or elem.default
|
||||
tbl[#tbl+1] = "field[0.3,"..(offs+0.2)..";8.2,1;"..elem.name..";;"..val.."]"
|
||||
offs = offs + 0.9
|
||||
elseif elem.type == "textlist" then
|
||||
val = fs_data["subm"..postfix.."_"..elem.name] or elem.default
|
||||
tbl[#tbl+1] = "textlist[0.0,"..(offs)..";8,1.4;"..elem.name..";"..elem.choices..";"..val.."]"
|
||||
offs = offs + 1.8
|
||||
end
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
local function runtime_data(postfix, type, fs_data)
|
||||
local _,fs_definition = get_active_subm_definition(postfix, type, fs_data)
|
||||
return get_subm_data(postfix, fs_definition, fs_data)
|
||||
end
|
||||
|
||||
local function decrement_timers(timers)
|
||||
for idx,_ in ipairs(timers) do
|
||||
timers[idx] = tonumber(timers[idx])
|
||||
if timers[idx] >= 0 then
|
||||
timers[idx] = timers[idx] - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Condition formspec
|
||||
--
|
||||
local function formspec_cond(_postfix_, fs_data)
|
||||
local tbl = {"size[8.2,10]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;cond]"..
|
||||
"field[0,0;0,0;_postfix_;;".._postfix_.."]"}
|
||||
|
||||
local sConditions = table.concat(aCondTitles, ",")
|
||||
local cond_idx, fs_definition = get_active_subm_definition(_postfix_, "cond", fs_data)
|
||||
tbl[#tbl+1] = "label[0,0.1;Condition type:]"
|
||||
tbl[#tbl+1] = "textlist[0,0.6;8,1.4;cond;"..sConditions..";"..cond_idx.."]"
|
||||
tbl = add_controls_to_table(tbl, _postfix_, fs_data, fs_definition)
|
||||
tbl[#tbl+1] = "button[4,9.4;2,1;_cancel_;cancel]"
|
||||
tbl[#tbl+1] = "button[6,9.4;2,1;_exit_;ok]"
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
-- evaluate the row condition
|
||||
local function eval_formspec_cond(meta, fs_data, fields, readonly)
|
||||
if readonly then return fs_data end
|
||||
-- determine condition type
|
||||
local cond = minetest.explode_textlist_event(fields.cond)
|
||||
if cond.type == "CHG" then
|
||||
fs_data["subm"..fields._postfix_.."_cond"] = cond.index
|
||||
end
|
||||
-- prepare data
|
||||
local _, fs_definition = get_active_subm_definition(fields._postfix_, "cond", fs_data)
|
||||
fs_data = field2fs_data(fs_definition, fields, fs_data)
|
||||
local data = get_subm_data(fields._postfix_, fs_definition, fs_data)
|
||||
-- update button for main menu
|
||||
fs_data["cond"..fields._postfix_] = fs_definition.button_label(data)
|
||||
|
||||
if fields._exit_ == nil then
|
||||
-- update formspec if exit is not pressed
|
||||
meta:set_string("formspec", formspec_cond(fields._postfix_, fs_data))
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Action formspec
|
||||
--
|
||||
local function formspec_actn(_postfix_, fs_data)
|
||||
local tbl = {"size[8.2,10]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;actn]"..
|
||||
"field[0,0;0,0;_postfix_;;".._postfix_.."]"}
|
||||
|
||||
local sActions = table.concat(aActnTitles, ",")
|
||||
local actn_idx, fs_definition = get_active_subm_definition(_postfix_, "actn", fs_data)
|
||||
tbl[#tbl+1] = "label[0,0.1;Action type:]"
|
||||
tbl[#tbl+1] = "textlist[0,0.6;8,1.4;actn;"..sActions..";"..actn_idx.."]"
|
||||
tbl = add_controls_to_table(tbl, _postfix_, fs_data, fs_definition)
|
||||
tbl[#tbl+1] = "button[4,9.4;2,1;_cancel_;cancel]"
|
||||
tbl[#tbl+1] = "button[6,9.4;2,1;_exit_;ok]"
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
-- evaluate the row action
|
||||
local function eval_formspec_actn(meta, fs_data, fields, readonly)
|
||||
if readonly then return fs_data end
|
||||
-- determine action type
|
||||
local actn = minetest.explode_textlist_event(fields.actn)
|
||||
if actn.type == "CHG" then
|
||||
fs_data["subm"..fields._postfix_.."_actn"] = actn.index
|
||||
end
|
||||
-- prepare data
|
||||
local _, fs_definition = get_active_subm_definition(fields._postfix_, "actn", fs_data)
|
||||
fs_data = field2fs_data(fs_definition, fields, fs_data)
|
||||
local data = get_subm_data(fields._postfix_, fs_definition, fs_data)
|
||||
-- update button for main menu
|
||||
fs_data["actn"..fields._postfix_] = fs_definition.button_label(data)
|
||||
|
||||
if fields._exit_ == nil then
|
||||
-- update formspec if exit is not pressed
|
||||
meta:set_string("formspec", formspec_actn(fields._postfix_, fs_data))
|
||||
end
|
||||
if fields._cancel_ then
|
||||
fields._exit_ = true
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Label text formspec
|
||||
--
|
||||
local function formspec_label(_postfix_, fs_data)
|
||||
local label = fs_data["label".._postfix_] or "<any text>"
|
||||
return "size[6,4]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;label]"..
|
||||
"field[0,0;0,0;_postfix_;;".._postfix_.."]"..
|
||||
"label[0.2,0.3;Label:]"..
|
||||
"field[0.3,1.5;5,1;label;;"..label.."]"..
|
||||
"button[4.5,3;1.5,1;_exit_;ok]"
|
||||
end
|
||||
|
||||
-- evaluate the row label
|
||||
local function eval_formspec_label(meta, fs_data, fields, readonly)
|
||||
if readonly then return fs_data end
|
||||
fs_data["subml"..fields._postfix_.."_label"] = fields.label
|
||||
if fields._exit_ == nil then
|
||||
meta:set_string("formspec", formspec_label(fields._postfix_, fs_data))
|
||||
end
|
||||
-- set the button label of the main menu based on the given input in the submenu
|
||||
fs_data["label"..fields._postfix_] = fs_data["subml"..fields._postfix_.."_label"]
|
||||
return fs_data
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Operand formspec
|
||||
--
|
||||
local function formspec_oprnd(_postfix_, fs_data)
|
||||
local oprnd = fs_data["submo".._postfix_.."_oprnd"] or ""
|
||||
return "size[6,4]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;oprnd]"..
|
||||
"field[0,0;0,0;_postfix_;;".._postfix_.."]"..
|
||||
"label[0.2,0.3;Operand:]"..
|
||||
"textlist[0,0.8;5.6,1.4;oprnd;or,and;"..oprnd.."]"..
|
||||
"button[4.5,3;1.5,1;_exit_;ok]"
|
||||
end
|
||||
|
||||
-- evaluate the row operand
|
||||
local function eval_formspec_oprnd(meta, fs_data, fields, readonly)
|
||||
if readonly then return fs_data end
|
||||
local oprnd = minetest.explode_textlist_event(fields.oprnd)
|
||||
if oprnd.type == "CHG" then
|
||||
fs_data["submo"..fields._postfix_.."_oprnd"] = oprnd.index
|
||||
end
|
||||
if fields._exit_ == nil then
|
||||
meta:set_string("formspec", formspec_oprnd(fields._postfix_, fs_data))
|
||||
end
|
||||
-- set the button label of the main menu based on the given input in the submenu
|
||||
fs_data["oprnd"..fields._postfix_] = fs_data["submo"..fields._postfix_.."_oprnd"] == 1 and "or" or "and"
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function formspec_main(state, fs_data, output)
|
||||
local tbl = {"size[15,10;true]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;main]"..
|
||||
"label[0.8,0;label:]label[3.8,0;IF cond 1:]label[7,0;and/or]label[8.3,0;cond 2:]label[11.7,0;THEN action:]"}
|
||||
|
||||
for idx = 1,NUM_RULES do
|
||||
local ypos = idx * 0.8 - 0.4
|
||||
tbl[#tbl+1] = "label[0,"..(0.2+ypos)..";"..idx.."]"
|
||||
tbl[#tbl+1] = "button[0.4,"..ypos..";3,1;label"..idx..";"..(fs_data["label"..idx] or "...").."]"
|
||||
tbl[#tbl+1] = "button[3.5,"..ypos..";3.4,1;cond1"..idx..";"..(fs_data["cond1"..idx] or "...").."]"
|
||||
tbl[#tbl+1] = "button[7,".. ypos..";1,1;oprnd".. idx..";"..(fs_data["oprnd"..idx] or "or").."]"
|
||||
tbl[#tbl+1] = "button[8,".. ypos..";3.4,1;cond2"..idx..";"..(fs_data["cond2"..idx] or "...").."]"
|
||||
tbl[#tbl+1] = "button[11.5,".. ypos..";3.4,1;actna"..idx..";"..(fs_data["actna"..idx] or "...").."]"
|
||||
end
|
||||
tbl[#tbl+1] = "image_button[14,9;1,1;".. tubelib.state_button(state) ..";button;]"
|
||||
tbl[#tbl+1] = "button[10.6,9;1.5,1;state;state]"
|
||||
tbl[#tbl+1] = "button[12.2,9;1.5,1;help;help]"
|
||||
tbl[#tbl+1] = "label[0.2,8.8;"..output.."]"
|
||||
tbl[#tbl+1] = "field[0.4,9.6;4.8,1;cmnd;;<cmnd>]"
|
||||
tbl[#tbl+1] = "button[5,9.3;1,1;ok;OK]"
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
local function eval_formspec_main(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_old", minetest.serialize(fs_data))
|
||||
for idx = 1,NUM_RULES do
|
||||
-- eval standard inputs
|
||||
if not readonly then
|
||||
fs_data["oprnd"..idx] = fields["oprnd"..idx] or fs_data["oprnd"..idx]
|
||||
end
|
||||
|
||||
-- eval submenu button events
|
||||
if fields["label"..idx] then
|
||||
meta:set_string("formspec", formspec_label(idx, fs_data))
|
||||
elseif fields["cond1"..idx] then
|
||||
meta:set_string("formspec", formspec_cond("1"..idx, fs_data))
|
||||
elseif fields["cond2"..idx] then
|
||||
meta:set_string("formspec", formspec_cond("2"..idx, fs_data))
|
||||
elseif fields["oprnd"..idx] then
|
||||
meta:set_string("formspec", formspec_oprnd(idx, fs_data))
|
||||
elseif fields["actna"..idx] then
|
||||
meta:set_string("formspec", formspec_actn("a"..idx, fs_data))
|
||||
end
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function formspec_help(offs)
|
||||
return "size[15,10]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;help]"..
|
||||
"label[0,"..(-offs/50)..";"..sHELP.."]"..
|
||||
--"label[0.2,0;test]"..
|
||||
"scrollbar[13.5,1;0.5,7;vertical;sb_help;"..offs.."]"..
|
||||
"button[13.5,9;1.5,1;close;close]"
|
||||
end
|
||||
|
||||
local function background(xpos, ypos, val)
|
||||
if val == true then
|
||||
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#008000]"
|
||||
elseif val == false then
|
||||
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#800000]"
|
||||
else
|
||||
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#202020]"
|
||||
end
|
||||
end
|
||||
|
||||
local function formspec_state(meta, fs_data)
|
||||
local number = meta:get_string("number")
|
||||
local state = meta:get_int("state")
|
||||
local tbl = {"size[15,10;true]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;state]"..
|
||||
"label[0.8,0;label:]label[3.8,0;IF cond 1:]label[7,0;and/or]label[8.3,0;cond 2:]label[11.7,0;THEN action:]"}
|
||||
|
||||
if state == tubelib.RUNNING and number then
|
||||
local inputs = tubelib.get_data(number, "inputs") or {}
|
||||
local act_gate = tubelib.get_data(number, "act_gate") or {}
|
||||
local timers = tubelib.get_data(number, "timers") or {}
|
||||
local flags = tubelib.get_data(number, "flags") or {}
|
||||
local conds = tubelib.get_data(number, "conds") or {}
|
||||
|
||||
for idx = 1,NUM_RULES do
|
||||
local ypos = idx * 0.6 + 0.2
|
||||
local s1 = fs_data["cond1"..idx] or " ... "
|
||||
local s2 = fs_data["cond2"..idx] or " ... "
|
||||
local sa = fs_data["actna"..idx] or " ... "
|
||||
if conds[idx] == nil then
|
||||
tbl[#tbl+1] = background(3.7, ypos, nil)
|
||||
tbl[#tbl+1] = background(8, ypos, nil)
|
||||
else
|
||||
tbl[#tbl+1] = background(3.7, ypos, conds[idx] == 1 or conds[idx] == 3)
|
||||
tbl[#tbl+1] = background(8, ypos, conds[idx] == 2 or conds[idx] == 3)
|
||||
end
|
||||
tbl[#tbl+1] = background(11.5, ypos, act_gate[idx])
|
||||
tbl[#tbl+1] = "label[0,".. ypos..";"..idx.."]"
|
||||
tbl[#tbl+1] = "label[0.5,"..ypos..";"..(fs_data["label"..idx] or " ... ").."]"
|
||||
tbl[#tbl+1] = "label[3.7,".. ypos..";"..s1.."]"
|
||||
tbl[#tbl+1] = "label[7.2,".. ypos..";"..(fs_data["oprnd"..idx] or "or").."]"
|
||||
tbl[#tbl+1] = "label[8,".. ypos..";"..s2.."]"
|
||||
tbl[#tbl+1] = "label[11.5,".. ypos..";"..sa.."]"
|
||||
end
|
||||
|
||||
tbl[#tbl+1] = "label[10,8.2; Seconds: "..(meta:get_int("runtime") or 1).."]"
|
||||
|
||||
tbl[#tbl+1] = "label[0,7;"..output("Inputs", "i(", ")", inputs).."]"
|
||||
tbl[#tbl+1] = "label[0,7.6;"..output("Timers", "t", "", timers).."]"
|
||||
tbl[#tbl+1] = "label[0,8.2;"..output("Flags", "f", "", flags).."]"
|
||||
|
||||
tbl[#tbl+1] = "label[0,9;Hint:]"
|
||||
tbl[#tbl+1] = "box[1.3,9;7,0.4;#008000]"
|
||||
tbl[#tbl+1] = "label[1.4,9;condition is true / action was executed]"
|
||||
tbl[#tbl+1] = "box[8.9,9;4,0.4;#800000]"
|
||||
tbl[#tbl+1] = "label[9,9;condition is false]"
|
||||
tbl[#tbl+1] = "box[1.3,9.6;7,0.4;#202020]"
|
||||
tbl[#tbl+1] = "label[1.4,9.6;action was not executed]"
|
||||
|
||||
end
|
||||
|
||||
tbl[#tbl+1] = "button[13.3,8;1.7,1;update;update]"
|
||||
tbl[#tbl+1] = "button[13.3,9;1.7,1;close;close]"
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
local function execute(meta, number, debug)
|
||||
local rt_rules = tubelib.get_data(number, "rt_rules")
|
||||
local inputs = tubelib.get_data(number, "inputs") or {}
|
||||
local act_gate = tubelib.get_data(number, "act_gate") or {}
|
||||
local timers = tubelib.get_data(number, "timers") or {}
|
||||
local flags = tubelib.get_data(number, "flags") or {}
|
||||
local conds = tubelib.get_data(number, "conds") or {}
|
||||
local actions = {}
|
||||
decrement_timers(timers)
|
||||
for i,item in ipairs(rt_rules) do
|
||||
local c1 = eval_cond(item.cond1, flags, timers, inputs, actions)
|
||||
local c2 = eval_cond(item.cond2, flags, timers, inputs, actions)
|
||||
conds[i] = c1 + c2*2
|
||||
if c1 + c2 >= item.cond_cnt then
|
||||
if act_gate[i] == nil then
|
||||
-- execute action
|
||||
exec_action(item.actn, flags, timers, number)
|
||||
actions[i] = true
|
||||
end
|
||||
act_gate[i] = true
|
||||
else
|
||||
act_gate[i] = nil
|
||||
end
|
||||
end
|
||||
tubelib.set_data(number, "inputs", inputs)
|
||||
tubelib.set_data(number, "act_gate", act_gate)
|
||||
tubelib.set_data(number, "timers", timers)
|
||||
tubelib.set_data(number, "flags", flags)
|
||||
tubelib.set_data(number, "conds", conds)
|
||||
end
|
||||
|
||||
local function check_rules(pos, elapsed)
|
||||
--local t = minetest.get_us_time()
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_int("runtime", (meta:get_int("runtime") or 1) + 1)
|
||||
local number = meta:get_string("number")
|
||||
local state = meta:get_int("state")
|
||||
if state == tubelib.RUNNING and number then
|
||||
execute(meta, number, debug)
|
||||
end
|
||||
--print("time", minetest.get_us_time() - t)
|
||||
return true
|
||||
end
|
||||
|
||||
local function switch_state(pos, state, fs_data)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("state", state)
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
if state == tubelib.RUNNING then
|
||||
meta:set_string("infotext", "SmartLine Controller "..number..": running")
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
else
|
||||
meta:set_string("infotext", "SmartLine Controller "..number..": stopped")
|
||||
minetest.get_node_timer(pos):stop()
|
||||
end
|
||||
end
|
||||
|
||||
local function start_controller(pos, number, fs_data)
|
||||
tubelib.set_data(number, "timers", {}) -- local timers
|
||||
tubelib.set_data(number, "inputs", {}) -- for rx commands
|
||||
tubelib.set_data(number, "flags", {}) -- to store flags
|
||||
tubelib.set_data(number, "conds", {}) -- to store conditions
|
||||
tubelib.set_data(number, "act_gate", {}) -- for action states
|
||||
switch_state(pos, tubelib.RUNNING, fs_data)
|
||||
end
|
||||
|
||||
local function stop_controller(pos, fs_data)
|
||||
switch_state(pos, tubelib.STOPPED, fs_data)
|
||||
end
|
||||
|
||||
local function formspec2runtime_rule(number, owner, fs_data)
|
||||
local rt_rules = {}
|
||||
local num2inp = {}
|
||||
for idx = 1,NUM_RULES do
|
||||
-- valid rule?
|
||||
if fs_data["subm1"..idx.."_cond"] and fs_data["subm2"..idx.."_cond"]
|
||||
and fs_data["subma"..idx.."_actn"] then
|
||||
-- add to list of runtine rules
|
||||
local rule = {
|
||||
cond_cnt = fs_data["oprnd"..idx] == "and" and 2 or 1,
|
||||
cond1 = runtime_data("1"..idx, "cond", fs_data),
|
||||
cond2 = runtime_data("2"..idx, "cond", fs_data),
|
||||
actn = runtime_data("a"..idx, "actn", fs_data),
|
||||
}
|
||||
rule.actn.owner = owner
|
||||
table.insert(rt_rules, rule)
|
||||
end
|
||||
end
|
||||
tubelib.set_data(number, "rt_rules", rt_rules)
|
||||
end
|
||||
|
||||
|
||||
local function get_keys(fs_data)
|
||||
-- collect all keys and remove row information
|
||||
local keys = {}
|
||||
for k,v in pairs(fs_data) do
|
||||
local key = string.sub(k,1,5).."*"..string.sub(k, 7)
|
||||
if type(v) == 'number' then
|
||||
keys[key] = 1 -- default value
|
||||
else
|
||||
keys[key] = "..." -- default value
|
||||
end
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
local function exchange_rules(fs_data, pos1, pos2)
|
||||
-- exchange elem by elem
|
||||
for k,v in pairs(get_keys(fs_data)) do
|
||||
local k1 = string.gsub(k, "*", pos1)
|
||||
local k2 = string.gsub(k, "*", pos2)
|
||||
local temp = fs_data[k1] or v
|
||||
fs_data[k1] = fs_data[k2] or v
|
||||
fs_data[k2] = temp
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function copy_rule(fs_data, pos1, pos2)
|
||||
-- copy elem by elem
|
||||
for k,v in pairs(get_keys(fs_data)) do
|
||||
local k1 = string.gsub(k, "*", pos1)
|
||||
local k2 = string.gsub(k, "*", pos2)
|
||||
fs_data[k2] = fs_data[k1] or v
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function delete_rule(fs_data, pos)
|
||||
for k,v in pairs(get_keys(fs_data)) do
|
||||
local k1 = string.gsub(k, "*", pos)
|
||||
fs_data[k1] = nil
|
||||
end
|
||||
return fs_data
|
||||
end
|
||||
|
||||
local function edit_command(fs_data, text)
|
||||
local cmnd, pos1, pos2 = text:match('^(%S)%s(%d+)%s(%d+)$')
|
||||
if pos2 == nil then
|
||||
cmnd, pos1 = text:match('^(%S)%s(%d+)$')
|
||||
end
|
||||
if cmnd and pos1 and pos2 then
|
||||
if cmnd == "x" then
|
||||
exchange_rules(fs_data, pos1, pos2)
|
||||
return "rows "..pos1.." and "..pos2.." exchanged"
|
||||
end
|
||||
if cmnd == "c" then
|
||||
copy_rule(fs_data, pos1, pos2)
|
||||
return "row "..pos1.." copied to "..pos2
|
||||
end
|
||||
elseif cmnd == "d" and pos1 then
|
||||
delete_rule(fs_data, pos1)
|
||||
return "row "..pos1.." deleted"
|
||||
end
|
||||
return "Invalid command '"..text.."'"
|
||||
end
|
||||
|
||||
local function on_receive_fields(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
local state = meta:get_int("state")
|
||||
if not player or not player:is_player() then
|
||||
return
|
||||
end
|
||||
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or {}
|
||||
local output = ""
|
||||
local readonly = player:get_player_name() ~= owner
|
||||
|
||||
-- FIRST: test if command entered?
|
||||
if fields.ok then
|
||||
if not readonly then
|
||||
output = edit_command(fs_data, fields.cmnd)
|
||||
stop_controller(pos, fs_data)
|
||||
meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, output))
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
end
|
||||
-- SECOND: eval none edit events (events based in __type__)?
|
||||
elseif fields.help then
|
||||
meta:set_string("formspec", formspec_help(1))
|
||||
elseif fields.state then
|
||||
meta:set_string("formspec", formspec_state(meta, fs_data))
|
||||
elseif fields.update then
|
||||
meta:set_string("formspec", formspec_state(meta, fs_data))
|
||||
elseif fields._cancel_ then
|
||||
fs_data = minetest.deserialize(meta:get_string("fs_old"))
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
elseif fields.close then
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
elseif fields.sb_help then
|
||||
local evt = minetest.explode_scrollbar_event(fields.sb_help)
|
||||
if evt.type == "CHG" then
|
||||
meta:set_string("formspec", formspec_help(evt.value))
|
||||
end
|
||||
elseif fields.button then
|
||||
if not readonly then
|
||||
local number = meta:get_string("number")
|
||||
local state = meta:get_int("state")
|
||||
if state == tubelib.RUNNING then
|
||||
stop_controller(pos, fs_data)
|
||||
meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, sOUTPUT))
|
||||
else
|
||||
formspec2runtime_rule(number, owner, fs_data)
|
||||
start_controller(pos, number, fs_data)
|
||||
meta:set_string("formspec", formspec_main(tubelib.RUNNING, fs_data, sOUTPUT))
|
||||
end
|
||||
end
|
||||
-- THIRD: evaluate edit events from sub-menus
|
||||
elseif fields._type_ == "main" then
|
||||
fs_data = eval_formspec_main(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
elseif fields._type_ == "label" then
|
||||
fs_data = eval_formspec_label(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
elseif fields._type_ == "cond" then
|
||||
fs_data = eval_formspec_cond(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
elseif fields._type_ == "oprnd" then
|
||||
fs_data = eval_formspec_oprnd(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
elseif fields._type_ == "actn" then
|
||||
fs_data = eval_formspec_actn(meta, fs_data, fields, readonly)
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
elseif fields._type_ == "help" then
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
elseif fields._type_ == "state" then
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
end
|
||||
-- FOURTH: back to main menu
|
||||
if fields._exit_ then
|
||||
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
|
||||
-- stop_controller(pos, fs_data)
|
||||
-- meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
-- end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("smartline:controller", {
|
||||
description = "SmartLine Controller",
|
||||
inventory_image = "smartline_controller_inventory.png",
|
||||
wield_image = "smartline_controller_inventory.png",
|
||||
stack_max = 1,
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_controller.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = tubelib.add_node(pos, "smartline:controller")
|
||||
local fs_data = {}
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_string("number", number)
|
||||
meta:set_int("state", tubelib.STOPPED)
|
||||
meta:set_int("debug", 0)
|
||||
meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, sOUTPUT))
|
||||
meta:set_string("infotext", "SmartLine Controller "..number..": stopped")
|
||||
end,
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
on_dig = function(pos, node, puncher, pointed_thing)
|
||||
if minetest.is_protected(pos, puncher:get_player_name()) then
|
||||
return
|
||||
end
|
||||
|
||||
minetest.node_dig(pos, node, puncher, pointed_thing)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
on_timer = check_rules,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=1, cracky=1, crumbly=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:controller",
|
||||
recipe = {
|
||||
{"", "default:mese_crystal", ""},
|
||||
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
|
||||
{"", "default:mese_crystal", ""},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
local function set_input(meta, payload, val)
|
||||
if payload then
|
||||
local number = meta:get_string("number")
|
||||
local inputs = tubelib.get_data(number, "inputs")
|
||||
if inputs then
|
||||
inputs[payload] = val
|
||||
tubelib.set_data(number, "inputs", inputs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tubelib.register_node("smartline:controller", {}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if topic == "on" then
|
||||
set_input(meta, payload, topic)
|
||||
elseif topic == "off" then
|
||||
set_input(meta, payload, topic)
|
||||
elseif topic == "state" then
|
||||
local state = meta:get_int("state")
|
||||
return tubelib.statestring(state)
|
||||
else
|
||||
return "unsupported"
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- List of Controller actions and conditions is dependent on loaded mods.
|
||||
-- Therefore, the order of actions and conditions has to be re-assembled each time.
|
||||
-- last order from last run is stored as meta data
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
local function old_to_new(newTypes, oldTypes)
|
||||
local res = {}
|
||||
if #oldTypes == 0 then
|
||||
return nil
|
||||
end
|
||||
local new = create_kv_list(newTypes)
|
||||
for idx,key in ipairs(oldTypes) do
|
||||
res[idx] = new[key] or 1
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function update_node_database(meta)
|
||||
local aOldCondTypes = minetest.deserialize(meta:get_string("aCondTypes")) or {}
|
||||
local aOldActnTypes = minetest.deserialize(meta:get_string("aActnTypes")) or {}
|
||||
|
||||
local tOld2NewCond = old_to_new(aCondTypes, aOldCondTypes)
|
||||
local tOld2NewActn = old_to_new(aActnTypes, aOldActnTypes)
|
||||
|
||||
meta:set_string("aCondTypes", minetest.serialize(aCondTypes))
|
||||
meta:set_string("aActnTypes", minetest.serialize(aActnTypes))
|
||||
|
||||
return tOld2NewCond, tOld2NewActn
|
||||
end
|
||||
|
||||
minetest.register_lbm({
|
||||
label = "[SmartLine] Controller update",
|
||||
name = "smartline:update",
|
||||
nodenames = {"smartline:controller"},
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
|
||||
|
||||
local tOld2NewCond, tOld2NewActn = update_node_database(meta)
|
||||
|
||||
if tOld2NewCond and tOld2NewActn then
|
||||
-- map from old to new indexes
|
||||
for idx = 1,NUM_RULES do
|
||||
fs_data["subm1"..idx.."_cond"] = tOld2NewCond[fs_data["subm1"..idx.."_cond"]]
|
||||
fs_data["subm2"..idx.."_cond"] = tOld2NewCond[fs_data["subm2"..idx.."_cond"]]
|
||||
fs_data["subma"..idx.."_actn"] = tOld2NewActn[fs_data["subma"..idx.."_actn"]]
|
||||
end
|
||||
|
||||
meta:set_string("fs_data", minetest.serialize(fs_data))
|
||||
end
|
||||
local number = meta:get_string("number")
|
||||
local owner = meta:get_string("owner")
|
||||
formspec2runtime_rule(number, owner, fs_data)
|
||||
end
|
||||
})
|
||||
|
7
smartline/depends.txt
Normal file
@ -0,0 +1,7 @@
|
||||
default
|
||||
doors
|
||||
tubelib
|
||||
display_lib?
|
||||
font_lib?
|
||||
mail?
|
||||
|
2
smartline/description.txt
Normal file
@ -0,0 +1,2 @@
|
||||
SmartLine is a Tubelib extension with small and smart sensors, actors and controllers.
|
||||
|
139
smartline/display.lua
Normal file
@ -0,0 +1,139 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
display.lua:
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
display_lib.register_display_entity("smartline:entity")
|
||||
|
||||
function display_update(pos, objref)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local text = meta:get_string("text") or ""
|
||||
text = string.gsub(text, "|", " \n")
|
||||
local texture = font_lib.make_multiline_texture(
|
||||
"default", text,
|
||||
120, 120, 9, "top", "#000")
|
||||
objref:set_properties({ textures = {texture},
|
||||
visual_size = {x=0.94, y=0.94} })
|
||||
end
|
||||
|
||||
|
||||
local lcd_box = {
|
||||
type = "wallmounted",
|
||||
wall_top = {-8/16, 15/32, -8/16, 8/16, 8/16, 8/16}
|
||||
}
|
||||
|
||||
minetest.register_node("smartline:display", {
|
||||
description = "SmartLine Display",
|
||||
inventory_image = 'smartline_display_inventory.png',
|
||||
tiles = {"smartline_display.png"},
|
||||
drawtype = "nodebox",
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "wallmounted",
|
||||
node_box = lcd_box,
|
||||
selection_box = lcd_box,
|
||||
light_source = 6,
|
||||
|
||||
display_entities = {
|
||||
["smartline:entity"] = { depth = 0.42,
|
||||
on_display_update = display_update},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "smartline:display")
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("number", number)
|
||||
meta:set_string("text", " \n \nMinetest\nSmartLine Tools\n \nDisplay\nNumber: "..number)
|
||||
meta:set_int("startscreen", 1)
|
||||
display_lib.update_entities(pos)
|
||||
end,
|
||||
|
||||
on_place = display_lib.on_place,
|
||||
on_construct = display_lib.on_construct,
|
||||
on_destruct = display_lib.on_destruct,
|
||||
on_rotate = display_lib.on_rotate,
|
||||
groups = {cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_glass_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:display",
|
||||
recipe = {
|
||||
{"", "", ""},
|
||||
{"default:glass", "dye:green", "tubelib:wlanchip"},
|
||||
{"", "default:copper_ingot", ""},
|
||||
},
|
||||
})
|
||||
|
||||
local function add_line(meta, payload)
|
||||
local text = meta:get_string("text")
|
||||
local rows
|
||||
if meta:get_int("startscreen") == 1 then
|
||||
rows = {}
|
||||
meta:set_int("startscreen", 0)
|
||||
else
|
||||
rows = string.split(text, "|")
|
||||
end
|
||||
if #rows > 8 then
|
||||
table.remove(rows, 1)
|
||||
end
|
||||
table.insert(rows, payload)
|
||||
text = table.concat(rows, "|")
|
||||
meta:set_string("text", text)
|
||||
end
|
||||
|
||||
local function write_row(meta, payload)
|
||||
local text = meta:get_string("text")
|
||||
if type(payload) == "table" then
|
||||
local row = tonumber(payload.row) or 1
|
||||
local str = payload.str or "oops"
|
||||
local rows
|
||||
if meta:get_int("startscreen") == 1 then
|
||||
rows = {}
|
||||
meta:set_int("startscreen", 0)
|
||||
else
|
||||
rows = string.split(text, "|")
|
||||
end
|
||||
if #rows < 9 then
|
||||
for i = #rows, 9 do
|
||||
table.insert(rows, " ")
|
||||
end
|
||||
end
|
||||
rows[row] = str
|
||||
text = table.concat(rows, "|")
|
||||
meta:set_string("text", text)
|
||||
end
|
||||
end
|
||||
|
||||
tubelib.register_node("smartline:display", {}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "text" then -- add one line and scroll if necessary
|
||||
local meta = minetest.get_meta(pos)
|
||||
add_line(meta, payload)
|
||||
display_lib.update_entities(pos)
|
||||
elseif topic == "row" then -- overwrite the given row
|
||||
local meta = minetest.get_meta(pos)
|
||||
write_row(meta, payload)
|
||||
display_lib.update_entities(pos)
|
||||
elseif topic == "clear" then -- clear the screen
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("text", "")
|
||||
display_lib.update_entities(pos)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
29
smartline/init.lua
Normal file
@ -0,0 +1,29 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
History:
|
||||
2018-01-01 v0.01 first version
|
||||
2018-01-26 v0.02 timer and sequencer added
|
||||
2018-02-01 v0.03 further commands, hints and cancel button aded
|
||||
|
||||
]]--
|
||||
|
||||
if minetest.get_modpath("display_lib") and display_lib ~= nil and
|
||||
minetest.get_modpath("font_lib") and font_lib ~= nil then
|
||||
dofile(minetest.get_modpath("smartline") .. "/playerdetector.lua")
|
||||
end
|
||||
dofile(minetest.get_modpath("smartline") .. "/button.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/signaltower.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/display.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/sequencer.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/timer.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/repeater.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/controller.lua")
|
||||
dofile(minetest.get_modpath("smartline") .. "/commands.lua")
|
1
smartline/mod.conf
Normal file
@ -0,0 +1 @@
|
||||
name=smartline
|
224
smartline/playerdetector.lua
Normal file
@ -0,0 +1,224 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
playerdetector.lua:
|
||||
|
||||
]]--
|
||||
|
||||
local function switch_on(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "smartline:playerdetector_active"
|
||||
minetest.swap_node(pos, node)
|
||||
local number = meta:get_string("number")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local owner = meta:get_string("owner")
|
||||
tubelib.send_message(numbers, owner, nil, "on", number)
|
||||
end
|
||||
|
||||
local function switch_off(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "smartline:playerdetector"
|
||||
minetest.swap_node(pos, node)
|
||||
local number = meta:get_string("number")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local owner = meta:get_string("owner")
|
||||
tubelib.send_message(numbers, owner, nil, "off", number)
|
||||
end
|
||||
|
||||
local function scan_for_player(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local names = meta:get_string("names") or ""
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(pos, 4)) do
|
||||
if object:is_player() then
|
||||
if names == "" then
|
||||
meta:set_string("player_name", object:get_player_name())
|
||||
return true
|
||||
end
|
||||
for _,name in ipairs(string.split(names, " ")) do
|
||||
if object:get_player_name() == name then
|
||||
meta:set_string("player_name", name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
meta:set_string("player_name", nil)
|
||||
return false
|
||||
end
|
||||
|
||||
local function formspec_help()
|
||||
return "size[10,9]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"label[3,0;Player Detector Help]"..
|
||||
"label[0,1;Input the number(s) of the receiving node(s).\n"..
|
||||
"Separate numbers via blanks, like '0123 0234'.\n\n"..
|
||||
"Input the player name(s) separated by blanks,\nor empty for all players.]"..
|
||||
"button_exit[4,8;2,1;exit;close]"
|
||||
end
|
||||
|
||||
|
||||
local function formspec(numbers, names)
|
||||
return "size[8,5]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"label[2,0;Player Detector]"..
|
||||
"field[0.3,1;8,1;numbers;Receiver node numbers:;"..numbers.."]" ..
|
||||
"field[0.3,2.5;8,1;names;Player name(s):;"..names.."]" ..
|
||||
"button_exit[5,3.5;2,1;exit;Save]"..
|
||||
"button[1,3.5;1,1;help;help]"
|
||||
end
|
||||
|
||||
local function on_receive_fields(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player:get_player_name() == owner then
|
||||
if fields.exit == "Save" then
|
||||
if tubelib.check_numbers(fields.numbers) then
|
||||
meta:set_string("numbers", fields.numbers)
|
||||
end
|
||||
meta:set_string("names", fields.names)
|
||||
meta:set_string("formspec", formspec(fields.numbers, fields.names))
|
||||
elseif fields.help ~= nil then
|
||||
meta:set_string("formspec", formspec_help())
|
||||
elseif fields.exit == "close" then
|
||||
local numbers = meta:get_string("numbers")
|
||||
local names = meta:get_string("names")
|
||||
meta:set_string("formspec", formspec(numbers, names))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("smartline:playerdetector", {
|
||||
description = "SmartLine Player Detector",
|
||||
inventory_image = "smartline_detector_inventory.png",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_detector.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "smartline:playerdetector")
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("number", number)
|
||||
local numbers = meta:get_string("numbers") or ""
|
||||
local names = meta:get_string("names") or ""
|
||||
meta:set_string("formspec", formspec(numbers, names))
|
||||
meta:set_string("infotext", "SmartLine Player Detector "..number)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
end,
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
on_timer = function (pos, elapsed)
|
||||
if scan_for_player(pos) then
|
||||
switch_on(pos)
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_node("smartline:playerdetector_active", {
|
||||
description = "SmartLine Player Detector",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_detector_active.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
on_timer = function (pos, elapsed)
|
||||
if not scan_for_player(pos) then
|
||||
switch_off(pos)
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
drop = "smartline:playerdetector"
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:playerdetector",
|
||||
recipe = {
|
||||
{"", "default:copper_ingot", ""},
|
||||
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
|
||||
{"", "", ""},
|
||||
},
|
||||
})
|
||||
|
||||
tubelib.register_node("smartline:playerdetector", {"smartline:playerdetector_active"}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
if topic == "set_numbers" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("numbers", payload)
|
||||
local names = meta:get_string("names") or ""
|
||||
meta:set_string("formspec", formspec(payload, names))
|
||||
return true
|
||||
elseif topic == "name" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
return meta:get_string("player_name")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
127
smartline/repeater.lua
Normal file
@ -0,0 +1,127 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
repeater.lua:
|
||||
Derived from Tubelib repeater
|
||||
|
||||
]]--
|
||||
|
||||
local OVER_LOAD_MAX = 5
|
||||
|
||||
local function formspec(meta)
|
||||
local numbers = meta:get_string("numbers")
|
||||
return "size[7,5]"..
|
||||
"field[0.5,2;6,1;number;Destination node numbers;"..numbers.."]" ..
|
||||
"button_exit[1,3;2,1;exit;Save]"
|
||||
end
|
||||
|
||||
minetest.register_node("smartline:repeater", {
|
||||
description = "SmartLine Repeater",
|
||||
inventory_image = "smartline_repeater_inventory.png",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_repeater.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_number = tubelib.add_node(pos, "smartline:repeater")
|
||||
meta:set_string("own_number", own_number)
|
||||
meta:set_string("formspec", formspec(meta))
|
||||
meta:set_string("infotext", "SmartLine Repeater "..own_number..": not connected")
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_int("overload_cnt", 0)
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
end,
|
||||
|
||||
on_receive_fields = function(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if owner ~= player:get_player_name() then
|
||||
return
|
||||
end
|
||||
|
||||
if tubelib.check_numbers(fields.number) then
|
||||
meta:set_string("numbers", fields.number)
|
||||
local own_number = meta:get_string("own_number")
|
||||
meta:set_string("infotext", "SmartLine Repeater "..own_number..": connected with "..fields.number)
|
||||
meta:set_string("formspec", formspec(meta))
|
||||
end
|
||||
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
if not timer:is_started() then
|
||||
timer:start(1)
|
||||
end
|
||||
end,
|
||||
|
||||
on_timer = function(pos,elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_int("overload_cnt", 0)
|
||||
return true
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:repeater",
|
||||
recipe = {
|
||||
{"default:copper_ingot", "", "default:copper_ingot"},
|
||||
{"tubelib:wlanchip", "", "tubelib:wlanchip"},
|
||||
{"", "", ""},
|
||||
},
|
||||
})
|
||||
|
||||
tubelib.register_node("smartline:repeater", {}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local overload_cnt = meta:get_int("overload_cnt") + 1
|
||||
meta:set_int("overload_cnt", overload_cnt)
|
||||
if overload_cnt > OVER_LOAD_MAX then
|
||||
local own_number = meta:get_string("own_number")
|
||||
meta:set_string("infotext", "SmartLine Repeater "..own_number..": fault (overloaded)")
|
||||
minetest.get_node_timer(pos):stop()
|
||||
return false
|
||||
elseif topic == "set_numbers" then
|
||||
local own_number = meta:get_string("own_number")
|
||||
meta:set_string("infotext", "SmartLine Repeater "..own_number..": connected with "..payload)
|
||||
meta:set_string("numbers", payload)
|
||||
meta:set_string("formspec", formspec(meta))
|
||||
return true
|
||||
else
|
||||
return tubelib.send_message(numbers, owner, nil, topic, payload)
|
||||
end
|
||||
end,
|
||||
})
|
BIN
smartline/screenshot.png
Normal file
After Width: | Height: | Size: 144 KiB |
281
smartline/sequencer.lua
Normal file
@ -0,0 +1,281 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
sequencer.lua:
|
||||
Derived from Tubelib sequencer
|
||||
|
||||
]]--
|
||||
|
||||
local RUNNING_STATE = 1
|
||||
local STOP_STATE = 0
|
||||
local NUM_SLOTS = 8
|
||||
|
||||
local sHELP = [[label[0,0;SmartLine Sequencer Help
|
||||
|
||||
Define a sequence of commands to control other machines.
|
||||
Numbers(s) are the node numbers, the command shall sent to.
|
||||
The commands 'on'/'off' are used for machines and other nodes.
|
||||
Offset is the time to the next line in seconds (0..999).
|
||||
If endless is set, the Sequencer restarts again and again.
|
||||
The command ' ' does nothing, only consuming the offset time.
|
||||
]
|
||||
]]
|
||||
|
||||
|
||||
local sAction = ",on,off"
|
||||
local kvAction = {[""]=1, ["on"]=2, ["off"]=3}
|
||||
local tAction = {nil, "on", "off"}
|
||||
|
||||
local function formspec(state, rules, endless)
|
||||
endless = endless == 1 and "true" or "false"
|
||||
local tbl = {"size[8,9.2]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"label[0,0;Number(s)]label[2.1,0;Command]label[6.4,0;Offset/s]"}
|
||||
|
||||
for idx, rule in ipairs(rules) do
|
||||
tbl[#tbl+1] = "field[0.2,"..(-0.2+idx)..";2,1;num"..idx..";;"..(rule.num or "").."]"
|
||||
tbl[#tbl+1] = "dropdown[2,"..(-0.4+idx)..";3.9,1;act"..idx..";"..sAction..";"..(rule.act or "").."]"
|
||||
tbl[#tbl+1] = "field[6.2,"..(-0.2+idx)..";2,1;offs"..idx..";;"..(rule.offs or "").."]"
|
||||
end
|
||||
tbl[#tbl+1] = "checkbox[0,8.5;endless;Run endless;"..endless.."]"
|
||||
tbl[#tbl+1] = "button[4.5,8.5;1.5,1;help;help]"
|
||||
tbl[#tbl+1] = "image_button[6.5,8.5;1,1;".. tubelib.state_button(state) ..";button;]"
|
||||
|
||||
return table.concat(tbl)
|
||||
end
|
||||
|
||||
local function formspec_help()
|
||||
return "size[13,10]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;help]"..
|
||||
sHELP..
|
||||
--"label[0.2,0;test]"..
|
||||
"button[11.5,9;1.5,1;close;close]"
|
||||
end
|
||||
|
||||
local function stop_the_sequencer(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", STOP_STATE)
|
||||
meta:set_string("infotext", "Tubelib Sequencer "..number..": stopped")
|
||||
local rules = minetest.deserialize(meta:get_string("rules"))
|
||||
local endless = meta:get_int("endless") or 0
|
||||
meta:set_string("formspec", formspec(tubelib.STOPPED, rules, endless))
|
||||
minetest.get_node_timer(pos):stop()
|
||||
return false
|
||||
end
|
||||
|
||||
local function get_next_slot(idx, rules, endless)
|
||||
idx = idx + 1
|
||||
if idx <= #rules and rules[idx].offs ~= "" and rules[idx].num ~= "" then
|
||||
return idx
|
||||
elseif endless == 1 then
|
||||
return 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function restart_timer(pos, time)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
if timer:is_started() then
|
||||
timer:stop()
|
||||
end
|
||||
if type(time) == "number" then
|
||||
timer:start(time)
|
||||
end
|
||||
end
|
||||
|
||||
local function check_rules(pos, elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local rules = minetest.deserialize(meta:get_string("rules"))
|
||||
if rules then
|
||||
local running = meta:get_int("running")
|
||||
local index = meta:get_int("index") or 1
|
||||
local number = meta:get_string("number")
|
||||
local endless = meta:get_int("endless") or 0
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
while true do -- process all rules as long as offs == 0
|
||||
local rule = rules[index]
|
||||
local offs = rules[index].offs
|
||||
tubelib.send_message(rule.num, placer_name, nil, tAction[rule.act], number)
|
||||
index = get_next_slot(index, rules, endless)
|
||||
if index ~= nil and offs ~= nil and running == 1 then
|
||||
-- after the last rule a pause with 2 or more sec is required
|
||||
if index == 1 and offs < 2 then
|
||||
offs = 2
|
||||
end
|
||||
meta:set_string("infotext", "Tubelib Sequencer "..number..": running ("..index.."/"..NUM_SLOTS..")")
|
||||
meta:set_int("index", index)
|
||||
if offs > 0 then
|
||||
minetest.after(0, restart_timer, pos, offs)
|
||||
return false
|
||||
end
|
||||
else
|
||||
return stop_the_sequencer(pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function start_the_sequencer(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", 1)
|
||||
meta:set_int("index", 1)
|
||||
meta:set_string("infotext", "SmartLine Sequencer "..number..": running (1/"..NUM_SLOTS..")")
|
||||
local rules = minetest.deserialize(meta:get_string("rules"))
|
||||
local endless = meta:get_int("endless") or 0
|
||||
meta:set_string("formspec", formspec(tubelib.RUNNING, rules, endless))
|
||||
minetest.get_node_timer(pos):start(0.1)
|
||||
return false
|
||||
end
|
||||
|
||||
local function on_receive_fields(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running")
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return
|
||||
end
|
||||
if fields.help ~= nil then
|
||||
meta:set_string("formspec", formspec_help())
|
||||
return
|
||||
end
|
||||
|
||||
local endless = meta:get_int("endless") or 0
|
||||
if fields.endless ~= nil then
|
||||
endless = fields.endless == "true" and 1 or 0
|
||||
meta:set_int("index", 1)
|
||||
end
|
||||
meta:set_int("endless", endless)
|
||||
|
||||
local rules = minetest.deserialize(meta:get_string("rules"))
|
||||
if fields.exit ~= nil then
|
||||
meta:set_string("formspec", formspec(tubelib.state(running), rules, endless))
|
||||
return
|
||||
end
|
||||
|
||||
for idx = 1,NUM_SLOTS do
|
||||
if fields["offs"..idx] ~= nil then
|
||||
rules[idx].offs = tonumber(fields["offs"..idx]) or ""
|
||||
end
|
||||
if fields["num"..idx] ~= nil and tubelib.check_numbers(fields["num"..idx]) then
|
||||
rules[idx].num = fields["num"..idx]
|
||||
end
|
||||
if fields["act"..idx] ~= nil then
|
||||
rules[idx].act = kvAction[fields["act"..idx]]
|
||||
end
|
||||
end
|
||||
meta:set_string("rules", minetest.serialize(rules))
|
||||
|
||||
if fields.button ~= nil then
|
||||
if running > STOP_STATE then
|
||||
stop_the_sequencer(pos)
|
||||
else
|
||||
start_the_sequencer(pos)
|
||||
end
|
||||
elseif fields.num1 ~= nil then -- any other change?
|
||||
stop_the_sequencer(pos)
|
||||
else
|
||||
local endless = meta:get_int("endless") or 0
|
||||
meta:set_string("formspec", formspec(tubelib.state(running), rules, endless))
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("smartline:sequencer", {
|
||||
description = "SmartLine Sequencer",
|
||||
inventory_image = "smartline_sequencer_inventory.png",
|
||||
wield_image = "smartline_sequencer_inventory.png",
|
||||
stack_max = 1,
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_sequencer.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = tubelib.add_node(pos, "smartline:sequencer")
|
||||
local rules = {}
|
||||
for idx = 1,NUM_SLOTS do
|
||||
rules[idx] = {offs = "", num = "", act = 1}
|
||||
end
|
||||
meta:set_string("placer_name", placer:get_player_name())
|
||||
meta:set_string("rules", minetest.serialize(rules))
|
||||
meta:set_string("number", number)
|
||||
meta:set_int("index", 1)
|
||||
meta:set_int("endless", 0)
|
||||
meta:get_int("running", STOP_STATE)
|
||||
meta:set_string("formspec", formspec(tubelib.STOPPED, rules, 0))
|
||||
end,
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
on_dig = function(pos, node, puncher, pointed_thing)
|
||||
if minetest.is_protected(pos, puncher:get_player_name()) then
|
||||
return
|
||||
end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running")
|
||||
if running ~= 1 then
|
||||
minetest.node_dig(pos, node, puncher, pointed_thing)
|
||||
tubelib.remove_node(pos)
|
||||
end
|
||||
end,
|
||||
|
||||
on_timer = check_rules,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:sequencer",
|
||||
recipe = {
|
||||
{"", "default:mese_crystal", ""},
|
||||
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
|
||||
{"", "", ""},
|
||||
},
|
||||
})
|
||||
|
||||
tubelib.register_node("smartline:sequencer", {}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "on" then
|
||||
start_the_sequencer(pos)
|
||||
elseif topic == "off" then
|
||||
-- do not stop immediately
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_int("endless", 0)
|
||||
end
|
||||
end,
|
||||
})
|
133
smartline/signaltower.lua
Normal file
@ -0,0 +1,133 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
signaltower.lua:
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
local function switch_on(pos, node, color)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("state", color)
|
||||
node.name = "smartline:signaltower_"..color
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
local function switch_off(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("state", "off")
|
||||
node.name = "smartline:signaltower"
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
minetest.register_node("smartline:signaltower", {
|
||||
description = "SmartLine Signal Tower",
|
||||
tiles = {
|
||||
'smartline_signaltower_top.png',
|
||||
'smartline_signaltower_top.png',
|
||||
'smartline_signaltower.png',
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -5/32, -16/32, -5/32, 5/32, 16/32, 5/32},
|
||||
},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "smartline:signaltower")
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("state", "off")
|
||||
meta:set_string("infotext", "SmartLine Signal Tower "..number)
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_on(pos, node, "green")
|
||||
end
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
light_source = 0,
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_glass_defaults(),
|
||||
})
|
||||
|
||||
for _,color in ipairs({"green", "amber", "red"}) do
|
||||
minetest.register_node("smartline:signaltower_"..color, {
|
||||
description = "SmartLine Signal Tower",
|
||||
tiles = {
|
||||
'smartline_signaltower_top.png',
|
||||
'smartline_signaltower_top.png',
|
||||
'smartline_signaltower_'..color..'.png',
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -5/32, -16/32, -5/32, 5/32, 16/32, 5/32},
|
||||
},
|
||||
},
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
light_source = LIGHT_MAX,
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly=0, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_glass_defaults(),
|
||||
drop = "smartline:signaltower",
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:signaltower",
|
||||
recipe = {
|
||||
{"dye:red", "default:copper_ingot", ""},
|
||||
{"dye:orange", "default:glass", ""},
|
||||
{"dye:green", "tubelib:wlanchip", ""},
|
||||
},
|
||||
})
|
||||
|
||||
tubelib.register_node("smartline:signaltower", {
|
||||
"smartline:signaltower_green",
|
||||
"smartline:signaltower_amber",
|
||||
"smartline:signaltower_red"}, {
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "green" then
|
||||
switch_on(pos, node, "green")
|
||||
elseif topic == "amber" then
|
||||
switch_on(pos, node, "amber")
|
||||
elseif topic == "red" then
|
||||
switch_on(pos, node, "red")
|
||||
elseif topic == "off" then
|
||||
switch_off(pos, node)
|
||||
elseif topic == "state" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
return meta:get_string("state")
|
||||
end
|
||||
end,
|
||||
})
|
BIN
smartline/textures/smartline.png
Normal file
After Width: | Height: | Size: 320 B |
BIN
smartline/textures/smartline_button_inventory.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
smartline/textures/smartline_button_off.png
Normal file
After Width: | Height: | Size: 406 B |
BIN
smartline/textures/smartline_button_on.png
Normal file
After Width: | Height: | Size: 407 B |
BIN
smartline/textures/smartline_controller.png
Normal file
After Width: | Height: | Size: 556 B |
BIN
smartline/textures/smartline_controller_inventory.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
smartline/textures/smartline_detector.png
Normal file
After Width: | Height: | Size: 435 B |
BIN
smartline/textures/smartline_detector_active.png
Normal file
After Width: | Height: | Size: 408 B |
BIN
smartline/textures/smartline_detector_inventory.png
Normal file
After Width: | Height: | Size: 575 B |
BIN
smartline/textures/smartline_display.png
Normal file
After Width: | Height: | Size: 246 B |
BIN
smartline/textures/smartline_display_inventory.png
Normal file
After Width: | Height: | Size: 353 B |
BIN
smartline/textures/smartline_repeater.png
Normal file
After Width: | Height: | Size: 495 B |
BIN
smartline/textures/smartline_repeater_inventory.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
smartline/textures/smartline_sequencer.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
smartline/textures/smartline_sequencer_inventory.png
Normal file
After Width: | Height: | Size: 894 B |
BIN
smartline/textures/smartline_signaltower.png
Normal file
After Width: | Height: | Size: 486 B |
BIN
smartline/textures/smartline_signaltower_amber.png
Normal file
After Width: | Height: | Size: 462 B |
BIN
smartline/textures/smartline_signaltower_green.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
smartline/textures/smartline_signaltower_red.png
Normal file
After Width: | Height: | Size: 467 B |
BIN
smartline/textures/smartline_signaltower_top.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
smartline/textures/smartline_timer.png
Normal file
After Width: | Height: | Size: 502 B |
BIN
smartline/textures/smartline_timer_inventory.png
Normal file
After Width: | Height: | Size: 750 B |
245
smartline/timer.lua
Normal file
@ -0,0 +1,245 @@
|
||||
--[[
|
||||
|
||||
SmartLine
|
||||
=========
|
||||
|
||||
Copyright (C) 2018 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
timer.lua:
|
||||
Derived from Tubelib timer
|
||||
|
||||
]]--
|
||||
|
||||
local CYCLE_TIME = 8
|
||||
|
||||
local sHELP = [[label[0,0;SmartLine Timer Help
|
||||
|
||||
tbd
|
||||
]
|
||||
]]
|
||||
|
||||
|
||||
local tTime = {
|
||||
["00:00"] = 1, ["02:00"] = 2, ["04:00"] = 3,
|
||||
["06:00"] = 4, ["08:00"] = 5, ["10:00"] = 6,
|
||||
["12:00"] = 7, ["14:00"] = 8, ["16:00"] = 9,
|
||||
["18:00"] =10, ["20:00"] =11, ["22:00"] =12,
|
||||
}
|
||||
|
||||
local sTime = "00:00,02:00,04:00,06:00,08:00,10:00,12:00,14:00,16:00,18:00,20:00,22:00"
|
||||
|
||||
local tAction = {
|
||||
[""] = 1,
|
||||
["on"] = 2,
|
||||
["off"] = 3,
|
||||
}
|
||||
|
||||
local sAction = ",on,off"
|
||||
|
||||
local function formspec(events, numbers, actions)
|
||||
return "size[8,8]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
|
||||
"label[0,0;Time]label[2.3,0;Number(s)]label[4.5,0;Command]"..
|
||||
"dropdown[0,1;2,1;e1;"..sTime..";"..events[1].."]"..
|
||||
"field[2.3,1.2;2,1;n1;;"..numbers[1].."]" ..
|
||||
"dropdown[4.5,1;3,1;a1;"..sAction..";"..tAction[actions[1]].."]"..
|
||||
|
||||
"dropdown[0,2;2,1;e2;"..sTime..";"..events[2].."]"..
|
||||
"field[2.3,2.2;2,1;n2;;"..numbers[2].."]" ..
|
||||
"dropdown[4.5,2;3,1;a2;"..sAction..";"..tAction[actions[2]].."]"..
|
||||
|
||||
"dropdown[0,3;2,1;e3;"..sTime..";"..events[3].."]"..
|
||||
"field[2.3,3.2;2,1;n3;;"..numbers[3].."]" ..
|
||||
"dropdown[4.5,3;3,1;a3;"..sAction..";"..tAction[actions[3]].."]"..
|
||||
|
||||
"dropdown[0,4;2,1;e4;"..sTime..";"..events[4].."]"..
|
||||
"field[2.3,4.2;2,1;n4;;"..numbers[4].."]" ..
|
||||
"dropdown[4.5,4;3,1;a4;"..sAction..";"..tAction[actions[4]].."]"..
|
||||
|
||||
"dropdown[0,5;2,1;e5;"..sTime..";"..events[5].."]"..
|
||||
"field[2.3,5.2;2,1;n5;;"..numbers[5].."]" ..
|
||||
"dropdown[4.5,5;3,1;a5;"..sAction..";"..tAction[actions[5]].."]"..
|
||||
|
||||
"dropdown[0,6;2,1;e6;"..sTime..";"..events[6].."]"..
|
||||
"field[2.3,6.2;2,1;n6;;"..numbers[6].."]" ..
|
||||
"dropdown[4.5,6;3,1;a6;"..sAction..";"..tAction[actions[6]].."]"..
|
||||
|
||||
"button[4.5,7;1.5,1;help;help]"..
|
||||
"button_exit[6.5,7;1.5,1;exit;close]"
|
||||
end
|
||||
|
||||
local function formspec_help()
|
||||
return "size[13,10]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"field[0,0;0,0;_type_;;help]"..
|
||||
sHELP..
|
||||
--"label[0.2,0;test]"..
|
||||
"button[11.5,9;1.5,1;close;close]"
|
||||
end
|
||||
|
||||
local function check_rules(pos,elapsed)
|
||||
local hour = math.floor(minetest.get_timeofday() * 24)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local events = minetest.deserialize(meta:get_string("events"))
|
||||
local numbers = minetest.deserialize(meta:get_string("numbers"))
|
||||
local actions = minetest.deserialize(meta:get_string("actions"))
|
||||
local done = minetest.deserialize(meta:get_string("done"))
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
local own_num = meta:get_string("own_num")
|
||||
|
||||
-- check all rules
|
||||
for idx,act in ipairs(actions) do
|
||||
if act ~= "" and numbers[idx] ~= "" then
|
||||
local hr = (events[idx] - 1) * 2
|
||||
if hour == hr and done[idx] == false then
|
||||
tubelib.send_message(numbers[idx], placer_name, nil, act, own_num)
|
||||
done[idx] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- prepare for the next day
|
||||
if hour == 23 then
|
||||
done = {false,false,false,false,false,false}
|
||||
end
|
||||
meta:set_string("done", minetest.serialize(done))
|
||||
meta:set_string("infotext","SmartLine Timer ("..own_num..")"..hour..":00")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
minetest.register_node("smartline:timer", {
|
||||
description = "SmartLine Timer",
|
||||
inventory_image = "smartline_timer_inventory.png",
|
||||
wield_image = "smartline_timer_inventory.png",
|
||||
stack_max = 1,
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png",
|
||||
"smartline.png^smartline_timer.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
|
||||
},
|
||||
},
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local events = {1,1,1,1,1,1}
|
||||
local numbers = {"0000","","","","",""}
|
||||
local actions = {"","","","","",""}
|
||||
local done = {false,false,false,false,false,false}
|
||||
local own_num = tubelib.add_node(pos, "smartline:timer")
|
||||
meta:set_string("own_num", own_num)
|
||||
meta:set_string("placer_name", placer:get_player_name())
|
||||
meta:set_string("events", minetest.serialize(events))
|
||||
meta:set_string("numbers", minetest.serialize(numbers))
|
||||
meta:set_string("actions", minetest.serialize(actions))
|
||||
meta:set_string("done", minetest.serialize(done))
|
||||
meta:set_string("formspec", formspec(events, numbers, actions))
|
||||
minetest.get_node_timer(pos):start(CYCLE_TIME)
|
||||
end,
|
||||
|
||||
on_receive_fields = function(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return
|
||||
end
|
||||
if fields.help ~= nil then
|
||||
meta:set_string("formspec", formspec_help())
|
||||
return
|
||||
end
|
||||
local events = minetest.deserialize(meta:get_string("events"))
|
||||
for idx, evt in ipairs({fields.e1, fields.e2, fields.e3, fields.e4, fields.e5, fields.e6}) do
|
||||
if evt ~= nil then
|
||||
events[idx] = tTime[evt]
|
||||
end
|
||||
end
|
||||
meta:set_string("events", minetest.serialize(events))
|
||||
|
||||
local numbers = minetest.deserialize(meta:get_string("numbers"))
|
||||
for idx, num in ipairs({fields.n1, fields.n2, fields.n3, fields.n4, fields.n5, fields.n6}) do
|
||||
if num ~= nil and tubelib.check_numbers(num)then
|
||||
numbers[idx] = num
|
||||
end
|
||||
end
|
||||
meta:set_string("numbers", minetest.serialize(numbers))
|
||||
|
||||
local actions = minetest.deserialize(meta:get_string("actions"))
|
||||
for idx, act in ipairs({fields.a1, fields.a2, fields.a3, fields.a4, fields.a5, fields.a6}) do
|
||||
if act ~= nil then
|
||||
actions[idx] = act
|
||||
end
|
||||
end
|
||||
meta:set_string("actions", minetest.serialize(actions))
|
||||
|
||||
meta:set_string("formspec", formspec(events, numbers, actions))
|
||||
local done = {false,false,false,false,false,false}
|
||||
meta:set_string("done", minetest.serialize(done))
|
||||
end,
|
||||
|
||||
on_timer = check_rules,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
paramtype2 = "facedir",
|
||||
sunlight_propagates = true,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "smartline:timer",
|
||||
recipe = {
|
||||
{"", "", ""},
|
||||
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
|
||||
{"", "default:mese_crystal", ""},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_lbm({
|
||||
label = "[Tubelib] Timer update",
|
||||
name = "smartline:update",
|
||||
nodenames = {"smartline:timer"},
|
||||
run_at_every_load = false,
|
||||
action = function(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local events = minetest.deserialize(meta:get_string("events"))
|
||||
local numbers = minetest.deserialize(meta:get_string("numbers"))
|
||||
local actions = {}
|
||||
for _,a in ipairs(minetest.deserialize(meta:get_string("actions"))) do
|
||||
if a == "start" then
|
||||
actions[#actions+1] = "on"
|
||||
elseif a == "stop" then
|
||||
actions[#actions+1] = "off"
|
||||
else
|
||||
actions[#actions+1] = a
|
||||
end
|
||||
end
|
||||
meta:set_string("actions", minetest.serialize(actions))
|
||||
meta:set_string("formspec", formspec(events, numbers, actions))
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
|
24
tubelib/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Tube Library
|
||||
|
||||
Minetest Tube Mod for item exchange via lumber tubes and wireless message communication between nodes.
|
||||
|
||||
The focus for this Mod are:
|
||||
- minimized server load and thus minimized server lags
|
||||
- simple and intuitive usage for players
|
||||
- simple API for programmers
|
||||
|
||||
The mod provides:
|
||||
- lumber tubes to connect 2 nodes
|
||||
- a Pusher node to pull/push items through tubes
|
||||
- a Distributor node with 4 output channels to sort incoming items
|
||||
- a Blackhole node which lets all items disappear (example/template for programmers)
|
||||
- a Button/switch node to send "switch on/off" messages
|
||||
- a Lamp node as receiving example for message communication
|
||||
- support for default node: furnace and chests
|
||||
|
||||
Hints for Admins: ![manual.md](https://github.com/joe7575/techpack/blob/master/tubelib/manual.md)
|
||||
Programmers Manual: ![api.md](https://github.com/joe7575/techpack/blob/master/tubelib/api.md)
|
||||
|
||||
## Dependencies
|
||||
default
|
||||
|
288
tubelib/api.md
Normal file
@ -0,0 +1,288 @@
|
||||
# Tubelib Programmers Interface
|
||||
|
||||
|
||||
Tubelib supports:
|
||||
- StackItem exchange via tubes and
|
||||
- wireless data communication between nodes.
|
||||
|
||||
|
||||
|
||||
## 1. StackItem Exchange
|
||||
|
||||
Tubes represent connections between two nodes, so that it is irrelevant
|
||||
if the receiving node is nearby, or far away connected via tubes.
|
||||
The length of the tube is limited to 100 nodes.
|
||||
|
||||
For StackItem exchange we have to distinguish the following roles:
|
||||
- client: An active node calling push/pull functions
|
||||
- server: A passive node typically with inventory, which will be called
|
||||
|
||||
Client and server API use the following special parameters:
|
||||
|
||||
- `side` the contact side where the items shall be pulled out or pushed in.
|
||||
This is one of B(ack), R(ight), F(ront), L(eft), D(own), U(p) according to the
|
||||
following diagram (view onto the placed node). It can be used to separate
|
||||
incoming items for different inventories.
|
||||
|
||||
```
|
||||
Up Back
|
||||
| /
|
||||
| /
|
||||
+--|-----+
|
||||
/ o /|
|
||||
+--------+ |
|
||||
Left ----| |o---- Right
|
||||
| o | |
|
||||
| / | +
|
||||
| / |/
|
||||
+-/------+
|
||||
/ |
|
||||
Front |
|
||||
|
|
||||
Down
|
||||
```
|
||||
|
||||
- `player_name`: Normally the name of the player, who placed the pushing node.
|
||||
But this could also be used to identify the player interacting with the
|
||||
pushing node.
|
||||
|
||||
The use of both parameters on server side is not required. See chap. 4 for an example
|
||||
of an inventory node.
|
||||
|
||||
|
||||
|
||||
## 2. Data communication
|
||||
|
||||
For the data communication an addressing method based on node numbers is used.
|
||||
Each registered node gets a unique node number with 4 figures (or more if needed).
|
||||
The numbers are stored in a storage list. That means, a new node, placed on
|
||||
the same position gets the same node number as the previously placed node on that
|
||||
position.
|
||||
|
||||
The communication supports two services:
|
||||
- `send_message`: Send a message to one or more nodes without response
|
||||
- `send_request`: Send a messages to exactly one node and return the response
|
||||
|
||||
It is up to the programmer, which messages shall be supported.
|
||||
But if a node can be switched on/off or started/stopped, use "on" and "off" as commands
|
||||
for both cases.
|
||||
|
||||
|
||||
|
||||
## 3. API Functions
|
||||
|
||||
|
||||
### Register, Add, Remove Nodes
|
||||
|
||||
Before a node can take part on the item exchange via tubes or data communication,
|
||||
it has to be registered once.
|
||||
|
||||
```LUA
|
||||
tubelib.register_node(name, add_names, node_definition)
|
||||
```
|
||||
Call this function only at load time!
|
||||
Parameters:
|
||||
- name: The node name like "tubelib:pusher"
|
||||
- add_names: A table with additional node names if needed, e.g.: "tubelib:pusher_active"
|
||||
- node_definition: A table with the server callback functions according to:
|
||||
|
||||
```LUA
|
||||
{
|
||||
on_pull_item = func(pos, side, player_name),
|
||||
-- Pull an item from the node inventory.
|
||||
-- The function shall return an item stack with one element
|
||||
-- like ItemStack("default:cobble") or nil.
|
||||
-- Param side: The node contact side, where the item shall be pulled out.
|
||||
-- Param player_name: Can be used to check access rights.
|
||||
|
||||
on_push_item = func(pos, side, item, player_name),
|
||||
-- Push the given item into the node inventory.
|
||||
-- Param side: The node contact side, where the item shall be pushed in.
|
||||
-- Param player_name: Can be used to check access rights.
|
||||
-- The function shall return true if successful, or false if not.
|
||||
|
||||
on_unpull_item = func(pos, side, item, player_name),
|
||||
-- Undo the previous pull and place the given item back into the inventory.
|
||||
-- Param side: The node contact side, where the item shall be unpulled.
|
||||
-- Param player_name: Can be used to check access rights.
|
||||
-- The function shall return true if successful, or false if not.
|
||||
|
||||
on_recv_message = func(pos, topic, payload),
|
||||
-- Execute the requested message
|
||||
-- Param topic: A topic string like "on"
|
||||
-- Param payload: Additional data for more come complex commands,
|
||||
-- payload can be a number, string, or table.
|
||||
-- The function shall return true/false for commands like on/off
|
||||
-- or return the requested data for commands like a "state" request.
|
||||
}
|
||||
```
|
||||
|
||||
**Each node has to call:**
|
||||
|
||||
```LUA
|
||||
tubelib.add_node(pos, name)
|
||||
```
|
||||
`add_node` shall be called when the node is placed.
|
||||
The function is used to register the nodes position for the communication node
|
||||
number and to update the tube surrounding.
|
||||
`pos` the node position, `name` is the node name.
|
||||
|
||||
|
||||
|
||||
```LUA
|
||||
tubelib.remove_node(pos)
|
||||
```
|
||||
'remove_node' shall be called then the node is dig.
|
||||
The function is used to remove the node number from the internal list.
|
||||
|
||||
|
||||
|
||||
### Item Exchange via Tubes
|
||||
|
||||
For item exchange as a pushing/pulling node the following functions exist:
|
||||
|
||||
```LUA
|
||||
tubelib.pull_items(pos, side, player_name)
|
||||
```
|
||||
Pull one item from the given position specified by `pos` and `side`.
|
||||
Parameters:
|
||||
- `pos` is the own node position
|
||||
- `side` is the contact side, where the item shall be pulled in
|
||||
- `player_name` can be used to check access rights.
|
||||
- The function returns an item stack with one element like ItemStack("default:cobble") or nil.
|
||||
|
||||
|
||||
```LUA
|
||||
tubelib.push_items(pos, side, items, player_name)
|
||||
```
|
||||
Push one item to the given position specified by `pos` and `side`.
|
||||
Parameters:
|
||||
- `pos` is the own node position
|
||||
- `side` is the contact side, where the item shall be pushed out
|
||||
- `item` is an item stack with one element like ItemStack("default:cobble")
|
||||
- `player_name` can be used to check access rights.
|
||||
The function returns true if successful, or false if not.
|
||||
|
||||
|
||||
|
||||
```LUA
|
||||
tubelib.unpull_items(pos, side, items, player_name)`
|
||||
```
|
||||
Undo the previous pull and place the item back into the inventory.
|
||||
Parameters:
|
||||
- `pos` is the own node position
|
||||
- `side` id the node contact side, where the item shall be unpulled
|
||||
- `player_name` can be used to check access rights.
|
||||
The function returns true if successful, or false if not.
|
||||
|
||||
|
||||
|
||||
### Wireless Data Communication
|
||||
|
||||
For data communication the following functions exist:
|
||||
|
||||
```LUA
|
||||
tubelib.send_message(numbers, placer_name, clicker_name, topic, payload)
|
||||
```
|
||||
Send a message to all nodes referenced by `numbers`, a string with
|
||||
one or more destination node numbers separated by blanks.
|
||||
The message is based on a topic string (e.g. "start") and
|
||||
a topic related payload.
|
||||
The placer and clicker names are needed to check the protection rights.
|
||||
`placer_name` is the name of the player, who places the node.
|
||||
`clicker_name` is the name of the player, who uses the node.
|
||||
`placer_name` of sending and receiving nodes have to be the same.
|
||||
If every player should be able to send a message, use nil for `clicker_name`.
|
||||
Because several nodes could be addressed, the function don't return any response.
|
||||
|
||||
|
||||
```LUA
|
||||
tubelib.send_request(number, placer_name, clicker_name, topic, payload)
|
||||
```
|
||||
In contrast to `send_message` this functions send a message to exactly one node
|
||||
referenced by `number` and returns the node response.
|
||||
The message is based on the topic string (e.g. "state") and
|
||||
topic related payload.
|
||||
The placer and clicker names are needed to check the protection rights.
|
||||
`placer_name` is the name of the player, who places the node.
|
||||
`clicker_name` is the name of the player, who uses the node.
|
||||
`placer_name` of sending and receiving nodes have to be the same.
|
||||
If every player should be able to send a message, use nil for clicker_name.
|
||||
|
||||
|
||||
|
||||
## 4. Code Snippets
|
||||
|
||||
### Register Node (from 'legacy_nodes.lua')
|
||||
|
||||
```LUA
|
||||
tubelib.register_node("default:chest", {"default:chest_open"}, {
|
||||
on_pull_item = function(pos, side, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.get_item(meta, "main")
|
||||
end,
|
||||
on_push_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end,
|
||||
on_unpull_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Add/remove node (from 'lamp.lua')
|
||||
|
||||
```LUA
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "tubelib:lamp")
|
||||
...
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos)
|
||||
end,
|
||||
```
|
||||
|
||||
### Item exchange via tubes (from 'pusher.lua')
|
||||
|
||||
```LUA
|
||||
local items = tubelib.pull_items(pos, "L", player_name)
|
||||
if items ~= nil then
|
||||
if tubelib.push_items(pos, "R", items, player_name) == false then
|
||||
tubelib.unpull_items(pos, "L", items, player_name)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Message communication (from 'button.lua')
|
||||
|
||||
```LUA
|
||||
local number = meta:get_string("number")
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
tubelib.send_message(number, placer_name, nil, "stop", nil)
|
||||
```
|
||||
|
||||
### 5. Example Code
|
||||
|
||||
Tubelib includes the following example nodes which can be used for study
|
||||
and as templates for own projects:
|
||||
|
||||
- pusher.lua: a simple client pushing/pulling items
|
||||
- blackhole.lua: a simple server client, makes all items disappear
|
||||
- button.lua: a simple communication node, only sending messages
|
||||
- lamp.lua: a simple communication node, only receiving messages
|
||||
|
||||
|
||||
## 6. Further information
|
||||
|
||||
The complete functionality is implemented in the file
|
||||
![command.lua](https://github.com/joe7575/Minetest-Tubelib/blob/master/command.lua).
|
||||
This file has further helper functions and is recommended for deeper study.
|
||||
|
||||
## 7. History
|
||||
|
||||
2017-10-02 First draft
|
||||
2017-10-29 Commands start/stop replaced by on/off
|
96
tubelib/blackhole.lua
Normal file
@ -0,0 +1,96 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
blackhole.lua:
|
||||
|
||||
Simple node which lets all items disappear.
|
||||
The blackhole supports the following message:
|
||||
- topic = "status", payload = nil,
|
||||
response is the number of disappeared items (0..n)
|
||||
]]--
|
||||
|
||||
-- +--------+
|
||||
-- / /|
|
||||
-- +--------+ |
|
||||
-- IN (L) -->| BLACK | |
|
||||
-- | HOLE | +
|
||||
-- | |/
|
||||
-- +--------+
|
||||
|
||||
|
||||
minetest.register_node("tubelib:blackhole", {
|
||||
description = "Tubelib Black Hole",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
'tubelib_front.png',
|
||||
'tubelib_front.png',
|
||||
'tubelib_black_hole.png',
|
||||
'tubelib_black_hole_inp.png',
|
||||
"tubelib_black_hole.png",
|
||||
"tubelib_black_hole.png",
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = tubelib.add_node(pos, "tubelib:blackhole") -- <<=== tubelib
|
||||
meta:set_string("number", number)
|
||||
meta:set_int("disappeared", 0)
|
||||
meta:set_string("infotext","0 items disappeared")
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos) -- <<=== tubelib
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:blackhole 2",
|
||||
recipe = {
|
||||
{"group:wood", "", "group:wood"},
|
||||
{"tubelib:tube1", "default:coal_lump", "default:coal_lump"},
|
||||
{"group:wood", "", "group:wood"},
|
||||
},
|
||||
})
|
||||
|
||||
--------------------------------------------------------------- tubelib
|
||||
tubelib.register_node("tubelib:blackhole", {}, {
|
||||
on_pull_item = nil, -- not needed
|
||||
on_unpull_item = nil, -- not needed
|
||||
|
||||
on_push_item = function(pos, side, item)
|
||||
if side == "L" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
local disappeared = meta:get_int("disappeared") + 1
|
||||
meta:set_int("disappeared", disappeared)
|
||||
meta:set_string("infotext", disappeared.." items disappeared")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "state" then
|
||||
return meta:get_int("disappeared")
|
||||
else
|
||||
return "not supported"
|
||||
end
|
||||
end,
|
||||
})
|
||||
--------------------------------------------------------------- tubelib
|
173
tubelib/button.lua
Normal file
@ -0,0 +1,173 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
button.lua:
|
||||
|
||||
Example of a simple communication node, only sending messages to other nodes.
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
local function switch_on(pos, node)
|
||||
node.name = "tubelib:button_active"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.sound_play("button", {
|
||||
pos = pos,
|
||||
gain = 0.5,
|
||||
max_hear_distance = 5,
|
||||
})
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = meta:get_string("own_num")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local cycle_time = meta:get_int("cycle_time")
|
||||
if cycle_time > 0 then -- button mode?
|
||||
minetest.get_node_timer(pos):start(cycle_time)
|
||||
end
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
local clicker_name = nil
|
||||
if meta:get_string("public") == "false" then
|
||||
clicker_name = meta:get_string("clicker_name")
|
||||
end
|
||||
tubelib.send_message(numbers, placer_name, clicker_name, "on", own_num) -- <<=== tubelib
|
||||
end
|
||||
|
||||
local function switch_off(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
node.name = "tubelib:button"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):stop()
|
||||
minetest.sound_play("button", {
|
||||
pos = pos,
|
||||
gain = 0.5,
|
||||
max_hear_distance = 5,
|
||||
})
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = meta:get_string("own_num")
|
||||
local numbers = meta:get_string("numbers")
|
||||
local placer_name = meta:get_string("placer_name")
|
||||
tubelib.send_message(numbers, placer_name, nil, "off", own_num) -- <<=== tubelib
|
||||
end
|
||||
|
||||
|
||||
minetest.register_node("tubelib:button", {
|
||||
description = "Tubelib Button/Switch",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
'tubelib_front.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
"tubelib_button_off.png",
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local own_num = tubelib.add_node(pos, "tubelib:button")
|
||||
meta:set_string("own_num", own_num)
|
||||
meta:set_string("formspec", "size[5,6]"..
|
||||
"dropdown[0.2,0;3;type;switch,button 2s,button 4s,button 8s,button 16s;1]"..
|
||||
"field[0.5,2;3,1;numbers;Insert destination block number(s);]" ..
|
||||
"checkbox[1,3;public;public;false]"..
|
||||
"button_exit[1,4;2,1;exit;Save]")
|
||||
meta:set_string("placer_name", placer:get_player_name())
|
||||
meta:set_string("public", "false")
|
||||
meta:set_int("cycle_time", 0)
|
||||
meta:set_string("infotext", "Tubelib Button "..own_num)
|
||||
end,
|
||||
|
||||
on_receive_fields = function(pos, formname, fields, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if tubelib.check_numbers(fields.numbers) then -- <<=== tubelib
|
||||
meta:set_string("numbers", fields.numbers)
|
||||
local own_num = meta:get_string("own_num")
|
||||
meta:set_string("infotext", "Tubelib Button "..own_num..", connected with block "..fields.numbers)
|
||||
else
|
||||
return
|
||||
end
|
||||
if fields.public then
|
||||
meta:set_string("public", fields.public)
|
||||
end
|
||||
local cycle_time = nil
|
||||
if fields.type == "switch" then
|
||||
cycle_time = 0
|
||||
elseif fields.type == "button 2s" then
|
||||
cycle_time = 2
|
||||
elseif fields.type == "button 4s" then
|
||||
cycle_time = 4
|
||||
elseif fields.type == "button 8s" then
|
||||
cycle_time = 8
|
||||
elseif fields.type == "button 16s" then
|
||||
cycle_time = 16
|
||||
end
|
||||
if cycle_time ~= nil then
|
||||
meta:set_int("cycle_time", cycle_time)
|
||||
end
|
||||
if fields.exit then
|
||||
meta:set_string("formspec", nil)
|
||||
end
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if meta:get_string("numbers") then
|
||||
meta:set_string("clicker_name", clicker:get_player_name())
|
||||
switch_on(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_node("tubelib:button_active", {
|
||||
description = "Tubelib Button/Switch",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
'tubelib_front.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
'tubelib_button.png',
|
||||
"tubelib_button_on.png",
|
||||
},
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("clicker_name", clicker:get_player_name())
|
||||
if meta:get_int("cycle_time") == nil or meta:get_int("cycle_time") == 0 then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
on_timer = switch_off,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly=0, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:button",
|
||||
recipe = {
|
||||
{"", "group:wood", ""},
|
||||
{"default:glass", "tubelib:wlanchip", ""},
|
||||
{"", "group:wood", ""},
|
||||
},
|
||||
})
|
||||
|
423
tubelib/command.lua
Normal file
@ -0,0 +1,423 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
command.lua:
|
||||
|
||||
See [api.md] for the interface documentation
|
||||
|
||||
]]--
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Data base storage
|
||||
-------------------------------------------------------------------
|
||||
local storage = minetest.get_mod_storage()
|
||||
local NextNumber = minetest.deserialize(storage:get_string("NextNumber")) or 1
|
||||
local Version = minetest.deserialize(storage:get_string("Version")) or 1
|
||||
local Number2Pos = minetest.deserialize(storage:get_string("Number2Pos")) or {}
|
||||
|
||||
local function update_mod_storage()
|
||||
storage:set_string("NextNumber", minetest.serialize(NextNumber))
|
||||
storage:set_string("Version", minetest.serialize(Version))
|
||||
storage:set_string("Number2Pos", minetest.serialize(Number2Pos))
|
||||
storage:set_string("Key2Number", nil) -- not used any more
|
||||
-- store data each hour
|
||||
minetest.after(60*60, update_mod_storage)
|
||||
print("[Tubelib] Data stored")
|
||||
end
|
||||
|
||||
minetest.register_on_shutdown(function()
|
||||
update_mod_storage()
|
||||
end)
|
||||
|
||||
-- store data after one hour
|
||||
minetest.after(60*60, update_mod_storage)
|
||||
|
||||
-- Key2Number will be generated at runtine
|
||||
local Key2Number = {}
|
||||
|
||||
local Name2Name = {} -- translation table
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Local helper functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
-- Localize functions to avoid table lookups (better performance).
|
||||
local string_find = string.find
|
||||
local string_split = string.split
|
||||
local tubelib_NodeDef = tubelib.NodeDef
|
||||
local get_neighbor_pos = tubelib.get_neighbor_pos
|
||||
local read_node_with_vm = tubelib.read_node_with_vm
|
||||
|
||||
-- Translate from facedir to contact side of the other node
|
||||
-- (left for one is right for the other node)
|
||||
local FacedirToSide = {[0] = "F", "L", "B", "R", "U", "D"}
|
||||
|
||||
-- Generate a key string based on the given pos table,
|
||||
-- Used internaly as table key,
|
||||
local function get_key_str(pos)
|
||||
pos = minetest.pos_to_string(pos)
|
||||
return string.sub(pos, 2, -2)
|
||||
end
|
||||
|
||||
-- Determine position related node number for addressing purposes
|
||||
local function get_number(pos)
|
||||
local key = get_key_str(pos)
|
||||
if not Key2Number[key] then
|
||||
Key2Number[key] = NextNumber
|
||||
NextNumber = NextNumber + 1
|
||||
end
|
||||
return string.format("%.04u", Key2Number[key])
|
||||
end
|
||||
|
||||
-- Determine the contact side of the node at the given pos
|
||||
-- param facedir: facedir to the node
|
||||
local function get_node_side(npos, facedir)
|
||||
local node = minetest.get_node_or_nil(npos) or read_node_with_vm(npos)
|
||||
if facedir < 4 then
|
||||
facedir = (facedir - node.param2 + 4) % 4
|
||||
end
|
||||
return FacedirToSide[facedir], node
|
||||
end
|
||||
|
||||
local function generate_Key2Number()
|
||||
local key
|
||||
for num,item in pairs(Number2Pos) do
|
||||
key = get_key_str(item.pos)
|
||||
Key2Number[key] = num
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- API helper functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
-- Check the given list of numbers.
|
||||
-- Returns true if number(s) is/are valid and point to real nodes.
|
||||
function tubelib.check_numbers(numbers)
|
||||
if numbers then
|
||||
for _,num in ipairs(string_split(numbers, " ")) do
|
||||
if Number2Pos[num] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Function returns { pos, name } for the node on the given position number.
|
||||
function tubelib.get_node_info(dest_num)
|
||||
if Number2Pos[dest_num] then
|
||||
return Number2Pos[dest_num]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Function returns the node number from the given position or
|
||||
-- nil, if no node number for this position is assigned.
|
||||
function tubelib.get_node_number(pos)
|
||||
local key = get_key_str(pos)
|
||||
local num = Key2Number[key]
|
||||
if num then
|
||||
num = string.format("%.04u", num)
|
||||
if Number2Pos[num] and Number2Pos[num].name then
|
||||
return num
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Store any node number related, additional data
|
||||
-- param number: node number, returned by tubelib.add_node
|
||||
-- param name: name of the data (string)
|
||||
-- param data: any data (number, string, table)
|
||||
function tubelib.set_data(number, name, data)
|
||||
if Number2Pos[number] and type(name) == "string" then
|
||||
Number2Pos[number]["u_"..name] = data
|
||||
end
|
||||
end
|
||||
|
||||
-- Read node number related data
|
||||
-- param number: node number, returned by tubelib.add_node
|
||||
-- param name: name of the data (string)
|
||||
function tubelib.get_data(number, name)
|
||||
if Number2Pos[number] and type(name) == "string" then
|
||||
return Number2Pos[number]["u_"..name]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Node construction/destruction functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
-- Add node to the tubelib lists and update the tube surrounding.
|
||||
-- Function determines and returns the node position number,
|
||||
-- needed for message communication.
|
||||
function tubelib.add_node(pos, name)
|
||||
-- store position
|
||||
local number = get_number(pos)
|
||||
Number2Pos[number] = {
|
||||
pos = pos,
|
||||
name = name,
|
||||
}
|
||||
-- update surrounding tubes
|
||||
tubelib.update_tubes(pos)
|
||||
return number
|
||||
end
|
||||
|
||||
-- Function removes the node from the tubelib lists.
|
||||
function tubelib.remove_node(pos)
|
||||
local number = get_number(pos)
|
||||
if Number2Pos[number] then
|
||||
Number2Pos[number] = {
|
||||
pos = pos,
|
||||
name = nil,
|
||||
time = minetest.get_day_count() -- used for aging
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Node register function
|
||||
-------------------------------------------------------------------
|
||||
|
||||
-- Register node for tubelib communication
|
||||
-- Call this function only at load time!
|
||||
-- Param name: The node name like "tubelib:pusher"
|
||||
-- Param add_names: Alternativ node names if needded, e.g.: "tubelib:pusher_active"
|
||||
-- Param node_definition: A table according to:
|
||||
-- {
|
||||
-- on_pull_item = func(pos, side, player_name),
|
||||
-- on_push_item = func(pos, side, item, player_name),
|
||||
-- on_unpull_item = func(pos, side, item, player_name),
|
||||
-- on_recv_message = func(pos, topic, payload),
|
||||
-- }
|
||||
function tubelib.register_node(name, add_names, node_definition)
|
||||
tubelib_NodeDef[name] = node_definition
|
||||
-- store facedir table for all known node names
|
||||
tubelib.knownNodes[name] = true
|
||||
Name2Name[name] = name
|
||||
for _,n in ipairs(add_names) do
|
||||
tubelib.knownNodes[n] = true
|
||||
Name2Name[n] = name
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Send message functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
function tubelib.send_message(numbers, placer_name, clicker_name, topic, payload)
|
||||
for _,num in ipairs(string_split(numbers, " ")) do
|
||||
if Number2Pos[num] and Number2Pos[num].name then
|
||||
local data = Number2Pos[num]
|
||||
if placer_name and not minetest.is_protected(data.pos, placer_name) then
|
||||
if clicker_name == nil or not minetest.is_protected(data.pos, clicker_name) then
|
||||
if data and data.name then
|
||||
if tubelib_NodeDef[data.name] and tubelib_NodeDef[data.name].on_recv_message then
|
||||
tubelib_NodeDef[data.name].on_recv_message(data.pos, topic, payload)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tubelib.send_request(number, topic, payload)
|
||||
if Number2Pos[number] and Number2Pos[number].name then
|
||||
local data = Number2Pos[number]
|
||||
if data and data.name then
|
||||
if tubelib_NodeDef[data.name] and tubelib_NodeDef[data.name].on_recv_message then
|
||||
return tubelib_NodeDef[data.name].on_recv_message(data.pos, topic, payload)
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Client side Push/Pull item functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
function tubelib.pull_items(pos, side, player_name)
|
||||
local npos, facedir = get_neighbor_pos(pos, side)
|
||||
local nside, node = get_node_side(npos, facedir)
|
||||
local name = Name2Name[node.name]
|
||||
if tubelib_NodeDef[name] and tubelib_NodeDef[name].on_pull_item then
|
||||
return tubelib_NodeDef[name].on_pull_item(npos, nside, player_name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function tubelib.push_items(pos, side, items, player_name)
|
||||
local npos, facedir = get_neighbor_pos(pos, side)
|
||||
local nside, node = get_node_side(npos, facedir)
|
||||
local name = Name2Name[node.name]
|
||||
if tubelib_NodeDef[name] and tubelib_NodeDef[name].on_push_item then
|
||||
return tubelib_NodeDef[name].on_push_item(npos, nside, items, player_name)
|
||||
elseif node.name == "air" then
|
||||
minetest.add_item(npos, items)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function tubelib.unpull_items(pos, side, items, player_name)
|
||||
local npos, facedir = get_neighbor_pos(pos, side)
|
||||
local nside, node = get_node_side(npos, facedir)
|
||||
local name = Name2Name[node.name]
|
||||
if tubelib_NodeDef[name] and tubelib_NodeDef[name].on_unpull_item then
|
||||
return tubelib_NodeDef[name].on_unpull_item(npos, nside, items, player_name)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Server side helper functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
-- Get one item from the given ItemList. The position within the list
|
||||
-- is incremented each time so that different item stacks will be considered.
|
||||
-- Returns nil if ItemList is empty.
|
||||
function tubelib.get_item(meta, listname)
|
||||
if meta == nil or meta.get_inventory == nil then return nil end
|
||||
local inv = meta:get_inventory()
|
||||
if inv:is_empty(listname) then
|
||||
return nil
|
||||
end
|
||||
local size = inv:get_size(listname)
|
||||
local startpos = meta:get_int("tubelib_startpos") or 0
|
||||
for idx = startpos, startpos+size do
|
||||
idx = (idx % size) + 1
|
||||
local items = inv:get_stack(listname, idx)
|
||||
if items:get_count() > 0 then
|
||||
local taken = items:take_item(1)
|
||||
inv:set_stack(listname, idx, items)
|
||||
meta:set_int("tubelib_startpos", idx)
|
||||
return taken
|
||||
end
|
||||
end
|
||||
meta:set_int("tubelib_startpos", 0)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get one item from the given ItemList, specified by stack number (1..n).
|
||||
-- Returns nil if ItemList is empty.
|
||||
function tubelib.get_this_item(meta, listname, number)
|
||||
if meta == nil or meta.get_inventory == nil then return nil end
|
||||
local inv = meta:get_inventory()
|
||||
if inv:is_empty(listname) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local items = inv:get_stack(listname, number)
|
||||
if items:get_count() > 0 then
|
||||
local taken = items:take_item(1)
|
||||
inv:set_stack(listname, number, items)
|
||||
return taken
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- Put the given item into the given ItemList.
|
||||
-- Function returns false if ItemList is full.
|
||||
function tubelib.put_item(meta, listname, item)
|
||||
if meta == nil or meta.get_inventory == nil then return false end
|
||||
local inv = meta:get_inventory()
|
||||
if inv:room_for_item(listname, item) then
|
||||
inv:add_item(listname, item)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Take the number of items from the given ItemList.
|
||||
-- Returns nil if the requested number is not available.
|
||||
function tubelib.get_num_items(meta, listname, num)
|
||||
if meta == nil or meta.get_inventory == nil then return nil end
|
||||
local inv = meta:get_inventory()
|
||||
if inv:is_empty(listname) then
|
||||
return nil
|
||||
end
|
||||
local size = inv:get_size(listname)
|
||||
for idx = 1, size do
|
||||
local items = inv:get_stack(listname, idx)
|
||||
if items:get_count() >= num then
|
||||
local taken = items:take_item(num)
|
||||
inv:set_stack(listname, idx, items)
|
||||
return taken
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Return "full", "loaded", or "empty" depending
|
||||
-- on the number of fuel stack items.
|
||||
-- Function only works on fuel inventories with one stacks/99 items
|
||||
function tubelib.fuelstate(meta, listname, item)
|
||||
if meta == nil or meta.get_inventory == nil then return nil end
|
||||
local inv = meta:get_inventory()
|
||||
if inv:is_empty(listname) then
|
||||
return "empty"
|
||||
end
|
||||
local list = inv:get_list(listname)
|
||||
if #list == 1 and list[1]:get_count() == 99 then
|
||||
return "full"
|
||||
else
|
||||
return "loaded"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Data Maintenance
|
||||
-------------------------------------------------------------------------------
|
||||
local function data_maintenance()
|
||||
print("[Tubelib] Data maintenance started")
|
||||
if Version == 1 then
|
||||
-- Add day_count for aging of unused positions
|
||||
for num,item in pairs(Number2Pos) do
|
||||
if Number2Pos[num].name == nil then
|
||||
Number2Pos[num].time = minetest.get_day_count()
|
||||
end
|
||||
end
|
||||
Version = 2
|
||||
else
|
||||
-- Remove old unused positions
|
||||
local Tbl = table.copy(Number2Pos)
|
||||
Number2Pos = {}
|
||||
local day_cnt = minetest.get_day_count()
|
||||
for num,item in pairs(Tbl) do
|
||||
if item.name then
|
||||
Number2Pos[num] = item
|
||||
-- data not older than 5 real days
|
||||
elseif item.time and (item.time + 360) > day_cnt then
|
||||
Number2Pos[num] = item
|
||||
else
|
||||
print("Position deleted", num)
|
||||
end
|
||||
end
|
||||
end
|
||||
print("[Tubelib] Data maintenance finished")
|
||||
end
|
||||
|
||||
generate_Key2Number()
|
||||
|
||||
-- maintain data after one minute
|
||||
-- (minetest.get_day_count() will not be valid at start time)
|
||||
minetest.after(60, data_maintenance)
|
||||
|
||||
|
1
tubelib/depends.txt
Normal file
@ -0,0 +1 @@
|
||||
default
|
2
tubelib/description.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Tubes Mod with message communication interface as basis for further technique Mods.
|
||||
|
449
tubelib/distributor.lua
Normal file
@ -0,0 +1,449 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
distributor.lua:
|
||||
|
||||
A more complex node acting as server and client.
|
||||
This node claims a position number and registers its message and items interface.
|
||||
The Distributor supports the following messages:
|
||||
- topic = "on", payload = nil
|
||||
- topic = "off" , payload = nil
|
||||
- topic = "state", payload = nil,
|
||||
response is "running", "stopped", "standby", or "not supported"
|
||||
]]--
|
||||
|
||||
local NUM_FILTER_ELEM = 6
|
||||
local NUM_FILTER_SLOTS = 4
|
||||
local TICKS_TO_SLEEP = 5
|
||||
local CYCLE_TIME = 2
|
||||
local STOP_STATE = 0
|
||||
local STANDBY_STATE = -1
|
||||
local FAULT_STATE = -2
|
||||
|
||||
-- Return a key/value table with all items and the corresponding stack numbers
|
||||
local function invlist_content_as_kvlist(list)
|
||||
local res = {}
|
||||
for idx,items in ipairs(list) do
|
||||
local name = items:get_name()
|
||||
if name ~= "" then
|
||||
res[name] = idx
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Return the total number of list entries
|
||||
local function invlist_num_entries(list)
|
||||
local res = 0
|
||||
for _,items in ipairs(list) do
|
||||
local name = items:get_name()
|
||||
if name ~= "" then
|
||||
res = res + items:get_count()
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Return a flat table with all items
|
||||
local function invlist_entries_as_list(list)
|
||||
local res = {}
|
||||
for _,items in ipairs(list) do
|
||||
local name = items:get_name()
|
||||
local count = items:get_count()
|
||||
if name ~= "" then
|
||||
for i = 1,count do
|
||||
res[#res+1] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function AddToTbl(kvTbl, new_items)
|
||||
for _, l in ipairs(new_items) do
|
||||
kvTbl[l] = true
|
||||
end
|
||||
return kvTbl
|
||||
end
|
||||
|
||||
|
||||
local function distributor_formspec(state, filter)
|
||||
return "size[10,8.5]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"list[context;src;0,0;2,4;]"..
|
||||
"image[2,1.5;1,1;tubelib_gui_arrow.png]"..
|
||||
"image_button[2,3;1,1;".. tubelib.state_button(state) ..";button;]"..
|
||||
"checkbox[3,0;filter1;On;"..dump(filter[1]).."]"..
|
||||
"checkbox[3,1;filter2;On;"..dump(filter[2]).."]"..
|
||||
"checkbox[3,2;filter3;On;"..dump(filter[3]).."]"..
|
||||
"checkbox[3,3;filter4;On;"..dump(filter[4]).."]"..
|
||||
"image[3.6,0;0.3,1;tubelib_red.png]"..
|
||||
"image[3.6,1;0.3,1;tubelib_green.png]"..
|
||||
"image[3.6,2;0.3,1;tubelib_blue.png]"..
|
||||
"image[3.6,3;0.3,1;tubelib_yellow.png]"..
|
||||
"list[context;red;4,0;6,1;]"..
|
||||
"list[context;green;4,1;6,1;]"..
|
||||
"list[context;blue;4,2;6,1;]"..
|
||||
"list[context;yellow;4,3;6,1;]"..
|
||||
"list[current_player;main;1,4.5;8,4;]"..
|
||||
"listring[context;src]"..
|
||||
"listring[current_player;main]"
|
||||
end
|
||||
|
||||
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
local list = inv:get_list(listname)
|
||||
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return 0
|
||||
end
|
||||
if listname == "src" then
|
||||
return stack:get_count()
|
||||
elseif invlist_num_entries(list) < NUM_FILTER_ELEM then
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
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 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 SlotColors = {"red", "green", "blue", "yellow"}
|
||||
local Num2Ascii = {"B", "L", "F", "R"} -- color to side translation
|
||||
local FilterCache = {} -- local cache for filter settings
|
||||
|
||||
local function filter_settings(pos)
|
||||
local hash = minetest.hash_node_position(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
local filter = minetest.deserialize(meta:get_string("filter")) or {false,false,false,false}
|
||||
local kvFilterItemNames = {} -- {<item:name> = true,...}
|
||||
local kvSide2ItemNames = {} -- {"F" = {<item:name>,...},...}
|
||||
|
||||
-- collect all filter settings
|
||||
for idx,slot in ipairs(SlotColors) do
|
||||
local side = Num2Ascii[idx]
|
||||
if filter[idx] == true then
|
||||
local list = inv:get_list(slot)
|
||||
local filter = invlist_entries_as_list(list)
|
||||
AddToTbl(kvFilterItemNames, filter)
|
||||
kvSide2ItemNames[side] = filter
|
||||
end
|
||||
end
|
||||
|
||||
FilterCache[hash] = {
|
||||
kvFilterItemNames = kvFilterItemNames,
|
||||
kvSide2ItemNames = kvSide2ItemNames
|
||||
}
|
||||
end
|
||||
|
||||
local function start_the_machine(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", TICKS_TO_SLEEP)
|
||||
node.name = "tubelib:distributor_active"
|
||||
minetest.swap_node(pos, node)
|
||||
meta:set_string("infotext", "Tubelib Distributor "..number..": running")
|
||||
local filter = minetest.deserialize(meta:get_string("filter"))
|
||||
meta:set_string("formspec", distributor_formspec(tubelib.RUNNING, filter))
|
||||
minetest.get_node_timer(pos):start(CYCLE_TIME)
|
||||
return false
|
||||
end
|
||||
|
||||
local function stop_the_machine(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", STOP_STATE)
|
||||
node.name = "tubelib:distributor"
|
||||
minetest.swap_node(pos, node)
|
||||
meta:set_string("infotext", "Tubelib Distributor "..number..": stopped")
|
||||
local filter = minetest.deserialize(meta:get_string("filter"))
|
||||
meta:set_string("formspec", distributor_formspec(tubelib.STOPPED, filter))
|
||||
minetest.get_node_timer(pos):stop()
|
||||
return false
|
||||
end
|
||||
|
||||
local function goto_sleep(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", STANDBY_STATE)
|
||||
node.name = "tubelib:distributor"
|
||||
minetest.swap_node(pos, node)
|
||||
meta:set_string("infotext", "Tubelib Distributor "..number..": standby")
|
||||
local filter = minetest.deserialize(meta:get_string("filter"))
|
||||
meta:set_string("formspec", distributor_formspec(tubelib.STANDBY, filter))
|
||||
minetest.get_node_timer(pos):start(CYCLE_TIME * TICKS_TO_SLEEP)
|
||||
return false
|
||||
end
|
||||
|
||||
-- move items to the output slots
|
||||
local function keep_running(pos, elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running") - 1
|
||||
local player_name = meta:get_string("player_name")
|
||||
--print("running", running)
|
||||
local slot_idx = meta:get_int("slot_idx") or 1
|
||||
meta:set_int("slot_idx", (slot_idx + 1) % NUM_FILTER_SLOTS)
|
||||
local side = Num2Ascii[slot_idx+1]
|
||||
local listname = SlotColors[slot_idx+1]
|
||||
local inv = meta:get_inventory()
|
||||
local list = inv:get_list("src")
|
||||
local kvSrc = invlist_content_as_kvlist(list)
|
||||
|
||||
-- calculate the filter settings only once
|
||||
local hash = minetest.hash_node_position(pos)
|
||||
if FilterCache[hash] == nil then
|
||||
filter_settings(pos)
|
||||
end
|
||||
|
||||
-- read data from Cache
|
||||
-- all filter items as key/value {<item:name> = true,...}
|
||||
local kvFilterItemNames = FilterCache[hash].kvFilterItemNames
|
||||
-- filter items of one slot as list {<item:name>,...}
|
||||
local names = FilterCache[hash].kvSide2ItemNames[side]
|
||||
|
||||
if names == nil then
|
||||
-- this slot is empty
|
||||
return true
|
||||
end
|
||||
|
||||
local busy = false
|
||||
-- move items from configured filters to the output
|
||||
if next(names) then
|
||||
for _,name in ipairs(names) do
|
||||
if kvSrc[name] then
|
||||
local item = tubelib.get_this_item(meta, "src", kvSrc[name]) -- <<=== tubelib
|
||||
if item then
|
||||
if not tubelib.push_items(pos, side, item, player_name) then -- <<=== tubelib
|
||||
tubelib.put_item(meta, "src", item)
|
||||
else
|
||||
busy = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- move additional items from unconfigured filters to the output
|
||||
if next(names) == nil then
|
||||
for name,_ in pairs(kvSrc) do
|
||||
if kvFilterItemNames[name] == nil then -- not in the filter so far?
|
||||
local item = tubelib.get_this_item(meta, "src", kvSrc[name]) -- <<=== tubelib
|
||||
if item then
|
||||
if not tubelib.push_items(pos, side, item, player_name) then -- <<=== tubelib
|
||||
tubelib.put_item(meta, "src", item)
|
||||
else
|
||||
busy = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if busy == true then
|
||||
if running <= 0 then
|
||||
return start_the_machine(pos)
|
||||
else
|
||||
running = TICKS_TO_SLEEP
|
||||
end
|
||||
else
|
||||
if running <= 0 then
|
||||
return goto_sleep(pos)
|
||||
end
|
||||
end
|
||||
|
||||
meta:set_int("running", running)
|
||||
return true
|
||||
end
|
||||
|
||||
local function on_receive_fields(pos, formname, fields, player)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return
|
||||
end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running")
|
||||
local filter = minetest.deserialize(meta:get_string("filter"))
|
||||
if fields.filter1 ~= nil then
|
||||
filter[1] = fields.filter1 == "true"
|
||||
elseif fields.filter2 ~= nil then
|
||||
filter[2] = fields.filter2 == "true"
|
||||
elseif fields.filter3 ~= nil then
|
||||
filter[3] = fields.filter3 == "true"
|
||||
elseif fields.filter4 ~= nil then
|
||||
filter[4] = fields.filter4 == "true"
|
||||
end
|
||||
meta:set_string("filter", minetest.serialize(filter))
|
||||
|
||||
filter_settings(pos)
|
||||
|
||||
if fields.button ~= nil then
|
||||
if running > STOP_STATE or running == FAULT_STATE then
|
||||
stop_the_machine(pos)
|
||||
else
|
||||
start_the_machine(pos)
|
||||
end
|
||||
else
|
||||
meta:set_string("formspec", distributor_formspec(tubelib.state(running), filter))
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("tubelib:distributor", {
|
||||
description = "Tubelib Distributor",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
'tubelib_distributor.png',
|
||||
'tubelib_distributor.png',
|
||||
'tubelib_distributor_yellow.png',
|
||||
'tubelib_distributor_green.png',
|
||||
"tubelib_distributor_red.png",
|
||||
"tubelib_distributor_blue.png",
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "tubelib:distributor") -- <<=== tubelib
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("player_name", placer:get_player_name())
|
||||
|
||||
local filter = {false,false,false,false}
|
||||
meta:set_string("formspec", distributor_formspec(tubelib.STOPPED, filter))
|
||||
meta:set_string("filter", minetest.serialize(filter))
|
||||
meta:set_string("number", number)
|
||||
meta:set_string("infotext", "Tubelib Distributor "..number..": stopped")
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size('src', 8)
|
||||
inv:set_size('yellow', 6)
|
||||
inv:set_size('green', 6)
|
||||
inv:set_size('red', 6)
|
||||
inv:set_size('blue', 6)
|
||||
end,
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
on_dig = function(pos, node, puncher, pointed_thing)
|
||||
if minetest.is_protected(pos, puncher:get_player_name()) then
|
||||
return
|
||||
end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
if inv:is_empty("src") then
|
||||
minetest.node_dig(pos, node, puncher, pointed_thing)
|
||||
tubelib.remove_node(pos) -- <<=== tubelib
|
||||
end
|
||||
end,
|
||||
|
||||
allow_metadata_inventory_put = allow_metadata_inventory_put,
|
||||
allow_metadata_inventory_take = allow_metadata_inventory_take,
|
||||
allow_metadata_inventory_move = allow_metadata_inventory_move,
|
||||
|
||||
on_timer = keep_running,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_node("tubelib:distributor_active", {
|
||||
description = "Tubelib Distributor",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
{
|
||||
image = "tubelib_distributor_active.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 32,
|
||||
aspect_h = 32,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
'tubelib_distributor.png',
|
||||
'tubelib_distributor_yellow.png',
|
||||
'tubelib_distributor_green.png',
|
||||
"tubelib_distributor_red.png",
|
||||
"tubelib_distributor_blue.png",
|
||||
},
|
||||
|
||||
on_receive_fields = on_receive_fields,
|
||||
|
||||
allow_metadata_inventory_put = allow_metadata_inventory_put,
|
||||
allow_metadata_inventory_take = allow_metadata_inventory_take,
|
||||
allow_metadata_inventory_move = allow_metadata_inventory_move,
|
||||
|
||||
on_timer = keep_running,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly=0, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:distributor 2",
|
||||
recipe = {
|
||||
{"group:wood", "default:steel_ingot", "group:wood"},
|
||||
{"tubelib:tube1", "default:mese_crystal", "tubelib:tube1"},
|
||||
{"group:wood", "default:steel_ingot", "group:wood"},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
--------------------------------------------------------------- tubelib
|
||||
tubelib.register_node("tubelib:distributor", {"tubelib:distributor_active"}, {
|
||||
on_pull_item = function(pos, side)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.get_item(meta, "src")
|
||||
end,
|
||||
on_push_item = function(pos, side, item)
|
||||
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, "src", item)
|
||||
end,
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
if topic == "on" then
|
||||
return start_the_machine(pos)
|
||||
elseif topic == "off" then
|
||||
return stop_the_machine(pos)
|
||||
elseif topic == "state" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running")
|
||||
return tubelib.statestring(running)
|
||||
else
|
||||
return "unsupported"
|
||||
end
|
||||
end,
|
||||
})
|
||||
--------------------------------------------------------------- tubelib
|
95
tubelib/init.lua
Normal file
@ -0,0 +1,95 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
History:
|
||||
2017-09-08 v0.01 first version
|
||||
2017-09-12 v0.02 bugfix in tubelib.get_pos() and others
|
||||
2017-09-21 v0.03 function get_num_items added
|
||||
2017-09-26 v0.04 param side added, node blackhole added
|
||||
2017-10-06 v0.05 Parameter 'player_name' added, furnace fuel detection changed
|
||||
2017-10-08 v0.06 tubelib.get_node_number() added, tubelib.version added
|
||||
2017-10-29 v0.07 Pusher bugfix, commands start/stop replaced by on/off
|
||||
2017-11-02 v0.08 Data base changed, aging of node positions added
|
||||
2017-11-04 v0.09 functions set_data/get_data added
|
||||
2018-01-27 v0.10 WLAN Chip added, recipes reviced, Pusher state 'blocked' added,
|
||||
function send_request changed
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
tubelib = {
|
||||
NodeDef = {}, -- node registration info
|
||||
}
|
||||
|
||||
tubelib.version = 0.10
|
||||
|
||||
|
||||
--------------------------- conversion to v0.04
|
||||
minetest.register_lbm({
|
||||
label = "[Tubelib] Distributor update",
|
||||
name = "tubelib:update",
|
||||
nodenames = {"tubelib:distributor", "tubelib:distributor_active"},
|
||||
run_at_every_load = false,
|
||||
action = function(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if minetest.deserialize(meta:get_string("filter")) == nil then
|
||||
local filter = {false,false,false,false}
|
||||
meta:set_string("filter", minetest.serialize(filter))
|
||||
end
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size('yellow', 6)
|
||||
inv:set_size('green', 6)
|
||||
inv:set_size('red', 6)
|
||||
inv:set_size('blue', 6)
|
||||
end
|
||||
})
|
||||
|
||||
--------------------------- conversion to v0.10
|
||||
minetest.register_lbm({
|
||||
label = "[Tubelib] Button update",
|
||||
name = "tubelib:update2",
|
||||
nodenames = {"tubelib:button", "tubelib:button_active"},
|
||||
run_at_every_load = false,
|
||||
action = function(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
if number ~= "" then
|
||||
meta:set_string("numbers", number)
|
||||
meta:set_string("number", nil)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_craftitem("tubelib:wlanchip", {
|
||||
description = "Tubelib WLAN Chip",
|
||||
inventory_image = "tubelib_wlanchip.png",
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:wlanchip 8",
|
||||
recipe = {
|
||||
{"default:mese_crystal", "default:copper_ingot", ""},
|
||||
{"default:gold_ingot", "default:glass", ""},
|
||||
{"", "", ""},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
dofile(minetest.get_modpath("tubelib") .. "/tubes.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/command.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/states.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/pusher.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/blackhole.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/button.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/lamp.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/distributor.lua")
|
||||
dofile(minetest.get_modpath("tubelib") .. "/legacy_nodes.lua")
|
||||
|
106
tubelib/lamp.lua
Normal file
@ -0,0 +1,106 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
lamp.lua:
|
||||
|
||||
Example of a simple communication node, only receiving messages from others.
|
||||
This node claims a position number and registers its message interface.
|
||||
The Lamp supports the following messages:
|
||||
- topic = "on", payload = nil
|
||||
- topic = "off" , payload = nil
|
||||
|
||||
]]--
|
||||
|
||||
local function switch_on(pos, node)
|
||||
node.name = "tubelib:lamp_on"
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
local function switch_off(pos, node)
|
||||
node.name = "tubelib:lamp"
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
minetest.register_node("tubelib:lamp", {
|
||||
description = "Tubelib Lamp",
|
||||
tiles = {
|
||||
'tubelib_lamp.png',
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local number = tubelib.add_node(pos, "tubelib:lamp") -- <<=== tubelib
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("infotext", "Tubelib Lamp "..number)
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_on(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos) -- <<=== tubelib
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
light_source = 0,
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_node("tubelib:lamp_on", {
|
||||
description = "Tubelib Lamp",
|
||||
tiles = {
|
||||
'tubelib_lamp.png',
|
||||
},
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
paramtype = "light",
|
||||
light_source = LIGHT_MAX,
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly=0, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:lamp 4",
|
||||
recipe = {
|
||||
{"wool:white", "wool:white", "wool:white"},
|
||||
{"tubelib:wlanchip", "default:coal_lump", ""},
|
||||
{"group:wood", "", "group:wood"},
|
||||
},
|
||||
})
|
||||
|
||||
--------------------------------------------------------------- tubelib
|
||||
tubelib.register_node("tubelib:lamp", {"tubelib:lamp_on"}, {
|
||||
on_pull_item = nil, -- lamp has no inventory
|
||||
on_push_item = nil, -- lamp has no inventory
|
||||
on_unpull_item = nil, -- lamp has no inventory
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "on" then
|
||||
switch_on(pos, node)
|
||||
elseif topic == "off" then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
})
|
||||
--------------------------------------------------------------- tubelib
|
107
tubelib/legacy_nodes.lua
Normal file
@ -0,0 +1,107 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
legacy_nodes.lua:
|
||||
|
||||
Tubelib support for chests and furnace
|
||||
|
||||
]]--
|
||||
|
||||
tubelib.register_node("default:chest", {"default:chest_open"}, {
|
||||
on_pull_item = function(pos, side)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.get_item(meta, "main")
|
||||
end,
|
||||
on_push_item = function(pos, side, item)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end,
|
||||
on_unpull_item = function(pos, side, item)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
tubelib.register_node("default:chest_locked", {"default:chest_locked_open"}, {
|
||||
on_pull_item = function(pos, side, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.get_item(meta, "main")
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
on_push_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end
|
||||
return false
|
||||
end,
|
||||
on_unpull_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.put_item(meta, "main", item)
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
})
|
||||
|
||||
tubelib.register_node("default:furnace", {"default:furnace_active"}, {
|
||||
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)
|
||||
local meta = minetest.get_meta(pos)
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
if minetest.get_craft_result({method="fuel", width=1, items={item}}).time ~= 0 then
|
||||
return tubelib.put_item(meta, "fuel", item)
|
||||
else
|
||||
return tubelib.put_item(meta, "src", item)
|
||||
end
|
||||
end,
|
||||
on_unpull_item = function(pos, side, item)
|
||||
local meta = minetest.get_meta(pos)
|
||||
return tubelib.put_item(meta, "dst", item)
|
||||
end,
|
||||
})
|
||||
|
||||
tubelib.register_node("shop:shop", {}, {
|
||||
on_pull_item = function(pos, side, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.get_item(meta, "register")
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
on_push_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.put_item(meta, "stock", item)
|
||||
end
|
||||
return false
|
||||
end,
|
||||
on_unpull_item = function(pos, side, item, player_name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
if player_name == owner or player_name == "" then
|
||||
return tubelib.put_item(meta, "register", item)
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
})
|
||||
|
||||
|
39
tubelib/manual.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Tubelib Library
|
||||
|
||||
## Hints for Admins and Players
|
||||
|
||||
Tubelib is little useful for itself, it makes only sense with extensions such as:
|
||||
- ![tubelib_addons1](https://github.com/joe7575/tubelib_addons1) with farming nodes like Harvester, Quarry, Grinder, Autocrafter, Fermenter and Reformer.
|
||||
- ![tubelib_addons2](https://github.com/joe7575/tubelib_addons2) with control task nodes like Timer, Sequencer, Repeater, Gate, Door, Access Lock and Color Lamp.
|
||||
|
||||
However Tubelib provides the following basic nodes:
|
||||
|
||||
### Tubes
|
||||
Tubes allow the item exchange between two nodes. Tube forks are not possible. You have to use chests
|
||||
or other inventory nodes as hubs to build more complex structures.
|
||||
Tubes for itself are passive. For item exchange you have to use pulling/pushing nodes in addition.
|
||||
The maximum tube length is limited to 100 nodes.
|
||||
|
||||
### Pusher
|
||||
The Pusher is able to pull one item out of one inventory node and pushing it into another inventory node directly or by means of tubes.
|
||||
It the source node is empty or the destination node full, the Pusher goes into STANDBY state for some seconds.
|
||||
|
||||
### Distributor
|
||||
The Distributor works as filter and pusher. It allows to divide and distribute incoming items into 4 tube channels.
|
||||
The channels can be switched on/off and individually configured with up to 6 items. The filter passes the configured
|
||||
items and restrains all others. To increase the throughput, one item can be added several times to a filter.
|
||||
An unconfigured but activated filter allows to pass up to 6 remaining items.
|
||||
|
||||
### Button/Switch
|
||||
The Button/Switch is a simple communication node for the Tubelib wireless communication.
|
||||
This node can be configured as button and switch. For the button configuration different switching
|
||||
times from 2 to 16 seconds are possible. The Button/Switch node has to be configured with the destination
|
||||
number of the receiving node (e.g. Lamp). This node allows to address several receivers by means or their numbers.
|
||||
All numbers of the receiving nodes have to be added a configuration time.
|
||||
|
||||
### Lamp
|
||||
The Lamp is a receiving node, showing its destination/communication number via "infotext".
|
||||
The Lamp can be switched on/off by means of the right mouse button (use function) or by means of messages commands
|
||||
from a Button/Switch or any other command sending node.
|
||||
|
||||
|
1
tubelib/mod.conf
Normal file
@ -0,0 +1 @@
|
||||
name=tubelib
|
243
tubelib/pusher.lua
Normal file
@ -0,0 +1,243 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
pusher.lua:
|
||||
|
||||
Simple node for push/pull operation of StackItems from chests or other
|
||||
inventory/server nodes to tubes or other inventory/server nodes.
|
||||
|
||||
The Pusher supports the following messages:
|
||||
- topic = "on", payload = nil
|
||||
- topic = "off", payload = nil
|
||||
- topic = "state", payload = nil,
|
||||
response is "running", "stopped", "standby", "blocked", or "not supported"
|
||||
|
||||
]]--
|
||||
|
||||
-- +--------+
|
||||
-- / /|
|
||||
-- +--------+ |
|
||||
-- IN (L) -->| |x--> OUT (R)
|
||||
-- | PUSHER | +
|
||||
-- | |/
|
||||
-- +--------+
|
||||
|
||||
local RUNNING_STATE = 10
|
||||
|
||||
local function switch_on(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", RUNNING_STATE)
|
||||
meta:set_string("infotext", "Pusher "..number..": running")
|
||||
node.name = "tubelib:pusher_active"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):start(2)
|
||||
return false
|
||||
end
|
||||
|
||||
local function switch_off(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", tubelib.STATE_STOPPED)
|
||||
meta:set_string("infotext", "Pusher "..number..": stopped")
|
||||
node.name = "tubelib:pusher"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):stop()
|
||||
return false
|
||||
end
|
||||
|
||||
local function goto_standby(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", tubelib.STATE_STANDBY)
|
||||
meta:set_string("infotext", "Pusher "..number..": standby")
|
||||
node.name = "tubelib:pusher"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):start(20)
|
||||
return false
|
||||
end
|
||||
|
||||
local function goto_blocked(pos, node)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
meta:set_int("running", tubelib.STATE_BLOCKED)
|
||||
meta:set_string("infotext", "Pusher "..number..": blocked")
|
||||
node.name = "tubelib:pusher"
|
||||
minetest.swap_node(pos, node)
|
||||
minetest.get_node_timer(pos):start(20)
|
||||
return false
|
||||
end
|
||||
|
||||
local function keep_running(pos, elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local number = meta:get_string("number")
|
||||
local running = meta:get_int("running") - 1
|
||||
local player_name = meta:get_string("player_name")
|
||||
local items = tubelib.pull_items(pos, "L", player_name) -- <<=== tubelib
|
||||
if items ~= nil then
|
||||
if tubelib.push_items(pos, "R", items, player_name) == false then -- <<=== tubelib
|
||||
-- place item back
|
||||
tubelib.unpull_items(pos, "L", items, player_name) -- <<=== tubelib
|
||||
local node = minetest.get_node(pos)
|
||||
return goto_blocked(pos, node)
|
||||
end
|
||||
if running <= 0 then
|
||||
local node = minetest.get_node(pos)
|
||||
return switch_on(pos, node)
|
||||
else
|
||||
-- reload running state
|
||||
running = RUNNING_STATE
|
||||
end
|
||||
else
|
||||
if running <= 0 then
|
||||
local node = minetest.get_node(pos)
|
||||
return goto_standby(pos, node)
|
||||
end
|
||||
end
|
||||
meta:set_int("running", running)
|
||||
return true
|
||||
end
|
||||
|
||||
minetest.register_node("tubelib:pusher", {
|
||||
description = "Tubelib Pusher",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
'tubelib_pusher1.png',
|
||||
'tubelib_pusher1.png',
|
||||
'tubelib_outp.png',
|
||||
'tubelib_inp.png',
|
||||
"tubelib_pusher1.png^[transformR180]",
|
||||
"tubelib_pusher1.png",
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("player_name", placer:get_player_name())
|
||||
local number = tubelib.add_node(pos, "tubelib:pusher") -- <<=== tubelib
|
||||
meta:set_string("number", number)
|
||||
meta:set_string("infotext", "Pusher "..number..": stopped")
|
||||
end,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_on(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos)
|
||||
tubelib.remove_node(pos) -- <<=== tubelib
|
||||
end,
|
||||
|
||||
on_timer = keep_running,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_node("tubelib:pusher_active", {
|
||||
description = "Tubelib Pusher",
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
{
|
||||
image = "tubelib_pusher.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 32,
|
||||
aspect_h = 32,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
image = "tubelib_pusher.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 32,
|
||||
aspect_h = 32,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
'tubelib_outp.png',
|
||||
'tubelib_inp.png',
|
||||
{
|
||||
image = "tubelib_pusher.png^[transformR180]",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 32,
|
||||
aspect_h = 32,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
image = "tubelib_pusher.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 32,
|
||||
aspect_h = 32,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not minetest.is_protected(pos, clicker:get_player_name()) then
|
||||
switch_off(pos, node)
|
||||
end
|
||||
end,
|
||||
|
||||
on_timer = keep_running,
|
||||
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly=0, not_in_creative_inventory=1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "tubelib:pusher 2",
|
||||
recipe = {
|
||||
{"group:wood", "wool:dark_green", "group:wood"},
|
||||
{"tubelib:tube1", "default:mese_crystal", "tubelib:tube1"},
|
||||
{"group:wood", "wool:dark_green", "group:wood"},
|
||||
},
|
||||
})
|
||||
|
||||
--------------------------------------------------------------- tubelib
|
||||
tubelib.register_node("tubelib:pusher", {"tubelib:pusher_active"}, {
|
||||
on_pull_item = nil, -- pusher has no inventory
|
||||
on_push_item = nil, -- pusher has no inventory
|
||||
on_unpull_item = nil, -- pusher has no inventory
|
||||
|
||||
on_recv_message = function(pos, topic, payload)
|
||||
local node = minetest.get_node(pos)
|
||||
if topic == "on" then
|
||||
return switch_on(pos, node)
|
||||
elseif topic == "off" then
|
||||
return switch_off(pos, node)
|
||||
elseif topic == "state" then
|
||||
local meta = minetest.get_meta(pos)
|
||||
local running = meta:get_int("running") or tubelib.STATE_STOPPED
|
||||
return tubelib.statestring(running)
|
||||
else
|
||||
return "not supported"
|
||||
end
|
||||
end,
|
||||
})
|
||||
--------------------------------------------------------------- tubelib
|
BIN
tubelib/sounds/button.ogg
Normal file
80
tubelib/states.lua
Normal file
@ -0,0 +1,80 @@
|
||||
--[[
|
||||
|
||||
Tube Library
|
||||
============
|
||||
|
||||
Copyright (C) 2017 Joachim Stolberg
|
||||
|
||||
LGPLv2.1+
|
||||
See LICENSE.txt for more information
|
||||
|
||||
states.lua:
|
||||
|
||||
A state model for tubelib nodes.
|
||||
|
||||
]]--
|
||||
|
||||
--
|
||||
-- Inventory Button States
|
||||
--
|
||||
|
||||
tubelib.STOPPED = 1 -- not operational
|
||||
tubelib.RUNNING = 2 -- in normal operation
|
||||
tubelib.STANDBY = 3 -- nothing to do or blocked anyhow
|
||||
tubelib.FAULT = 4 -- any fault state, which has to be fixed by the player
|
||||
|
||||
tubelib.StatesImg = {
|
||||
"tubelib_inv_button_off.png",
|
||||
"tubelib_inv_button_on.png",
|
||||
"tubelib_inv_button_standby.png",
|
||||
"tubelib_inv_button_error.png"
|
||||
}
|
||||
|
||||
-- Return state button image for the node inventory
|
||||
function tubelib.state_button(state)
|
||||
if state and state < 5 and state > 0 then
|
||||
return tubelib.StatesImg[state]
|
||||
end
|
||||
return tubelib.StatesImg[tubelib.FAULT]
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- 'running' variable States
|
||||
--
|
||||
|
||||
tubelib.STATE_RUNNING = 1 -- in normal operation
|
||||
tubelib.STATE_STOPPED = 0 -- not operational
|
||||
tubelib.STATE_STANDBY = -1 -- nothing to do
|
||||
tubelib.STATE_BLOCKED = -2 -- pushing node is blocked due to a full inventory
|
||||
tubelib.STATE_FAULT = -3 -- any fault state, which has to be fixed by the player
|
||||
|
||||
-- Return machine state based on the running counter
|
||||
function tubelib.state(running)
|
||||
if running > 0 then
|
||||
return tubelib.RUNNING
|
||||
elseif running == 0 then
|
||||
return tubelib.STOPPED
|
||||
elseif running == -1 then
|
||||
return tubelib.STANDBY
|
||||
elseif running == -2 then
|
||||
return tubelib.BLOCKED
|
||||
else
|
||||
return tubelib.FAULT
|
||||
end
|
||||
end
|
||||
|
||||
-- Return state string based on the running counter
|
||||
function tubelib.statestring(running)
|
||||
if running > 0 then
|
||||
return "running"
|
||||
elseif running == 0 then
|
||||
return "stopped"
|
||||
elseif running == -1 then
|
||||
return "standby"
|
||||
elseif running == -2 then
|
||||
return "blocked"
|
||||
else
|
||||
return "fault"
|
||||
end
|
||||
end
|
BIN
tubelib/textures/tubelib_black_hole.png
Normal file
After Width: | Height: | Size: 821 B |
BIN
tubelib/textures/tubelib_black_hole_inp.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tubelib/textures/tubelib_blue.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
tubelib/textures/tubelib_button.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
tubelib/textures/tubelib_button_off.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tubelib/textures/tubelib_button_on.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tubelib/textures/tubelib_distributor.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tubelib/textures/tubelib_distributor_active.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
tubelib/textures/tubelib_distributor_blue.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_distributor_green.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_distributor_red.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_distributor_yellow.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_front.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_green.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
tubelib/textures/tubelib_gui_arrow.png
Normal file
After Width: | Height: | Size: 214 B |
BIN
tubelib/textures/tubelib_hole.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
tubelib/textures/tubelib_hole2.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
tubelib/textures/tubelib_inp.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_inv_button_error.png
Normal file
After Width: | Height: | Size: 898 B |
BIN
tubelib/textures/tubelib_inv_button_off.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tubelib/textures/tubelib_inv_button_on.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tubelib/textures/tubelib_inv_button_standby.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
tubelib/textures/tubelib_knee.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tubelib/textures/tubelib_knee2.png
Normal file
After Width: | Height: | Size: 853 B |
BIN
tubelib/textures/tubelib_lamp.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
tubelib/textures/tubelib_outp.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
tubelib/textures/tubelib_pusher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
tubelib/textures/tubelib_pusher1.png
Normal file
After Width: | Height: | Size: 444 B |
BIN
tubelib/textures/tubelib_red.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
tubelib/textures/tubelib_tube.png
Normal file
After Width: | Height: | Size: 969 B |