2019-02-27 14:52:34 +01:00
|
|
|
--[[
|
|
|
|
|
|
|
|
=======================================================================
|
|
|
|
SmartLine Modules Mod
|
|
|
|
by Micu (c) 2018, 2019
|
|
|
|
|
|
|
|
Crops Watcher
|
|
|
|
|
|
|
|
Advanced optical device to assist in crop farming automation.
|
|
|
|
It scans rectangular area of selected radius for crops (wheat, tomatoes
|
|
|
|
etc) and signals if field is ready for harvest. Device recognizes all
|
|
|
|
registered farming nodes. Crop Watcher sees plants at its level and
|
|
|
|
down to 2 levels below node (0, -1, -2), with exception of nodes
|
|
|
|
directly under its box.
|
|
|
|
Configuration options are accessible via panel: ID numbers to send
|
|
|
|
messages to, scan radius (1-16) and minimum number of crops for area
|
|
|
|
to be considered a valid crop field (0 to maximum field area).
|
|
|
|
When Tubelib ID numbers are entered, scan can be initiated by sending
|
|
|
|
"on" message to Watcher (by Timer, Button etc) - if field is ready to
|
|
|
|
harvest, device immediately responses with "on" command sent to
|
|
|
|
specified IDs (for example Tubelib Harvester). No messages are sent for
|
|
|
|
other states - Crops Watcher never sends "off" commands to not interfere
|
|
|
|
with machinery automation.
|
|
|
|
Event-based node - it does not use node timer.
|
|
|
|
|
|
|
|
Placement: place in the center of the field, up to 2 nodes above ground
|
|
|
|
level.
|
|
|
|
|
|
|
|
Status:
|
|
|
|
- "error" - there are no crops in the area or they fall below defined
|
|
|
|
minimum
|
|
|
|
- "growing" - there are enough crops in the area, some of them are still
|
|
|
|
growing
|
|
|
|
- "ready" - there are enough crops in the area and all are ready for
|
|
|
|
harvest
|
|
|
|
|
|
|
|
Events (optional):
|
|
|
|
- "on" - sent when device received "on" message, scanned area and
|
|
|
|
result is "ready"
|
|
|
|
|
|
|
|
Supported SaferLua functions:
|
|
|
|
- $get_status(...)
|
2019-03-03 22:07:39 +01:00
|
|
|
- $get_crops_status(...)
|
2019-02-27 14:52:34 +01:00
|
|
|
|
|
|
|
Punch node to see current status and crop numbers.
|
|
|
|
|
|
|
|
License: LGPLv2.1+
|
|
|
|
=======================================================================
|
|
|
|
|
|
|
|
]]--
|
|
|
|
|
|
|
|
|
|
|
|
--[[
|
|
|
|
---------------
|
|
|
|
Local variables
|
|
|
|
---------------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
-- range constants
|
|
|
|
local RADIUS_MIN = 1
|
|
|
|
local RADIUS_MAX = 16
|
|
|
|
local LEVEL_TOP = 0
|
|
|
|
local LEVEL_BOTTOM = -2
|
|
|
|
|
|
|
|
-- states
|
|
|
|
local CW_ERROR = 1
|
|
|
|
local CW_GROWING = 2
|
|
|
|
local CW_READY = 3
|
|
|
|
local cwstate = {
|
|
|
|
[CW_ERROR] = "error",
|
|
|
|
[CW_GROWING] = "growing",
|
|
|
|
[CW_READY] = "ready"
|
|
|
|
}
|
|
|
|
|
|
|
|
-- tables with farming crop nodes
|
|
|
|
local cropnames = {}
|
|
|
|
local isgrowncrop = {}
|
|
|
|
for _, c in pairs(farming.registered_plants) do
|
|
|
|
for i = 1, c.steps do
|
|
|
|
cropnames[#cropnames + 1] = c.crop .. "_" .. i
|
|
|
|
end
|
|
|
|
isgrowncrop[c.crop .. "_" .. c.steps] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
-------
|
|
|
|
Helpers
|
|
|
|
-------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
-- get status for crops area for watcher at pos
|
|
|
|
-- return 1: CW_ERROR for not enough growable crops,
|
|
|
|
-- CW_GROWING for crops in growing state,
|
|
|
|
-- CW_READY if all crops are ready for harvest
|
|
|
|
-- return 2: total number of crops in area
|
|
|
|
-- return 3: grown crops in area
|
|
|
|
local function get_cropswatcher_area_state(pos)
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
|
|
local radius = meta:get_int("radius") or 8
|
|
|
|
local cropsmin = meta:get_int("crops_min") or 0
|
|
|
|
local _, crops = minetest.find_nodes_in_area(
|
|
|
|
vector.add(pos, { x = -radius, y = LEVEL_BOTTOM, z = -radius }),
|
|
|
|
vector.add(pos, { x = radius, y = LEVEL_TOP, z = radius }),
|
|
|
|
cropnames)
|
|
|
|
local _, subcrops = minetest.find_nodes_in_area(
|
|
|
|
vector.add(pos, { x = 0, y = LEVEL_BOTTOM, z = 0 }),
|
|
|
|
vector.add(pos, { x = 0, y = LEVEL_TOP, z = 0 }),
|
|
|
|
cropnames)
|
|
|
|
local crnum = 0
|
|
|
|
local grown = 0
|
|
|
|
for c, n in pairs(crops) do
|
|
|
|
crnum = crnum + n
|
|
|
|
if isgrowncrop[c] then
|
|
|
|
grown = grown + n
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- do not count nodes at the same (x, z) coordinates
|
|
|
|
for c, n in pairs(subcrops) do
|
|
|
|
crnum = crnum - n
|
|
|
|
if isgrowncrop[c] then
|
|
|
|
grown = grown - n
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if (cropsmin == 0 and crnum == 0) or
|
|
|
|
(cropsmin > 0 and crnum < cropsmin) then
|
|
|
|
return CW_ERROR, crnum, grown
|
|
|
|
elseif grown == crnum then
|
|
|
|
return CW_READY, crnum, grown
|
|
|
|
end
|
|
|
|
return CW_GROWING, crnum, grown
|
|
|
|
end
|
|
|
|
|
|
|
|
-- calculate max number of crops in area:
|
|
|
|
-- note: (2*r+1)^2-1 = 4*r*(r+1)
|
|
|
|
local function get_max_crops(radius)
|
|
|
|
return 4 * radius * (radius + 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
--------
|
|
|
|
Formspec
|
|
|
|
--------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
-- configuration formspec
|
|
|
|
local function formspec(meta, opt_radius, opt_cropsmin)
|
|
|
|
local number = meta:get_string("own_num")
|
|
|
|
local radius = opt_radius or meta:get_int("radius") or 8
|
|
|
|
local cropsmin = opt_cropsmin or meta:get_int("crops_min") or 0
|
|
|
|
local cropsmax = get_max_crops(radius)
|
|
|
|
cropsmin = math.min(cropsmin, cropsmax)
|
|
|
|
local radiuslist = ""
|
|
|
|
for i = RADIUS_MIN, RADIUS_MAX do
|
|
|
|
radiuslist = radiuslist .. i
|
|
|
|
if i < RADIUS_MAX then
|
|
|
|
radiuslist = radiuslist .. ","
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return "size[8.4,3.6]" ..
|
|
|
|
"label[3,0;" .. minetest.colorize("#FFFF00", "Crops Watcher ") ..
|
|
|
|
minetest.colorize("#00FFFF", number) .. "]" ..
|
|
|
|
"label[0,1;Enter destination number(s) (optional)]" ..
|
|
|
|
"field[4,1.1;4.5,1;numbers;;${numbers}]" ..
|
|
|
|
"field_close_on_enter[numbers;true]" ..
|
|
|
|
"label[0,2;Select radius]" ..
|
|
|
|
"dropdown[1.5,1.85;0.75;radius;" .. radiuslist .. ";" .. radius .. "]" ..
|
|
|
|
"label[2.8,2;Minimal number of crops in area (0 - " .. cropsmax .. ")]" ..
|
|
|
|
"field[7.2,2.1;1.3,1;cropsmin;;" .. cropsmin .. "]" ..
|
|
|
|
"field_close_on_enter[cropsmin;true]" ..
|
|
|
|
"button_exit[5.2,2.8;1.5,1;ok;OK]" ..
|
|
|
|
"button_exit[6.8,2.8;1.5,1;cancel;Cancel]"
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
-----------------
|
|
|
|
Node registration
|
|
|
|
-----------------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
minetest.register_node("slmodules:cropswatcher", {
|
|
|
|
description = "SmartLine Crops Watcher",
|
|
|
|
tiles = {
|
|
|
|
-- up, down, right, left, back, front
|
|
|
|
"slmodules_default_plate.png",
|
|
|
|
"slmodules_default_plate.png",
|
|
|
|
"slmodules_cropswatcher_side.png",
|
|
|
|
"slmodules_cropswatcher_side.png",
|
|
|
|
"slmodules_cropswatcher_side.png",
|
|
|
|
"slmodules_cropswatcher_side.png",
|
|
|
|
},
|
|
|
|
|
|
|
|
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
|
|
local number = tubelib.add_node(pos, "slmodules:cropswatcher")
|
|
|
|
meta:set_string("own_num", number)
|
|
|
|
meta:set_string("infotext", "Crops Watcher " .. number)
|
|
|
|
meta:set_string("owner", placer:get_player_name())
|
|
|
|
meta:set_string("numbers", "")
|
|
|
|
meta:set_int("radius", 8)
|
|
|
|
meta:set_int("crops_min", 0)
|
|
|
|
meta:set_string("formspec", formspec(meta))
|
|
|
|
end,
|
|
|
|
|
|
|
|
can_dig = function(pos, player)
|
|
|
|
if minetest.is_protected(pos, player:get_player_name()) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end,
|
|
|
|
|
|
|
|
after_dig_node = function(pos, oldnode, oldmetadata, digger)
|
|
|
|
tubelib.remove_node(pos)
|
|
|
|
end,
|
|
|
|
|
|
|
|
on_punch = function(pos, node, puncher, pointed_thing)
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
|
|
local player_name = puncher:get_player_name()
|
|
|
|
if meta:get_string("owner") ~= player_name then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local sn, cn, gn = get_cropswatcher_area_state(pos)
|
|
|
|
local msgclr = { ["error"] = "#FFBFBF",
|
|
|
|
["growing"] = "#FFFFBF",
|
|
|
|
["ready"] = "#BFFFBF" }
|
|
|
|
minetest.chat_send_player(player_name,
|
|
|
|
minetest.colorize("#FFFF00", "[CropsWatcher:" ..
|
|
|
|
meta:get_string("own_num") .. "]") .. " Status is " ..
|
|
|
|
minetest.colorize(msgclr[cwstate[sn]], "\"" ..
|
|
|
|
cwstate[sn] .. "\"") ..
|
|
|
|
", total crops: " .. cn .. ", fully grown: " .. gn)
|
|
|
|
return true
|
|
|
|
end,
|
|
|
|
|
|
|
|
on_receive_fields = function(pos, formname, fields, sender)
|
|
|
|
if minetest.is_protected(pos, sender:get_player_name()) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
|
|
if fields.ok or fields.key_enter_field == "numbers" or
|
|
|
|
fields.key_enter_field == "cropsmin" then
|
|
|
|
local number = meta:get_string("own_num")
|
|
|
|
local numbers = fields.numbers and fields.numbers:trim() or ""
|
|
|
|
local cropsmin = tonumber(fields.cropsmin and fields.cropsmin:trim() or "0") or 0
|
|
|
|
local radius = tonumber(fields.radius)
|
|
|
|
cropsmin = math.max(math.min(cropsmin, get_max_crops(radius)), 0)
|
|
|
|
meta:set_string("numbers", numbers)
|
|
|
|
meta:set_int("radius", radius)
|
|
|
|
meta:set_int("crops_min", cropsmin)
|
|
|
|
meta:set_string("infotext", "Crops Watcher " ..
|
|
|
|
number .. (numbers ~= "" and
|
|
|
|
" (connected with: " .. numbers .. ")" or ""))
|
|
|
|
meta:set_string("formspec", formspec(meta))
|
|
|
|
elseif fields.radius and not fields.quit then
|
|
|
|
local radius = tonumber(fields.radius)
|
|
|
|
meta:set_string("formspec", formspec(meta, radius,
|
|
|
|
math.max(math.min(fields.cropsmin, get_max_crops(radius)), 0)))
|
|
|
|
elseif fields.cancel or fields.quit then
|
|
|
|
meta:set_string("formspec", formspec(meta))
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
|
|
|
|
on_rotate = screwdriver.disallow,
|
|
|
|
paramtype = "light",
|
|
|
|
sunlight_propagates = true,
|
|
|
|
paramtype2 = "facedir",
|
|
|
|
groups = { choppy = 2, cracky = 2, crumbly = 2 },
|
|
|
|
is_ground_content = false,
|
|
|
|
sounds = default.node_sound_metal_defaults(),
|
|
|
|
})
|
|
|
|
|
|
|
|
tubelib.register_node("slmodules:cropswatcher", {}, {
|
|
|
|
on_recv_message = function(pos, topic, payload)
|
|
|
|
if topic == "on" then
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
|
|
local owner = meta:get_string("owner")
|
|
|
|
local number = meta:get_string("own_num")
|
|
|
|
local numbers = meta:get_string("numbers") or ""
|
|
|
|
if numbers ~= "" and get_cropswatcher_area_state(pos) == CW_READY then
|
|
|
|
-- send message: topic = "on", payload = "NUMBER"
|
|
|
|
tubelib.send_message(numbers, owner, owner, "on", number)
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
elseif topic == "state" then
|
|
|
|
return cwstate[get_cropswatcher_area_state(pos)]
|
2019-03-03 22:07:39 +01:00
|
|
|
elseif topic == "crops" then
|
|
|
|
local sn, cn, gn = get_cropswatcher_area_state(pos)
|
|
|
|
return { cwstate[sn], cn, gn }
|
2019-02-27 14:52:34 +01:00
|
|
|
else
|
|
|
|
return "unsupported"
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
|
|
|
--[[
|
|
|
|
--------
|
|
|
|
Crafting
|
|
|
|
--------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
minetest.register_craft({
|
|
|
|
output = "slmodules:cropswatcher",
|
|
|
|
type = "shaped",
|
|
|
|
recipe = {
|
|
|
|
{ "default:steelblock", "default:copper_ingot", "default:steelblock" },
|
|
|
|
{ "default:glass", "default:diamond", "default:mese_crystal" },
|
|
|
|
{ "group:wood", "tubelib:wlanchip", "group:wood" },
|
|
|
|
},
|
|
|
|
})
|
2019-03-03 22:07:39 +01:00
|
|
|
|
|
|
|
--[[
|
|
|
|
--------
|
|
|
|
SaferLua
|
|
|
|
--------
|
|
|
|
]]--
|
|
|
|
|
|
|
|
sl_controller.register_action("get_crops_status", {
|
|
|
|
cmnd = function(self, num)
|
|
|
|
num = tostring(num or "")
|
|
|
|
return unpack(tubelib.send_request(num, "crops", nil) or { "", "", "" })
|
|
|
|
end,
|
|
|
|
help = " $get_crops_status(num)\n" ..
|
|
|
|
" Read Crops Watcher device state.\n" ..
|
|
|
|
" Function returns 3 values:\n" ..
|
|
|
|
' 1: "error", "growing" or "ready"\n' ..
|
|
|
|
" 2: total number of crops\n" ..
|
|
|
|
" 3: number of fully grown crops\n" ..
|
|
|
|
' example: state, total, grown = $get_crops_status("1234")\n'
|
|
|
|
})
|