diff --git a/game_api.txt b/game_api.txt index bddf7e27..652c2696 100644 --- a/game_api.txt +++ b/game_api.txt @@ -165,11 +165,11 @@ Doors API The doors mod allows modders to register custom doors and trapdoors. -`doors.registered_doors[name] = Door definition` - * Table of registered doors, indexed by door name +`doors.registered_doors[name] = Door definition` + * Table of registered doors, indexed by door name -`doors.registered_trapdoors[name] = Trapdoor definition` - * Table of registered trap doors, indexed by trap door name +`doors.registered_trapdoors[name] = Trapdoor definition` + * Table of registered trap doors, indexed by trap door name `doors.register_door(name, def)` @@ -212,7 +212,7 @@ The doors mod allows modders to register custom doors and trapdoors. * `pos` Position of the door * `node` Node definition * `clicker` Player definition for the player that clicked on the door - + ### Door definition description = "Door description", @@ -226,7 +226,7 @@ The doors mod allows modders to register custom doors and trapdoors. sound_open = sound play for open door, -- optional sound_close = sound play for close door, -- optional protected = false, -- If true, only placer can open the door (locked for others) - on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) -- optional function containing the on_rightclick callback, defaults to a doors.door_toggle-wrapper ### Trapdoor definition @@ -245,9 +245,9 @@ The doors mod allows modders to register custom doors and trapdoors. sound_open = sound play for open door, -- optional sound_close = sound play for close door, -- optional protected = false, -- If true, only placer can open the door (locked for others) - on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) -- function containing the on_rightclick callback - on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) -- function containing the on_rightclick callback ### Fence gate definition @@ -258,7 +258,7 @@ The doors mod allows modders to register custom doors and trapdoors. material = "default:wood", groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2}, sounds = default.node_sound_wood_defaults(), -- optional - on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) -- function containing the on_rightclick callback @@ -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 } diff --git a/mods/farming/api.lua b/mods/farming/api.lua index 91d557c7..b4a6b782 100644 --- a/mods/farming/api.lua +++ b/mods/farming/api.lua @@ -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 - -- wear tool + 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 diff --git a/mods/farming/mod.conf b/mods/farming/mod.conf index 9a76a6a7..f60df82e 100644 --- a/mods/farming/mod.conf +++ b/mods/farming/mod.conf @@ -1,4 +1,4 @@ name = farming description = Minetest Game mod: farming depends = default, wool, stairs -optional_depends = dungeon_loot +optional_depends = dungeon_loot, creative diff --git a/mods/farming/nodes.lua b/mods/farming/nodes.lua index b5f90f96..f42e3e3b 100644 --- a/mods/farming/nodes.lua +++ b/mods/farming/nodes.lua @@ -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") - 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 wet_lvl == 0 then - minetest.set_node(pos, {name = wet}) - 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}) - end - -- if its wet turn it back into dry soil - elseif wet_lvl == 1 then - minetest.set_node(pos, {name = dry}) - end + 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 wet_lvl == 0 then + node.name = soil.wet + minetest.set_node(pos, node) end + return + end + + -- 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 + 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 = { - max_items = 1, - items = { - {items = {"farming:seed_wheat"}, rarity = 5}, - {items = {"default:grass_1"}}, + 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 = { - max_items = 1, - items = { - {items = {"farming:seed_cotton"}, rarity = 8}, - {items = {"default:junglegrass"}}, +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