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
This commit is contained in:
HybridDog 2016-05-23 13:10:16 +02:00
parent dd91a1bfe5
commit 4e8db84635
4 changed files with 204 additions and 213 deletions

View file

@ -347,13 +347,21 @@ The farming API allows you to easily register plants and hoes.
### Hoe Definition
{
description = "", -- Description for tooltip
inventory_image = "unknown_item.png", -- Image to be used as wield- and inventory image
max_uses = 30, -- Uses until destroyed
material = "", -- Material for recipes
recipe = { -- Craft recipe, if material isn't used
-- Description for tooltip
description = "My Hoe",
-- Image to be used as wield- and inventory image
inventory_image = "unknown_item.png",
-- Uses until destroyed
max_uses = 30,
-- Material for recipes
material = "",
-- Craft recipe, if material isn't used
recipe = {
{"air", "air", "air"},
{"", "group:stick"},
{"", "group:stick"},
@ -363,14 +371,27 @@ The farming API allows you to easily register plants and hoes.
### Plant definition
{
description = "", -- Description of seed item
harvest_description = "", -- Description of harvest item
-- (optional, derived automatically if not provided)
inventory_image = "unknown_item.png", -- Image to be used as seed's wield- and inventory image
steps = 8, -- How many steps the plant has to grow, until it can be harvested
-- ^ Always provide a plant texture for each step, format: modname_plantname_i.png (i = stepnumber)
minlight = 13, -- Minimum light to grow
maxlight = default.LIGHT_MAX -- Maximum light to grow
-- Description of seed item
description = "My Plant",
-- Description of harvest item (optional, derived automatically if not
-- provided)
harvest_description = "",
-- Image to be used as seed's wield- and inventory image
inventory_image = "unknown_item.png",
-- How many steps the plant has to grow, until it can be harvested
steps = 8,
-- ^ Always provide a plant texture for each step, format:
-- modname_plantname_i.png (i = stepnumber)
-- Groups of soil nodes on which the seed can grow
fertility = {"grassland"},
-- Minimum and maximum light to grow
minlight = 1,
maxlight = default.LIGHT_MAX
}

View file

@ -1,73 +1,58 @@
-- farming/api.lua
-- support for MT game translation.
local S = farming.get_translator
-- Wear out hoes, place soil
-- TODO Ignore group:flower
farming.registered_plants = {}
farming.hoe_on_use = function(itemstack, user, pointed_thing, uses)
local pt = pointed_thing
-- check if pointing at a node
if not pt then
return
end
if pt.type ~= "node" then
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
local under = minetest.get_node(pt.under)
local p = {x=pt.under.x, y=pt.under.y+1, z=pt.under.z}
local above = minetest.get_node(p)
-- return if any of the nodes is not registered
if not minetest.registered_nodes[under.name] then
return
end
if not minetest.registered_nodes[above.name] then
if minetest.get_node(pt.above).name ~= "air" then
-- The hoe is obstructed from moving if there is no free space
return
end
-- check if the node above the pointed thing is air
if above.name ~= "air" then
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
-- check if pointing at soil
if minetest.get_item_group(under.name, "soil") ~= 1 then
-- 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
-- check if (wet) soil defined
local regN = minetest.registered_nodes
if regN[under.name].soil == nil or regN[under.name].soil.wet == nil or regN[under.name].soil.dry == nil then
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
if minetest.is_protected(pt.under, user:get_player_name()) then
minetest.record_protection_violation(pt.under, user:get_player_name())
return
end
if minetest.is_protected(pt.above, user:get_player_name()) then
minetest.record_protection_violation(pt.above, user:get_player_name())
return
end
-- turn the node into soil and play sound
minetest.set_node(pt.under, {name = regN[under.name].soil.dry})
-- 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 not (creative and creative.is_enabled_for
and creative.is_enabled_for(user:get_player_name())) then
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()
itemstack:add_wear(65535/(uses-1))
-- tool break sound
if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
if wdef.sound and wdef.sound.breaks then
minetest.sound_play(wdef.sound.breaks, {pos = pt.above,
gain = 0.5}, true)
end
@ -76,31 +61,29 @@ farming.hoe_on_use = function(itemstack, user, pointed_thing, uses)
end
-- Register new hoes
farming.register_hoe = function(name, def)
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
if def.description == nil then
def.description = S("Hoe")
end
if def.inventory_image == nil then
def.inventory_image = "unknown_item.png"
end
if def.max_uses == nil then
def.max_uses = 30
end
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)
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({
@ -129,62 +112,43 @@ local function tick_again(pos)
end
-- Seed placement
farming.place_seed = function(itemstack, placer, pointed_thing, plantname)
local pt = pointed_thing
-- check if pointing at a node
if not pt then
return itemstack
end
if pt.type ~= "node" then
return itemstack
end
local under = minetest.get_node(pt.under)
local above = minetest.get_node(pt.above)
local player_name = placer and placer:get_player_name() or ""
if minetest.is_protected(pt.under, player_name) then
minetest.record_protection_violation(pt.under, player_name)
return
end
if minetest.is_protected(pt.above, player_name) then
minetest.record_protection_violation(pt.above, player_name)
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
-- return if any of the nodes is not registered
if not minetest.registered_nodes[under.name] then
return itemstack
end
if not minetest.registered_nodes[above.name] then
return itemstack
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
-- check if pointing at the top of the node
if pt.above.y ~= pt.under.y+1 then
return itemstack
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
-- check if you can replace the node above the pointed node
if not minetest.registered_nodes[above.name].buildable_to then
return itemstack
-- 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
-- check if pointing at soil
if minetest.get_item_group(under.name, "soil") < 2 then
return itemstack
end
-- add the node and remove 1 item from the itemstack
minetest.log("action", player_name .. " places node " .. plantname .. " at " ..
minetest.pos_to_string(pt.above))
-- 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 not (creative and creative.is_enabled_for
and creative.is_enabled_for(player_name)) then
itemstack:take_item()
if minetest.global_exists("creative")
and creative.is_enabled_for(playername) then
return
end
itemstack:take_item()
return itemstack
end
@ -200,14 +164,15 @@ farming.grow_plant = function(pos, elapsed)
-- 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})
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 _, v in pairs(def.fertility) do
if minetest.get_item_group(soil_node.name, v) ~= 0 then
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
@ -252,48 +217,36 @@ farming.grow_plant = function(pos, elapsed)
end
-- Register plants
farming.register_plant = function(name, def)
local mname = name:split(":")[1]
local pname = name:split(":")[2]
function farming.register_plant(name, def)
local mname, pname = unpack(name:split(":"))
-- Check def table
if not def.description then
def.description = S("Seed")
end
if not def.harvest_description then
def.harvest_description = pname:gsub("^%l", string.upper)
end
if not def.inventory_image then
def.inventory_image = "unknown_item.png"
end
if not def.steps then
return nil
end
if not def.minlight then
def.minlight = 1
end
if not def.maxlight then
def.maxlight = 14
end
if not def.fertility then
def.fertility = {}
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 g = {seed = 1, snappy = 3, attached_node = 1, flammable = 2}
for k, v in pairs(def.fertility) do
g[v] = 1
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 = g,
groups = seed_groups,
paramtype = "light",
paramtype2 = "wallmounted",
place_param2 = def.place_param2 or nil, -- this isn't actually used for placement
@ -321,7 +274,8 @@ farming.register_plant = function(name, def)
pointed_thing) or itemstack
end
return farming.place_seed(itemstack, placer, pointed_thing, mname .. ":seed_" .. pname)
return farming.place_seed(itemstack, placer, pointed_thing,
mname .. ":seed_" .. pname) or itemstack
end,
next_plant = mname .. ":" .. pname .. "_1",
on_timer = farming.grow_plant,
@ -350,8 +304,6 @@ farming.register_plant = function(name, def)
{items = {mname .. ":seed_" .. pname}, rarity = base_rarity * 2},
}
}
local nodegroups = {snappy = 3, flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1}
nodegroups[pname] = i
local next_plant = nil
@ -374,7 +326,8 @@ farming.register_plant = function(name, def)
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
},
groups = nodegroups,
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,
@ -387,15 +340,12 @@ farming.register_plant = function(name, def)
minetest.register_lbm({
name = ":" .. mname .. ":start_nodetimer_" .. pname,
nodenames = lbm_nodes,
action = function(pos, node)
tick_again(pos)
end,
action = tick_again,
})
-- Return
local r = {
return {
seed = mname .. ":seed_" .. pname,
harvest = mname .. ":" .. pname
}
return r
end

View file

@ -1,4 +1,4 @@
name = farming
description = Minetest Game mod: farming
depends = default, wool, stairs
optional_depends = dungeon_loot
optional_depends = dungeon_loot, creative

View file

@ -63,7 +63,8 @@ minetest.register_node("farming:soil", {
description = S("Soil"),
tiles = {"default_dirt.png^farming_soil.png", "default_dirt.png"},
drop = "default:dirt",
groups = {crumbly=3, not_in_creative_inventory=1, soil=2, grassland = 1, field = 1},
groups = {crumbly = 3, not_in_creative_inventory = 1, soil = 2,
grassland = 1, field = 1},
sounds = default.node_sound_dirt_defaults(),
soil = {
base = "default:dirt",
@ -74,9 +75,11 @@ minetest.register_node("farming:soil", {
minetest.register_node("farming:soil_wet", {
description = S("Wet Soil"),
tiles = {"default_dirt.png^farming_soil_wet.png", "default_dirt.png^farming_soil_wet_side.png"},
tiles = {"default_dirt.png^farming_soil_wet.png",
"default_dirt.png^farming_soil_wet_side.png"},
drop = "default:dirt",
groups = {crumbly=3, not_in_creative_inventory=1, soil=3, wet = 1, grassland = 1, field = 1},
groups = {crumbly = 3, not_in_creative_inventory = 1, soil = 3, wet = 1,
grassland = 1, field = 1},
sounds = default.node_sound_dirt_defaults(),
soil = {
base = "default:dirt",
@ -89,7 +92,8 @@ minetest.register_node("farming:dry_soil", {
description = S("Savanna Soil"),
tiles = {"default_dry_dirt.png^farming_soil.png", "default_dry_dirt.png"},
drop = "default:dry_dirt",
groups = {crumbly=3, not_in_creative_inventory=1, soil=2, grassland = 1, field = 1},
groups = {crumbly=3, not_in_creative_inventory=1, soil=2, grassland = 1,
field = 1},
sounds = default.node_sound_dirt_defaults(),
soil = {
base = "default:dry_dirt",
@ -100,9 +104,11 @@ minetest.register_node("farming:dry_soil", {
minetest.register_node("farming:dry_soil_wet", {
description = S("Wet Savanna Soil"),
tiles = {"default_dry_dirt.png^farming_soil_wet.png", "default_dry_dirt.png^farming_soil_wet_side.png"},
tiles = {"default_dry_dirt.png^farming_soil_wet.png",
"default_dry_dirt.png^farming_soil_wet_side.png"},
drop = "default:dry_dirt",
groups = {crumbly=3, not_in_creative_inventory=1, soil=3, wet = 1, grassland = 1, field = 1},
groups = {crumbly=3, not_in_creative_inventory=1, soil=3, wet = 1,
grassland = 1, field = 1},
sounds = default.node_sound_dirt_defaults(),
soil = {
base = "default:dry_dirt",
@ -123,7 +129,8 @@ minetest.register_node("farming:desert_sand_soil", {
description = S("Desert Sand Soil"),
drop = "default:desert_sand",
tiles = {"farming_desert_sand_soil.png", "default_desert_sand.png"},
groups = {crumbly=3, not_in_creative_inventory = 1, falling_node=1, sand=1, soil = 2, desert = 1, field = 1},
groups = {crumbly = 3, not_in_creative_inventory = 1, falling_node = 1,
sand = 1, soil = 2, desert = 1, field = 1},
sounds = default.node_sound_sand_defaults(),
soil = {
base = "default:desert_sand",
@ -135,8 +142,10 @@ minetest.register_node("farming:desert_sand_soil", {
minetest.register_node("farming:desert_sand_soil_wet", {
description = S("Wet Desert Sand Soil"),
drop = "default:desert_sand",
tiles = {"farming_desert_sand_soil_wet.png", "farming_desert_sand_soil_wet_side.png"},
groups = {crumbly=3, falling_node=1, sand=1, not_in_creative_inventory=1, soil=3, wet = 1, desert = 1, field = 1},
tiles = {"farming_desert_sand_soil_wet.png",
"farming_desert_sand_soil_wet_side.png"},
groups = {crumbly = 3, falling_node = 1, sand = 1, desert = 1,
not_in_creative_inventory = 1, soil = 3, wet = 1, field = 1},
sounds = default.node_sound_sand_defaults(),
soil = {
base = "default:desert_sand",
@ -182,49 +191,56 @@ minetest.register_abm({
interval = 15,
chance = 4,
action = function(pos, node)
local n_def = minetest.registered_nodes[node.name] or nil
local wet = n_def.soil.wet or nil
local base = n_def.soil.base or nil
local dry = n_def.soil.dry or nil
if not n_def or not n_def.soil or not wet or not base or not dry then
return
end
pos.y = pos.y + 1
local nn = minetest.get_node_or_nil(pos)
if not nn or not nn.name then
if not nn then
return
end
local nn_def = minetest.registered_nodes[nn.name] or nil
local nn_def = minetest.registered_nodes[nn.name]
pos.y = pos.y - 1
if nn_def and nn_def.walkable and minetest.get_item_group(nn.name, "plant") == 0 then
minetest.set_node(pos, {name = base})
local soil = minetest.registered_nodes[node.name].soil
assert(soil and soil.wet and soil.base and soil.dry,
"Field node " .. node.name .. " lacks of either 'wet', 'base' " ..
"or 'dry' properties.")
if nn_def and nn_def.walkable and
minetest.get_item_group(nn.name, "plant") == 0 then
node.name = soil.base
minetest.set_node(pos, node)
return
end
-- check if there is water nearby
local wet_lvl = minetest.get_item_group(node.name, "wet")
-- Make the node wet if water is near it
if minetest.find_node_near(pos, 3, {"group:water"}) then
-- if it is dry soil and not base node, turn it into wet soil
-- If it is dry soil and not base node, turn it into wet soil
if wet_lvl == 0 then
minetest.set_node(pos, {name = wet})
node.name = soil.wet
minetest.set_node(pos, node)
end
else
-- only turn back if there are no unloaded blocks (and therefore
-- possible water sources) nearby
if not minetest.find_node_near(pos, 3, {"ignore"}) then
-- turn it back into base if it is already dry
if wet_lvl == 0 then
-- only turn it back if there is no plant/seed on top of it
if minetest.get_item_group(nn.name, "plant") == 0 and minetest.get_item_group(nn.name, "seed") == 0 then
minetest.set_node(pos, {name = base})
return
end
-- if its wet turn it back into dry soil
-- Only dry out if there are no unloaded blocks (and therefore
-- possible water sources) nearby
if minetest.find_node_near(pos, 3, {"ignore"}) then
return
end
-- Turn it back into base if it is already dry and no plant/seed
-- is on top of it
if wet_lvl == 0 then
if minetest.get_item_group(nn.name, "plant") == 0 and
minetest.get_item_group(nn.name, "seed") == 0 then
node.name = soil.base
minetest.set_node(pos, node)
end
-- If it is wet turn it back into dry soil
elseif wet_lvl == 1 then
minetest.set_node(pos, {name = dry})
end
end
node.name = soil.dry
minetest.set_node(pos, node)
end
end,
})
@ -233,13 +249,15 @@ minetest.register_abm({
-- Make default:grass_* occasionally drop wheat seed
for i = 1, 5 do
minetest.override_item("default:grass_"..i, {drop = {
minetest.override_item("default:grass_" .. i, {
drop = {
max_items = 1,
items = {
{items = {"farming:seed_wheat"}, rarity = 5},
{items = {"default:grass_1"}},
}
}})
}
})
end
@ -250,13 +268,15 @@ end
-- This source is kept for now to avoid disruption but should probably be
-- removed in future as players get used to the new source.
minetest.override_item("default:junglegrass", {drop = {
minetest.override_item("default:junglegrass", {
drop = {
max_items = 1,
items = {
{items = {"farming:seed_cotton"}, rarity = 8},
{items = {"default:junglegrass"}},
}
}})
}
})
-- Wild cotton as a source of cotton seed