techpack/tubelib_addons1/harvester.lua
2018-03-18 21:27:44 +01:00

553 lines
16 KiB
Lua

--[[
Tubelib Addons 1
================
Copyright (C) 2017 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
harvester.lua
Harvester machine to chop wood, leaves and harvest farming crops.
The machine is able to harvest an square area of up to 33x33 blocks (radius = 16).
The base node has to be placed in the middle of the harvesting area.
The Harvester processes one node every 4 seconds.
It requires one item Bio Fuel per 20 nodes.
]]--
local CYCLE_TIME = 4
local MAX_HEIGHT = 18
local MAX_DIAMETER = 33
local BURNING_TIME = 20
local TICKS_TO_SLEEP = 10
local STOP_STATE = 0
local RUNNING = 1
local STANDBY_STATE = -1
local FAULT_STATE = -3
local OFFSET = 5
local Radius2Idx = {[4]=1 ,[6]=2, [8]=3, [10]=4, [12]=5, [14]=6, [16]=7}
local function gen_working_steps()
-- Working steps like a snail shell from inner to outer
local t = {}
for steps = 1,MAX_DIAMETER,2 do
for idx = 1,steps do
t[#t+1] = 0
end
for idx = 1,steps do
t[#t+1] = 1
end
steps = steps + 1
for idx = 1,steps do
t[#t+1] = 2
end
for idx = 1,steps do
t[#t+1] = 3
end
end
return t
end
local WorkingSteps = gen_working_steps()
-- valid harvesting nodes and the results for the inventory
local ResultNodes = {
["default:tree"] = "default:tree",
["default:aspen_tree"] = "default:aspen_tree",
["default:pine_tree"] = "default:pine_tree",
["default:acacia_tree"] = "default:acacia_tree",
["default:jungletree"] = "default:jungletree",
["default:leaves"] = "default:leaves",
["default:aspen_leaves"] = "default:aspen_leaves",
["default:pine_needles"] = "default:pine_needles",
["default:acacia_leaves"] = "default:acacia_leaves",
["default:jungleleaves"] = "default:jungleleaves",
["default:bush_leaves"] = "default:bush_leaves",
["default:acacia_bush_leaves"] = "default:acacia_bush_leaves",
["default:cactus"] = "default:cactus",
["default:papyrus"] = "default:papyrus",
["farming:wheat_8"] = "farming:wheat",
["farming:cotton_8"] = "farming:cotton",
["default:apple"] = "default:apple",
["farming:carrot_8"] = "farming:carrot 2",
["farming:potato_4"] = "farming:potato 3",
["farming:tomato_8"] = "farming:tomato 3",
["farming:cucumber_4"] = "farming:cucumber 2",
["farming:corn_8"] = "farming:corn 2",
["farming:coffee_5"] = "farming:coffee_beans 2",
["farming:melon_8"] = "farming:melon_slice 9",
["farming:pumpkin_8"] = "farming:pumpkin_slice 9",
["farming:raspberry_4"] = "farming:raspberries",
["farming:blueberry_4"] = "farming:blueberries",
["farming:rhubarb_3"] = "farming:rhubarb 2",
["farming:beanpole_5"] = "farming:beans 3",
["farming:grapes_8"] = "farming:grapes 3",
["farming:barley_7"] = "farming:barley",
["farming:chili_8"] = "farming:chili_pepper 2",
["farming:hemp_8"] = "farming:hemp_leaf",
}
-- Which sapling/seed belongs to which tree/crop
local SaplingList = {
["default:tree"] = "default:sapling",
["default:aspen_tree"] = "default:aspen_sapling",
["default:pine_tree"] = "default:pine_sapling",
["default:acacia_tree"] = "default:acacia_sapling",
["default:jungletree"] = "default:junglesapling",
["farming:wheat_8"] = "farming:wheat_1",
["farming:cotton_8"] = "farming:cotton_1",
["farming:carrot_8"] = "farming:carrot_1",
["farming:potato_4"] = "farming:potato_1",
["farming:tomato_8"] = "farming:tomato_1",
["farming:cucumber_4"] = "farming:cucumber_1",
["farming:corn_8"] = "farming:corn_1",
["farming:coffee_5"] = "farming:coffee_1",
["farming:melon_8"] = "farming:melon_1",
["farming:pumpkin_8"] = "farming:pumpkin_1",
["farming:raspberry_4"] = "farming:raspberry_1",
["farming:blueberry_4"] = "farming:blueberry_1",
["farming:rhubarb_3"] = "farming:rhubarb_1",
["farming:beanpole_5"] = "farming:beanpole_1",
["farming:grapes_8"] = "farming:grapes_1",
["farming:barley_7"] = "farming:barley_1",
["farming:chili_8"] = "farming:chili_1",
["farming:hemp_8"] = "farming:hemp_1",
}
local function formspec(this, state)
-- some recalculations
local endless = this.endless == 1 and "true" or "false"
local fuel = this.fuel * 100/BURNING_TIME
if state ~= tubelib.RUNNING then
fuel = 0
end
local radius = Radius2Idx[this.radius] or 2
return "size[9,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"dropdown[0,0;1.5;radius;4,6,8,10,12,14,16;"..radius.."]"..
"label[1.6,0.2;Area radius]"..
"checkbox[0,1;endless;Run endless;"..endless.."]"..
"list[context;main;5,0;4,4;]"..
"list[context;fuel;1.5,3;1,1;]"..
"item_image[1.5,3;1,1;tubelib_addons1:biofuel]"..
"image[2.5,3;1,1;default_furnace_fire_bg.png^[lowpart:"..
fuel..":default_furnace_fire_fg.png]"..
"image_button[3.5,3;1,1;".. tubelib.state_button(state) ..";button;]"..
"list[current_player;main;0.5,4.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
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 == "main" then
return stack:get_count()
elseif listname == "fuel" and stack:get_name() == "tubelib_addons1:biofuel" then
return stack:get_count()
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
-- start on top of the base block
local function working_start_pos(pos)
local working_pos = table.copy(pos)
working_pos.y = working_pos.y + MAX_HEIGHT
return working_pos
end
local function get_next_pos(old_pos, idx)
local facedir = WorkingSteps[idx]
return vector.add(old_pos, core.facedir_to_dir(facedir))
end
local function start_the_machine(pos, this, meta)
this.running = RUNNING
this.idx = 0
this.working_pos = working_start_pos(pos)
meta:set_string("infotext", "Tubelib Harvester "..this.number..": running")
meta:set_string("formspec", formspec(this, tubelib.RUNNING))
minetest.get_node_timer(pos):start(CYCLE_TIME)
meta:set_string("this", minetest.serialize(this))
return false
end
local function stop_the_machine(pos, this, meta)
this.running = STOP_STATE
meta:set_string("infotext", "Tubelib Harvester "..this.number..": stopped")
meta:set_string("formspec", formspec(this, tubelib.STOPPED))
minetest.get_node_timer(pos):stop()
meta:set_string("this", minetest.serialize(this))
return false
end
local function goto_standby(pos, this, meta)
this.running = STANDBY_STATE
meta:set_string("infotext", "Tubelib Harvester "..this.number..": standby")
meta:set_string("formspec", formspec(this, tubelib.STANDBY))
minetest.get_node_timer(pos):start(CYCLE_TIME * TICKS_TO_SLEEP)
meta:set_string("this", minetest.serialize(this))
return false
end
local function goto_fault(pos, this, meta)
this.running = FAULT_STATE
meta:set_string("infotext", "Tubelib Harvester "..this.number..": fault")
meta:set_string("formspec", formspec(this, tubelib.FAULT))
minetest.get_node_timer(pos):stop()
meta:set_string("this", minetest.serialize(this))
return false
end
-- Remove saplings lying arround
local function remove_all_sapling_items(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 4)) do
local lua_entity = object:get_luaentity()
if not object:is_player() and lua_entity and lua_entity.name == "__builtin:item" then
object:remove()
end
end
end
-- Remove wood/leave nodes and place sapling if necessary
-- Return false if inventory is full
-- else return true
local function remove_or_replace_node(pos, inv, node)
local next_pos = table.copy(pos)
next_pos.y = next_pos.y - 1
-- Not enough space in the inventory
if not inv:room_for_item("main", ItemStack(node.name)) then
return false
end
local next_node = minetest.get_node_or_nil(next_pos)
if next_node then
-- don't remove the last cactus block
if node.name == "default:cactus" and next_node.name ~= "default:cactus" then
return true
end
-- don't remove the last papyrus block
if node.name == "default:papyrus" and next_node.name ~= "default:papyrus" then
return true
end
minetest.remove_node(pos)
inv:add_item("main", ItemStack(ResultNodes[node.name]))
if ResultNodes[next_node.name] == nil and next_node.name ~= "air" then -- hit the ground?
if SaplingList[node.name] then
node.name = SaplingList[node.name]
minetest.set_node(pos, {name=node.name, paramtype2 = "wallmounted", param2=1})
if node.name:sub(1,8) == "farming:" then
-- farming_redo installed?
if farming.mod == "redo" then
-- nothing to do
else
-- We have to simulate "on_place" and start the timer by hand
-- because the after_place_node function checks player rights and can't therefore
-- be used.
minetest.get_node_timer(pos):start(math.random(166,288))
end
else
-- default trees
minetest.get_node_timer(pos):start(math.random(2400,4800))
end
end
remove_all_sapling_items(pos)
end
end
return true
end
-- check the fuel level and return false if empty
local function check_fuel(pos, this, meta)
if this.fuel <= 0 then
if tubelib.get_this_item(meta, "fuel", 1) == nil then
return false
end
this.fuel = BURNING_TIME
else
this.fuel = this.fuel - 1
end
return true
end
local function calc_new_pos(pos, this, meta)
this.idx = this.idx + 1
if this.idx >= this.max then
if this.endless == 1 then
this.idx = 0
this.working_pos = working_start_pos(pos)
return true
else
return false
end
end
this.working_pos = get_next_pos(this.working_pos, this.idx)
return true
end
-- Scan the space below the given position
-- Return false if inventory is full
-- else return true
local function harvest_field(this, meta)
local inv = meta:get_inventory()
local pos = table.copy(this.working_pos)
local start_y_pos = pos.y - 1
local stop_y_pos = pos.y - MAX_HEIGHT - OFFSET
if minetest.is_protected(pos, this.owner) then
return true
end
for y_pos = start_y_pos,stop_y_pos,-1 do
pos.y = y_pos
local node = minetest.get_node_or_nil(pos)
if node and node.name ~= "air" then
if ResultNodes[node.name] then
if not remove_or_replace_node(pos, inv, node) then
return false
end
else
return true -- hit the ground
end
end
end
return true
end
local function not_standby(pos, this, meta)
if this and this.running == STANDBY_STATE then
if harvest_field(this, meta) then
minetest.after(0, start_the_machine, pos, this, meta)
end
return false
end
return true
end
-- move the copter to the next pos and harvest the field below
local function keep_running(pos, elapsed)
local meta = minetest.get_meta(pos)
local this = minetest.deserialize(meta:get_string("this"))
--print(this.working_pos.x, this.working_pos.z, this.running)
if not_standby(pos, this, meta) then
if check_fuel(pos, this, meta) then
if calc_new_pos(pos, this, meta) then
if harvest_field(this, meta) then
meta:set_string("this", minetest.serialize(this))
meta:set_string("infotext",
"Tubelib Harvester "..this.number..
": running ("..this.idx.."/"..this.max..")")
return true
else
goto_standby(pos, this, meta)
end
else
stop_the_machine(pos, this, meta)
end
else
goto_fault(pos, this, meta)
end
return false
end
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 this = minetest.deserialize(meta:get_string("this"))
local radius = this.radius
if fields.radius ~= nil then
radius = tonumber(fields.radius)
end
if radius ~= this.radius then
stop_the_machine(pos, this, meta)
this.radius = radius
this.max = (radius*2 + 1) * (radius*2 + 1)
end
if fields.endless ~= nil then
this.endless = fields.endless == "true" and 1 or 0
end
if fields.button ~= nil then
if this.running > STOP_STATE or this.running == FAULT_STATE then
stop_the_machine(pos, this, meta)
else
start_the_machine(pos, this, meta)
end
else
meta:set_string("formspec", formspec(this, tubelib.state(this.running)))
end
meta:set_string("this", minetest.serialize(this))
end
minetest.register_node("tubelib_addons1:harvester_base", {
description = "Tubelib Harvester Base",
tiles = {
-- up, down, right, left, back, front
'tubelib_front.png',
'tubelib_addons1_harvester.png',
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('main', 16)
inv:set_size('fuel', 1)
end,
after_place_node = function(pos, placer)
local number = tubelib.add_node(pos, "tubelib_addons1:harvester_base")
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Tubelib Harvester "..number..": stopped")
local this = {
number = number,
owner = placer:get_player_name(),
working_pos = working_start_pos(pos),
fuel = 0,
running = STOP_STATE,
endless = 0,
radius = 6,
idx = 1,
max = (6+1+6) * (6+1+6)
}
meta:set_string("this", minetest.serialize(this))
meta:set_string("formspec", formspec(this, tubelib.STOPPED))
end,
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("main") then
minetest.node_dig(pos, node, puncher, pointed_thing)
tubelib.remove_node(pos)
end
end,
on_receive_fields = on_receive_fields,
on_timer = keep_running,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype = "light",
sunlight_propagates = true,
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("tubelib_addons1:harvester_base_active", {
description = "Tubelib Harvester Base",
tiles = {
-- up, down, right, left, back, front
'tubelib_front.png',
'tubelib_addons1_harvester.png',
},
on_receive_fields = on_receive_fields,
on_timer = keep_running,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype = "light",
sunlight_propagates = true,
groups = {crumbly=0, not_in_creative_inventory=1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_craft({
output = "tubelib_addons1:harvester_base",
recipe = {
{"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"},
{"default:steel_ingot", "default:mese_crystal", "tubelib:tube1"},
{"group:wood", "default:mese_crystal", "group:wood"},
},
})
tubelib.register_node("tubelib_addons1:harvester_base", {}, {
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)
if item:get_name() == "tubelib_addons1:biofuel" then
local meta = minetest.get_meta(pos)
return tubelib.put_item(meta, "fuel", item)
end
return false
end,
on_unpull_item = function(pos, side, item)
local meta = minetest.get_meta(pos)
return tubelib.put_item(meta, "main", item)
end,
on_recv_message = function(pos, topic, payload)
local meta = minetest.get_meta(pos)
local this = minetest.deserialize(meta:get_string("this"))
if topic == "on" then
start_the_machine(pos, this, meta)
elseif topic == "off" then
stop_the_machine(pos, this, meta)
elseif topic == "state" then
return tubelib.statestring(this.running)
elseif topic == "fuel" then
local meta = minetest.get_meta(pos)
return tubelib.fuelstate(meta, "fuel")
else
return "unsupported"
end
end,
})
-- update to v0.08
minetest.register_lbm({
label = "[tubelib_addons1] Harvester update",
name = "tubelib_addons1:update",
nodenames = {"tubelib_addons1:harvester_base", "tubelib:harvester_base_active"},
run_at_every_load = false,
action = function(pos, node)
local meta = minetest.get_meta(pos)
local this = minetest.deserialize(meta:get_string("this"))
if this then
this.working_pos = this.copter_pos or working_start_pos(pos)
meta:set_string("this", minetest.serialize(this))
end
end
})