init.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. --register stoppers for movestones/pistons
  2. mesecon.mvps_stoppers = {}
  3. mesecon.on_mvps_move = {}
  4. mesecon.mvps_unmov = {}
  5. --- Objects (entities) that cannot be moved
  6. function mesecon.register_mvps_unmov(objectname)
  7. mesecon.mvps_unmov[objectname] = true;
  8. end
  9. function mesecon.is_mvps_unmov(objectname)
  10. return mesecon.mvps_unmov[objectname]
  11. end
  12. -- Nodes that cannot be pushed / pulled by movestones, pistons
  13. function mesecon.is_mvps_stopper(node, pushdir, stack, stackid)
  14. -- unknown nodes are always stoppers
  15. if not minetest.registered_nodes[node.name] then
  16. return true
  17. end
  18. local get_stopper = mesecon.mvps_stoppers[node.name]
  19. if type (get_stopper) == "function" then
  20. get_stopper = get_stopper(node, pushdir, stack, stackid)
  21. end
  22. return get_stopper
  23. end
  24. function mesecon.register_mvps_stopper(nodename, get_stopper)
  25. if get_stopper == nil then
  26. get_stopper = true
  27. end
  28. mesecon.mvps_stoppers[nodename] = get_stopper
  29. end
  30. -- Functions to be called on mvps movement
  31. function mesecon.register_on_mvps_move(callback)
  32. mesecon.on_mvps_move[#mesecon.on_mvps_move+1] = callback
  33. end
  34. local function on_mvps_move(moved_nodes)
  35. for _, callback in ipairs(mesecon.on_mvps_move) do
  36. callback(moved_nodes)
  37. end
  38. end
  39. function mesecon.mvps_process_stack(stack)
  40. -- update mesecons for placed nodes ( has to be done after all nodes have been added )
  41. for _, n in ipairs(stack) do
  42. mesecon.on_placenode(n.pos, minetest.get_node(n.pos))
  43. end
  44. end
  45. -- tests if the node can be pushed into, e.g. air, water, grass
  46. local function node_replaceable(name)
  47. if minetest.registered_nodes[name] then
  48. return minetest.registered_nodes[name].buildable_to or false
  49. end
  50. return false
  51. end
  52. function mesecon.mvps_get_stack(pos, dir, maximum, all_pull_sticky)
  53. -- determine the number of nodes to be pushed
  54. local nodes = {}
  55. local frontiers = {pos}
  56. while #frontiers > 0 do
  57. local np = frontiers[1]
  58. local nn = minetest.get_node(np)
  59. if not node_replaceable(nn.name) then
  60. table.insert(nodes, {node = nn, pos = np})
  61. if #nodes > maximum then return nil end
  62. -- add connected nodes to frontiers, connected is a vector list
  63. -- the vectors must be absolute positions
  64. local connected = {}
  65. if minetest.registered_nodes[nn.name]
  66. and minetest.registered_nodes[nn.name].mvps_sticky then
  67. connected = minetest.registered_nodes[nn.name].mvps_sticky(np, nn)
  68. end
  69. table.insert(connected, vector.add(np, dir))
  70. -- If adjacent node is sticky block and connects add that
  71. -- position to the connected table
  72. for _, r in ipairs(mesecon.rules.alldirs) do
  73. local adjpos = vector.add(np, r)
  74. local adjnode = minetest.get_node(adjpos)
  75. if minetest.registered_nodes[adjnode.name]
  76. and minetest.registered_nodes[adjnode.name].mvps_sticky then
  77. local sticksto = minetest.registered_nodes[adjnode.name]
  78. .mvps_sticky(adjpos, adjnode)
  79. -- connects to this position?
  80. for _, link in ipairs(sticksto) do
  81. if vector.equals(link, np) then
  82. table.insert(connected, adjpos)
  83. end
  84. end
  85. end
  86. end
  87. if all_pull_sticky then
  88. table.insert(connected, vector.subtract(np, dir))
  89. end
  90. -- Make sure there are no duplicates in frontiers / nodes before
  91. -- adding nodes in "connected" to frontiers
  92. for _, cp in ipairs(connected) do
  93. local duplicate = false
  94. for _, rp in ipairs(nodes) do
  95. if vector.equals(cp, rp.pos) then
  96. duplicate = true
  97. end
  98. end
  99. for _, fp in ipairs(frontiers) do
  100. if vector.equals(cp, fp) then
  101. duplicate = true
  102. end
  103. end
  104. if not duplicate then
  105. table.insert(frontiers, cp)
  106. end
  107. end
  108. end
  109. table.remove(frontiers, 1)
  110. end
  111. return nodes
  112. end
  113. function mesecon.mvps_set_owner(pos, placer)
  114. local meta = minetest.get_meta(pos)
  115. local owner = placer and placer.get_player_name and placer:get_player_name()
  116. if owner and owner ~= "" then
  117. meta:set_string("owner", owner)
  118. else
  119. meta:set_string("owner", "$unknown") -- to distinguish from older pistons
  120. end
  121. end
  122. function mesecon.mvps_claim(pos, player_name)
  123. if not player_name or player_name == "" then return end
  124. local meta = minetest.get_meta(pos)
  125. if meta:get_string("infotext") == "" then return end
  126. if meta:get_string("owner") == player_name then return end -- already owned
  127. if minetest.is_protected(pos, player_name) then
  128. minetest.chat_send_player(player_name, "Can't reclaim: protected")
  129. return
  130. end
  131. meta:set_string("owner", player_name)
  132. meta:set_string("infotext", "")
  133. return true
  134. end
  135. local function add_pos(positions, pos)
  136. local hash = minetest.hash_node_position(pos)
  137. positions[hash] = pos
  138. end
  139. local function are_protected(positions, player_name)
  140. local mode = mesecon.setting("mvps_protection_mode", "compat")
  141. if mode == "ignore" then
  142. return false
  143. end
  144. local name = player_name
  145. if player_name == "" or not player_name then -- legacy MVPS
  146. if mode == "normal" then
  147. name = "$unknown" -- sentinel, for checking for *any* protection
  148. elseif mode == "compat" then
  149. return false
  150. elseif mode == "restrict" then
  151. return true
  152. else
  153. error("Invalid protection mode")
  154. end
  155. end
  156. local is_protected = minetest.is_protected
  157. for _, pos in pairs(positions) do
  158. if is_protected(pos, name) then
  159. return true
  160. end
  161. end
  162. return false
  163. end
  164. function mesecon.mvps_push(pos, dir, maximum, player_name)
  165. return mesecon.mvps_push_or_pull(pos, dir, dir, maximum, false, player_name)
  166. end
  167. function mesecon.mvps_pull_all(pos, dir, maximum, player_name)
  168. return mesecon.mvps_push_or_pull(pos, vector.multiply(dir, -1), dir, maximum, true, player_name)
  169. end
  170. function mesecon.mvps_pull_single(pos, dir, maximum, player_name)
  171. return mesecon.mvps_push_or_pull(pos, vector.multiply(dir, -1), dir, maximum, false, player_name)
  172. end
  173. -- pos: pos of mvps
  174. -- stackdir: direction of building the stack
  175. -- movedir: direction of actual movement
  176. -- maximum: maximum nodes to be pushed
  177. -- all_pull_sticky: All nodes are sticky in the direction that they are pulled from
  178. -- player_name: Player responsible for the action.
  179. -- - empty string means legacy MVPS, actual check depends on configuration
  180. -- - "$unknown" is a sentinel for forcing the check
  181. function mesecon.mvps_push_or_pull(pos, stackdir, movedir, maximum, all_pull_sticky, player_name)
  182. local nodes = mesecon.mvps_get_stack(pos, movedir, maximum, all_pull_sticky)
  183. if not nodes then return end
  184. local protection_check_set = {}
  185. if vector.equals(stackdir, movedir) then -- pushing
  186. add_pos(protection_check_set, pos)
  187. end
  188. -- determine if one of the nodes blocks the push / pull
  189. for id, n in ipairs(nodes) do
  190. if mesecon.is_mvps_stopper(n.node, movedir, nodes, id) then
  191. return
  192. end
  193. add_pos(protection_check_set, n.pos)
  194. add_pos(protection_check_set, vector.add(n.pos, movedir))
  195. end
  196. if are_protected(protection_check_set, player_name) then
  197. return false, "protected"
  198. end
  199. -- remove all nodes
  200. for _, n in ipairs(nodes) do
  201. n.meta = minetest.get_meta(n.pos):to_table()
  202. local node_timer = minetest.get_node_timer(n.pos)
  203. if node_timer:is_started() then
  204. n.node_timer = {node_timer:get_timeout(), node_timer:get_elapsed()}
  205. end
  206. minetest.remove_node(n.pos)
  207. end
  208. -- update mesecons for removed nodes ( has to be done after all nodes have been removed )
  209. for _, n in ipairs(nodes) do
  210. mesecon.on_dignode(n.pos, n.node)
  211. end
  212. -- add nodes
  213. for _, n in ipairs(nodes) do
  214. local np = vector.add(n.pos, movedir)
  215. minetest.set_node(np, n.node)
  216. minetest.get_meta(np):from_table(n.meta)
  217. if n.node_timer then
  218. minetest.get_node_timer(np):set(unpack(n.node_timer))
  219. end
  220. end
  221. local moved_nodes = {}
  222. local oldstack = mesecon.tablecopy(nodes)
  223. for i in ipairs(nodes) do
  224. moved_nodes[i] = {}
  225. moved_nodes[i].oldpos = nodes[i].pos
  226. nodes[i].pos = vector.add(nodes[i].pos, movedir)
  227. moved_nodes[i].pos = nodes[i].pos
  228. moved_nodes[i].node = nodes[i].node
  229. moved_nodes[i].meta = nodes[i].meta
  230. moved_nodes[i].node_timer = nodes[i].node_timer
  231. end
  232. on_mvps_move(moved_nodes)
  233. return true, nodes, oldstack
  234. end
  235. function mesecon.mvps_move_objects(pos, dir, nodestack, movefactor)
  236. local objects_to_move = {}
  237. local dir_k
  238. local dir_l
  239. for k, v in pairs(dir) do
  240. if v ~= 0 then
  241. dir_k = k
  242. dir_l = v
  243. break
  244. end
  245. end
  246. movefactor = movefactor or 1
  247. dir = vector.multiply(dir, movefactor)
  248. for id, obj in pairs(minetest.get_objects_inside_radius(pos, #nodestack + 1)) do
  249. local obj_pos = obj:get_pos()
  250. local cbox = obj:get_properties().collisionbox
  251. local min_pos = vector.add(obj_pos, vector.new(cbox[1], cbox[2], cbox[3]))
  252. local max_pos = vector.add(obj_pos, vector.new(cbox[4], cbox[5], cbox[6]))
  253. local ok = true
  254. for k, v in pairs(pos) do
  255. local edge1, edge2
  256. if k ~= dir_k then
  257. edge1 = v - 0.51 -- More than 0.5 to move objects near to the stack.
  258. edge2 = v + 0.51
  259. else
  260. edge1 = v - 0.5 * dir_l
  261. edge2 = v + (#nodestack + 0.5 * movefactor) * dir_l
  262. -- Make sure, edge1 is bigger than edge2:
  263. if edge1 > edge2 then
  264. edge1, edge2 = edge2, edge1
  265. end
  266. end
  267. if min_pos[k] > edge2 or max_pos[k] < edge1 then
  268. ok = false
  269. break
  270. end
  271. end
  272. if ok then
  273. local ent = obj:get_luaentity()
  274. if obj:is_player() or (ent and not mesecon.is_mvps_unmov(ent.name)) then
  275. local np = vector.add(obj_pos, dir)
  276. -- Move only if destination is not solid or object is inside stack:
  277. local nn = minetest.get_node(np)
  278. local node_def = minetest.registered_nodes[nn.name]
  279. local obj_offset = dir_l * (obj_pos[dir_k] - pos[dir_k])
  280. if (node_def and not node_def.walkable) or
  281. (obj_offset >= 0 and
  282. obj_offset <= #nodestack - 0.5) then
  283. obj:move_to(np)
  284. end
  285. end
  286. end
  287. end
  288. end
  289. -- Never push into unloaded blocks. Don’t try to pull from them, either.
  290. -- TODO: load blocks instead, as with wires.
  291. mesecon.register_mvps_stopper("ignore")
  292. mesecon.register_mvps_stopper("doors:door_steel_b_1")
  293. mesecon.register_mvps_stopper("doors:door_steel_t_1")
  294. mesecon.register_mvps_stopper("doors:door_steel_b_2")
  295. mesecon.register_mvps_stopper("doors:door_steel_t_2")
  296. mesecon.register_mvps_stopper("default:chest_locked")
  297. mesecon.register_on_mvps_move(mesecon.move_hot_nodes)
  298. mesecon.register_on_mvps_move(function(moved_nodes)
  299. for i = 1, #moved_nodes do
  300. local moved_node = moved_nodes[i]
  301. mesecon.on_placenode(moved_node.pos, moved_node.node)
  302. minetest.after(0, function()
  303. minetest.check_for_falling(moved_node.oldpos)
  304. minetest.check_for_falling(moved_node.pos)
  305. end)
  306. local node_def = minetest.registered_nodes[moved_node.node.name]
  307. if node_def and node_def.mesecon and node_def.mesecon.on_mvps_move then
  308. node_def.mesecon.on_mvps_move(moved_node.pos, moved_node.node,
  309. moved_node.oldpos, moved_node.meta)
  310. end
  311. end
  312. end)