-- Minetest: builtin/item.lua
+local builtin_shared = ...
+
local function copy_pointed_thing(pointed_thing)
return {
type = pointed_thing.type,
if above then
-- The position where a node would be placed
return pointed_thing.above
- else
- -- The position where a node would be dug
- return pointed_thing.under
end
+ -- The position where a node would be dug
+ return pointed_thing.under
elseif pointed_thing.type == "object" then
- obj = pointed_thing.ref
- if obj ~= nil then
- return obj:getpos()
- else
- return nil
- end
- else
- return nil
+ return pointed_thing.ref and pointed_thing.ref:get_pos()
end
end
end
end
+-- Table of possible dirs
+local facedir_to_dir = {
+ {x= 0, y=0, z= 1},
+ {x= 1, y=0, z= 0},
+ {x= 0, y=0, z=-1},
+ {x=-1, y=0, z= 0},
+ {x= 0, y=-1, z= 0},
+ {x= 0, y=1, z= 0},
+}
+-- Mapping from facedir value to index in facedir_to_dir.
+local facedir_to_dir_map = {
+ [0]=1, 2, 3, 4,
+ 5, 2, 6, 4,
+ 6, 2, 5, 4,
+ 1, 5, 3, 6,
+ 1, 6, 3, 5,
+ 1, 4, 3, 2,
+}
function core.facedir_to_dir(facedir)
- --a table of possible dirs
- return ({{x=0, y=0, z=1},
- {x=1, y=0, z=0},
- {x=0, y=0, z=-1},
- {x=-1, y=0, z=0},
- {x=0, y=-1, z=0},
- {x=0, y=1, z=0}})
-
- --indexed into by a table of correlating facedirs
- [({[0]=1, 2, 3, 4,
- 5, 2, 6, 4,
- 6, 2, 5, 4,
- 1, 5, 3, 6,
- 1, 6, 3, 5,
- 1, 4, 3, 2})
-
- --indexed into by the facedir in question
- [facedir]]
+ return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
end
function core.dir_to_wallmounted(dir)
end
end
-function core.get_node_drops(nodename, toolname)
- local drop = ItemStack({name=nodename}):get_definition().drop
+-- table of dirs in wallmounted order
+local wallmounted_to_dir = {
+ [0] = {x = 0, y = 1, z = 0},
+ {x = 0, y = -1, z = 0},
+ {x = 1, y = 0, z = 0},
+ {x = -1, y = 0, z = 0},
+ {x = 0, y = 0, z = 1},
+ {x = 0, y = 0, z = -1},
+}
+function core.wallmounted_to_dir(wallmounted)
+ return wallmounted_to_dir[wallmounted % 8]
+end
+
+function core.dir_to_yaw(dir)
+ return -math.atan2(dir.x, dir.z)
+end
+
+function core.yaw_to_dir(yaw)
+ return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
+end
+
+function core.is_colored_paramtype(ptype)
+ return (ptype == "color") or (ptype == "colorfacedir") or
+ (ptype == "colorwallmounted")
+end
+
+function core.strip_param2_color(param2, paramtype2)
+ if not core.is_colored_paramtype(paramtype2) then
+ return nil
+ end
+ if paramtype2 == "colorfacedir" then
+ param2 = math.floor(param2 / 32) * 32
+ elseif paramtype2 == "colorwallmounted" then
+ param2 = math.floor(param2 / 8) * 8
+ end
+ -- paramtype2 == "color" requires no modification.
+ return param2
+end
+
+function core.get_node_drops(node, toolname)
+ -- Compatibility, if node is string
+ local nodename = node
+ local param2 = 0
+ -- New format, if node is table
+ if (type(node) == "table") then
+ nodename = node.name
+ param2 = node.param2
+ end
+ local def = core.registered_nodes[nodename]
+ local drop = def and def.drop
+ local ptype = def and def.paramtype2
+ -- get color, if there is color (otherwise nil)
+ local palette_index = core.strip_param2_color(param2, ptype)
if drop == nil then
-- default drop
+ if palette_index then
+ local stack = ItemStack(nodename)
+ stack:get_meta():set_int("palette_index", palette_index)
+ return {stack:to_string()}
+ end
return {nodename}
elseif type(drop) == "string" then
-- itemstring drop
- return {drop}
+ return drop ~= "" and {drop} or {}
elseif drop.items == nil then
-- drop = {} to disable default drop
return {}
-- Extended drop table
local got_items = {}
local got_count = 0
- local _, item, tool
for _, item in ipairs(drop.items) do
local good_rarity = true
local good_tool = true
end
if item.tools ~= nil then
good_tool = false
+ end
+ if item.tools ~= nil and toolname then
for _, tool in ipairs(item.tools) do
if tool:sub(1, 1) == '~' then
good_tool = toolname:find(tool:sub(2)) ~= nil
break
end
end
- end
+ end
if good_rarity and good_tool then
got_count = got_count + 1
for _, add_item in ipairs(item.items) do
+ -- add color, if necessary
+ if item.inherit_color and palette_index then
+ local stack = ItemStack(add_item)
+ stack:get_meta():set_int("palette_index", palette_index)
+ add_item = stack:to_string()
+ end
got_items[#got_items+1] = add_item
end
if drop.max_items ~= nil and got_count == drop.max_items then
return got_items
end
-function core.item_place_node(itemstack, placer, pointed_thing, param2)
- local item = itemstack:peek_item()
+local function user_name(user)
+ return user and user:get_player_name() or ""
+end
+
+-- Returns a logging function. For empty names, does not log.
+local function make_log(name)
+ return name ~= "" and core.log or function() end
+end
+
+function core.item_place_node(itemstack, placer, pointed_thing, param2,
+ prevent_after_place)
local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then
- return itemstack, false
+ return itemstack, nil
end
local under = pointed_thing.under
local oldnode_under = core.get_node_or_nil(under)
local above = pointed_thing.above
local oldnode_above = core.get_node_or_nil(above)
+ local playername = user_name(placer)
+ local log = make_log(playername)
if not oldnode_under or not oldnode_above then
- core.log("info", placer:get_player_name() .. " tried to place"
+ log("info", playername .. " tried to place"
.. " node in unloaded position " .. core.pos_to_string(above))
- return itemstack, false
+ return itemstack, nil
end
- local olddef_under = ItemStack({name=oldnode_under.name}):get_definition()
+ local olddef_under = core.registered_nodes[oldnode_under.name]
olddef_under = olddef_under or core.nodedef_default
- local olddef_above = ItemStack({name=oldnode_above.name}):get_definition()
+ local olddef_above = core.registered_nodes[oldnode_above.name]
olddef_above = olddef_above or core.nodedef_default
if not olddef_above.buildable_to and not olddef_under.buildable_to then
- core.log("info", placer:get_player_name() .. " tried to place"
+ log("info", playername .. " tried to place"
.. " node in invalid position " .. core.pos_to_string(above)
.. ", replacing " .. oldnode_above.name)
- return itemstack, false
+ return itemstack, nil
end
-- Place above pointed node
-- If node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then
- core.log("info", "node under is buildable to")
+ log("info", "node under is buildable to")
place_to = {x = under.x, y = under.y, z = under.z}
end
- if core.is_protected(place_to, placer:get_player_name()) then
- core.log("action", placer:get_player_name()
+ if core.is_protected(place_to, playername) then
+ log("action", playername
.. " tried to place " .. def.name
.. " at protected position "
.. core.pos_to_string(place_to))
- core.record_protection_violation(place_to, placer:get_player_name())
- return itemstack
+ core.record_protection_violation(place_to, playername)
+ return itemstack, nil
end
- core.log("action", placer:get_player_name() .. " places node "
- .. def.name .. " at " .. core.pos_to_string(place_to))
-
local oldnode = core.get_node(place_to)
- local newnode = {name = def.name, param1 = 0, param2 = param2}
+ local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
-- Calculate direction for wall mounted stuff like torches and signs
- if def.paramtype2 == 'wallmounted' and not param2 then
+ if def.place_param2 ~= nil then
+ newnode.param2 = def.place_param2
+ elseif (def.paramtype2 == "wallmounted" or
+ def.paramtype2 == "colorwallmounted") and not param2 then
local dir = {
x = under.x - above.x,
y = under.y - above.y,
}
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
- elseif def.paramtype2 == 'facedir' and not param2 then
- local placer_pos = placer:getpos()
+ elseif (def.paramtype2 == "facedir" or
+ def.paramtype2 == "colorfacedir") and not param2 then
+ local placer_pos = placer and placer:get_pos()
if placer_pos then
local dir = {
x = above.x - placer_pos.x,
z = above.z - placer_pos.z
}
newnode.param2 = core.dir_to_facedir(dir)
- core.log("action", "facedir: " .. newnode.param2)
+ log("info", "facedir: " .. newnode.param2)
+ end
+ end
+
+ local metatable = itemstack:get_meta():to_table().fields
+
+ -- Transfer color information
+ if metatable.palette_index and not def.place_param2 then
+ local color_divisor = nil
+ if def.paramtype2 == "color" then
+ color_divisor = 1
+ elseif def.paramtype2 == "colorwallmounted" then
+ color_divisor = 8
+ elseif def.paramtype2 == "colorfacedir" then
+ color_divisor = 32
+ end
+ if color_divisor then
+ local color = math.floor(metatable.palette_index / color_divisor)
+ local other = newnode.param2 % color_divisor
+ newnode.param2 = color * color_divisor + other
end
end
-- Check if the node is attached and if it can be placed there
if core.get_item_group(def.name, "attached_node") ~= 0 and
- not check_attached_node(place_to, newnode) then
- core.log("action", "attached node " .. def.name ..
+ not builtin_shared.check_attached_node(place_to, newnode) then
+ log("action", "attached node " .. def.name ..
" can not be placed at " .. core.pos_to_string(place_to))
- return itemstack, false
+ return itemstack, nil
end
+ log("action", playername .. " places node "
+ .. def.name .. " at " .. core.pos_to_string(place_to))
+
-- Add node and update
core.add_node(place_to, newnode)
+ -- Play sound if it was done by a player
+ if playername ~= "" and def.sounds and def.sounds.place then
+ core.sound_play(def.sounds.place, {
+ pos = place_to,
+ exclude_player = playername,
+ }, true)
+ end
+
local take_item = true
-- Run callback
- if def.after_place_node then
+ if def.after_place_node and not prevent_after_place then
-- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
end
-- Run script hook
- local _, callback
for _, callback in ipairs(core.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
if take_item then
itemstack:take_item()
end
- return itemstack, true
+ return itemstack, place_to
end
+-- deprecated, item_place does not call this
function core.item_place_object(itemstack, placer, pointed_thing)
local pos = core.get_pointed_thing_position(pointed_thing, true)
if pos ~= nil then
local nn = n.name
if core.registered_nodes[nn] and core.registered_nodes[nn].on_rightclick then
return core.registered_nodes[nn].on_rightclick(pointed_thing.under, n,
- placer, itemstack, pointed_thing) or itemstack, false
+ placer, itemstack, pointed_thing) or itemstack, nil
end
end
+ -- Place if node, otherwise do nothing
if itemstack:get_definition().type == "node" then
return core.item_place_node(itemstack, placer, pointed_thing, param2)
end
+ return itemstack, nil
+end
+
+function core.item_secondary_use(itemstack, placer)
return itemstack
end
function core.item_drop(itemstack, dropper, pos)
- if dropper.is_player then
- local v = dropper:get_look_dir()
- local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
- local cs = itemstack:get_count()
- if dropper:get_player_control().sneak then
- cs = 1
- end
- local item = itemstack:take_item(cs)
- local obj = core.add_item(p, item)
- if obj then
- v.x = v.x*2
- v.y = v.y*2 + 2
- v.z = v.z*2
- obj:setvelocity(v)
+ local dropper_is_player = dropper and dropper:is_player()
+ local p = table.copy(pos)
+ local cnt = itemstack:get_count()
+ if dropper_is_player then
+ p.y = p.y + 1.2
+ end
+ local item = itemstack:take_item(cnt)
+ local obj = core.add_item(p, item)
+ if obj then
+ if dropper_is_player then
+ local dir = dropper:get_look_dir()
+ dir.x = dir.x * 2.9
+ dir.y = dir.y * 2.9 + 2
+ dir.z = dir.z * 2.9
+ obj:set_velocity(dir)
+ obj:get_luaentity().dropped_by = dropper:get_player_name()
end
-
- else
- core.add_item(pos, itemstack)
+ return itemstack
end
- return itemstack
+ -- If we reach this, adding the object to the
+ -- environment failed
end
function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
return result
end
end
+ local def = itemstack:get_definition()
if itemstack:take_item() ~= nil then
user:set_hp(user:get_hp() + hp_change)
+ if def and def.sound and def.sound.eat then
+ core.sound_play(def.sound.eat, {
+ pos = user:get_pos(),
+ max_hear_distance = 16
+ }, true)
+ end
+
if replace_with_item then
if itemstack:is_empty() then
itemstack:add_item(replace_with_item)
else
local inv = user:get_inventory()
- if inv:room_for_item("main", {name=replace_with_item}) then
+ -- Check if inv is null, since non-players don't have one
+ if inv and inv:room_for_item("main", {name=replace_with_item}) then
inv:add_item("main", replace_with_item)
else
- local pos = user:getpos()
+ local pos = user:get_pos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
function core.item_eat(hp_change, replace_with_item)
return function(itemstack, user, pointed_thing) -- closure
- return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
+ if user then
+ return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
+ end
end
end
function core.handle_node_drops(pos, drops, digger)
-- Add dropped items to object's inventory
- if digger:get_inventory() then
- local _, dropped_item
- for _, dropped_item in ipairs(drops) do
- local left = digger:get_inventory():add_item("main", dropped_item)
- if not left:is_empty() then
- local p = {
- x = pos.x + math.random()/2-0.25,
- y = pos.y + math.random()/2-0.25,
- z = pos.z + math.random()/2-0.25,
- }
- core.add_item(p, left)
- end
+ local inv = digger and digger:get_inventory()
+ local give_item
+ if inv then
+ give_item = function(item)
+ return inv:add_item("main", item)
+ end
+ else
+ give_item = function(item)
+ -- itemstring to ItemStack for left:is_empty()
+ return ItemStack(item)
+ end
+ end
+
+ for _, dropped_item in pairs(drops) do
+ local left = give_item(dropped_item)
+ if not left:is_empty() then
+ local p = {
+ x = pos.x + math.random()/2-0.25,
+ y = pos.y + math.random()/2-0.25,
+ z = pos.z + math.random()/2-0.25,
+ }
+ core.add_item(p, left)
end
end
end
function core.node_dig(pos, node, digger)
- local def = ItemStack({name=node.name}):get_definition()
- if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then
- core.log("info", digger:get_player_name() .. " tried to dig "
+ local diggername = user_name(digger)
+ local log = make_log(diggername)
+ local def = core.registered_nodes[node.name]
+ if def and (not def.diggable or
+ (def.can_dig and not def.can_dig(pos, digger))) then
+ log("info", diggername .. " tried to dig "
.. node.name .. " which is not diggable "
.. core.pos_to_string(pos))
return
end
- if core.is_protected(pos, digger:get_player_name()) then
- core.log("action", digger:get_player_name()
+ if core.is_protected(pos, diggername) then
+ log("action", diggername
.. " tried to dig " .. node.name
.. " at protected position "
.. core.pos_to_string(pos))
- core.record_protection_violation(pos, digger:get_player_name())
+ core.record_protection_violation(pos, diggername)
return
end
- core.log('action', digger:get_player_name() .. " digs "
+ log('action', diggername .. " digs "
.. node.name .. " at " .. core.pos_to_string(pos))
- local wielded = digger:get_wielded_item()
- local drops = core.get_node_drops(node.name, wielded:get_name())
+ local wielded = digger and digger:get_wielded_item()
+ local drops = core.get_node_drops(node, wielded and wielded:get_name())
- local wdef = wielded:get_definition()
- local tp = wielded:get_tool_capabilities()
- local dp = core.get_dig_params(def.groups, tp)
- if wdef and wdef.after_use then
- wielded = wdef.after_use(wielded, digger, node, dp) or wielded
- else
- -- Wear out tool
- if not core.setting_getbool("creative_mode") then
- wielded:add_wear(dp.wear)
+ if wielded then
+ local wdef = wielded:get_definition()
+ local tp = wielded:get_tool_capabilities()
+ local dp = core.get_dig_params(def and def.groups, tp)
+ if wdef and wdef.after_use then
+ wielded = wdef.after_use(wielded, digger, node, dp) or wielded
+ else
+ -- Wear out tool
+ if not core.is_creative_enabled(diggername) then
+ wielded:add_wear(dp.wear)
+ if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then
+ core.sound_play(wdef.sound.breaks, {
+ pos = pos,
+ gain = 0.5
+ }, true)
+ end
+ end
end
+ digger:set_wielded_item(wielded)
+ end
+
+ -- Check to see if metadata should be preserved.
+ if def and def.preserve_metadata then
+ local oldmeta = core.get_meta(pos):to_table().fields
+ -- Copy pos and node because the callback can modify them.
+ local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
+ local drop_stacks = {}
+ for k, v in pairs(drops) do
+ drop_stacks[k] = ItemStack(v)
+ end
+ drops = drop_stacks
+ def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
end
- digger:set_wielded_item(wielded)
-- Handle drops
core.handle_node_drops(pos, drops, digger)
local oldmetadata = nil
- if def.after_dig_node then
+ if def and def.after_dig_node then
oldmetadata = core.get_meta(pos):to_table()
end
-- Remove node and update
core.remove_node(pos)
+ -- Play sound if it was done by a player
+ if diggername ~= "" and def and def.sounds and def.sounds.dug then
+ core.sound_play(def.sounds.dug, {
+ pos = pos,
+ exclude_player = diggername,
+ }, true)
+ end
+
-- Run callback
- if def.after_dig_node then
+ if def and def.after_dig_node then
-- Copy pos and node because callback can modify them
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
end
-- Run script hook
- local _, callback
for _, callback in ipairs(core.registered_on_dignodes) do
local origin = core.callback_origins[callback]
if origin then
core.set_last_run_mod(origin.mod)
- --print("Running " .. tostring(callback) ..
- -- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
- else
- --print("No data associated with callback")
end
-- Copy pos and node because callback can modify them
end
end
+function core.itemstring_with_palette(item, palette_index)
+ local stack = ItemStack(item) -- convert to ItemStack
+ stack:get_meta():set_int("palette_index", palette_index)
+ return stack:to_string()
+end
+
+function core.itemstring_with_color(item, colorstring)
+ local stack = ItemStack(item) -- convert to ItemStack
+ stack:get_meta():set_string("color", colorstring)
+ return stack:to_string()
+end
+
-- This is used to allow mods to redefine core.item_place and so on
-- NOTE: This is not the preferred way. Preferred way is to provide enough
-- callbacks to not require redefining global functions. -celeron55
-- Item definition defaults
--
+local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
+
core.nodedef_default = {
-- Item properties
type="node",
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
tool_capabilities = nil,
diggable = true,
climbable = false,
buildable_to = false,
+ floodable = false,
liquidtype = "none",
liquid_alternative_flowing = "",
liquid_alternative_source = "",
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_use = nil,
}
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_use = nil,
}
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
- stack_max = 99,
+ stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'),
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_drop = nil,
on_use = nil,
}