]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/game/item.lua
Fix Minetest blaming the wrong mod for errors (#12241)
[dragonfireclient.git] / builtin / game / item.lua
1 -- Minetest: builtin/item.lua
2
3 local builtin_shared = ...
4
5 local function copy_pointed_thing(pointed_thing)
6         return {
7                 type  = pointed_thing.type,
8                 above = vector.new(pointed_thing.above),
9                 under = vector.new(pointed_thing.under),
10                 ref   = pointed_thing.ref,
11         }
12 end
13
14 --
15 -- Item definition helpers
16 --
17
18 function core.get_pointed_thing_position(pointed_thing, above)
19         if pointed_thing.type == "node" then
20                 if above then
21                         -- The position where a node would be placed
22                         return pointed_thing.above
23                 end
24                 -- The position where a node would be dug
25                 return pointed_thing.under
26         elseif pointed_thing.type == "object" then
27                 return pointed_thing.ref and pointed_thing.ref:get_pos()
28         end
29 end
30
31 local function has_all_groups(tbl, required_groups)
32         if type(required_groups) == "string" then
33                 return (tbl[required_groups] or 0) ~= 0
34         end
35         for _, group in ipairs(required_groups) do
36                 if (tbl[group] or 0) == 0 then
37                         return false
38                 end
39         end
40         return true
41 end
42
43 function core.get_node_drops(node, toolname)
44         -- Compatibility, if node is string
45         local nodename = node
46         local param2 = 0
47         -- New format, if node is table
48         if (type(node) == "table") then
49                 nodename = node.name
50                 param2 = node.param2
51         end
52         local def = core.registered_nodes[nodename]
53         local drop = def and def.drop
54         local ptype = def and def.paramtype2
55         -- get color, if there is color (otherwise nil)
56         local palette_index = core.strip_param2_color(param2, ptype)
57         if drop == nil then
58                 -- default drop
59                 if palette_index then
60                         local stack = ItemStack(nodename)
61                         stack:get_meta():set_int("palette_index", palette_index)
62                         return {stack:to_string()}
63                 end
64                 return {nodename}
65         elseif type(drop) == "string" then
66                 -- itemstring drop
67                 return drop ~= "" and {drop} or {}
68         elseif drop.items == nil then
69                 -- drop = {} to disable default drop
70                 return {}
71         end
72
73         -- Extended drop table
74         local got_items = {}
75         local got_count = 0
76         for _, item in ipairs(drop.items) do
77                 local good_rarity = true
78                 local good_tool = true
79                 if item.rarity ~= nil then
80                         good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
81                 end
82                 if item.tools ~= nil or item.tool_groups ~= nil then
83                         good_tool = false
84                 end
85                 if item.tools ~= nil and toolname then
86                         for _, tool in ipairs(item.tools) do
87                                 if tool:sub(1, 1) == '~' then
88                                         good_tool = toolname:find(tool:sub(2)) ~= nil
89                                 else
90                                         good_tool = toolname == tool
91                                 end
92                                 if good_tool then
93                                         break
94                                 end
95                         end
96                 end
97                 if item.tool_groups ~= nil and toolname then
98                         local tooldef = core.registered_items[toolname]
99                         if tooldef ~= nil and type(tooldef.groups) == "table" then
100                                 if type(item.tool_groups) == "string" then
101                                         -- tool_groups can be a string which specifies the required group
102                                         good_tool = core.get_item_group(toolname, item.tool_groups) ~= 0
103                                 else
104                                         -- tool_groups can be a list of sufficient requirements.
105                                         -- i.e. if any item in the list can be satisfied then the tool is good
106                                         assert(type(item.tool_groups) == "table")
107                                         for _, required_groups in ipairs(item.tool_groups) do
108                                                 -- required_groups can be either a string (a single group),
109                                                 -- or an array of strings where all must be in tooldef.groups
110                                                 good_tool = has_all_groups(tooldef.groups, required_groups)
111                                                 if good_tool then
112                                                         break
113                                                 end
114                                         end
115                                 end
116                         end
117                 end
118                 if good_rarity and good_tool then
119                         got_count = got_count + 1
120                         for _, add_item in ipairs(item.items) do
121                                 -- add color, if necessary
122                                 if item.inherit_color and palette_index then
123                                         local stack = ItemStack(add_item)
124                                         stack:get_meta():set_int("palette_index", palette_index)
125                                         add_item = stack:to_string()
126                                 end
127                                 got_items[#got_items+1] = add_item
128                         end
129                         if drop.max_items ~= nil and got_count == drop.max_items then
130                                 break
131                         end
132                 end
133         end
134         return got_items
135 end
136
137 local function user_name(user)
138         return user and user:get_player_name() or ""
139 end
140
141 -- Returns a logging function. For empty names, does not log.
142 local function make_log(name)
143         return name ~= "" and core.log or function() end
144 end
145
146 function core.item_place_node(itemstack, placer, pointed_thing, param2,
147                 prevent_after_place)
148         local def = itemstack:get_definition()
149         if def.type ~= "node" or pointed_thing.type ~= "node" then
150                 return itemstack, nil
151         end
152
153         local under = pointed_thing.under
154         local oldnode_under = core.get_node_or_nil(under)
155         local above = pointed_thing.above
156         local oldnode_above = core.get_node_or_nil(above)
157         local playername = user_name(placer)
158         local log = make_log(playername)
159
160         if not oldnode_under or not oldnode_above then
161                 log("info", playername .. " tried to place"
162                         .. " node in unloaded position " .. core.pos_to_string(above))
163                 return itemstack, nil
164         end
165
166         local olddef_under = core.registered_nodes[oldnode_under.name]
167         olddef_under = olddef_under or core.nodedef_default
168         local olddef_above = core.registered_nodes[oldnode_above.name]
169         olddef_above = olddef_above or core.nodedef_default
170
171         if not olddef_above.buildable_to and not olddef_under.buildable_to then
172                 log("info", playername .. " tried to place"
173                         .. " node in invalid position " .. core.pos_to_string(above)
174                         .. ", replacing " .. oldnode_above.name)
175                 return itemstack, nil
176         end
177
178         -- Place above pointed node
179         local place_to = vector.new(above)
180
181         -- If node under is buildable_to, place into it instead (eg. snow)
182         if olddef_under.buildable_to then
183                 log("info", "node under is buildable to")
184                 place_to = vector.new(under)
185         end
186
187         if core.is_protected(place_to, playername) then
188                 log("action", playername
189                                 .. " tried to place " .. def.name
190                                 .. " at protected position "
191                                 .. core.pos_to_string(place_to))
192                 core.record_protection_violation(place_to, playername)
193                 return itemstack, nil
194         end
195
196         local oldnode = core.get_node(place_to)
197         local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
198
199         -- Calculate direction for wall mounted stuff like torches and signs
200         if def.place_param2 ~= nil then
201                 newnode.param2 = def.place_param2
202         elseif (def.paramtype2 == "wallmounted" or
203                         def.paramtype2 == "colorwallmounted") and not param2 then
204                 local dir = vector.subtract(under, above)
205                 newnode.param2 = core.dir_to_wallmounted(dir)
206         -- Calculate the direction for furnaces and chests and stuff
207         elseif (def.paramtype2 == "facedir" or
208                         def.paramtype2 == "colorfacedir") and not param2 then
209                 local placer_pos = placer and placer:get_pos()
210                 if placer_pos then
211                         local dir = vector.subtract(above, placer_pos)
212                         newnode.param2 = core.dir_to_facedir(dir)
213                         log("info", "facedir: " .. newnode.param2)
214                 end
215         end
216
217         local metatable = itemstack:get_meta():to_table().fields
218
219         -- Transfer color information
220         if metatable.palette_index and not def.place_param2 then
221                 local color_divisor = nil
222                 if def.paramtype2 == "color" then
223                         color_divisor = 1
224                 elseif def.paramtype2 == "colorwallmounted" then
225                         color_divisor = 8
226                 elseif def.paramtype2 == "colorfacedir" then
227                         color_divisor = 32
228                 elseif def.paramtype2 == "colordegrotate" then
229                         color_divisor = 32
230                 end
231                 if color_divisor then
232                         local color = math.floor(metatable.palette_index / color_divisor)
233                         local other = newnode.param2 % color_divisor
234                         newnode.param2 = color * color_divisor + other
235                 end
236         end
237
238         -- Check if the node is attached and if it can be placed there
239         if core.get_item_group(def.name, "attached_node") ~= 0 and
240                 not builtin_shared.check_attached_node(place_to, newnode) then
241                 log("action", "attached node " .. def.name ..
242                         " can not be placed at " .. core.pos_to_string(place_to))
243                 return itemstack, nil
244         end
245
246         log("action", playername .. " places node "
247                 .. def.name .. " at " .. core.pos_to_string(place_to))
248
249         -- Add node and update
250         core.add_node(place_to, newnode)
251
252         -- Play sound if it was done by a player
253         if playername ~= "" and def.sounds and def.sounds.place then
254                 core.sound_play(def.sounds.place, {
255                         pos = place_to,
256                         exclude_player = playername,
257                 }, true)
258         end
259
260         local take_item = true
261
262         -- Run callback
263         if def.after_place_node and not prevent_after_place then
264                 -- Deepcopy place_to and pointed_thing because callback can modify it
265                 local place_to_copy = vector.new(place_to)
266                 local pointed_thing_copy = copy_pointed_thing(pointed_thing)
267                 if def.after_place_node(place_to_copy, placer, itemstack,
268                                 pointed_thing_copy) then
269                         take_item = false
270                 end
271         end
272
273         -- Run script hook
274         for _, callback in ipairs(core.registered_on_placenodes) do
275                 -- Deepcopy pos, node and pointed_thing because callback can modify them
276                 local place_to_copy = vector.new(place_to)
277                 local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
278                 local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
279                 local pointed_thing_copy = copy_pointed_thing(pointed_thing)
280                 if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
281                         take_item = false
282                 end
283         end
284
285         if take_item then
286                 itemstack:take_item()
287         end
288         return itemstack, place_to
289 end
290
291 -- deprecated, item_place does not call this
292 function core.item_place_object(itemstack, placer, pointed_thing)
293         local pos = core.get_pointed_thing_position(pointed_thing, true)
294         if pos ~= nil then
295                 local item = itemstack:take_item()
296                 core.add_item(pos, item)
297         end
298         return itemstack
299 end
300
301 function core.item_place(itemstack, placer, pointed_thing, param2)
302         -- Call on_rightclick if the pointed node defines it
303         if pointed_thing.type == "node" and placer and
304                         not placer:get_player_control().sneak then
305                 local n = core.get_node(pointed_thing.under)
306                 local nn = n.name
307                 if core.registered_nodes[nn] and core.registered_nodes[nn].on_rightclick then
308                         return core.registered_nodes[nn].on_rightclick(pointed_thing.under, n,
309                                         placer, itemstack, pointed_thing) or itemstack, nil
310                 end
311         end
312
313         -- Place if node, otherwise do nothing
314         if itemstack:get_definition().type == "node" then
315                 return core.item_place_node(itemstack, placer, pointed_thing, param2)
316         end
317         return itemstack, nil
318 end
319
320 function core.item_secondary_use(itemstack, placer)
321         return itemstack
322 end
323
324 function core.item_drop(itemstack, dropper, pos)
325         local dropper_is_player = dropper and dropper:is_player()
326         local p = table.copy(pos)
327         local cnt = itemstack:get_count()
328         if dropper_is_player then
329                 p.y = p.y + 1.2
330         end
331         local item = itemstack:take_item(cnt)
332         local obj = core.add_item(p, item)
333         if obj then
334                 if dropper_is_player then
335                         local dir = dropper:get_look_dir()
336                         dir.x = dir.x * 2.9
337                         dir.y = dir.y * 2.9 + 2
338                         dir.z = dir.z * 2.9
339                         obj:set_velocity(dir)
340                         obj:get_luaentity().dropped_by = dropper:get_player_name()
341                 end
342                 return itemstack
343         end
344         -- If we reach this, adding the object to the
345         -- environment failed
346 end
347
348 function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
349         for _, callback in pairs(core.registered_on_item_eats) do
350                 local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
351                 if result then
352                         return result
353                 end
354         end
355         -- read definition before potentially emptying the stack
356         local def = itemstack:get_definition()
357         if itemstack:take_item():is_empty() then
358                 return itemstack
359         end
360
361         if def and def.sound and def.sound.eat then
362                 core.sound_play(def.sound.eat, {
363                         pos = user:get_pos(),
364                         max_hear_distance = 16
365                 }, true)
366         end
367
368         -- Changing hp might kill the player causing mods to do who-knows-what to the
369         -- inventory, so do this before set_hp().
370         if replace_with_item then
371                 if itemstack:is_empty() then
372                         itemstack:add_item(replace_with_item)
373                 else
374                         local inv = user:get_inventory()
375                         -- Check if inv is null, since non-players don't have one
376                         if inv and inv:room_for_item("main", {name=replace_with_item}) then
377                                 inv:add_item("main", replace_with_item)
378                         else
379                                 local pos = user:get_pos()
380                                 pos.y = math.floor(pos.y + 0.5)
381                                 core.add_item(pos, replace_with_item)
382                         end
383                 end
384         end
385         user:set_wielded_item(itemstack)
386
387         user:set_hp(user:get_hp() + hp_change)
388
389         return nil -- don't overwrite wield item a second time
390 end
391
392 function core.item_eat(hp_change, replace_with_item)
393         return function(itemstack, user, pointed_thing)  -- closure
394                 if user then
395                         return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
396                 end
397         end
398 end
399
400 function core.node_punch(pos, node, puncher, pointed_thing)
401         -- Run script hook
402         for _, callback in ipairs(core.registered_on_punchnodes) do
403                 -- Copy pos and node because callback can modify them
404                 local pos_copy = vector.new(pos)
405                 local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
406                 local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
407                 callback(pos_copy, node_copy, puncher, pointed_thing_copy)
408         end
409 end
410
411 function core.handle_node_drops(pos, drops, digger)
412         -- Add dropped items to object's inventory
413         local inv = digger and digger:get_inventory()
414         local give_item
415         if inv then
416                 give_item = function(item)
417                         return inv:add_item("main", item)
418                 end
419         else
420                 give_item = function(item)
421                         -- itemstring to ItemStack for left:is_empty()
422                         return ItemStack(item)
423                 end
424         end
425
426         for _, dropped_item in pairs(drops) do
427                 local left = give_item(dropped_item)
428                 if not left:is_empty() then
429                         local p = vector.offset(pos,
430                                 math.random()/2-0.25,
431                                 math.random()/2-0.25,
432                                 math.random()/2-0.25
433                         )
434                         core.add_item(p, left)
435                 end
436         end
437 end
438
439 function core.node_dig(pos, node, digger)
440         local diggername = user_name(digger)
441         local log = make_log(diggername)
442         local def = core.registered_nodes[node.name]
443         -- Copy pos because the callback could modify it
444         if def and (not def.diggable or
445                         (def.can_dig and not def.can_dig(vector.new(pos), digger))) then
446                 log("info", diggername .. " tried to dig "
447                         .. node.name .. " which is not diggable "
448                         .. core.pos_to_string(pos))
449                 return false
450         end
451
452         if core.is_protected(pos, diggername) then
453                 log("action", diggername
454                                 .. " tried to dig " .. node.name
455                                 .. " at protected position "
456                                 .. core.pos_to_string(pos))
457                 core.record_protection_violation(pos, diggername)
458                 return false
459         end
460
461         log('action', diggername .. " digs "
462                 .. node.name .. " at " .. core.pos_to_string(pos))
463
464         local wielded = digger and digger:get_wielded_item()
465         local drops = core.get_node_drops(node, wielded and wielded:get_name())
466
467         if wielded then
468                 local wdef = wielded:get_definition()
469                 local tp = wielded:get_tool_capabilities()
470                 local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
471                 if wdef and wdef.after_use then
472                         wielded = wdef.after_use(wielded, digger, node, dp) or wielded
473                 else
474                         -- Wear out tool
475                         if not core.is_creative_enabled(diggername) then
476                                 wielded:add_wear(dp.wear)
477                                 if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then
478                                         core.sound_play(wdef.sound.breaks, {
479                                                 pos = pos,
480                                                 gain = 0.5
481                                         }, true)
482                                 end
483                         end
484                 end
485                 digger:set_wielded_item(wielded)
486         end
487
488         -- Check to see if metadata should be preserved.
489         if def and def.preserve_metadata then
490                 local oldmeta = core.get_meta(pos):to_table().fields
491                 -- Copy pos and node because the callback can modify them.
492                 local pos_copy = vector.new(pos)
493                 local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
494                 local drop_stacks = {}
495                 for k, v in pairs(drops) do
496                         drop_stacks[k] = ItemStack(v)
497                 end
498                 drops = drop_stacks
499                 def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
500         end
501
502         -- Handle drops
503         core.handle_node_drops(pos, drops, digger)
504
505         local oldmetadata = nil
506         if def and def.after_dig_node then
507                 oldmetadata = core.get_meta(pos):to_table()
508         end
509
510         -- Remove node and update
511         core.remove_node(pos)
512
513         -- Play sound if it was done by a player
514         if diggername ~= "" and def and def.sounds and def.sounds.dug then
515                 core.sound_play(def.sounds.dug, {
516                         pos = pos,
517                         exclude_player = diggername,
518                 }, true)
519         end
520
521         -- Run callback
522         if def and def.after_dig_node then
523                 -- Copy pos and node because callback can modify them
524                 local pos_copy = vector.new(pos)
525                 local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
526                 def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
527         end
528
529         -- Run script hook
530         for _, callback in ipairs(core.registered_on_dignodes) do
531                 local origin = core.callback_origins[callback]
532                 core.set_last_run_mod(origin.mod)
533
534                 -- Copy pos and node because callback can modify them
535                 local pos_copy = vector.new(pos)
536                 local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
537                 callback(pos_copy, node_copy, digger)
538         end
539
540         return true
541 end
542
543 function core.itemstring_with_palette(item, palette_index)
544         local stack = ItemStack(item) -- convert to ItemStack
545         stack:get_meta():set_int("palette_index", palette_index)
546         return stack:to_string()
547 end
548
549 function core.itemstring_with_color(item, colorstring)
550         local stack = ItemStack(item) -- convert to ItemStack
551         stack:get_meta():set_string("color", colorstring)
552         return stack:to_string()
553 end
554
555 -- This is used to allow mods to redefine core.item_place and so on
556 -- NOTE: This is not the preferred way. Preferred way is to provide enough
557 --       callbacks to not require redefining global functions. -celeron55
558 local function redef_wrapper(table, name)
559         return function(...)
560                 return table[name](...)
561         end
562 end
563
564 --
565 -- Item definition defaults
566 --
567
568 local default_stack_max = tonumber(core.settings:get("default_stack_max")) or 99
569
570 core.nodedef_default = {
571         -- Item properties
572         type="node",
573         -- name intentionally not defined here
574         description = "",
575         groups = {},
576         inventory_image = "",
577         wield_image = "",
578         wield_scale = vector.new(1, 1, 1),
579         stack_max = default_stack_max,
580         usable = false,
581         liquids_pointable = false,
582         tool_capabilities = nil,
583         node_placement_prediction = nil,
584
585         -- Interaction callbacks
586         on_place = redef_wrapper(core, 'item_place'), -- core.item_place
587         on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
588         on_use = nil,
589         can_dig = nil,
590
591         on_punch = redef_wrapper(core, 'node_punch'), -- core.node_punch
592         on_rightclick = nil,
593         on_dig = redef_wrapper(core, 'node_dig'), -- core.node_dig
594
595         on_receive_fields = nil,
596
597         -- Node properties
598         drawtype = "normal",
599         visual_scale = 1.0,
600         -- Don't define these because otherwise the old tile_images and
601         -- special_materials wouldn't be read
602         --tiles ={""},
603         --special_tiles = {
604         --      {name="", backface_culling=true},
605         --      {name="", backface_culling=true},
606         --},
607         post_effect_color = {a=0, r=0, g=0, b=0},
608         paramtype = "none",
609         paramtype2 = "none",
610         is_ground_content = true,
611         sunlight_propagates = false,
612         walkable = true,
613         pointable = true,
614         diggable = true,
615         climbable = false,
616         buildable_to = false,
617         floodable = false,
618         liquidtype = "none",
619         liquid_alternative_flowing = "",
620         liquid_alternative_source = "",
621         liquid_viscosity = 0,
622         drowning = 0,
623         light_source = 0,
624         damage_per_second = 0,
625         selection_box = {type="regular"},
626         legacy_facedir_simple = false,
627         legacy_wallmounted = false,
628 }
629
630 core.craftitemdef_default = {
631         type="craft",
632         -- name intentionally not defined here
633         description = "",
634         groups = {},
635         inventory_image = "",
636         wield_image = "",
637         wield_scale = vector.new(1, 1, 1),
638         stack_max = default_stack_max,
639         liquids_pointable = false,
640         tool_capabilities = nil,
641
642         -- Interaction callbacks
643         on_place = redef_wrapper(core, 'item_place'), -- core.item_place
644         on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
645         on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
646         on_use = nil,
647 }
648
649 core.tooldef_default = {
650         type="tool",
651         -- name intentionally not defined here
652         description = "",
653         groups = {},
654         inventory_image = "",
655         wield_image = "",
656         wield_scale = vector.new(1, 1, 1),
657         stack_max = 1,
658         liquids_pointable = false,
659         tool_capabilities = nil,
660
661         -- Interaction callbacks
662         on_place = redef_wrapper(core, 'item_place'), -- core.item_place
663         on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
664         on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
665         on_use = nil,
666 }
667
668 core.noneitemdef_default = {  -- This is used for the hand and unknown items
669         type="none",
670         -- name intentionally not defined here
671         description = "",
672         groups = {},
673         inventory_image = "",
674         wield_image = "",
675         wield_scale = vector.new(1, 1, 1),
676         stack_max = default_stack_max,
677         liquids_pointable = false,
678         tool_capabilities = nil,
679
680         -- Interaction callbacks
681         on_place = redef_wrapper(core, 'item_place'),
682         on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
683         on_drop = nil,
684         on_use = nil,
685 }