diff --git a/mods/CORE/mcl_util/init.lua b/mods/CORE/mcl_util/init.lua index a12f70e46..0db60688d 100644 --- a/mods/CORE/mcl_util/init.lua +++ b/mods/CORE/mcl_util/init.lua @@ -1,6 +1,9 @@ mcl_util = {} -dofile(minetest.get_modpath(minetest.get_current_modname()).."/roman_numerals.lua") +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +dofile(modpath.."/roman_numerals.lua") +dofile(modpath.."/nodes.lua") -- Updates all values in t using values from to*. function table.update(t, ...) @@ -126,24 +129,6 @@ function mcl_util.validate_vector (vect) return false end --- Luanti 5.3.0 or less can only measure the light level. This came in at 5.4 --- This function has been known to fail in multiple places so the error handling is added increase safety and improve --- debugging. See: --- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392 -function mcl_util.get_natural_light (pos, time) - local status, retVal = pcall(minetest.get_natural_light, pos, time) - if status then - return retVal - else - minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time)) - if (pos) then - local node = minetest.get_node(pos) - minetest.log("warning", "Node at pos: " .. dump(node.name)) - end - end - return 0 -end - function mcl_util.file_exists(name) if type(name) ~= "string" then return end local f = io.open(name) @@ -154,119 +139,6 @@ function mcl_util.file_exists(name) return true end --- Based on minetest.rotate_and_place - ---[[ -Attempt to predict the desired orientation of the pillar-like node -defined by `itemstack`, and place it accordingly in one of 3 possible -orientations (X, Y or Z). - -Stacks are handled normally if the `infinitestacks` -field is false or omitted (else, the itemstack is not changed). -* `invert_wall`: if `true`, place wall-orientation on the ground and ground- - orientation on wall - -This function is a simplified version of minetest.rotate_and_place. -The Luanti function is seen as inappropriate because this includes mirror -images of possible orientations, causing problems with pillar shadings. -]] -function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall) - local unode = minetest.get_node_or_nil(pointed_thing.under) - if not unode then - return - end - local undef = minetest.registered_nodes[unode.name] - if undef and undef.on_rightclick and not invert_wall then - undef.on_rightclick(pointed_thing.under, unode, placer, - itemstack, pointed_thing) - return - end - local fdir = minetest.dir_to_facedir(placer:get_look_dir()) - local wield_name = itemstack:get_name() - - local above = pointed_thing.above - local under = pointed_thing.under - local is_x = (above.x ~= under.x) - local is_y = (above.y ~= under.y) - local is_z = (above.z ~= under.z) - - local anode = minetest.get_node_or_nil(above) - if not anode then - return - end - local pos = pointed_thing.above - local node = anode - - if undef and undef.buildable_to then - pos = pointed_thing.under - node = unode - end - - if minetest.is_protected(pos, placer:get_player_name()) then - minetest.record_protection_violation(pos, placer:get_player_name()) - return - end - - local ndef = minetest.registered_nodes[node.name] - if not ndef or not ndef.buildable_to then - return - end - - local p2 - if is_y then - p2 = 0 - elseif is_x then - p2 = 12 - elseif is_z then - p2 = 6 - end - minetest.set_node(pos, {name = wield_name, param2 = p2}) - - if not infinitestacks then - itemstack:take_item() - return itemstack - end -end - --- Wrapper of above function for use as `on_place` callback (Recommended). --- Similar to minetest.rotate_node. -function mcl_util.rotate_axis(itemstack, placer, pointed_thing) - mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, - minetest.is_creative_enabled(placer:get_player_name()), - placer:get_player_control().sneak) - return itemstack -end - --- Returns position of the neighbor of a double chest node --- or nil if node is invalid. --- This function assumes that the large chest is actually intact --- * pos: Position of the node to investigate --- * param2: param2 of that node --- * side: Which "half" the investigated node is. "left" or "right" -function mcl_util.get_double_container_neighbor_pos(pos, param2, side) - if side == "right" then - if param2 == 0 then - return {x = pos.x - 1, y = pos.y, z = pos.z} - elseif param2 == 1 then - return {x = pos.x, y = pos.y, z = pos.z + 1} - elseif param2 == 2 then - return {x = pos.x + 1, y = pos.y, z = pos.z} - elseif param2 == 3 then - return {x = pos.x, y = pos.y, z = pos.z - 1} - end - else - if param2 == 0 then - return {x = pos.x + 1, y = pos.y, z = pos.z} - elseif param2 == 1 then - return {x = pos.x, y = pos.y, z = pos.z - 1} - elseif param2 == 2 then - return {x = pos.x - 1, y = pos.y, z = pos.z} - elseif param2 == 3 then - return {x = pos.x, y = pos.y, z = pos.z + 1} - end - end -end - --- Selects item stack to transfer from ---@param src_inventory InvRef Source innentory to pull from ---@param src_list string Name of source inventory list to pull from @@ -424,61 +296,6 @@ function mcl_util.is_fuel(item) return minetest.get_craft_result({method = "fuel", width = 1, items = {item}}).time ~= 0 end --- Returns a on_place function for plants --- * condition: function(pos, node, itemstack) --- * A function which is called by the on_place function to check if the node can be placed --- * Must return true, if placement is allowed, false otherwise. --- * If it returns a string, placement is allowed, but will place this itemstring as a node instead --- * pos, node: Position and node table of plant node --- * itemstack: Itemstack to place -function mcl_util.generate_on_place_plant_function(condition) - return function(itemstack, placer, pointed_thing) - if pointed_thing.type ~= "node" then - -- no interaction possible with entities - return itemstack - end - - -- Call on_rightclick if the pointed node defines it - local node = minetest.get_node(pointed_thing.under) - if placer and not placer:get_player_control().sneak then - if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then - return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack - end - end - - local place_pos - local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name] - local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name] - if not def_under or not def_above then - return itemstack - end - if def_under.buildable_to and def_under.name ~= itemstack:get_name() then - place_pos = pointed_thing.under - elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then - place_pos = pointed_thing.above - pointed_thing.under = pointed_thing.above - else - return itemstack - end - - -- Check placement rules - local result, param2 = condition(place_pos, node, itemstack) - if result == true then - local idef = itemstack:get_definition() - local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2) - - if success then - if idef.sounds and idef.sounds.place then - minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true) - end - end - itemstack = new_itemstack - end - - return itemstack - end -end - -- adjust the y level of an object to the center of its collisionbox -- used to get the origin position of entity explosions function mcl_util.get_object_center(obj) @@ -750,243 +567,6 @@ function mcl_util.set_bone_position(obj, bone, pos, rot) end end ----Return a function to use in `on_place`. ---- ----Allow to bypass the `buildable_to` node field in a `on_place` callback. ---- ----You have to make sure that the nodes you return true for have `buildable_to = true`. ----@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name` ----@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack? -function mcl_util.bypass_buildable_to(func) - -------------------------- - -- LUANTI CODE: UTILS ---- - -------------------------- - - local function copy_pointed_thing(pointed_thing) - return { - type = pointed_thing.type, - above = pointed_thing.above and vector.copy(pointed_thing.above), - under = pointed_thing.under and vector.copy(pointed_thing.under), - ref = pointed_thing.ref, - } - end - - 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 minetest.log or function() end - end - - local function check_attached_node(p, n, group_rating) - local def = core.registered_nodes[n.name] - local d = vector.zero() - if group_rating == 3 then - -- always attach to floor - d.y = -1 - elseif group_rating == 4 then - -- always attach to ceiling - d.y = 1 - elseif group_rating == 2 then - -- attach to facedir or 4dir direction - if (def.paramtype2 == "facedir" or - def.paramtype2 == "colorfacedir") then - -- Attach to whatever facedir is "mounted to". - -- For facedir, this is where tile no. 5 point at. - - -- The fallback vector here is in case 'facedir to dir' is nil due - -- to voxelmanip placing a wallmounted node without resetting a - -- pre-existing param2 value that is out-of-range for facedir. - -- The fallback vector corresponds to param2 = 0. - d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1) - elseif (def.paramtype2 == "4dir" or - def.paramtype2 == "color4dir") then - -- Similar to facedir handling - d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1) - end - elseif def.paramtype2 == "wallmounted" or - def.paramtype2 == "colorwallmounted" then - -- Attach to whatever this node is "mounted to". - -- This where tile no. 2 points at. - - -- The fallback vector here is used for the same reason as - -- for facedir nodes. - d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0) - else - d.y = -1 - end - local p2 = vector.add(p, d) - local nn = core.get_node(p2).name - local def2 = core.registered_nodes[nn] - if def2 and not def2.walkable then - return false - end - return true - end - - return function(itemstack, placer, pointed_thing, param2) - ------------------- - -- LUANTI CODE ---- - ------------------- - local def = itemstack:get_definition() - if def.type ~= "node" or pointed_thing.type ~= "node" then - return itemstack - end - - local under = pointed_thing.under - local oldnode_under = minetest.get_node_or_nil(under) - local above = pointed_thing.above - local oldnode_above = minetest.get_node_or_nil(above) - local playername = user_name(placer) - local log = make_log(playername) - - if not oldnode_under or not oldnode_above then - log("info", playername .. " tried to place" - .. " node in unloaded position " .. minetest.pos_to_string(above)) - return itemstack - end - - local olddef_under = minetest.registered_nodes[oldnode_under.name] - olddef_under = olddef_under or minetest.nodedef_default - local olddef_above = minetest.registered_nodes[oldnode_above.name] - olddef_above = olddef_above or minetest.nodedef_default - - if not olddef_above.buildable_to and not olddef_under.buildable_to then - log("info", playername .. " tried to place" - .. " node in invalid position " .. minetest.pos_to_string(above) - .. ", replacing " .. oldnode_above.name) - return itemstack - end - - --------------------- - -- CUSTOMIZED CODE -- - --------------------- - - -- Place above pointed node - local place_to = vector.copy(above) - - -- If node under is buildable_to, check for callback result and place into it instead - if olddef_under.buildable_to and not func(oldnode_under.name) then - log("info", "node under is buildable to") - place_to = vector.copy(under) - end - - ------------------- - -- LUANTI CODE ---- - ------------------- - - if minetest.is_protected(place_to, playername) then - log("action", playername - .. " tried to place " .. def.name - .. " at protected position " - .. minetest.pos_to_string(place_to)) - minetest.record_protection_violation(place_to, playername) - return itemstack - end - - local oldnode = minetest.get_node(place_to) - local newnode = {name = def.name, param1 = 0, param2 = param2 or 0} - - -- Calculate direction for wall mounted stuff like torches and signs - 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 = vector.subtract(under, above) - newnode.param2 = minetest.dir_to_wallmounted(dir) - -- Calculate the direction for furnaces and chests and stuff - elseif (def.paramtype2 == "facedir" or - def.paramtype2 == "colorfacedir" or - def.paramtype2 == "4dir" or - def.paramtype2 == "color4dir") and not param2 then - local placer_pos = placer and placer:get_pos() - if placer_pos then - local dir = vector.subtract(above, placer_pos) - newnode.param2 = minetest.dir_to_facedir(dir) - 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 - elseif def.paramtype2 == "color4dir" then - color_divisor = 4 - elseif def.paramtype2 == "colordegrotate" 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 - local an = minetest.get_item_group(def.name, "attached_node") - if an ~= 0 and - not check_attached_node(place_to, newnode, an) then - log("action", "attached node " .. def.name .. - " cannot be placed at " .. minetest.pos_to_string(place_to)) - return itemstack - end - - log("action", playername .. " places node " - .. def.name .. " at " .. minetest.pos_to_string(place_to)) - - -- Add node and update - minetest.add_node(place_to, newnode) - - -- Play sound if it was done by a player - if playername ~= "" and def.sounds and def.sounds.place then - minetest.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 - -- Deepcopy place_to and pointed_thing because callback can modify it - local place_to_copy = vector.copy(place_to) - local pointed_thing_copy = copy_pointed_thing(pointed_thing) - if def.after_place_node(place_to_copy, placer, itemstack, - pointed_thing_copy) then - take_item = false - end - end - - -- Run script hook - for _, callback in ipairs(minetest.registered_on_placenodes) do - -- Deepcopy pos, node and pointed_thing because callback can modify them - local place_to_copy = vector.copy(place_to) - local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2} - local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2} - local pointed_thing_copy = copy_pointed_thing(pointed_thing) - if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then - take_item = false - end - end - - if take_item then - itemstack:take_item() - end - return itemstack - end -end - --[[Check for a protection violation in a given area. -- -- Applies is_protected() to a 3D lattice of points in the defined volume. The points are spaced @@ -1032,33 +612,6 @@ function mcl_util.check_position_protection(position, player) return false end -local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0} -function mcl_util.get_palette_indexes_from_pos(pos) - local biome_data = minetest.get_biome_data(pos) - local biome = biome_data.biome - local biome_name = minetest.get_biome_name(biome) - local reg_biome = minetest.registered_biomes[biome_name] - if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then - local gpi = reg_biome._mcl_grass_palette_index - local fpi = reg_biome._mcl_foliage_palette_index - local wpi = reg_biome._mcl_water_palette_index - local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi} - return palette_indexes - else - return palette_indexes - end -end - -function mcl_util.get_colorwallmounted_rotation(pos) - local colorwallmounted_node = minetest.get_node(pos) - for i = 0, 32, 1 do - local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8) - if colorwallmounted_rotation < 6 then - return colorwallmounted_rotation - end - end -end - ---Move items from one inventory list to another, drop items that do not fit in provided pos and direction. ---@param src_inv mt.InvRef ---@param src_listname string diff --git a/mods/CORE/mcl_util/nodes.lua b/mods/CORE/mcl_util/nodes.lua new file mode 100644 index 000000000..7fcfe37eb --- /dev/null +++ b/mods/CORE/mcl_util/nodes.lua @@ -0,0 +1,453 @@ +-- Functions related to nodes and node definitions + +-- Luanti 5.3.0 or less can only measure the light level. This came in at 5.4 +-- This function has been known to fail in multiple places so the error handling is added increase safety and improve +-- debugging. See: +-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392 +function mcl_util.get_natural_light (pos, time) + local status, retVal = pcall(minetest.get_natural_light, pos, time) + if status then + return retVal + else + minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time)) + if (pos) then + local node = minetest.get_node(pos) + minetest.log("warning", "Node at pos: " .. dump(node.name)) + end + end + return 0 +end + +-- Based on minetest.rotate_and_place + +--[[ +Attempt to predict the desired orientation of the pillar-like node +defined by `itemstack`, and place it accordingly in one of 3 possible +orientations (X, Y or Z). + +Stacks are handled normally if the `infinitestacks` +field is false or omitted (else, the itemstack is not changed). +* `invert_wall`: if `true`, place wall-orientation on the ground and ground- + orientation on wall + +This function is a simplified version of minetest.rotate_and_place. +The Luanti function is seen as inappropriate because this includes mirror +images of possible orientations, causing problems with pillar shadings. +]] +function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall) + local unode = minetest.get_node_or_nil(pointed_thing.under) + if not unode then + return + end + local undef = minetest.registered_nodes[unode.name] + if undef and undef.on_rightclick and not invert_wall then + undef.on_rightclick(pointed_thing.under, unode, placer, + itemstack, pointed_thing) + return + end + local fdir = minetest.dir_to_facedir(placer:get_look_dir()) + local wield_name = itemstack:get_name() + + local above = pointed_thing.above + local under = pointed_thing.under + local is_x = (above.x ~= under.x) + local is_y = (above.y ~= under.y) + local is_z = (above.z ~= under.z) + + local anode = minetest.get_node_or_nil(above) + if not anode then + return + end + local pos = pointed_thing.above + local node = anode + + if undef and undef.buildable_to then + pos = pointed_thing.under + node = unode + end + + if minetest.is_protected(pos, placer:get_player_name()) then + minetest.record_protection_violation(pos, placer:get_player_name()) + return + end + + local ndef = minetest.registered_nodes[node.name] + if not ndef or not ndef.buildable_to then + return + end + + local p2 + if is_y then + p2 = 0 + elseif is_x then + p2 = 12 + elseif is_z then + p2 = 6 + end + minetest.set_node(pos, {name = wield_name, param2 = p2}) + + if not infinitestacks then + itemstack:take_item() + return itemstack + end +end + +-- Wrapper of above function for use as `on_place` callback (Recommended). +-- Similar to minetest.rotate_node. +function mcl_util.rotate_axis(itemstack, placer, pointed_thing) + mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, + minetest.is_creative_enabled(placer:get_player_name()), + placer:get_player_control().sneak) + return itemstack +end + +-- Returns position of the neighbor of a double chest node +-- or nil if node is invalid. +-- This function assumes that the large chest is actually intact +-- * pos: Position of the node to investigate +-- * param2: param2 of that node +-- * side: Which "half" the investigated node is. "left" or "right" +function mcl_util.get_double_container_neighbor_pos(pos, param2, side) + if side == "right" then + if param2 == 0 then + return {x = pos.x - 1, y = pos.y, z = pos.z} + elseif param2 == 1 then + return {x = pos.x, y = pos.y, z = pos.z + 1} + elseif param2 == 2 then + return {x = pos.x + 1, y = pos.y, z = pos.z} + elseif param2 == 3 then + return {x = pos.x, y = pos.y, z = pos.z - 1} + end + else + if param2 == 0 then + return {x = pos.x + 1, y = pos.y, z = pos.z} + elseif param2 == 1 then + return {x = pos.x, y = pos.y, z = pos.z - 1} + elseif param2 == 2 then + return {x = pos.x - 1, y = pos.y, z = pos.z} + elseif param2 == 3 then + return {x = pos.x, y = pos.y, z = pos.z + 1} + end + end +end + +-- Returns a on_place function for plants +-- * condition: function(pos, node, itemstack) +-- * A function which is called by the on_place function to check if the node can be placed +-- * Must return true, if placement is allowed, false otherwise. +-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead +-- * pos, node: Position and node table of plant node +-- * itemstack: Itemstack to place +function mcl_util.generate_on_place_plant_function(condition) + return function(itemstack, placer, pointed_thing) + if pointed_thing.type ~= "node" then + -- no interaction possible with entities + return itemstack + end + + -- Call on_rightclick if the pointed node defines it + local node = minetest.get_node(pointed_thing.under) + if placer and not placer:get_player_control().sneak then + if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then + return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack + end + end + + local place_pos + local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name] + local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name] + if not def_under or not def_above then + return itemstack + end + if def_under.buildable_to and def_under.name ~= itemstack:get_name() then + place_pos = pointed_thing.under + elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then + place_pos = pointed_thing.above + pointed_thing.under = pointed_thing.above + else + return itemstack + end + + -- Check placement rules + local result, param2 = condition(place_pos, node, itemstack) + if result == true then + local idef = itemstack:get_definition() + local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2) + + if success then + if idef.sounds and idef.sounds.place then + minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true) + end + end + itemstack = new_itemstack + end + + return itemstack + end +end + +---Return a function to use in `on_place`. +--- +---Allow to bypass the `buildable_to` node field in a `on_place` callback. +--- +---You have to make sure that the nodes you return true for have `buildable_to = true`. +---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name` +---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack? +function mcl_util.bypass_buildable_to(func) + -------------------------- + -- MINETEST CODE: UTILS -- + -------------------------- + + local function copy_pointed_thing(pointed_thing) + return { + type = pointed_thing.type, + above = pointed_thing.above and vector.copy(pointed_thing.above), + under = pointed_thing.under and vector.copy(pointed_thing.under), + ref = pointed_thing.ref, + } + end + + 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 minetest.log or function() end + end + + local function check_attached_node(p, n, group_rating) + local def = core.registered_nodes[n.name] + local d = vector.zero() + if group_rating == 3 then + -- always attach to floor + d.y = -1 + elseif group_rating == 4 then + -- always attach to ceiling + d.y = 1 + elseif group_rating == 2 then + -- attach to facedir or 4dir direction + if (def.paramtype2 == "facedir" or + def.paramtype2 == "colorfacedir") then + -- Attach to whatever facedir is "mounted to". + -- For facedir, this is where tile no. 5 point at. + + -- The fallback vector here is in case 'facedir to dir' is nil due + -- to voxelmanip placing a wallmounted node without resetting a + -- pre-existing param2 value that is out-of-range for facedir. + -- The fallback vector corresponds to param2 = 0. + d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1) + elseif (def.paramtype2 == "4dir" or + def.paramtype2 == "color4dir") then + -- Similar to facedir handling + d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1) + end + elseif def.paramtype2 == "wallmounted" or + def.paramtype2 == "colorwallmounted" then + -- Attach to whatever this node is "mounted to". + -- This where tile no. 2 points at. + + -- The fallback vector here is used for the same reason as + -- for facedir nodes. + d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0) + else + d.y = -1 + end + local p2 = vector.add(p, d) + local nn = core.get_node(p2).name + local def2 = core.registered_nodes[nn] + if def2 and not def2.walkable then + return false + end + return true + end + + return function(itemstack, placer, pointed_thing, param2) + ------------------- + -- MINETEST CODE -- + ------------------- + local def = itemstack:get_definition() + if def.type ~= "node" or pointed_thing.type ~= "node" then + return itemstack + end + + local under = pointed_thing.under + local oldnode_under = minetest.get_node_or_nil(under) + local above = pointed_thing.above + local oldnode_above = minetest.get_node_or_nil(above) + local playername = user_name(placer) + local log = make_log(playername) + + if not oldnode_under or not oldnode_above then + log("info", playername .. " tried to place" + .. " node in unloaded position " .. minetest.pos_to_string(above)) + return itemstack + end + + local olddef_under = minetest.registered_nodes[oldnode_under.name] + olddef_under = olddef_under or minetest.nodedef_default + local olddef_above = minetest.registered_nodes[oldnode_above.name] + olddef_above = olddef_above or minetest.nodedef_default + + if not olddef_above.buildable_to and not olddef_under.buildable_to then + log("info", playername .. " tried to place" + .. " node in invalid position " .. minetest.pos_to_string(above) + .. ", replacing " .. oldnode_above.name) + return itemstack + end + + --------------------- + -- CUSTOMIZED CODE -- + --------------------- + + -- Place above pointed node + local place_to = vector.copy(above) + + -- If node under is buildable_to, check for callback result and place into it instead + if olddef_under.buildable_to and not func(oldnode_under.name) then + log("info", "node under is buildable to") + place_to = vector.copy(under) + end + + ------------------- + -- MINETEST CODE -- + ------------------- + + if minetest.is_protected(place_to, playername) then + log("action", playername + .. " tried to place " .. def.name + .. " at protected position " + .. minetest.pos_to_string(place_to)) + minetest.record_protection_violation(place_to, playername) + return itemstack + end + + local oldnode = minetest.get_node(place_to) + local newnode = {name = def.name, param1 = 0, param2 = param2 or 0} + + -- Calculate direction for wall mounted stuff like torches and signs + 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 = vector.subtract(under, above) + newnode.param2 = minetest.dir_to_wallmounted(dir) + -- Calculate the direction for furnaces and chests and stuff + elseif (def.paramtype2 == "facedir" or + def.paramtype2 == "colorfacedir" or + def.paramtype2 == "4dir" or + def.paramtype2 == "color4dir") and not param2 then + local placer_pos = placer and placer:get_pos() + if placer_pos then + local dir = vector.subtract(above, placer_pos) + newnode.param2 = minetest.dir_to_facedir(dir) + 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 + elseif def.paramtype2 == "color4dir" then + color_divisor = 4 + elseif def.paramtype2 == "colordegrotate" 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 + local an = minetest.get_item_group(def.name, "attached_node") + if an ~= 0 and + not check_attached_node(place_to, newnode, an) then + log("action", "attached node " .. def.name .. + " cannot be placed at " .. minetest.pos_to_string(place_to)) + return itemstack + end + + log("action", playername .. " places node " + .. def.name .. " at " .. minetest.pos_to_string(place_to)) + + -- Add node and update + minetest.add_node(place_to, newnode) + + -- Play sound if it was done by a player + if playername ~= "" and def.sounds and def.sounds.place then + minetest.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 + -- Deepcopy place_to and pointed_thing because callback can modify it + local place_to_copy = vector.copy(place_to) + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if def.after_place_node(place_to_copy, placer, itemstack, + pointed_thing_copy) then + take_item = false + end + end + + -- Run script hook + for _, callback in ipairs(minetest.registered_on_placenodes) do + -- Deepcopy pos, node and pointed_thing because callback can modify them + local place_to_copy = vector.copy(place_to) + local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2} + local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then + take_item = false + end + end + + if take_item then + itemstack:take_item() + end + return itemstack + end +end + +local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0} +function mcl_util.get_palette_indexes_from_pos(pos) + local biome_data = minetest.get_biome_data(pos) + local biome = biome_data.biome + local biome_name = minetest.get_biome_name(biome) + local reg_biome = minetest.registered_biomes[biome_name] + if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then + local gpi = reg_biome._mcl_grass_palette_index + local fpi = reg_biome._mcl_foliage_palette_index + local wpi = reg_biome._mcl_water_palette_index + local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi} + return palette_indexes + else + return palette_indexes + end +end + +function mcl_util.get_colorwallmounted_rotation(pos) + local colorwallmounted_node = minetest.get_node(pos) + for i = 0, 32, 1 do + local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8) + if colorwallmounted_rotation < 6 then + return colorwallmounted_rotation + end + end +end + +