Add nil checks for placer
[oweals/minetest_game.git] / mods / farming / api.lua
1
2 -- Wear out hoes, place soil
3 -- TODO Ignore group:flower
4 farming.registered_plants = {}
5
6 farming.hoe_on_use = function(itemstack, user, pointed_thing, uses)
7         local pt = pointed_thing
8         -- check if pointing at a node
9         if not pt then
10                 return
11         end
12         if pt.type ~= "node" then
13                 return
14         end
15
16         local under = minetest.get_node(pt.under)
17         local p = {x=pt.under.x, y=pt.under.y+1, z=pt.under.z}
18         local above = minetest.get_node(p)
19
20         -- return if any of the nodes is not registered
21         if not minetest.registered_nodes[under.name] then
22                 return
23         end
24         if not minetest.registered_nodes[above.name] then
25                 return
26         end
27
28         -- check if the node above the pointed thing is air
29         if above.name ~= "air" then
30                 return
31         end
32
33         -- check if pointing at soil
34         if minetest.get_item_group(under.name, "soil") ~= 1 then
35                 return
36         end
37
38         -- check if (wet) soil defined
39         local regN = minetest.registered_nodes
40         if regN[under.name].soil == nil or regN[under.name].soil.wet == nil or regN[under.name].soil.dry == nil then
41                 return
42         end
43
44         if minetest.is_protected(pt.under, user:get_player_name()) then
45                 minetest.record_protection_violation(pt.under, user:get_player_name())
46                 return
47         end
48         if minetest.is_protected(pt.above, user:get_player_name()) then
49                 minetest.record_protection_violation(pt.above, user:get_player_name())
50                 return
51         end
52
53         -- turn the node into soil and play sound
54         minetest.set_node(pt.under, {name = regN[under.name].soil.dry})
55         minetest.sound_play("default_dig_crumbly", {
56                 pos = pt.under,
57                 gain = 0.5,
58         })
59
60         if not (creative and creative.is_enabled_for
61                         and creative.is_enabled_for(user:get_player_name())) then
62                 -- wear tool
63                 local wdef = itemstack:get_definition()
64                 itemstack:add_wear(65535/(uses-1))
65                 -- tool break sound
66                 if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
67                         minetest.sound_play(wdef.sound.breaks, {pos = pt.above, gain = 0.5})
68                 end
69         end
70         return itemstack
71 end
72
73 -- Register new hoes
74 farming.register_hoe = function(name, def)
75         -- Check for : prefix (register new hoes in your mod's namespace)
76         if name:sub(1,1) ~= ":" then
77                 name = ":" .. name
78         end
79         -- Check def table
80         if def.description == nil then
81                 def.description = "Hoe"
82         end
83         if def.inventory_image == nil then
84                 def.inventory_image = "unknown_item.png"
85         end
86         if def.recipe == nil then
87                 def.recipe = {
88                         {"air","air",""},
89                         {"","group:stick",""},
90                         {"","group:stick",""}
91                 }
92         end
93         if def.max_uses == nil then
94                 def.max_uses = 30
95         end
96         -- Register the tool
97         minetest.register_tool(name, {
98                 description = def.description,
99                 inventory_image = def.inventory_image,
100                 on_use = function(itemstack, user, pointed_thing)
101                         return farming.hoe_on_use(itemstack, user, pointed_thing, def.max_uses)
102                 end,
103                 groups = def.groups,
104                 sound = {breaks = "default_tool_breaks"},
105         })
106         -- Register its recipe
107         if def.material == nil then
108                 minetest.register_craft({
109                         output = name:sub(2),
110                         recipe = def.recipe
111                 })
112         else
113                 minetest.register_craft({
114                         output = name:sub(2),
115                         recipe = {
116                                 {def.material, def.material, ""},
117                                 {"", "group:stick", ""},
118                                 {"", "group:stick", ""}
119                         }
120                 })
121         end
122 end
123
124 -- how often node timers for plants will tick, +/- some random value
125 local function tick(pos)
126         minetest.get_node_timer(pos):start(math.random(166, 286))
127 end
128 -- how often a growth failure tick is retried (e.g. too dark)
129 local function tick_again(pos)
130         minetest.get_node_timer(pos):start(math.random(40, 80))
131 end
132
133 -- Seed placement
134 farming.place_seed = function(itemstack, placer, pointed_thing, plantname)
135         local pt = pointed_thing
136         -- check if pointing at a node
137         if not pt then
138                 return itemstack
139         end
140         if pt.type ~= "node" then
141                 return itemstack
142         end
143
144         local under = minetest.get_node(pt.under)
145         local above = minetest.get_node(pt.above)
146
147         local player_name = placer and placer:get_player_name() or ""
148
149         if minetest.is_protected(pt.under, player_name) then
150                 minetest.record_protection_violation(pt.under, player_name)
151                 return
152         end
153         if minetest.is_protected(pt.above, player_name) then
154                 minetest.record_protection_violation(pt.above, player_name)
155                 return
156         end
157
158         -- return if any of the nodes is not registered
159         if not minetest.registered_nodes[under.name] then
160                 return itemstack
161         end
162         if not minetest.registered_nodes[above.name] then
163                 return itemstack
164         end
165
166         -- check if pointing at the top of the node
167         if pt.above.y ~= pt.under.y+1 then
168                 return itemstack
169         end
170
171         -- check if you can replace the node above the pointed node
172         if not minetest.registered_nodes[above.name].buildable_to then
173                 return itemstack
174         end
175
176         -- check if pointing at soil
177         if minetest.get_item_group(under.name, "soil") < 2 then
178                 return itemstack
179         end
180
181         -- add the node and remove 1 item from the itemstack
182         minetest.add_node(pt.above, {name = plantname, param2 = 1})
183         tick(pt.above)
184         if not (creative and creative.is_enabled_for
185                         and creative.is_enabled_for(player_name)) then
186                 itemstack:take_item()
187         end
188         return itemstack
189 end
190
191 farming.grow_plant = function(pos, elapsed)
192         local node = minetest.get_node(pos)
193         local name = node.name
194         local def = minetest.registered_nodes[name]
195
196         if not def.next_plant then
197                 -- disable timer for fully grown plant
198                 return
199         end
200
201         -- grow seed
202         if minetest.get_item_group(node.name, "seed") and def.fertility then
203                 local soil_node = minetest.get_node_or_nil({x = pos.x, y = pos.y - 1, z = pos.z})
204                 if not soil_node then
205                         tick_again(pos)
206                         return
207                 end
208                 -- omitted is a check for light, we assume seeds can germinate in the dark.
209                 for _, v in pairs(def.fertility) do
210                         if minetest.get_item_group(soil_node.name, v) ~= 0 then
211                                 local placenode = {name = def.next_plant}
212                                 if def.place_param2 then
213                                         placenode.param2 = def.place_param2
214                                 end
215                                 minetest.swap_node(pos, placenode)
216                                 if minetest.registered_nodes[def.next_plant].next_plant then
217                                         tick(pos)
218                                         return
219                                 end
220                         end
221                 end
222
223                 return
224         end
225
226         -- check if on wet soil
227         local below = minetest.get_node({x = pos.x, y = pos.y - 1, z = pos.z})
228         if minetest.get_item_group(below.name, "soil") < 3 then
229                 tick_again(pos)
230                 return
231         end
232
233         -- check light
234         local light = minetest.get_node_light(pos)
235         if not light or light < def.minlight or light > def.maxlight then
236                 tick_again(pos)
237                 return
238         end
239
240         -- grow
241         local placenode = {name = def.next_plant}
242         if def.place_param2 then
243                 placenode.param2 = def.place_param2
244         end
245         minetest.swap_node(pos, placenode)
246
247         -- new timer needed?
248         if minetest.registered_nodes[def.next_plant].next_plant then
249                 tick(pos)
250         end
251         return
252 end
253
254 -- Register plants
255 farming.register_plant = function(name, def)
256         local mname = name:split(":")[1]
257         local pname = name:split(":")[2]
258
259         -- Check def table
260         if not def.description then
261                 def.description = "Seed"
262         end
263         if not def.inventory_image then
264                 def.inventory_image = "unknown_item.png"
265         end
266         if not def.steps then
267                 return nil
268         end
269         if not def.minlight then
270                 def.minlight = 1
271         end
272         if not def.maxlight then
273                 def.maxlight = 14
274         end
275         if not def.fertility then
276                 def.fertility = {}
277         end
278
279         farming.registered_plants[pname] = def
280
281         -- Register seed
282         local lbm_nodes = {mname .. ":seed_" .. pname}
283         local g = {seed = 1, snappy = 3, attached_node = 1, flammable = 2}
284         for k, v in pairs(def.fertility) do
285                 g[v] = 1
286         end
287         minetest.register_node(":" .. mname .. ":seed_" .. pname, {
288                 description = def.description,
289                 tiles = {def.inventory_image},
290                 inventory_image = def.inventory_image,
291                 wield_image = def.inventory_image,
292                 drawtype = "signlike",
293                 groups = g,
294                 paramtype = "light",
295                 paramtype2 = "wallmounted",
296                 place_param2 = def.place_param2 or nil, -- this isn't actually used for placement
297                 walkable = false,
298                 sunlight_propagates = true,
299                 selection_box = {
300                         type = "fixed",
301                         fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
302                 },
303                 fertility = def.fertility,
304                 sounds = default.node_sound_dirt_defaults({
305                         dig = {name = "", gain = 0},
306                         dug = {name = "default_grass_footstep", gain = 0.2},
307                         place = {name = "default_place_node", gain = 0.25},
308                 }),
309
310                 on_place = function(itemstack, placer, pointed_thing)
311                         local under = pointed_thing.under
312                         local node = minetest.get_node(under)
313                         local udef = minetest.registered_nodes[node.name]
314                         if udef and udef.on_rightclick and
315                                         not (placer and placer:is_player() and
316                                         placer:get_player_control().sneak) then
317                                 return udef.on_rightclick(under, node, placer, itemstack,
318                                         pointed_thing) or itemstack
319                         end
320
321                         return farming.place_seed(itemstack, placer, pointed_thing, mname .. ":seed_" .. pname)
322                 end,
323                 next_plant = mname .. ":" .. pname .. "_1",
324                 on_timer = farming.grow_plant,
325                 minlight = def.minlight,
326                 maxlight = def.maxlight,
327         })
328
329         -- Register harvest
330         minetest.register_craftitem(":" .. mname .. ":" .. pname, {
331                 description = pname:gsub("^%l", string.upper),
332                 inventory_image = mname .. "_" .. pname .. ".png",
333                 groups = {flammable = 2},
334         })
335
336         -- Register growing steps
337         for i = 1, def.steps do
338                 local base_rarity = 1
339                 if def.steps ~= 1 then
340                         base_rarity =  8 - (i - 1) * 7 / (def.steps - 1)
341                 end
342                 local drop = {
343                         items = {
344                                 {items = {mname .. ":" .. pname}, rarity = base_rarity},
345                                 {items = {mname .. ":" .. pname}, rarity = base_rarity * 2},
346                                 {items = {mname .. ":seed_" .. pname}, rarity = base_rarity},
347                                 {items = {mname .. ":seed_" .. pname}, rarity = base_rarity * 2},
348                         }
349                 }
350                 local nodegroups = {snappy = 3, flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1}
351                 nodegroups[pname] = i
352
353                 local next_plant = nil
354
355                 if i < def.steps then
356                         next_plant = mname .. ":" .. pname .. "_" .. (i + 1)
357                         lbm_nodes[#lbm_nodes + 1] = mname .. ":" .. pname .. "_" .. i
358                 end
359
360                 minetest.register_node(":" .. mname .. ":" .. pname .. "_" .. i, {
361                         drawtype = "plantlike",
362                         waving = 1,
363                         tiles = {mname .. "_" .. pname .. "_" .. i .. ".png"},
364                         paramtype = "light",
365                         paramtype2 = def.paramtype2 or nil,
366                         place_param2 = def.place_param2 or nil,
367                         walkable = false,
368                         buildable_to = true,
369                         drop = drop,
370                         selection_box = {
371                                 type = "fixed",
372                                 fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},
373                         },
374                         groups = nodegroups,
375                         sounds = default.node_sound_leaves_defaults(),
376                         next_plant = next_plant,
377                         on_timer = farming.grow_plant,
378                         minlight = def.minlight,
379                         maxlight = def.maxlight,
380                 })
381         end
382
383         -- replacement LBM for pre-nodetimer plants
384         minetest.register_lbm({
385                 name = ":" .. mname .. ":start_nodetimer_" .. pname,
386                 nodenames = lbm_nodes,
387                 action = function(pos, node)
388                         tick_again(pos)
389                 end,
390         })
391
392         -- Return
393         local r = {
394                 seed = mname .. ":seed_" .. pname,
395                 harvest = mname .. ":" .. pname
396         }
397         return r
398 end