mirror of
https://github.com/luanti-org/minetest_game.git
synced 2025-05-21 06:43:17 -04:00
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
351 lines
9.6 KiB
Lua
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
|