techpack/tubelib_addons1/harvester.lua

477 lines
13 KiB
Lua

--[[
Tubelib Addons 1
================
Copyright (C) 2017-2019 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
harvester.lua
Harvester machine to chop wood, leaves and harvest farming crops and flowers.
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 6 seconds.
It requires one item Bio Fuel per 20 nodes.
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
local CYCLE_TIME = 6
local MAX_HEIGHT = 18 -- harvesting altitude
local MAX_DIAMETER = 33
local BURNING_TIME = 20 -- fuel
local STANDBY_TICKS = 4 -- used for blocked state
local COUNTDOWN_TICKS = 2
local OFFSET = 5 -- for uneven terrains
-- 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 Radius2Idx = {[4]=1 ,[6]=2, [8]=3, [10]=4, [12]=5, [14]=6, [16]=7}
local function formspec(self, pos, meta)
-- some recalculations
local this = minetest.deserialize(meta:get_string("this"))
local endless = this.endless == 1 and "true" or "false"
local fuel = this.fuel * 100/BURNING_TIME
if self:get_state(meta) ~= 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;".. self:get_state_button_image(meta) ..";state_button;]"..
"list[current_player;main;0.5,4.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local State = tubelib.NodeStates:new({
node_name_passive = "tubelib_addons1:harvester_base",
node_name_defect = "tubelib_addons1:harvester_defect",
infotext_name = "Tubelib Harvester",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
has_item_meter = true,
aging_factor = 15,
on_start = function(pos, meta, oldstate)
local this = minetest.deserialize(meta:get_string("this"))
this.idx = 0
this.working_pos = working_start_pos(pos)
meta:set_string("this", minetest.serialize(this))
end,
formspec_func = formspec,
})
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()
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 inv = M(pos):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
local function get_next_pos(old_pos, idx)
local facedir = WorkingSteps[idx]
return vector.add(old_pos, core.facedir_to_dir(facedir))
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(this, pos, inv, node, order)
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
minetest.remove_node(pos)
inv:add_item("main", ItemStack(order.drop))
this.num_items = this.num_items + 1
if tubelib_addons1.GroundNodes[next_node.name] ~= nil and order.plant then -- hit the ground?
minetest.set_node(pos, {name=order.plant, paramtype2 = "wallmounted", param2=1})
if order.t1 ~= nil then
-- 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(order.t1, order.t2))
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
local order = tubelib_addons1.FarmingNodes[node.name] or tubelib_addons1.Flowers[node.name]
if order then
if not remove_or_replace_node(this, pos, inv, node, order) then
return false
end
else
return true -- hit the ground
end
end
end
return true
end
local function not_blocked(pos, this, meta)
if State:get_state(meta) == tubelib.BLOCKED then
if harvest_field(this, meta) then
minetest.after(0, State.start, State, pos, meta)
end
return false
end
return true
end
-- move the "harvesting copter" to the next pos and harvest the field below
local function keep_running(pos, elapsed)
if tubelib.data_not_corrupted(pos) then
local meta = M(pos)
local this = minetest.deserialize(meta:get_string("this"))
this.num_items = 0
if not_blocked(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..")")
State:keep_running(pos, meta, COUNTDOWN_TICKS, this.num_items)
else
State:blocked(pos, meta)
end
else
State:stop(pos, meta)
end
else
State:fault(pos, meta)
end
end
return State:is_active(meta)
end
return false
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local meta = M(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
this.radius = radius
this.max = (radius*2 + 1) * (radius*2 + 1)
meta:set_string("this", minetest.serialize(this))
State:stop(pos, meta)
end
if fields.endless ~= nil then
this.endless = fields.endless == "true" and 1 or 0
end
meta:set_string("this", minetest.serialize(this))
State:state_button_event(pos, fields)
end
minetest.register_node("tubelib_addons1:harvester_base", {
description = "Tubelib Harvester Base",
tiles = {
-- up, down, right, left, back, front
'tubelib_front.png',
'tubelib_front.png',
'tubelib_addons1_harvester.png',
},
after_place_node = function(pos, placer)
local meta = M(pos)
local inv = meta:get_inventory()
inv:set_size('main', 16)
inv:set_size('fuel', 1)
local number = tubelib.add_node(pos, "tubelib_addons1:harvester_base")
local this = {
number = number,
owner = placer:get_player_name(),
working_pos = working_start_pos(pos),
fuel = 0,
endless = 0,
radius = 6,
idx = 0,
max = (6+1+6) * (6+1+6)
}
meta:set_string("this", minetest.serialize(this))
State:node_init(pos, number)
end,
can_dig = function(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("main")
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
State:after_dig_node(pos, oldnode, oldmetadata, digger)
tubelib.remove_node(pos)
end,
on_rotate = screwdriver.disallow,
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,
drop = "",
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_addons1:harvester_defect", {
description = "Tubelib Harvester Base",
tiles = {
-- up, down, right, left, back, front
'tubelib_front.png',
'tubelib_front.png',
'tubelib_addons1_harvester.png^tubelib_defect.png',
},
on_construct = function(pos)
local inv = M(pos):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 this = {
number = number,
owner = placer:get_player_name(),
working_pos = working_start_pos(pos),
fuel = 0,
endless = 0,
radius = 6,
idx = 0,
max = (6+1+6) * (6+1+6)
}
local meta = M(pos)
meta:set_string("this", minetest.serialize(this))
State:node_init(pos, number)
State:defect(pos, meta)
end,
can_dig = function(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("main")
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
tubelib.remove_node(pos)
end,
on_rotate = screwdriver.disallow,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2, 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:tubeS"},
{"group:wood", "default:mese_crystal", "group:wood"},
},
})
tubelib.register_node("tubelib_addons1:harvester_base", {"tubelib_addons1:harvester_defect"}, {
on_pull_stack = function(pos, side)
return tubelib.get_stack(M(pos), "main")
end,
on_pull_item = function(pos, side)
return tubelib.get_item(M(pos), "main")
end,
on_push_item = function(pos, side, item)
return tubelib.put_item(M(pos), "fuel", item)
end,
on_unpull_item = function(pos, side, item)
return tubelib.put_item(M(pos), "main", item)
end,
on_recv_message = function(pos, topic, payload)
if topic == "fuel" then
return tubelib.fuelstate(M(pos), "fuel")
end
local resp = State:on_receive_message(pos, topic, payload)
if resp then
return resp
else
return "unsupported"
end
end,
on_node_load = function(pos)
State:on_node_load(pos)
end,
on_node_repair = function(pos)
return State:on_node_repair(pos)
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 = M(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
})