-- This file supplies the majority of Unified Dyes' API unifieddyes.player_current_dye = {} unifieddyes.player_selected_dye = {} unifieddyes.player_last_right_clicked = {} unifieddyes.palette_has_color = {} unifieddyes.player_showall = {} -- if a node with a palette is placed in the world, -- but the itemstack used to place it has no palette_index (color byte), -- create something appropriate to make it officially white. minetest.register_on_placenode( function(pos, newnode, placer, oldnode, itemstack, pointed_thing) local def = minetest.registered_items[newnode.name] if not def or not def.palette or def.after_place_node or not placer then return false end if not string.find(itemstack:to_string(), "palette_index") then local param2 local color = 0 if def.palette == "unifieddyes_palette_extended.png" and def.paramtype2 == "color" then param2 = 240 color = 240 elseif def.palette == "unifieddyes_palette_colorwallmounted.png" and def.paramtype2 == "colorwallmounted" then param2 = newnode.param2 % 8 elseif string.find(def.palette, "unifieddyes_palette_") and def.paramtype2 == "colorfacedir" then -- it's a split palette param2 = newnode.param2 % 32 end if param2 then minetest.swap_node(pos, {name = newnode.name, param2 = param2}) minetest.get_meta(pos):set_int("palette_index", color) end end end ) -- The complementary function: strip-off the color if the node being dug is still white/neutral -- adapted from -- https://github.com/minetest/minetest/blob/fe8d04d0b3c2e3af7c406fb6527f1b5230a30137/builtin/game/item.lua#L460-L562 local function node_dig_without_color(pos, node, digger) if not digger then return false end local diggername = digger:get_player_name() local def = ItemStack(node.name):get_definition() -- Copy pos because the callback could modify it if not def.diggable or (def.can_dig and not def.can_dig(vector.copy(pos), digger)) then minetest.log("info", diggername .. " tried to dig " .. node.name .. " which is not diggable " .. minetest.pos_to_string(pos)) return false end if minetest.is_protected(pos, diggername) then minetest.log("action", diggername .. " tried to dig " .. node.name .. " at protected position " .. minetest.pos_to_string(pos)) minetest.record_protection_violation(pos, diggername) return false end minetest.log('action', diggername .. " digs " .. node.name .. " at " .. minetest.pos_to_string(pos)) local wielded = digger and digger:get_wielded_item() local drops = {node.name} -- this is instead of asking minetest to generate the node drops if wielded then local wdef = wielded:get_definition() local tp = wielded:get_tool_capabilities() local dp = minetest.get_dig_params(def and def.groups, tp, wielded:get_wear()) if wdef and wdef.after_use then wielded = wdef.after_use(wielded, digger, node, dp) or wielded else -- Wear out tool if not minetest.is_creative_enabled(diggername) then wielded:add_wear(dp.wear) if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then minetest.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 = minetest.get_meta(pos):to_table().fields -- Copy pos and node because the callback can modify them. local pos_copy = vector.copy(pos) 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 -- Handle drops minetest.handle_node_drops(pos, drops, digger) local oldmetadata if def and def.after_dig_node then oldmetadata = minetest.get_meta(pos):to_table() end -- Remove node and update minetest.remove_node(pos) -- Play sound if it was done by a player if diggername ~= "" and def and def.sounds and def.sounds.dug then minetest.sound_play(def.sounds.dug, { pos = pos, exclude_player = diggername, }, true) end -- Run callback if def and def.after_dig_node then -- Copy pos and node because callback can modify them local pos_copy = vector.copy(pos) local node_copy = { name = node.name, param1 = node.param1, param2 = node.param2 } def.after_dig_node(pos_copy, node_copy, oldmetadata, digger) end -- Run script hook for _, callback in ipairs(minetest.registered_on_dignodes) do local origin = minetest.callback_origins[callback] minetest.set_last_run_mod(origin.mod) -- Copy pos and node because callback can modify them local pos_copy = vector.copy(pos) local node_copy = { name = node.name, param1 = node.param1, param2 = node.param2 } callback(pos_copy, node_copy, digger) end return true end function unifieddyes.on_dig(pos, node, digger) local param2 = minetest.get_node(pos).param2 local def = minetest.registered_items[node.name] local del_color if def.paramtype2 == "color" and param2 == 240 and def.palette == "unifieddyes_palette_extended.png" then del_color = true elseif (def.paramtype2 == "colorwallmounted" or def.paramtype2 == "colorfacedir") and minetest.strip_param2_color(param2, def.paramtype2) == 0 and string.find(def.palette, "unifieddyes_palette_") then del_color = true end if del_color then return node_dig_without_color(pos, node, digger) else return minetest.node_dig(pos, node, digger) end end -- just stubs to keep old mods from crashing when expecting auto-coloring -- or getting back the dye on dig. function unifieddyes.recolor_on_place(foo) end function unifieddyes.after_dig_node(foo) end -- This helper function creates multiple copies of the passed node, -- for the split palette - one per hue, plus grey - and assigns -- proper palettes and other attributes function unifieddyes.generate_split_palette_nodes(name, def, drop) for _, color in ipairs(unifieddyes.HUES_WITH_GREY) do local def2 = table.copy(def) local desc_color = string.gsub(string.upper(string.sub(color, 1, 1))..string.sub(color, 2), "_", " ") if string.sub(def2.description, -1) == ")" then def2.description = string.sub(def2.description, 1, -2)..", "..desc_color.." shades)" else def2.description = def2.description.."("..desc_color.." shades)" end def2.palette = "unifieddyes_palette_"..color.."s.png" def2.paramtype2 = "colorfacedir" def2.groups.ud_param2_colorable = 1 def2.is_ground_content = false if drop then def2.drop = { items = { {items = {drop.."_"..color}, inherit_color = true }, } } end minetest.register_node(":"..name.."_"..color, def2) end end -- This helper function creates a colored itemstack function unifieddyes.make_colored_itemstack(item, palette, color) local paletteidx = unifieddyes.getpaletteidx(color, palette) local stack = ItemStack(item) stack:get_meta():set_int("palette_index", paletteidx) return stack:to_string(),paletteidx end -- these helper functions register all of the recipes needed to create colored -- nodes with any of the dyes supported by that node's palette. local function register_c(craft, h, sat, val) local hue = (type(h) == "table") and h[1] or h local color if val then if craft.palette == "wallmounted" then color = val..hue..sat else color = val..hue..sat end else color = hue -- if val is nil, then it's grey. end local dye = "dye:"..color local recipe = minetest.serialize(craft.recipe) recipe = string.gsub(recipe, "MAIN_DYE", dye) recipe = string.gsub(recipe, "NEUTRAL_NODE", craft.neutral_node) local newrecipe = minetest.deserialize(recipe) local coutput = craft.output or "" local output = coutput if craft.output_prefix then if craft.palette ~= "split" then output = craft.output_prefix..color..craft.output_suffix..coutput else if hue == "white" or hue == "black" or string.find(hue, "grey") then output = craft.output_prefix.."grey"..craft.output_suffix..coutput elseif hue == "pink" then dye = "dye:light_red" output = craft.output_prefix.."red"..craft.output_suffix..coutput else output = craft.output_prefix..hue..craft.output_suffix..coutput end end end local colored_itemstack = unifieddyes.make_colored_itemstack(output, craft.palette, dye) minetest.register_craft({ output = colored_itemstack, type = craft.type, recipe = newrecipe }) end function unifieddyes.register_color_craft(craft) local hues_table = unifieddyes.HUES_EXTENDED local sats_table = unifieddyes.SATS local vals_table = unifieddyes.VALS_SPLIT local greys_table = unifieddyes.GREYS if craft.palette == "wallmounted" then register_c(craft, "green", "", "light_") register_c(craft, "blue", "", "light_") hues_table = unifieddyes.HUES_WALLMOUNTED sats_table = {""} vals_table = unifieddyes.VALS elseif craft.palette == "extended" then vals_table = unifieddyes.VALS_EXTENDED greys_table = unifieddyes.GREYS_EXTENDED end for _, hue in ipairs(hues_table) do for _, val in ipairs(vals_table) do for _, sat in ipairs(sats_table) do if sat == "_s50" and val ~= "" and val ~= "medium_" and val ~= "dark_" then break end register_c(craft, hue, sat, val) end end end for _, grey in ipairs(greys_table) do register_c(craft, grey) end register_c(craft, "pink") end -- code borrowed from homedecor -- call this function to reset the rotation of a "wallmounted" object on place function unifieddyes.fix_rotation(pos, placer, itemstack, pointed_thing) local node = minetest.get_node(pos) local colorbits = node.param2 - (node.param2 % 8) local yaw = placer:get_look_horizontal() local dir = minetest.yaw_to_dir(yaw) -- -1.5) local pitch = placer:get_look_vertical() local fdir = minetest.dir_to_wallmounted(dir) if pitch < -(math.pi/8) then fdir = 0 elseif pitch > math.pi/8 then fdir = 1 end minetest.swap_node(pos, { name = node.name, param2 = fdir+colorbits }) end -- use this when you have a "wallmounted" node that should never be oriented -- to floor or ceiling... function unifieddyes.fix_rotation_nsew(pos, placer, itemstack, pointed_thing) local node = minetest.get_node(pos) local colorbits = node.param2 - (node.param2 % 8) local yaw = placer:get_look_horizontal() local dir = minetest.yaw_to_dir(yaw+1.5) local fdir = minetest.dir_to_wallmounted(dir) minetest.swap_node(pos, { name = node.name, param2 = fdir+colorbits }) end -- ... and use this one to force that kind of node off of floor/ceiling -- orientation after the screwdriver rotates it. function unifieddyes.fix_after_screwdriver_nsew(pos, node, user, mode, new_param2) local new_fdir = new_param2 % 8 local color = new_param2 - new_fdir if new_fdir < 2 then new_fdir = 2 minetest.swap_node(pos, { name = node.name, param2 = new_fdir + color }) return true end end function unifieddyes.is_buildable_to(placer_name, ...) for _, pos in ipairs({...}) do local node = minetest.get_node_or_nil(pos) local def = node and minetest.registered_nodes[node.name] if not (def and def.buildable_to) or minetest.is_protected(pos, placer_name) then return false end end return true end function unifieddyes.get_hsv(name) -- expects a node/item name local hue = "" local a for _, i in ipairs(unifieddyes.HUES_EXTENDED) do a,_ = string.find(name, "_"..i[1]) if a then hue = i[1] break end end if string.find(name, "_light_grey") then hue = "light_grey" elseif string.find(name, "_lightgrey") then hue = "light_grey" elseif string.find(name, "_dark_grey") then hue = "dark_grey" elseif string.find(name, "_darkgrey") then hue = "dark_grey" elseif string.find(name, "_grey") then hue = "grey" elseif string.find(name, "_white") then hue = "white" elseif string.find(name, "_black") then hue = "black" end local sat = "" if string.find(name, "_s50") then sat = "_s50" end local val = "" if string.find(name, "dark_") then val = "dark_" end if string.find(name, "medium_") then val = "medium_" end if string.find(name, "light_") then val = "light_" end return hue, sat, val end -- code partially borrowed from cheapie's plasticbox mod -- in the function below, color is just a color string, while -- palette_type can be: -- -- "extended" = 256 color palette -- "split" = 200 color palette split into pieces for colorfacedir -- "wallmounted" = 32-color abridged palette function unifieddyes.getpaletteidx(color, palette_type) if string.sub(color,1,4) == "dye:" then color = string.sub(color,5,-1) elseif string.sub(color,1,12) == "unifieddyes:" then color = string.sub(color,13,-1) else return end if palette_type == "wallmounted" then if unifieddyes.gpidx_grayscale_wallmounted[color] then return (unifieddyes.gpidx_grayscale_wallmounted[color] * 8), 0 end elseif palette_type == "split" then if unifieddyes.gpidx_grayscale[color] then return (unifieddyes.gpidx_grayscale[color] * 32), 0 end elseif palette_type == "extended" then if unifieddyes.gpidx_grayscale_extended[color] then return unifieddyes.gpidx_grayscale_extended[color]+240, 0 end end local shade = "" -- assume full if string.sub(color,1,6) == "faint_" then shade = "faint" color = string.sub(color,7,-1) elseif string.sub(color,1,7) == "pastel_" then shade = "pastel" color = string.sub(color,8,-1) elseif string.sub(color,1,6) == "light_" then shade = "light" color = string.sub(color,7,-1) elseif string.sub(color,1,7) == "bright_" then shade = "bright" color = string.sub(color,8,-1) elseif string.sub(color,1,7) == "medium_" then shade = "medium" color = string.sub(color,8,-1) elseif string.sub(color,1,5) == "dark_" then shade = "dark" color = string.sub(color,6,-1) end if string.sub(color,-4,-1) == "_s50" then shade = shade.."s50" color = string.sub(color,1,-5) end if palette_type == "wallmounted" then if color == "green" and shade == "light" then return 48,3 elseif color == "brown" then return 17,1 elseif color == "pink" then return 56,7 elseif color == "blue" and shade == "light" then return 40,5 elseif unifieddyes.gpidx_hues_wallmounted[color] and unifieddyes.gpidx_shades_wallmounted[shade] then return (unifieddyes.gpidx_shades_wallmounted[shade] * 64 + unifieddyes.gpidx_hues_wallmounted[color] * 8), unifieddyes.gpidx_hues_wallmounted[color] end else if color == "brown" then color = "orange" shade = "medium" elseif color == "pink" then color = "red" shade = "light" end if palette_type == "split" then -- it's colorfacedir if unifieddyes.gpidx_hues_extended[color] and unifieddyes.gpidx_shades_split[shade] then return (unifieddyes.gpidx_shades_split[shade] * 32), unifieddyes.gpidx_hues_extended[color]+1 end elseif palette_type == "extended" then if unifieddyes.gpidx_hues_extended[color] and unifieddyes.gpidx_shades_extended[shade] then return (unifieddyes.gpidx_hues_extended[color] + unifieddyes.gpidx_shades_extended[shade]*24), unifieddyes.gpidx_hues_extended[color] end end end end function unifieddyes.get_color_from_dye_name(name) if name == "dye:black" then return "000000" elseif name == "dye:white" then return "ffffff" end local item = minetest.registered_items[name] if not item then return end local inv_image = item.inventory_image if not inv_image then return end return string.match(inv_image,"colorize:#(......):200") end -- get a node's dye color based on its palette and param2 function unifieddyes.color_to_name(param2, def) if not param2 or not def or not def.palette then return end if def.palette == "unifieddyes_palette_extended.png" then local color = param2 local v = 0 local s = 1 if color < 24 then v = 1 elseif color > 23 and color < 48 then v = 2 elseif color > 47 and color < 72 then v = 3 elseif color > 71 and color < 96 then v = 4 elseif color > 95 and color < 120 then v = 5 elseif color > 119 and color < 144 then v = 5 s = 2 elseif color > 143 and color < 168 then v = 6 elseif color > 167 and color < 192 then v = 6 s = 2 elseif color > 191 and color < 216 then v = 7 elseif color > 215 and color < 240 then v = 7 s = 2 end if color > 239 then if color == 240 then return "white" elseif color == 244 then return "light_grey" elseif color == 247 then return "grey" elseif color == 251 then return "dark_grey" elseif color == 255 then return "black" else return "grey_"..15-(color-240) end else local h = color - math.floor(color/24)*24 return unifieddyes.VALS_EXTENDED[v]..unifieddyes.HUES_EXTENDED[h+1][1]..unifieddyes.SATS[s] end elseif def.palette == "unifieddyes_palette_colorwallmounted.png" then local color = math.floor(param2 / 8) if color == 0 then return "white" elseif color == 1 then return "light_grey" elseif color == 2 then return "grey" elseif color == 3 then return "dark_grey" elseif color == 4 then return "black" elseif color == 5 then return "light_blue" elseif color == 6 then return "light_green" elseif color == 7 then return "pink" end local v = math.floor(color/8) local h = color - v * 8 return unifieddyes.VALS[v]..unifieddyes.HUES_WALLMOUNTED[h+1] elseif string.find(def.palette, "unifieddyes_palette") then -- it's the split palette -- palette names in this mode are always "unifieddyes_palette_COLORs.png" local s = string.sub(def.palette, 21) local color = string.sub(s, 1, string.find(s, "s.png")-1) local v = math.floor(param2/32) if color ~= "grey" then if v == 0 then return "faint_"..color elseif v == 1 then return color elseif v == 2 then return color.."_s50" elseif v == 3 then return "light_"..color elseif v == 4 then return "medium_"..color elseif v == 5 then return "medium_"..color.."_s50" elseif v == 6 then return "dark_"..color elseif v == 7 then return "dark_"..color.."_s50" end else if v > 0 and v < 6 then return unifieddyes.GREYS[v] else return "white" end end end end