expansion.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. local S = minetest.get_translator("homedecor_common")
  2. -- vectors to place one node next to or behind another
  3. homedecor.fdir_to_right = {
  4. { 1, 0 },
  5. { 0, -1 },
  6. { -1, 0 },
  7. { 0, 1 },
  8. }
  9. homedecor.fdir_to_left = {
  10. { -1, 0 },
  11. { 0, 1 },
  12. { 1, 0 },
  13. { 0, -1 },
  14. }
  15. homedecor.fdir_to_fwd = {
  16. { 0, 1 },
  17. { 1, 0 },
  18. { 0, -1 },
  19. { -1, 0 },
  20. }
  21. -- special case for wallmounted nodes
  22. homedecor.wall_fdir_to_right = {
  23. nil,
  24. nil,
  25. { -1, 0 },
  26. { 1, 0 },
  27. { 0, -1 },
  28. { 0, 1 },
  29. }
  30. homedecor.wall_fdir_to_left = {
  31. nil,
  32. nil,
  33. { 1, 0 },
  34. { -1, 0 },
  35. { 0, 1 },
  36. { 0, -1 },
  37. }
  38. homedecor.wall_fdir_to_fwd = {
  39. nil,
  40. nil,
  41. { 0, -1 },
  42. { 0, 1 },
  43. { 1, 0 },
  44. { -1, 0 },
  45. }
  46. local placeholder_node = "air"
  47. minetest.register_alias("homedecor:expansion_placeholder", "air")
  48. --- select which node was pointed at based on it being known, not ignored, buildable_to
  49. -- returns nil if no node could be selected
  50. local function select_node(pointed_thing)
  51. local pos = pointed_thing.under
  52. local node = minetest.get_node_or_nil(pos)
  53. local def = node and minetest.registered_nodes[node.name]
  54. if not def or not def.buildable_to then
  55. pos = pointed_thing.above
  56. node = minetest.get_node_or_nil(pos)
  57. def = node and minetest.registered_nodes[node.name]
  58. end
  59. return def and pos, def
  60. end
  61. --- check if all nodes can and may be build to
  62. local function is_buildable_to(placer_name, ...)
  63. for _, pos in ipairs({...}) do
  64. local node = minetest.get_node_or_nil(pos)
  65. local def = node and minetest.registered_nodes[node.name]
  66. if not (def and def.buildable_to) or minetest.is_protected(pos, placer_name) then
  67. return false
  68. end
  69. end
  70. return true
  71. end
  72. -- place one or two nodes if and only if both can be placed
  73. local function stack(itemstack, placer, fdir, pos, def, pos2, node1, node2, pointed_thing)
  74. local placer_name = placer:get_player_name() or ""
  75. if is_buildable_to(placer_name, pos, pos2) then
  76. local lfdir = fdir or minetest.dir_to_facedir(placer:get_look_dir())
  77. minetest.set_node(pos, { name = node1, param2 = lfdir })
  78. node2 = node2 or "air" -- this can be used to clear buildable_to nodes even though we are using a multinode mesh
  79. -- do not assume by default, as we still might want to allow overlapping in some cases
  80. local has_facedir = node2 ~= "air"
  81. if node2 == "placeholder" then
  82. has_facedir = false
  83. node2 = placeholder_node
  84. end
  85. minetest.set_node(pos2, { name = node2, param2 = (has_facedir and lfdir) or nil })
  86. -- call after_place_node of the placed node if available
  87. local ctrl_node_def = minetest.registered_nodes[node1]
  88. if ctrl_node_def and ctrl_node_def.after_place_node then
  89. ctrl_node_def.after_place_node(pos, placer, itemstack, pointed_thing)
  90. end
  91. if not creative.is_enabled_for(placer_name) then
  92. itemstack:take_item()
  93. end
  94. end
  95. return itemstack
  96. end
  97. local function rightclick_pointed_thing(pos, placer, itemstack, pointed_thing)
  98. local node = minetest.get_node_or_nil(pos)
  99. if not node then return false end
  100. local def = minetest.registered_nodes[node.name]
  101. if not def or not def.on_rightclick then return false end
  102. return def.on_rightclick(pos, node, placer, itemstack, pointed_thing) or itemstack
  103. end
  104. -- Stack one node above another
  105. -- leave the last argument nil if it's one 2m high node
  106. function homedecor.stack_vertically(itemstack, placer, pointed_thing, node1, node2)
  107. local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack, pointed_thing)
  108. if rightclick_result then return rightclick_result end
  109. local pos, def = select_node(pointed_thing)
  110. if not pos then return itemstack end
  111. local top_pos = { x=pos.x, y=pos.y+1, z=pos.z }
  112. return stack(itemstack, placer, nil, pos, def, top_pos, node1, node2, pointed_thing)
  113. end
  114. -- Stack one door node above another
  115. -- like homedecor.stack_vertically but tests first if it was placed as a right wing, then uses node1_right and node2_right instead
  116. function homedecor.stack_wing(itemstack, placer, pointed_thing, node1, node2, node1_right, node2_right)
  117. local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack, pointed_thing)
  118. if rightclick_result then return rightclick_result end
  119. local pos, def = select_node(pointed_thing)
  120. if not pos then return itemstack end
  121. local forceright = placer:get_player_control()["sneak"]
  122. local fdir = minetest.dir_to_facedir(placer:get_look_dir())
  123. local is_right_wing = node1 == minetest.get_node(
  124. {
  125. x = pos.x + homedecor.fdir_to_left[fdir+1][1],
  126. y = pos.y,
  127. z = pos.z + homedecor.fdir_to_left[fdir+1][2] }).name
  128. if forceright or is_right_wing then
  129. node1, node2 = node1_right, node2_right
  130. end
  131. local top_pos = { x=pos.x, y=pos.y+1, z=pos.z }
  132. return stack(itemstack, placer, fdir, pos, def, top_pos, node1, node2, pointed_thing)
  133. end
  134. function homedecor.stack_sideways(itemstack, placer, pointed_thing, node1, node2, dir)
  135. local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack, pointed_thing)
  136. if rightclick_result then return rightclick_result end
  137. local pos, def = select_node(pointed_thing)
  138. if not pos then return itemstack end
  139. local fdir = minetest.dir_to_facedir(placer:get_look_dir())
  140. local fdir_transform = dir and homedecor.fdir_to_right or homedecor.fdir_to_fwd
  141. local pos2 = { x = pos.x + fdir_transform[fdir+1][1], y=pos.y, z = pos.z + fdir_transform[fdir+1][2] }
  142. return stack(itemstack, placer, fdir, pos, def, pos2, node1, node2, pointed_thing)
  143. end
  144. function homedecor.bed_expansion(pos, placer, itemstack, pointed_thing, trybunks)
  145. local thisnode = minetest.get_node(pos)
  146. local param2 = thisnode.param2
  147. local fdir = param2 % 8
  148. local fxd = homedecor.wall_fdir_to_fwd[fdir+1][1]
  149. local fzd = homedecor.wall_fdir_to_fwd[fdir+1][2]
  150. local forwardpos = {x=pos.x+fxd, y=pos.y, z=pos.z+fzd}
  151. local forwardnode = minetest.get_node(forwardpos)
  152. local def = minetest.registered_nodes[forwardnode.name]
  153. local placer_name = placer:get_player_name()
  154. if not (def and def.buildable_to) then
  155. minetest.chat_send_player( placer:get_player_name(),
  156. S("Not enough room - the space for the headboard is occupied!"))
  157. minetest.set_node(pos, {name = "air"})
  158. return true
  159. end
  160. if minetest.is_protected(forwardpos, placer_name) then
  161. minetest.chat_send_player( placer:get_player_name(),
  162. S("Someone already owns the spot where the headboard goes."))
  163. return true
  164. end
  165. minetest.set_node(forwardpos, {name = "air"})
  166. local lxd = homedecor.wall_fdir_to_left[fdir+1][1]
  167. local lzd = homedecor.wall_fdir_to_left[fdir+1][2]
  168. local leftpos = {x=pos.x+lxd, y=pos.y, z=pos.z+lzd}
  169. local leftnode = minetest.get_node(leftpos)
  170. local rxd = homedecor.wall_fdir_to_right[fdir+1][1]
  171. local rzd = homedecor.wall_fdir_to_right[fdir+1][2]
  172. local rightpos = {x=pos.x+rxd, y=pos.y, z=pos.z+rzd}
  173. local rightnode = minetest.get_node(rightpos)
  174. if leftnode.name == "homedecor:bed_regular" then
  175. local newname = string.gsub(thisnode.name, "_regular", "_kingsize")
  176. minetest.set_node(pos, {name = "air"})
  177. minetest.swap_node(leftpos, { name = newname, param2 = param2})
  178. elseif rightnode.name == "homedecor:bed_regular" then
  179. local newname = string.gsub(thisnode.name, "_regular", "_kingsize")
  180. minetest.set_node(rightpos, {name = "air"})
  181. minetest.swap_node(pos, { name = newname, param2 = param2})
  182. end
  183. local toppos = {x=pos.x, y=pos.y+1.0, z=pos.z}
  184. local topposfwd = {x=toppos.x+fxd, y=toppos.y, z=toppos.z+fzd}
  185. if trybunks and is_buildable_to(placer_name, toppos, topposfwd) then
  186. local newname = string.gsub(thisnode.name, "_regular", "_extended")
  187. local newparam2 = param2 % 8
  188. -- FIXME: is newparam2 a legacy unused variable from a8729575abfbd15cc622b413b71976c9157fbab4? or should this variable be used somewhere?
  189. minetest.swap_node(toppos, { name = thisnode.name, param2 = param2})
  190. minetest.swap_node(pos, { name = newname, param2 = param2})
  191. itemstack:take_item()
  192. end
  193. end
  194. function homedecor.unextend_bed(pos)
  195. local bottomnode = minetest.get_node({x=pos.x, y=pos.y-1.0, z=pos.z})
  196. local param2 = bottomnode.param2
  197. if bottomnode.name == "homedecor:bed_extended" then
  198. local newname = string.gsub(bottomnode.name, "_extended", "_regular")
  199. minetest.swap_node({x=pos.x, y=pos.y-1.0, z=pos.z}, { name = newname, param2 = param2})
  200. end
  201. end
  202. function homedecor.place_banister(itemstack, placer, pointed_thing)
  203. local rightclick_result = rightclick_pointed_thing(pointed_thing.under, placer, itemstack, pointed_thing)
  204. if rightclick_result then return rightclick_result end
  205. local pos, _ = select_node(pointed_thing)
  206. if not pos then return itemstack end
  207. local fdir = minetest.dir_to_facedir(placer:get_look_dir())
  208. local meta = itemstack:get_meta()
  209. local pindex = meta:get_int("palette_index")
  210. local abovepos = { x=pos.x, y=pos.y+1, z=pos.z }
  211. local abovenode = minetest.get_node(abovepos)
  212. local adef = minetest.registered_nodes[abovenode.name]
  213. local placer_name = placer:get_player_name()
  214. if not (adef and adef.buildable_to) then
  215. minetest.chat_send_player(placer_name, S("Not enough room - the upper space is occupied!" ))
  216. return itemstack
  217. end
  218. if minetest.is_protected(abovepos, placer_name) then
  219. minetest.chat_send_player(placer_name, S("Someone already owns that spot."))
  220. return itemstack
  221. end
  222. local lxd = homedecor.fdir_to_left[fdir+1][1]
  223. local lzd = homedecor.fdir_to_left[fdir+1][2]
  224. local rxd = homedecor.fdir_to_right[fdir+1][1]
  225. local rzd = homedecor.fdir_to_right[fdir+1][2]
  226. local fxd = homedecor.fdir_to_fwd[fdir+1][1]
  227. local fzd = homedecor.fdir_to_fwd[fdir+1][2]
  228. local below_pos = { x=pos.x, y=pos.y-1, z=pos.z }
  229. local fwd_pos = { x=pos.x+fxd, y=pos.y, z=pos.z+fzd }
  230. local left_pos = { x=pos.x+lxd, y=pos.y, z=pos.z+lzd }
  231. local right_pos = { x=pos.x+rxd, y=pos.y, z=pos.z+rzd }
  232. local left_fwd_pos = { x=pos.x+lxd+fxd, y=pos.y, z=pos.z+lzd+fzd }
  233. local right_fwd_pos = { x=pos.x+rxd+fxd, y=pos.y, z=pos.z+rzd+fzd }
  234. local right_fwd_above_pos = { x=pos.x+rxd+fxd, y=pos.y+1, z=pos.z+rzd+fzd }
  235. local left_fwd_above_pos = { x=pos.x+lxd+fxd, y=pos.y+1, z=pos.z+lzd+fzd }
  236. local right_fwd_below_pos = { x=pos.x+rxd+fxd, y=pos.y-1, z=pos.z+rzd+fzd }
  237. local left_fwd_below_pos = { x=pos.x+lxd+fxd, y=pos.y-1, z=pos.z+lzd+fzd }
  238. local below_node = minetest.get_node(below_pos)
  239. --local fwd_node = minetest.get_node(fwd_pos)
  240. local left_node = minetest.get_node(left_pos)
  241. local right_node = minetest.get_node(right_pos)
  242. local left_fwd_node = minetest.get_node(left_fwd_pos)
  243. local right_fwd_node = minetest.get_node(right_fwd_pos)
  244. local left_below_node = minetest.get_node({x=left_pos.x, y=left_pos.y-1, z=left_pos.z})
  245. local right_below_node = minetest.get_node({x=right_pos.x, y=right_pos.y-1, z=right_pos.z})
  246. --local right_fwd_above_node = minetest.get_node(right_fwd_above_pos)
  247. --local left_fwd_above_node = minetest.get_node(left_fwd_above_pos)
  248. local right_fwd_below_node = minetest.get_node(right_fwd_below_pos)
  249. local left_fwd_below_node = minetest.get_node(left_fwd_below_pos)
  250. local new_place_name = itemstack:get_name()
  251. -- try to place a diagonal one on the side of blocks stacked like stairs
  252. -- or follow an existing diagonal with another.
  253. if (left_below_node and string.find(left_below_node.name, "banister_.-_diagonal_right")
  254. and below_node and is_buildable_to(placer_name, below_pos, below_pos))
  255. or not is_buildable_to(placer_name, right_fwd_above_pos, right_fwd_above_pos) then
  256. new_place_name = string.gsub(new_place_name, "_horizontal", "_diagonal_right")
  257. elseif (right_below_node and string.find(right_below_node.name, "banister_.-_diagonal_left")
  258. and below_node and is_buildable_to(placer_name, below_pos, below_pos))
  259. or not is_buildable_to(placer_name, left_fwd_above_pos, left_fwd_above_pos) then
  260. new_place_name = string.gsub(new_place_name, "_horizontal", "_diagonal_left")
  261. -- try to follow a diagonal with the corresponding horizontal
  262. -- from the top of a diagonal...
  263. elseif left_below_node and string.find(left_below_node.name, "homedecor:banister_.*_diagonal") then
  264. fdir = left_below_node.param2
  265. new_place_name = string.gsub(left_below_node.name, "_diagonal_.-$", "_horizontal")
  266. elseif right_below_node and string.find(right_below_node.name, "homedecor:banister_.*_diagonal") then
  267. fdir = right_below_node.param2
  268. new_place_name = string.gsub(right_below_node.name, "_diagonal_.-$", "_horizontal")
  269. -- try to place a horizontal in-line with the nearest diagonal, at the top
  270. elseif left_fwd_below_node and string.find(left_fwd_below_node.name, "homedecor:banister_.*_diagonal")
  271. and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
  272. fdir = left_fwd_below_node.param2
  273. pos = fwd_pos
  274. new_place_name = string.gsub(left_fwd_below_node.name, "_diagonal_.-$", "_horizontal")
  275. elseif right_fwd_below_node and string.find(right_fwd_below_node.name, "homedecor:banister_.*_diagonal")
  276. and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
  277. fdir = right_fwd_below_node.param2
  278. pos = fwd_pos
  279. new_place_name = string.gsub(right_fwd_below_node.name, "_diagonal_.-$", "_horizontal")
  280. -- try to follow a diagonal with a horizontal, at the bottom of the diagonal
  281. elseif left_node and string.find(left_node.name, "homedecor:banister_.*_diagonal") then
  282. fdir = left_node.param2
  283. new_place_name = string.gsub(left_node.name, "_diagonal_.-$", "_horizontal")
  284. elseif right_node and string.find(right_node.name, "homedecor:banister_.*_diagonal") then
  285. fdir = right_node.param2
  286. new_place_name = string.gsub(right_node.name, "_diagonal_.-$", "_horizontal")
  287. -- try to place a horizontal in-line with the nearest diagonal, at the bottom
  288. elseif left_fwd_node and string.find(left_fwd_node.name, "homedecor:banister_.*_diagonal")
  289. and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
  290. fdir = left_fwd_node.param2
  291. pos = fwd_pos
  292. new_place_name = string.gsub(left_fwd_node.name, "_diagonal_.-$", "_horizontal")
  293. elseif right_fwd_node and string.find(right_fwd_node.name, "homedecor:banister_.*_diagonal")
  294. and is_buildable_to(placer_name, fwd_pos, fwd_pos) then
  295. fdir = right_fwd_node.param2
  296. pos = fwd_pos
  297. new_place_name = string.gsub(right_fwd_node.name, "_diagonal_.-$", "_horizontal")
  298. end
  299. -- manually invert left-right orientation
  300. if placer:get_player_control()["sneak"] then
  301. if string.find(new_place_name, "banister_.*_diagonal") then
  302. new_place_name = string.gsub(new_place_name, "_left", "_right")
  303. else
  304. new_place_name = string.gsub(new_place_name, "_right", "_left")
  305. end
  306. end
  307. minetest.set_node(pos, {name = new_place_name, param2 = fdir+pindex})
  308. itemstack:take_item()
  309. return itemstack
  310. end