summaryrefslogtreecommitdiff
path: root/homedecor/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'homedecor/handlers')
-rw-r--r--homedecor/handlers/expansion.lua362
-rw-r--r--homedecor/handlers/furnaces.lua279
-rw-r--r--homedecor/handlers/init.lua22
-rw-r--r--homedecor/handlers/inventory.lua173
-rw-r--r--homedecor/handlers/nodeboxes.lua62
-rw-r--r--homedecor/handlers/registration.lua95
-rw-r--r--homedecor/handlers/sit.lua32
-rw-r--r--homedecor/handlers/water_particles.lua115
8 files changed, 1140 insertions, 0 deletions
diff --git a/homedecor/handlers/expansion.lua b/homedecor/handlers/expansion.lua
new file mode 100644
index 0000000..0767837
--- /dev/null
+++ b/homedecor/handlers/expansion.lua
@@ -0,0 +1,362 @@
+local S = homedecor.gettext
+
+-- vectors to place one node next to or behind another
+
+homedecor.fdir_to_right = {
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, 1 },
+}
+
+homedecor.fdir_to_left = {
+ { -1, 0 },
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+}
+
+homedecor.fdir_to_fwd = {
+ { 0, 1 },
+ { 1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+}
+
+local placeholder_node = "homedecor:expansion_placeholder"
+minetest.register_node(placeholder_node, {
+ description = "Expansion placeholder (you hacker you!)",
+ groups = { not_in_creative_inventory=1 },
+ drawtype = "airlike",
+ paramtype = "light",
+ walkable = false,
+ selection_box = { type = "fixed", fixed = { 0, 0, 0, 0, 0, 0 } },
+ is_ground_content = false,
+ sunlight_propagates = true,
+ buildable_to = false,
+})
+
+--- select which node was pointed at based on it being known, not ignored, buildable_to
+-- returns nil if no node could be selected
+local function select_node(pointed_thing)
+ local pos = pointed_thing.under
+ local node = minetest.get_node_or_nil(pos)
+ local def = node and minetest.registered_nodes[node.name]
+
+ if not def or not def.buildable_to then
+ pos = pointed_thing.above
+ node = minetest.get_node_or_nil(pos)
+ def = node and minetest.registered_nodes[node.name]
+ end
+ return def and pos, def
+end
+
+--- check if all nodes can and may be build to
+local function 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
+
+-- place one or two nodes if and only if both can be placed
+local function stack(itemstack, placer, fdir, pos, def, pos2, node1, node2)
+ local placer_name = placer:get_player_name() or ""
+ if is_buildable_to(placer_name, pos, pos2) then
+ local fdir = fdir or minetest.dir_to_facedir(placer:get_look_dir())
+ minetest.set_node(pos, { name = node1, param2 = fdir })
+ node2 = node2 or "air" -- this can be used to clear buildable_to nodes even though we are using a multinode mesh
+ -- do not assume by default, as we still might want to allow overlapping in some cases
+ local has_facedir = node2 ~= "air"
+ if node2 == "placeholder" then
+ has_facedir = false
+ node2 = placeholder_node
+ end
+ minetest.set_node(pos2, { name = node2, param2 = (has_facedir and fdir) or nil })
+
+ -- call after_place_node of the placed node if available
+ local ctrl_node_def = minetest.registered_nodes[node1]
+ if ctrl_node_def and ctrl_node_def.after_place_node then
+ ctrl_node_def.after_place_node(pos, placer)
+ end
+
+ if not homedecor.expect_infinite_stacks then
+ itemstack:take_item()
+ end
+ end
+ return itemstack
+end
+
+local function rightclick_pointed_thing(pos, placer, itemstack)
+ local node = minetest.get_node_or_nil(pos)
+ if not node then return false end
+ local def = minetest.registered_nodes[node.name]
+ if not def or not def.on_rightclick then return false end
+ return def.on_rightclick(pos, node, placer, itemstack) or itemstack
+end
+
+-- Stack one node above another
+-- leave the last argument nil if it's one 2m high node
+function homedecor.stack_vertically(itemstack, placer, pointed_thing, node1, node2)
+ local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack)
+ if rightclick_result then return rightclick_result end
+
+ local pos, def = select_node(pointed_thing)
+ if not pos then return itemstack end
+
+ local top_pos = { x=pos.x, y=pos.y+1, z=pos.z }
+
+ return stack(itemstack, placer, nil, pos, def, top_pos, node1, node2)
+end
+
+-- Stack one door node above another
+-- like homedecor.stack_vertically but tests first if it was placed as a right wing, then uses node1_right and node2_right instead
+
+function homedecor.stack_wing(itemstack, placer, pointed_thing, node1, node2, node1_right, node2_right)
+ local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack)
+ if rightclick_result then return rightclick_result end
+
+ local pos, def = select_node(pointed_thing)
+ if not pos then return itemstack end
+
+ local forceright = placer:get_player_control()["sneak"]
+ local fdir = minetest.dir_to_facedir(placer:get_look_dir())
+
+ local is_right_wing = node1 == minetest.get_node({ x = pos.x + homedecor.fdir_to_left[fdir+1][1], y=pos.y, z = pos.z + homedecor.fdir_to_left[fdir+1][2] }).name
+ if forceright or is_right_wing then
+ node1, node2 = node1_right, node2_right
+ end
+
+ local top_pos = { x=pos.x, y=pos.y+1, z=pos.z }
+ return stack(itemstack, placer, fdir, pos, def, top_pos, node1, node2)
+end
+
+function homedecor.stack_sideways(itemstack, placer, pointed_thing, node1, node2, dir)
+ local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack)
+ if rightclick_result then return rightclick_result end
+
+ local pos, def = select_node(pointed_thing)
+ if not pos then return itemstack end
+
+ local fdir = minetest.dir_to_facedir(placer:get_look_dir())
+ local fdir_transform = dir and homedecor.fdir_to_right or homedecor.fdir_to_fwd
+
+ local pos2 = { x = pos.x + fdir_transform[fdir+1][1], y=pos.y, z = pos.z + fdir_transform[fdir+1][2] }
+
+ return stack(itemstack, placer, fdir, pos, def, pos2, node1, node2)
+end
+
+function homedecor.bed_expansion(pos, placer, itemstack, pointed_thing, color)
+
+ local thisnode = minetest.get_node(pos)
+ local fdir = thisnode.param2
+
+ local fxd = homedecor.fdir_to_fwd[fdir+1][1]
+ local fzd = homedecor.fdir_to_fwd[fdir+1][2]
+
+ local forwardpos = {x=pos.x+fxd, y=pos.y, z=pos.z+fzd}
+ local forwardnode = minetest.get_node(forwardpos)
+
+ local def = minetest.registered_nodes[forwardnode.name]
+ local placer_name = placer:get_player_name()
+
+ if not (def and def.buildable_to) then
+ minetest.chat_send_player( placer:get_player_name(), "Not enough room - the space for the headboard is occupied!" )
+ minetest.set_node(pos, {name = "air"})
+ return true
+ end
+
+ if minetest.is_protected(forwardpos, placer_name) then
+ minetest.chat_send_player( placer:get_player_name(), "Someone already owns the spot where the headboard goes." )
+ return true
+ end
+
+ minetest.set_node(forwardpos, {name = "air"})
+
+ local lxd = homedecor.fdir_to_left[fdir+1][1]
+ local lzd = homedecor.fdir_to_left[fdir+1][2]
+ local leftpos = {x=pos.x+lxd, y=pos.y, z=pos.z+lzd}
+ local leftnode = minetest.get_node(leftpos)
+
+ local rxd = homedecor.fdir_to_right[fdir+1][1]
+ local rzd = homedecor.fdir_to_right[fdir+1][2]
+ local rightpos = {x=pos.x+rxd, y=pos.y, z=pos.z+rzd}
+ local rightnode = minetest.get_node(rightpos)
+
+ if leftnode.name == "homedecor:bed_"..color.."_regular" then
+ local newname = string.gsub(thisnode.name, "_regular", "_kingsize")
+ minetest.set_node(pos, {name = "air"})
+ minetest.set_node(leftpos, { name = newname, param2 = fdir})
+ elseif rightnode.name == "homedecor:bed_"..color.."_regular" then
+ local newname = string.gsub(thisnode.name, "_regular", "_kingsize")
+ minetest.set_node(rightpos, {name = "air"})
+ minetest.set_node(pos, { name = newname, param2 = fdir})
+ end
+
+ local topnode = minetest.get_node({x=pos.x, y=pos.y+1.0, z=pos.z})
+ local bottomnode = minetest.get_node({x=pos.x, y=pos.y-1.0, z=pos.z})
+
+ if string.find(topnode.name, "homedecor:bed_.*_regular$") then
+ if fdir == topnode.param2 then
+ local newname = string.gsub(thisnode.name, "_regular", "_extended")
+ minetest.set_node(pos, { name = newname, param2 = fdir})
+ end
+ end
+
+ if string.find(bottomnode.name, "homedecor:bed_.*_regular$") then
+ if fdir == bottomnode.param2 then
+ local newname = string.gsub(bottomnode.name, "_regular", "_extended")
+ minetest.set_node({x=pos.x, y=pos.y-1.0, z=pos.z}, { name = newname, param2 = fdir})
+ end
+ end
+end
+
+function homedecor.unextend_bed(pos, color)
+ local bottomnode = minetest.get_node({x=pos.x, y=pos.y-1.0, z=pos.z})
+ local fdir = bottomnode.param2
+ if string.find(bottomnode.name, "homedecor:bed_.*_extended$") then
+ local newname = string.gsub(bottomnode.name, "_extended", "_regular")
+ minetest.set_node({x=pos.x, y=pos.y-1.0, z=pos.z}, { name = newname, param2 = fdir})
+ end
+end
+
+function homedecor.place_banister(itemstack, placer, pointed_thing)
+ local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack)
+ if rightclick_result then return rightclick_result end
+
+ local pos, def = select_node(pointed_thing)
+ if not pos then return itemstack end
+
+ local fdir = minetest.dir_to_facedir(placer:get_look_dir())
+
+ local abovepos = { x=pos.x, y=pos.y+1, z=pos.z }
+ local abovenode = minetest.get_node(abovepos)
+
+ local adef = minetest.registered_nodes[abovenode.name]
+ local placer_name = placer:get_player_name()
+
+ if not (adef and adef.buildable_to) then
+ minetest.chat_send_player(placer_name, "Not enough room - the upper space is occupied!" )
+ return itemstack
+ end
+
+ if minetest.is_protected(abovepos, placer_name) then
+ minetest.chat_send_player(placer_name, "Someone already owns that spot." )
+ return itemstack
+ end
+
+ local lxd = homedecor.fdir_to_left[fdir+1][1]
+ local lzd = homedecor.fdir_to_left[fdir+1][2]
+
+ local rxd = homedecor.fdir_to_right[fdir+1][1]
+ local rzd = homedecor.fdir_to_right[fdir+1][2]
+
+ local fxd = homedecor.fdir_to_fwd[fdir+1][1]
+ local fzd = homedecor.fdir_to_fwd[fdir+1][2]
+
+ local below_pos = { x=pos.x, y=pos.y-1, z=pos.z }
+ local fwd_pos = { x=pos.x+fxd, y=pos.y, z=pos.z+fzd }
+ local left_pos = { x=pos.x+lxd, y=pos.y, z=pos.z+lzd }
+ local right_pos = { x=pos.x+rxd, y=pos.y, z=pos.z+rzd }
+ local left_fwd_pos = { x=pos.x+lxd+fxd, y=pos.y, z=pos.z+lzd+fzd }
+ local right_fwd_pos = { x=pos.x+rxd+fxd, y=pos.y, z=pos.z+rzd+fzd }
+ local right_fwd_above_pos = { x=pos.x+rxd+fxd, y=pos.y+1, z=pos.z+rzd+fzd }
+ local left_fwd_above_pos = { x=pos.x+lxd+fxd, y=pos.y+1, z=pos.z+lzd+fzd }
+ local right_fwd_below_pos = { x=pos.x+rxd+fxd, y=pos.y-1, z=pos.z+rzd+fzd }
+ local left_fwd_below_pos = { x=pos.x+lxd+fxd, y=pos.y-1, z=pos.z+lzd+fzd }
+
+ local below_node = minetest.get_node(below_pos)
+ local fwd_node = minetest.get_node(fwd_pos)
+ local left_node = minetest.get_node(left_pos)
+ local right_node = minetest.get_node(right_pos)
+ local left_fwd_node = minetest.get_node(left_fwd_pos)
+ local right_fwd_node = minetest.get_node(right_fwd_pos)
+ local left_below_node = minetest.get_node({x=left_pos.x, y=left_pos.y-1, z=left_pos.z})
+ local right_below_node = minetest.get_node({x=right_pos.x, y=right_pos.y-1, z=right_pos.z})
+ local right_fwd_above_node = minetest.get_node(right_fwd_above_pos)
+ local left_fwd_above_node = minetest.get_node(left_fwd_above_pos)
+ local right_fwd_below_node = minetest.get_node(right_fwd_below_pos)
+ local left_fwd_below_node = minetest.get_node(left_fwd_below_pos)
+
+ local new_place_name = itemstack:get_name()
+ local n = 0
+
+ -- try to place a diagonal one on the side of blocks stacked like stairs
+ -- or follow an existing diagonal with another.
+ if (left_below_node and string.find(left_below_node.name, "banister_.-_diagonal_right")
+ and below_node and is_buildable_to(placer_name, below_pos, below_pos))
+ or not is_buildable_to(placer_name, right_fwd_above_pos, right_fwd_above_pos) then
+ new_place_name = string.gsub(new_place_name, "_horizontal", "_diagonal_right")
+ elseif (right_below_node and string.find(right_below_node.name, "banister_.-_diagonal_left")
+ and below_node and is_buildable_to(placer_name, below_pos, below_pos))
+ or not is_buildable_to(placer_name, left_fwd_above_pos, left_fwd_above_pos) then
+ new_place_name = string.gsub(new_place_name, "_horizontal", "_diagonal_left")
+
+ -- try to follow a diagonal with the corresponding horizontal
+ -- from the top of a diagonal...
+ elseif left_below_node and string.find(left_below_node.name, "homedecor:banister_.*_diagonal") then
+ fdir = left_below_node.param2
+ new_place_name = string.gsub(left_below_node.name, "_diagonal_.-$", "_horizontal")
+ elseif right_below_node and string.find(right_below_node.name, "homedecor:banister_.*_diagonal") then
+ fdir = right_below_node.param2
+ new_place_name = string.gsub(right_below_node.name, "_diagonal_.-$", "_horizontal")
+
+ -- try to place a horizontal in-line with the nearest diagonal, at the top
+ elseif left_fwd_below_node and string.find(left_fwd_below_node.name, "homedecor:banister_.*_diagonal")
+ and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
+ fdir = left_fwd_below_node.param2
+ pos = fwd_pos
+ new_place_name = string.gsub(left_fwd_below_node.name, "_diagonal_.-$", "_horizontal")
+ elseif right_fwd_below_node and string.find(right_fwd_below_node.name, "homedecor:banister_.*_diagonal")
+ and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
+ fdir = right_fwd_below_node.param2
+ pos = fwd_pos
+ new_place_name = string.gsub(right_fwd_below_node.name, "_diagonal_.-$", "_horizontal")
+
+ -- try to follow a diagonal with a horizontal, at the bottom of the diagonal
+ elseif left_node and string.find(left_node.name, "homedecor:banister_.*_diagonal") then
+ fdir = left_node.param2
+ new_place_name = string.gsub(left_node.name, "_diagonal_.-$", "_horizontal")
+ elseif right_node and string.find(right_node.name, "homedecor:banister_.*_diagonal") then
+ fdir = right_node.param2
+ new_place_name = string.gsub(right_node.name, "_diagonal_.-$", "_horizontal")
+
+ -- try to place a horizontal in-line with the nearest diagonal, at the bottom
+ elseif left_fwd_node and string.find(left_fwd_node.name, "homedecor:banister_.*_diagonal")
+ and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
+ fdir = left_fwd_node.param2
+ pos = fwd_pos
+ new_place_name = string.gsub(left_fwd_node.name, "_diagonal_.-$", "_horizontal")
+ elseif right_fwd_node and string.find(right_fwd_node.name, "homedecor:banister_.*_diagonal")
+ and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
+ fdir = right_fwd_node.param2
+ pos = fwd_pos
+ new_place_name = string.gsub(right_fwd_node.name, "_diagonal_.-$", "_horizontal")
+
+ -- try to follow a horizontal with another of the same
+ elseif left_node and string.find(left_node.name, "homedecor:banister_.*_horizontal") then
+ fdir = left_node.param2
+ new_place_name = left_node.name
+ elseif right_node and string.find(right_node.name, "homedecor:banister_.*_horizontal") then
+ fdir = right_node.param2
+ new_place_name = right_node.name
+ end
+
+ -- manually invert left-right orientation
+ if placer:get_player_control()["sneak"] then
+ if string.find(new_place_name, "banister_.*_diagonal") then
+ new_place_name = string.gsub(new_place_name, "_left", "_right")
+ else
+ new_place_name = string.gsub(new_place_name, "_right", "_left")
+ end
+ end
+
+ minetest.set_node(pos, {name = new_place_name, param2 = fdir})
+ itemstack:take_item()
+ return itemstack
+end
+
diff --git a/homedecor/handlers/furnaces.lua b/homedecor/handlers/furnaces.lua
new file mode 100644
index 0000000..aaa2fef
--- /dev/null
+++ b/homedecor/handlers/furnaces.lua
@@ -0,0 +1,279 @@
+-- This code supplies an oven/stove. Basically it's just a copy of the default furnace with different textures.
+
+local S = homedecor.gettext
+
+local function swap_node(pos, name)
+ local node = minetest.get_node(pos)
+ if node.name == name then return end
+ node.name = name
+ minetest.swap_node(pos, node)
+end
+
+local function make_formspec(furnacedef, percent)
+ local fire
+
+ if percent and (percent > 0) then
+ fire = ("%s^[lowpart:%d:%s"):format(
+ furnacedef.fire_bg,
+ (100-percent),
+ furnacedef.fire_fg
+ )
+ else
+ fire = "default_furnace_fire_bg.png"
+ end
+
+ local w = furnacedef.output_width
+ local h = math.ceil(furnacedef.output_slots / furnacedef.output_width)
+
+ return "size["..math.max(8, 6 + w)..",9]"..
+ "image[2,2;1,1;"..fire.."]"..
+ "list[current_name;fuel;2,3;1,1;]"..
+ "list[current_name;src;2,1;1,1;]"..
+ "list[current_name;dst;5,1;"..w..","..h..";]"..
+ "list[current_player;main;0,5;8,4;]"
+end
+
+--[[
+furnacedef = {
+ description = "Oven",
+ tiles = { ... },
+ tiles_active = { ... },
+ ^ +Y -Y +X -X +Z -Z
+ tile_format = "oven_%s%s.png",
+ ^ First '%s' replaced by one of "top", "bottom", "side", "front".
+ ^ Second '%s' replaced by "" for inactive, and "_active" for active "front"
+ ^ "side" is used for left, right and back.
+ ^ tiles_active for front is set
+ output_slots = 4,
+ output_width = 2,
+ cook_speed = 1,
+ ^ Higher values cook stuff faster.
+ extra_nodedef_fields = { ... },
+ ^ Stuff here is copied verbatim into both active and inactive nodedefs
+ ^ Useful for overriding drawtype, etc.
+}
+]]
+
+local function make_tiles(tiles, fmt, active)
+ if not fmt then return tiles end
+ tiles = { }
+ for i,side in ipairs{"top", "bottom", "side", "side", "side", "front"} do
+ if active and (i == 6) then
+ tiles[i] = fmt:format(side, "_active")
+ else
+ tiles[i] = fmt:format(side, "")
+ end
+ end
+ return tiles
+end
+
+local furnace_can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty("fuel")
+ and inv:is_empty("dst")
+ and inv:is_empty("src")
+end
+
+function homedecor.register_furnace(name, furnacedef)
+ furnacedef.fire_fg = furnacedef.fire_bg or "default_furnace_fire_fg.png"
+ furnacedef.fire_bg = furnacedef.fire_bg or "default_furnace_fire_bg.png"
+
+ furnacedef.output_slots = furnacedef.output_slots or 4
+ furnacedef.output_width = furnacedef.output_width or 2
+
+ furnacedef.cook_speed = furnacedef.cook_speed or 1
+
+ local description = furnacedef.description or "Furnace"
+
+ local furnace_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", make_formspec(furnacedef, 0))
+ meta:set_string("infotext", description)
+ local inv = meta:get_inventory()
+ inv:set_size("fuel", 1)
+ inv:set_size("src", 1)
+ inv:set_size("dst", furnacedef.output_slots)
+ end
+
+ local furnace_allow_put = function(pos, listname, index, stack, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ if listname == "fuel" then
+ if minetest.get_craft_result({method="fuel",width=1,items={stack}}).time ~= 0 then
+ if inv:is_empty("src") then
+ meta:set_string("infotext", S("%s is empty"):format(description))
+ end
+ return stack:get_count()
+ else
+ return 0
+ end
+ elseif listname == "src" then
+ return stack:get_count()
+ elseif listname == "dst" then
+ return 0
+ end
+ end
+ local furnace_allow_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+ local meta = minetest.get_meta(pos)
+ local inv = meta:get_inventory()
+ local stack = inv:get_stack(from_list, from_index)
+ if to_list == "fuel" then
+ if minetest.get_craft_result({method="fuel",width=1,items={stack}}).time ~= 0 then
+ if inv:is_empty("src") then
+ meta:set_string("infotext", S("%s is empty"):format(description))
+ end
+ return count
+ else
+ return 0
+ end
+ elseif to_list == "src" then
+ return count
+ elseif to_list == "dst" then
+ return 0
+ end
+ end
+
+ local def = {
+ description = description,
+ tiles = make_tiles(furnacedef.tiles, furnacedef.tile_format, false),
+ groups = furnacedef.groups or {cracky=2},
+ sounds = furnacedef.sounds or default.node_sound_wood_defaults(),
+ on_construct = furnace_construct,
+ can_dig = furnace_can_dig,
+ allow_metadata_inventory_put = furnace_allow_put,
+ allow_metadata_inventory_move = furnace_allow_move,
+ inventory = { lockable = true }
+ }
+
+ local def_active = {
+ description = description .. " (active)",
+ tiles = make_tiles(furnacedef.tiles_active, furnacedef.tile_format, true),
+ light_source = 8,
+ drop = "homedecor:" .. name,
+ groups = furnacedef.groups or {cracky=2, not_in_creative_inventory=1},
+ sounds = furnacedef.sounds or default.node_sound_stone_defaults(),
+ on_construct = furnace_construct,
+ can_dig = furnace_can_dig,
+ allow_metadata_inventory_put = furnace_allow_put,
+ allow_metadata_inventory_move = furnace_allow_move,
+ inventory = { lockable = true }
+ }
+
+ if furnacedef.extra_nodedef_fields then
+ for k, v in pairs(furnacedef.extra_nodedef_fields) do
+ def[k] = v
+ def_active[k] = v
+ end
+ end
+
+ local name_active = name.."_active"
+
+ homedecor.register(name, def)
+ homedecor.register(name_active, def_active)
+
+ local name, name_active = "homedecor:"..name, "homedecor:"..name_active
+
+ minetest.register_abm({
+ nodenames = {name, name_active, name.."_locked", name_active.."_locked"},
+ label = "furnaces",
+ interval = 1.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local meta = minetest.get_meta(pos)
+ for i, name in ipairs({
+ "fuel_totaltime",
+ "fuel_time",
+ "src_totaltime",
+ "src_time"
+ }) do
+ if meta:get_string(name) == "" then
+ meta:set_float(name, 0.0)
+ end
+ end
+
+ local inv = meta:get_inventory()
+
+ local srclist = inv:get_list("src")
+ local cooked = nil
+ local aftercooked
+
+ if srclist then
+ cooked, aftercooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
+ end
+
+ local was_active = false
+
+ if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then
+ was_active = true
+ meta:set_float("fuel_time", meta:get_float("fuel_time") + 1)
+ meta:set_float("src_time", meta:get_float("src_time") + furnacedef.cook_speed)
+ if cooked and cooked.item and meta:get_float("src_time") >= cooked.time then
+ -- check if there's room for output in "dst" list
+ if inv:room_for_item("dst",cooked.item) then
+ -- Put result in "dst" list
+ inv:add_item("dst", cooked.item)
+ -- take stuff from "src" list
+ inv:set_stack("src", 1, aftercooked.items[1])
+ end
+ meta:set_string("src_time", 0)
+ end
+ end
+
+ -- XXX: Quick patch, make it better in the future.
+ local locked = node.name:find("_locked$") and "_locked" or ""
+ local desc = minetest.registered_nodes[name..locked].description
+
+ if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then
+ local percent = math.floor(meta:get_float("fuel_time") /
+ meta:get_float("fuel_totaltime") * 100)
+ meta:set_string("infotext",S("%s active: %d%%"):format(desc,percent))
+ swap_node(pos,name_active..locked)
+ meta:set_string("formspec", make_formspec(furnacedef, percent))
+ return
+ end
+
+ local fuel = nil
+ local afterfuel
+ local cooked = nil
+ local fuellist = inv:get_list("fuel")
+ local srclist = inv:get_list("src")
+
+ if srclist then
+ cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
+ end
+ if fuellist then
+ fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})
+ end
+
+ if (not fuel) or (fuel.time <= 0) then
+ meta:set_string("infotext",desc..S(": Out of fuel"))
+ swap_node(pos, name..locked)
+ meta:set_string("formspec", make_formspec(furnacedef, 0))
+ return
+ end
+
+ if cooked.item:is_empty() then
+ if was_active then
+ meta:set_string("infotext",S("%s is empty"):format(desc))
+ swap_node(pos, name..locked)
+ meta:set_string("formspec", make_formspec(furnacedef, 0))
+ end
+ return
+ end
+
+ if not inv:room_for_item("dst", cooked.item) then
+ meta:set_string("infotext", desc..S(": output bins are full"))
+ swap_node(pos, name..locked)
+ meta:set_string("formspec", make_formspec(furnacedef, 0))
+ return
+ end
+
+ meta:set_string("fuel_totaltime", fuel.time)
+ meta:set_string("fuel_time", 0)
+
+ inv:set_stack("fuel", 1, afterfuel.items[1])
+ end,
+ })
+
+end
diff --git a/homedecor/handlers/init.lua b/homedecor/handlers/init.lua
new file mode 100644
index 0000000..12003ca
--- /dev/null
+++ b/homedecor/handlers/init.lua
@@ -0,0 +1,22 @@
+local handlerpath = homedecor.modpath .. "/handlers/"
+
+-- nodebox arithmetics and helpers
+-- (please keep non-generic nodeboxes with their node definition)
+dofile(handlerpath.."nodeboxes.lua")
+
+-- expand and unexpand decor
+dofile(handlerpath.."expansion.lua")
+
+-- register nodes that cook stuff
+dofile(handlerpath.."furnaces.lua")
+
+-- inventory related functionality, like initialization, ownership and spawning locked versions
+dofile(handlerpath.."inventory.lua")
+
+-- glue it all together into a registration function
+dofile(handlerpath.."registration.lua")
+
+-- some nodes have particle spawners
+dofile(handlerpath.."water_particles.lua")
+
+dofile(handlerpath.."sit.lua")
diff --git a/homedecor/handlers/inventory.lua b/homedecor/handlers/inventory.lua
new file mode 100644
index 0000000..51ac70f
--- /dev/null
+++ b/homedecor/handlers/inventory.lua
@@ -0,0 +1,173 @@
+local S = homedecor.gettext
+
+local default_can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos)
+ return meta:get_inventory():is_empty("main")
+end
+
+local background = default.gui_bg .. default.gui_bg_img .. default.gui_slots
+local default_inventory_formspecs = {
+ ["4"]="size[8,6]".. background ..
+ "list[context;main;2,0;4,1;]"..
+ "list[current_player;main;0,2;8,4;]",
+
+ ["6"]="size[8,6]".. background ..
+ "list[context;main;1,0;6,1;]"..
+ "list[current_player;main;0,2;8,4;]",
+
+ ["8"]="size[8,6]".. background ..
+ "list[context;main;0,0;8,1;]"..
+ "list[current_player;main;0,2;8,4;]",
+
+ ["12"]="size[8,7]".. background ..
+ "list[context;main;1,0;6,2;]"..
+ "list[current_player;main;0,3;8,4;]",
+
+ ["16"]="size[8,7]".. background ..
+ "list[context;main;0,0;8,2;]"..
+ "list[current_player;main;0,3;8,4;]",
+
+ ["24"]="size[8,8]".. background ..
+ "list[context;main;0,0;8,3;]"..
+ "list[current_player;main;0,4;8,4;]",
+
+ ["32"]="size[8,9]".. background ..
+ "list[context;main;0,0.3;8,4;]"..
+ "list[current_player;main;0,4.85;8,1;]"..
+ "list[current_player;main;0,6.08;8,3;8]"..
+ default.get_hotbar_bg(0,4.85),
+
+ ["50"]="size[10,10]".. background ..
+ "list[context;main;0,0;10,5;]"..
+ "list[current_player;main;1,6;8,4;]",
+}
+
+local function get_formspec_by_size(size)
+ --TODO heuristic to use the "next best size"
+ local formspec = default_inventory_formspecs[tostring(size)]
+ return formspec or default_inventory_formspecs
+end
+
+----
+-- handle inventory setting
+-- inventory = {
+-- size = 16,
+-- formspec = …,
+-- locked = false,
+-- lockable = true,
+-- }
+--
+function homedecor.handle_inventory(name, def, original_def)
+ local inventory = def.inventory
+ if not inventory then return end
+ def.inventory = nil
+
+ if inventory.size then
+ local on_construct = def.on_construct
+ def.on_construct = function(pos)
+ local size = inventory.size
+ local meta = minetest.get_meta(pos)
+ meta:get_inventory():set_size("main", size)
+ meta:set_string("formspec", inventory.formspec or get_formspec_by_size(size))
+ if on_construct then on_construct(pos) end
+ end
+ end
+
+ def.can_dig = def.can_dig or default_can_dig
+ def.on_metadata_inventory_move = def.on_metadata_inventory_move or function(pos, from_list, from_index, to_list, to_index, count, player)
+ minetest.log("action", S("%s moves stuff in %s at %s"):format(
+ player:get_player_name(), name, minetest.pos_to_string(pos)
+ ))
+ end
+ def.on_metadata_inventory_put = def.on_metadata_inventory_put or function(pos, listname, index, stack, player)
+ minetest.log("action", S("%s moves stuff to %s at %s"):format(
+ player:get_player_name(), name, minetest.pos_to_string(pos)
+ ))
+ end
+ def.on_metadata_inventory_take = def.on_metadata_inventory_take or function(pos, listname, index, stack, player)
+ minetest.log("action", S("%s takes stuff from %s at %s"):format(
+ player:get_player_name(), name, minetest.pos_to_string(pos)
+ ))
+ end
+
+ local locked = inventory.locked
+ if locked then
+ local after_place_node = def.after_place_node
+ def.after_place_node = function(pos, placer)
+ local meta = minetest.get_meta(pos)
+ local owner = placer:get_player_name() or ""
+
+ meta:set_string("owner", owner)
+ meta:set_string("infotext", S("%s (owned by %s)"):format(def.infotext or def.description, owner))
+ return after_place_node and after_place_node(pos, placer)
+ end
+
+ local allow_move = def.allow_metadata_inventory_move
+ def.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+ local meta = minetest.get_meta(pos)
+ local owner = meta:get_string("owner")
+ local playername = player:get_player_name()
+
+ if (playername ~= owner) then
+ minetest.log("action", string.format("%s tried to access a %s belonging to %s at %s",
+ playername, name, owner, minetest.pos_to_string(pos)
+ ))
+ return 0
+ end
+
+ return allow_move and allow_move(pos, from_list, from_index, to_list, to_index, count, player)
+ or count
+ end
+
+ local allow_put = def.allow_metadata_inventory_put
+ def.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
+ local meta = minetest.get_meta(pos)
+ local owner = meta:get_string("owner")
+ local playername = player:get_player_name()
+
+ if (playername ~= owner) then
+ minetest.log("action", string.format("%s tried to access a %s belonging to %s at %s",
+ playername, name, owner, minetest.pos_to_string(pos)
+ ))
+ return 0
+ end
+ return allow_put and allow_put(pos, listname, index, stack, player)
+ or stack:get_count()
+ end
+
+ local allow_take = def.allow_metadata_inventory_take
+ def.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
+ local meta = minetest.get_meta(pos)
+ local owner = meta:get_string("owner")
+ local playername = player:get_player_name()
+
+ if (playername ~= owner) then
+ minetest.log("action", string.format("%s tried to access a %s belonging to %s at %s",
+ playername, name, owner, minetest.pos_to_string(pos)
+ ))
+ return 0
+ end
+ return allow_take and allow_take(pos, listname, index, stack, player)
+ or stack:get_count()
+ end
+ end
+
+ local lockable = inventory.lockable
+ if lockable then
+ local locked_def = table.copy(original_def)
+ locked_def.description = S("Locked %s"):format(def.description or name)
+
+ local locked_inventory = locked_def.inventory
+ locked_inventory.locked = true
+ locked_inventory.lockable = nil -- avoid loops of locked locked stuff
+
+ local locked_name = name .. "_locked"
+ homedecor.register(locked_name, locked_def)
+ minetest.register_craft({
+ type = "shapeless",
+ output = "homedecor:" .. locked_name,
+ recipe = { "homedecor:" .. name, "default:steel_ingot" }
+ })
+ end
+
+end
diff --git a/homedecor/handlers/nodeboxes.lua b/homedecor/handlers/nodeboxes.lua
new file mode 100644
index 0000000..0d8306e
--- /dev/null
+++ b/homedecor/handlers/nodeboxes.lua
@@ -0,0 +1,62 @@
+-- please keep any non-generic nodeboxe with its node definition
+-- this file should not accumulate any left over nodeboxes
+-- but is meant to host any abstractions or calculations based on nodeboxes
+
+-- a box is defined as {x1, y1, z1, x2, y2, z2}
+homedecor.box = {
+ -- slab starting from -x (after rotation: left)
+ slab_x = function(depth) return { -0.5, -0.5, -0.5, -0.5+depth, 0.5, 0.5 } end,
+ -- bottom slab (starting from -y) with height optionally shifted upwards
+ slab_y = function(height, shift) return { -0.5, -0.5+(shift or 0), -0.5, 0.5, -0.5+height+(shift or 0), 0.5 } end,
+ -- slab starting from -z (+z with negative depth)
+ slab_z = function(depth)
+ -- for consistency with the other functions here, we have to assume that a "z" slab starts from -z and extends by depth,
+ -- but since conventionally a lot of nodes place slabs against +z for player convenience, we define
+ -- a "negative" depth as a depth extending from the other side, i.e. +z
+ if depth > 0 then
+ -- slab starting from -z
+ return { -0.5, -0.5, -0.5, 0.5, 0.5, -0.5+depth }
+ else
+ -- slab starting from +z (z1=0.5-(-depth))
+ return { -0.5, -0.5, 0.5+depth, 0.5, 0.5, 0.5 }
+ end
+ end,
+ bar_y = function(radius) return {-radius, -0.5, -radius, radius, 0.5, radius} end,
+ cuboid = function(radius_x, radius_y, radius_z) return {-radius_x, -radius_y, -radius_z, radius_x, radius_y, radius_z} end,
+}
+
+homedecor.nodebox = {
+ -- { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
+ -- can be used in-place as:
+ -- { type="regular" },
+ regular = { type="regular" },
+ null = { type = "fixed", fixed = { 0, 0, 0, 0, 0, 0 } },
+ corner_xz = function(depth_x, depth_z) return {
+ type="fixed",
+ fixed={
+ homedecor.box.slab_x(depth_x),
+ homedecor.box.slab_z(depth_z),
+ -- { -0.5, -0.5, -0.5, 0.5-depth, 0.5, -0.5+depth } -- slab_x without the overlap, but actually looks a bit worse
+ }
+ } end,
+}
+
+local mt = {}
+mt.__index = function(table, key)
+ local ref = homedecor.box[key]
+ local ref_type = type(ref)
+ if ref_type == "function" then
+ return function(...)
+ return { type = "fixed", fixed = ref(...) }
+ end
+ elseif ref_type == "table" then
+ return { type = "fixed", fixed = ref }
+ elseif ref_type == "nil" then
+ error(key .. "could not be found among nodebox presets and functions")
+ end
+ error("unexpected datatype " .. tostring(type(ref)) .. " while looking for " .. key)
+end
+setmetatable(homedecor.nodebox, mt)
+
+
+
diff --git a/homedecor/handlers/registration.lua b/homedecor/handlers/registration.lua
new file mode 100644
index 0000000..b25378f
--- /dev/null
+++ b/homedecor/handlers/registration.lua
@@ -0,0 +1,95 @@
+homedecor = homedecor or {}
+local S = homedecor.gettext
+local placeholder_node = "homedecor:expansion_placeholder"
+
+--wrapper around minetest.register_node that sets sane defaults and interprets some specialized settings
+function homedecor.register(name, original_def)
+ local def = table.copy(original_def)
+
+ def.drawtype = def.drawtype
+ or (def.mesh and "mesh")
+ or (def.node_box and "nodebox")
+
+ def.paramtype = def.paramtype or "light"
+
+ -- avoid facedir for some drawtypes as they might be used internally for something else
+ -- even if undocumented
+ if not (def.drawtype == "glasslike_framed"
+ or def.drawtype == "raillike"
+ or def.drawtype == "plantlike"
+ or def.drawtype == "firelike") then
+
+ def.paramtype2 = def.paramtype2 or "facedir"
+ end
+
+ homedecor.handle_inventory(name, def, original_def)
+
+ local infotext = def.infotext
+ --def.infotext = nil -- currently used to set locked refrigerator infotexts
+
+ if infotext then
+ local on_construct = def.on_construct
+ def.on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", infotext)
+ if on_construct then on_construct(pos) end
+ end
+ end
+
+ local expand = def.expand
+ def.expand = nil
+ local after_unexpand = def.after_unexpand
+ def.after_unexpand = nil
+
+ if expand then
+ -- dissallow rotating only half the expanded node by default
+ -- unless we know better
+ def.on_rotate = def.on_rotate
+ or (def.mesh and expand.top and screwdriver.rotate_simple)
+ or screwdriver.disallow
+
+ def.on_place = def.on_place or function(itemstack, placer, pointed_thing)
+ if expand.top then
+ return homedecor.stack_vertically(itemstack, placer, pointed_thing, itemstack:get_name(), expand.top)
+ elseif expand.right then
+ return homedecor.stack_sideways(itemstack, placer, pointed_thing, itemstack:get_name(), expand.right, true)
+ elseif expand.forward then
+ return homedecor.stack_sideways(itemstack, placer, pointed_thing, itemstack:get_name(), expand.forward, false)
+ end
+ end
+ def.after_dig_node = def.after_dig_node or function(pos, oldnode, oldmetadata, digger)
+ if expand.top and expand.forward ~= "air" then
+ local top_pos = { x=pos.x, y=pos.y+1, z=pos.z }
+ local node = minetest.get_node(top_pos).name
+ if node == expand.top or node == placeholder_node then
+ minetest.remove_node(top_pos)
+ end
+ end
+
+ local fdir = oldnode.param2
+ if not fdir or fdir > 3 then return end
+
+ if expand.right and expand.forward ~= "air" then
+ local right_pos = { x=pos.x+homedecor.fdir_to_right[fdir+1][1], y=pos.y, z=pos.z+homedecor.fdir_to_right[fdir+1][2] }
+ local node = minetest.get_node(right_pos).name
+ if node == expand.right or node == placeholder_node then
+ minetest.remove_node(right_pos)
+ end
+ end
+ if expand.forward and expand.forward ~= "air" then
+ local forward_pos = { x=pos.x+homedecor.fdir_to_fwd[fdir+1][1], y=pos.y, z=pos.z+homedecor.fdir_to_fwd[fdir+1][2] }
+ local node = minetest.get_node(forward_pos).name
+ if node == expand.forward or node == placeholder_node then
+ minetest.remove_node(forward_pos)
+ end
+ end
+
+ if after_unexpand then
+ after_unexpand(pos)
+ end
+ end
+ end
+
+ -- register the actual minetest node
+ minetest.register_node("homedecor:" .. name, def)
+end
diff --git a/homedecor/handlers/sit.lua b/homedecor/handlers/sit.lua
new file mode 100644
index 0000000..636a585
--- /dev/null
+++ b/homedecor/handlers/sit.lua
@@ -0,0 +1,32 @@
+function homedecor.sit(pos, node, clicker)
+ do return end -- delete it when the engine is stabler for the player's physics
+ local meta = minetest.get_meta(pos)
+ local param2 = node.param2
+ local name = clicker:get_player_name()
+
+ if name == meta:get_string("is_sit") then
+ meta:set_string("is_sit", "")
+ pos.y = pos.y-0.5
+ clicker:setpos(pos)
+ clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
+ clicker:set_physics_override(1, 1, 1)
+ default.player_attached[name] = false
+ default.player_set_animation(clicker, "stand", 30)
+ else
+ meta:set_string("is_sit", clicker:get_player_name())
+ clicker:set_eye_offset({x=0,y=-7,z=2}, {x=0,y=0,z=0})
+ clicker:set_physics_override(0, 0, 0)
+ clicker:setpos(pos)
+ default.player_attached[name] = true
+ default.player_set_animation(clicker, "sit", 30)
+ if param2 == 0 then
+ clicker:set_look_yaw(3.15)
+ elseif param2 == 1 then
+ clicker:set_look_yaw(7.9)
+ elseif param2 == 2 then
+ clicker:set_look_yaw(6.28)
+ elseif param2 == 3 then
+ clicker:set_look_yaw(4.75)
+ else return end
+ end
+end
diff --git a/homedecor/handlers/water_particles.lua b/homedecor/handlers/water_particles.lua
new file mode 100644
index 0000000..a3334b0
--- /dev/null
+++ b/homedecor/handlers/water_particles.lua
@@ -0,0 +1,115 @@
+-- variables taken by the start... function
+--
+-- pos and node are as usual, from e.g. on_rightclick.
+--
+-- in the { particledef } table:
+--
+-- outletx/y/z are the exact coords of the starting point
+-- for the spawner, relative to the center of the node
+--
+-- velocityx/y/z are the speed of the particles,
+-- (x and z are relative to a node placed while looking north/facedir 0)
+-- negative Y values flow downward.
+--
+-- spread is the radius from the starting point,
+-- along X and Z only, to randomly spawn particles.
+--
+-- soundname is the filename (without .ogg) of the sound file
+-- to be played along with the particle stream
+
+function homedecor.start_particle_spawner(pos, node, particledef, soundname)
+
+ local this_spawner_meta = minetest.get_meta(pos)
+ local id = this_spawner_meta:get_int("active")
+ local s_handle = this_spawner_meta:get_int("sound")
+
+ if id ~= 0 then
+ if s_handle then
+ minetest.after(0, function(s_handle)
+ minetest.sound_stop(s_handle)
+ end, s_handle)
+ end
+ minetest.delete_particlespawner(id)
+ this_spawner_meta:set_int("active", nil)
+ this_spawner_meta:set_int("sound", nil)
+ return
+ end
+
+ local fdir = node.param2
+
+ if fdir and fdir < 4 and (not id or id == 0) then
+
+ local outletx = particledef.outlet.x
+ local outlety = particledef.outlet.y
+ local outletz = particledef.outlet.z
+ local velocityx = particledef.velocity_x
+ local velocityy = particledef.velocity_y
+ local velocityz = particledef.velocity_z
+ local spread = particledef.spread
+
+ local minx_t = { outletx - spread, -outletz - spread, outletx - spread, outletz - spread }
+ local maxx_t = { outletx + spread, -outletz + spread, outletx + spread, outletz + spread }
+ local minz_t = { -outletz - spread, outletx - spread, outletz - spread, outletx - spread }
+ local maxz_t = { -outletz + spread, outletx + spread, outletz + spread, outletx + spread }
+
+ local minvelx_t = { velocityx.min, velocityz.min, -velocityx.max, -velocityz.max }
+ local maxvelx_t = { velocityx.max, velocityz.max, -velocityx.min, -velocityz.min }
+ local minvelz_t = { velocityz.min, velocityx.min, -velocityz.max, velocityx.min }
+ local maxvelz_t = { velocityz.max, velocityx.max, -velocityz.min, velocityx.max }
+
+ local minx = minx_t[fdir + 1]
+ local maxx = maxx_t[fdir + 1]
+ local minz = minz_t[fdir + 1]
+ local maxz = maxz_t[fdir + 1]
+
+ local minvelx = minvelx_t[fdir + 1]
+ local minvelz = minvelz_t[fdir + 1]
+ local maxvelx = maxvelx_t[fdir + 1]
+ local maxvelz = maxvelz_t[fdir + 1]
+
+ id = minetest.add_particlespawner({
+ amount = 60,
+ time = 0,
+ collisiondetection = true,
+ minpos = {x=pos.x - minx, y=pos.y + outlety, z=pos.z - minz},
+ maxpos = {x=pos.x - maxx, y=pos.y + outlety, z=pos.z - maxz},
+ minvel = {x = minvelx, y = velocityy, z = minvelz},
+ maxvel = {x = maxvelx, y = velocityy, z = maxvelz},
+ minacc = {x=0, y=0, z=0},
+ maxacc = {x=0, y=-0.05, z=0},
+ minexptime = 2,
+ maxexptime = 4,
+ minsize = 0.5,
+ maxsize = 1,
+ texture = "homedecor_water_particle.png",
+ })
+ s_handle = minetest.sound_play(soundname, {
+ pos = pos,
+ max_hear_distance = 5,
+ loop = true
+ })
+ this_spawner_meta:set_int("active", id)
+ this_spawner_meta:set_int("sound", s_handle)
+ return
+ end
+end
+
+function homedecor.stop_particle_spawner(pos)
+ local this_spawner_meta = minetest.get_meta(pos)
+ local id = this_spawner_meta:get_int("active")
+ local s_handle = this_spawner_meta:get_int("sound")
+
+ if id ~= 0 then
+ minetest.delete_particlespawner(id)
+ end
+
+ if s_handle then
+ minetest.after(0, function(s_handle)
+ minetest.sound_stop(s_handle)
+ end, s_handle)
+ end
+
+ this_spawner_meta:set_int("active", nil)
+ this_spawner_meta:set_int("sound", nil)
+end
+