techpack/tubelib_addons1/harvester.lua
2021-09-03 21:33:50 +02:00

510 lines
14 KiB
Lua

--[[
Tubelib Addons 1
================
Copyright (C) 2017-2021 Joachim Stolberg
AGPL v3
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.
]]--
-- Load support for I18n
local S = tubelib_addons1.S
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local CYCLE_TIME = 6
local START_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, altitude)
local working_pos = table.copy(pos)
working_pos.y = working_pos.y + (altitude or START_HEIGHT)
return working_pos
end
local Radius2Idx = {[4]=1 ,[6]=2, [8]=3, [10]=4, [12]=5, [14]=6, [16]=7}
local Altitude2Idx = {[-2]=1 ,[-1]=2, [0]=3, [1]=4, [2]=5, [4]=6, [6]=7, [8]=8, [10]=9, [14]=10, [18]=11}
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
local altitude = Altitude2Idx[this.altitude or START_HEIGHT] or 11
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;"..S("Area radius").."]"..
"dropdown[0,1;1.5;altitude;-2,-1,0,1,2,4,6,8,10,14,18;"..altitude.."]"..
"label[1.6,1.2;"..S("Altitude ").."]"..
"checkbox[0,2;endless;"..S("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 = S("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, this.altitude)
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 tubelib.is_fuel(stack) 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
local function is_plantable_ground(node)
if minetest.get_item_group(node.name, "soil") ~= 0 then
return true
end
if minetest.get_item_group(node.name, "sand") ~= 0 then
return true
end
return false
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 is_plantable_ground(next_node) 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
local fuel_item = tubelib.get_this_item(meta, "fuel", 1)
if fuel_item == nil then
return false
end
if not tubelib.is_fuel(fuel_item) then
tubelib.put_item(meta, "fuel", fuel_item)
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, this.altitude)
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 - (this.altitude or START_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 minetest.is_protected(pos, this.owner) and 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",
S("Tubelib Harvester").." "..this.number..
S(": 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
local altitude = this.altitude or START_HEIGHT
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.altitude ~= nil then
altitude = tonumber(fields.altitude)
end
if altitude ~= this.altitude then
this.altitude = altitude
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 = S("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, START_HEIGHT),
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") and inv:is_empty("fuel")
end,
on_dig = function(pos, node, player)
State:on_dig_node(pos, node, player)
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,
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 = S("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, START_HEIGHT),
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") and inv:is_empty("fuel")
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)
if not tubelib.is_fuel(item) then
return false
end
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, this.altitude)
meta:set_string("this", minetest.serialize(this))
end
end
})