Initial commit

This commit is contained in:
Joachim Stolberg 2018-03-10 22:14:57 +01:00
parent bc4fce2585
commit 31cf34f063
166 changed files with 11112 additions and 0 deletions

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?

@ -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

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

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?

@ -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

@ -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

Binary file not shown.

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,
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Some files were not shown because too many files have changed in this diff Show More