minetest/mods/farming/api.lua
HybridDog 4e8db84635 Cleanup farming code and small additional changes
Some additional changes:
Hoes only work if the node is pointed to from above.
Hoe description, inventory_image and max_uses must be specified (when registering).
Nodes with the field group must have all soil values in the node definition.
Field nodes do not loose their param2 value (e.g. rotation) when they dry, get wet or get hoed.
The node above the hoed one is not checked for protection because that node is not changed; similarly, the node under a seed is not tested for protection.
Document the fertility
test for nil player in the hoe on_use function
properly test for the creative mod
ensure that the on_place function returns an itemstack
change missing soil fields message
2020-09-24 13:56:28 +02:00

351 lines
9.6 KiB
Lua

-- farming/api.lua
-- Wear out hoes, place soil
-- TODO Ignore group:flower
farming.registered_plants = {}
function farming.hoe_on_use(itemstack, user, pt, uses)
if not pt or pt.type ~= "node" or pt.above.y ~= pt.under.y + 1 then
-- Only nodes pointed on the top can be hoed
return
end
if minetest.get_node(pt.above).name ~= "air" then
-- The hoe is obstructed from moving if there is no free space
return
end
local node_under = minetest.get_node(pt.under)
local node_under_def = minetest.registered_nodes[node_under.name]
if not node_under_def or
minetest.get_item_group(node_under.name, "soil") ~= 1 then
-- Not a soil node
return
end
-- Test if soil properties are defined
local soil = node_under_def.soil
if not soil or not soil.wet or not soil.dry then
return
end
local playername = user and user:get_player_name() or ""
if minetest.is_protected(pt.under, playername) then
minetest.record_protection_violation(pt.under, playername)
return
end
-- Put the node which should appear after applying the hoe
node_under.name = node_under_def.soil.dry
minetest.swap_node(pt.under, node_under)
minetest.sound_play("default_dig_crumbly", {
pos = pt.under,
gain = 0.5,
}, true)
if minetest.global_exists("creative")
and creative.is_enabled_for(playername) then
return
end
-- wear tool
itemstack:add_wear(65535 / (uses - 1))
if itemstack:is_empty() then
local wdef = itemstack:get_definition()
if wdef.sound and wdef.sound.breaks then
minetest.sound_play(wdef.sound.breaks, {pos = pt.above,
gain = 0.5}, true)
end
end
return itemstack
end
-- Register new hoes
function farming.register_hoe(name, def)
-- Check for : prefix (register new hoes in your mod's namespace)
if name:sub(1,1) ~= ":" then
name = ":" .. name
end
-- Check def table
assert(def.description, "Missing hoe description for " .. name)
assert(def.inventory_image, "Missing inventory image for " .. name)
assert(def.max_uses and def.max_uses > 1,
"max_uses are invalid (hoe " .. name .. ")")
-- Register the tool
minetest.register_tool(name, {
description = def.description,
inventory_image = def.inventory_image,
on_use = function(itemstack, user, pointed_thing)
return farming.hoe_on_use(itemstack, user, pointed_thing,
def.max_uses)
end,
groups = def.groups,
sound = {breaks = "default_tool_breaks"},
})
-- Register its recipe
if def.recipe then
minetest.register_craft({
output = name:sub(2),
recipe = def.recipe
})
elseif def.material then
minetest.register_craft({
output = name:sub(2),
recipe = {
{def.material, def.material},
{"", "group:stick"},
{"", "group:stick"}
}
})
end
end
-- how often node timers for plants will tick, +/- some random value
local function tick(pos)
minetest.get_node_timer(pos):start(math.random(166, 286))
end
-- how often a growth failure tick is retried (e.g. too dark)
local function tick_again(pos)
minetest.get_node_timer(pos):start(math.random(40, 80))
end
-- Seed placement
function farming.place_seed(itemstack, placer, pt, plantname)
if not pt or pt.type ~= "node" or pt.above.y ~= pt.under.y + 1 then
-- Seeds can only be placed on top of a node
return
end
local playername = placer and placer:get_player_name() or ""
if minetest.is_protected(pt.above, playername) then
minetest.record_protection_violation(pt.above, playername)
return
end
local node_above_def = minetest.registered_nodes[
minetest.get_node(pt.above).name]
if not node_above_def or not node_above_def.buildable_to then
-- We cannot put the seed here
return
end
-- The seed must be placed onto a soil node
local node_under = minetest.get_node(pt.under)
if minetest.get_item_group(node_under.name, "soil") < 2 then
return
end
-- Put the seed node
minetest.log("action", playername .. " places node " .. plantname ..
" at " .. minetest.pos_to_string(pt.above))
minetest.add_node(pt.above, {name = plantname, param2 = 1})
tick(pt.above)
if minetest.global_exists("creative")
and creative.is_enabled_for(playername) then
return
end
itemstack:take_item()
return itemstack
end
farming.grow_plant = function(pos, elapsed)
local node = minetest.get_node(pos)
local name = node.name
local def = minetest.registered_nodes[name]
if not def.next_plant then
-- disable timer for fully grown plant
return
end
-- grow seed
if minetest.get_item_group(node.name, "seed") and def.fertility then
local soil_node = minetest.get_node_or_nil(
{x = pos.x, y = pos.y - 1, z = pos.z})
if not soil_node then
tick_again(pos)
return
end
-- omitted is a check for light, we assume seeds can germinate in the dark.
for _, groupname in ipairs(def.fertility) do
if minetest.get_item_group(soil_node.name, groupname) ~= 0 then
local placenode = {name = def.next_plant}
if def.place_param2 then
placenode.param2 = def.place_param2
end
minetest.swap_node(pos, placenode)
if minetest.registered_nodes[def.next_plant].next_plant then
tick(pos)
return
end
end
end
return
end
-- check if on wet soil
local below = minetest.get_node({x = pos.x, y = pos.y - 1, z = pos.z})
if minetest.get_item_group(below.name, "soil") < 3 then
tick_again(pos)
return
end
-- check light
local light = minetest.get_node_light(pos)
if not light or light < def.minlight or light > def.maxlight then
tick_again(pos)
return
end
-- grow
local placenode = {name = def.next_plant}
if def.place_param2 then
placenode.param2 = def.place_param2
end
minetest.swap_node(pos, placenode)
-- new timer needed?
if minetest.registered_nodes[def.next_plant].next_plant then
tick(pos)
end
return
end
-- Register plants
function farming.register_plant(name, def)
local mname, pname = unpack(name:split(":"))
-- Check def table
if not def.harvest_description then
def.harvest_description = pname:gsub("^%l", string.upper)
end
assert(def.description, "Missing description for " .. name)
assert(def.inventory_image, "Missing inventory_image for " .. name)
assert(def.steps, "Missing number of steps for " .. name)
assert(def.fertility and def.fertility[1], "Missing fertility for " .. name)
def.minlight = def.minlight or 1
def.maxlight = def.maxlight or default.LIGHT_MAX
farming.registered_plants[pname] = def
-- Register seed
local lbm_nodes = {mname .. ":seed_" .. pname}
local seed_groups = {seed = 1, snappy = 3, attached_node = 1, flammable = 2}
for _, groupname in ipairs(def.fertility) do
seed_groups[groupname] = 1
end
minetest.register_node(":" .. mname .. ":seed_" .. pname, {
description = def.description,
tiles = {def.inventory_image},
inventory_image = def.inventory_image,
wield_image = def.inventory_image,
drawtype = "signlike",
groups = seed_groups,
paramtype = "light",
paramtype2 = "wallmounted",
place_param2 = def.place_param2 or nil, -- this isn't actually used for placement
walkable = false,
sunlight_propagates = true,
selection_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
},
fertility = def.fertility,
sounds = default.node_sound_dirt_defaults({
dig = {name = "", gain = 0},
dug = {name = "default_grass_footstep", gain = 0.2},
place = {name = "default_place_node", gain = 0.25},
}),
on_place = function(itemstack, placer, pointed_thing)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
return farming.place_seed(itemstack, placer, pointed_thing,
mname .. ":seed_" .. pname) or itemstack
end,
next_plant = mname .. ":" .. pname .. "_1",
on_timer = farming.grow_plant,
minlight = def.minlight,
maxlight = def.maxlight,
})
-- Register harvest
minetest.register_craftitem(":" .. mname .. ":" .. pname, {
description = def.harvest_description,
inventory_image = mname .. "_" .. pname .. ".png",
groups = def.groups or {flammable = 2},
})
-- Register growing steps
for i = 1, def.steps do
local base_rarity = 1
if def.steps ~= 1 then
base_rarity = 8 - (i - 1) * 7 / (def.steps - 1)
end
local drop = {
items = {
{items = {mname .. ":" .. pname}, rarity = base_rarity},
{items = {mname .. ":" .. pname}, rarity = base_rarity * 2},
{items = {mname .. ":seed_" .. pname}, rarity = base_rarity},
{items = {mname .. ":seed_" .. pname}, rarity = base_rarity * 2},
}
}
local next_plant = nil
if i < def.steps then
next_plant = mname .. ":" .. pname .. "_" .. (i + 1)
lbm_nodes[#lbm_nodes + 1] = mname .. ":" .. pname .. "_" .. i
end
minetest.register_node(":" .. mname .. ":" .. pname .. "_" .. i, {
drawtype = "plantlike",
waving = 1,
tiles = {mname .. "_" .. pname .. "_" .. i .. ".png"},
paramtype = "light",
paramtype2 = def.paramtype2 or nil,
place_param2 = def.place_param2 or nil,
walkable = false,
buildable_to = true,
drop = drop,
selection_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
},
groups = {snappy = 3, flammable = 2, plant = 1,
not_in_creative_inventory = 1, attached_node = 1, [pname] = i},
sounds = default.node_sound_leaves_defaults(),
next_plant = next_plant,
on_timer = farming.grow_plant,
minlight = def.minlight,
maxlight = def.maxlight,
})
end
-- replacement LBM for pre-nodetimer plants
minetest.register_lbm({
name = ":" .. mname .. ":start_nodetimer_" .. pname,
nodenames = lbm_nodes,
action = tick_again,
})
-- Return
return {
seed = mname .. ":seed_" .. pname,
harvest = mname .. ":" .. pname
}
end