init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. filler = {}
  2. filler.blacklist = {}
  3. filler.endless_placeable = {}
  4. filler.placing_from_top_to_bottom = {}
  5. filler.blacklist["protector:protect"] = true
  6. filler.blacklist["protector:protect2"] = true
  7. --filler.endless_placeable["air"] = true
  8. --filler.endless_placeable["default:water_source"] = true
  9. filler.placing_from_top_to_bottom["air"] = true
  10. local max_volume = 32^3
  11. local color_pos1 = "#ffbb00"
  12. local color_pos2 = "#00bbff"
  13. local speed = 0.1
  14. local sound_placing_failed = "default_item_smoke" --"default_cool_lava" --"default_tool_breaks"
  15. local sound_set_pos = "default_place_node_hard"
  16. local sound_scan_node = "default_dig_metal"
  17. local marker_time = 4
  18. local ownercheck = false -- set to true makes the device belonging to one user only (anti_griefing)
  19. local recipe_on = false -- tool can be crafted
  20. local mod_storage = minetest.get_mod_storage()
  21. local function add_rollback_storage(player, pos, node)
  22. local t = minetest.deserialize(mod_storage:get_string(player.."_rollback_storage")) or {}
  23. table.insert(t, 1, {pos = pos, node = node})
  24. mod_storage:set_string(player.."_rollback_storage", minetest.serialize(t))
  25. end
  26. local function get_rollback_storage(player)
  27. local t = minetest.deserialize(mod_storage:get_string(player.."_rollback_storage")) or {}
  28. local e = table.remove(t, 1)
  29. mod_storage:set_string(player.."_rollback_storage", minetest.serialize(t))
  30. return e
  31. end
  32. local function refresh_rollback_storage(player)
  33. local t = minetest.deserialize(mod_storage:get_string(player.."_rollback_storage")) or {}
  34. mod_storage:set_string(player.."_rollback_storage", nil)
  35. end
  36. local function make_it_one(n)
  37. if n<0 then n=-1 end
  38. if n>0 then n=1 end
  39. return n
  40. end
  41. local function get_volume(pos1, pos2)
  42. if not pos1 or not pos2 then
  43. return 0
  44. end
  45. local lv = vector.subtract(pos1, pos2)
  46. return (math.abs(lv.x)+1) * (math.abs(lv.y)+1) * (math.abs(lv.z)+1)
  47. end
  48. local function get_pos_infotext(pos1, pos2)
  49. local lv = vector.subtract(pos1, pos2)
  50. lv.x = math.abs(lv.x)+1
  51. lv.y = math.abs(lv.y)+1
  52. lv.z = math.abs(lv.z)+1
  53. local it = lv.x.."x"..lv.y.."x"..lv.z
  54. local volume = lv.x*lv.y*lv.z
  55. if volume > max_volume then
  56. it = it.." = "..minetest.colorize("#ff0000", volume.." Blocks")
  57. else
  58. it = it.." = "..volume.." Blocks"
  59. end
  60. return it
  61. end
  62. local function check_owner(itemstack,name)
  63. local owner = itemstack:get_meta():get_string("owner")
  64. if not owner or owner == "" then
  65. itemstack:get_meta():set_string("owner", name)
  66. end
  67. if owner ~= name then
  68. minetest.chat_send_player(name, core.colorize("#ffbb00", ">>> This is not yours <<<"))
  69. return false
  70. end
  71. return true
  72. end
  73. local function set_pos(itemstack, pos, player)
  74. local pos_name = minetest.pos_to_string(pos)
  75. local meta = itemstack:get_meta()
  76. local turner = meta:get_int("turner")
  77. local color = color_pos1
  78. local pos1 = minetest.string_to_pos(meta:get_string("pos1")) or {x=0,y=0,z=0}
  79. local pos2 = minetest.string_to_pos(meta:get_string("pos2")) or {x=0,y=0,z=0}
  80. if turner == 1 then
  81. pos2 = pos
  82. meta:set_string("pos2", pos_name)
  83. color = color_pos2
  84. turner = 0
  85. else
  86. pos1 = pos
  87. meta:set_string("pos1", pos_name)
  88. turner = 1
  89. end
  90. meta:set_int("turner", turner)
  91. minetest.chat_send_player(player:get_player_name(),
  92. "Filling Tool: "..minetest.colorize(color, "Pos")..
  93. " set to "..pos_name.." "..get_pos_infotext(pos1, pos2)
  94. )
  95. minetest.sound_play({name = sound_set_pos}, {pos = pos})
  96. local pos_under = table.copy(pos)
  97. pos_under.y = pos_under.y - 1
  98. if minetest.get_node(pos_under).name ~= "air" then
  99. minetest.add_particle({
  100. pos = pos,
  101. expirationtime = marker_time,
  102. vertical = true,
  103. size = 10,
  104. texture = "default_mese_post_light_side.png^[multiply:"..color,
  105. glow = 5
  106. })
  107. else
  108. minetest.add_particle({
  109. pos = pos,
  110. expirationtime = marker_time,
  111. size = 5,
  112. texture = "default_meselamp.png^[multiply:"..color,
  113. glow = 5
  114. })
  115. end
  116. return itemstack
  117. end
  118. local function get_next_pos(cpos, bpos, epos, dpos)
  119. if cpos.x ~= epos.x then
  120. cpos.x = cpos.x + dpos.x
  121. elseif cpos.z ~= epos.z then
  122. cpos.z = cpos.z + dpos.z
  123. cpos.x = bpos.x
  124. elseif cpos.y ~= epos.y then
  125. cpos.y = cpos.y + dpos.y
  126. cpos.x = bpos.x
  127. cpos.z = bpos.z
  128. else
  129. return false
  130. end
  131. return cpos
  132. end
  133. local function pos_can_place(pos, node_name, player)
  134. local node = minetest.get_node(pos).name
  135. if node == "ignore" then
  136. return false
  137. end
  138. local pnode = minetest.registered_nodes[node]
  139. local node_under = minetest.registered_nodes[minetest.get_node({x = pos.x, y = pos.y-1, z = pos.z}).name]
  140. if not pnode or not pnode.buildable_to then
  141. return false
  142. elseif node_under and minetest.get_item_group(node_name, "attached_node") > 0 and
  143. node_under.walkable == false then
  144. return false
  145. end
  146. return true
  147. end
  148. local function rollback_filling(player)
  149. local player_name = player:get_player_name()
  150. local inv = player:get_inventory()
  151. if player:get_attribute("filler_deactivate") == "true" then
  152. minetest.chat_send_player(player_name, "Filling Tool: Deactivated.")
  153. player:set_attribute("filler_deactivate", "false")
  154. player:set_attribute("filler_activated", "false")
  155. return
  156. end
  157. local rollback_storage = get_rollback_storage(player_name)
  158. if not rollback_storage or rollback_storage == {} then
  159. player:set_attribute("filler_activated", "false")
  160. return
  161. end
  162. local node = minetest.get_node(rollback_storage.pos)
  163. while node.name ~= rollback_storage.node.name do
  164. rollback_storage = get_rollback_storage(player_name)
  165. if not rollback_storage or rollback_storage == {} then
  166. player:set_attribute("filler_activated", "false")
  167. return
  168. end
  169. node = minetest.get_node(rollback_storage.pos)
  170. end
  171. minetest.set_node(rollback_storage.pos, {name="air"}) -- this more save in case of clay and other things which change shape if dug
  172. if inv:room_for_item("main", node.name) then
  173. inv:add_item("main", node.name) -- in case of rollback, dug items should be returned
  174. else
  175. minetest.item_drop(ItemStack(node.name), player, rollback_storage.pos)
  176. end
  177. local node_sounds = minetest.registered_nodes[node.name].sounds
  178. if node_sounds and node_sounds.dug then
  179. minetest.sound_play(minetest.registered_nodes[node.name].sounds.dug, {pos = rollback_storage.pos})
  180. else
  181. --minetest.sound_play("", {pos = rollback_storage.pos})
  182. end
  183. minetest.after(speed, rollback_filling, player)
  184. end
  185. local function fill_area(cpos, bpos, epos, node, player, dpos, inv) --cpos, dpos and inv to improve performance
  186. local player_name = player:get_player_name()
  187. if player:get_attribute("filler_deactivate") == "true" then
  188. minetest.chat_send_player(player_name, "Filling Tool: Deactivated.")
  189. player:set_attribute("filler_deactivate", "false")
  190. player:set_attribute("filler_activated", "false")
  191. return
  192. end
  193. while not pos_can_place(cpos, node.name) do
  194. cpos = get_next_pos(cpos, bpos, epos, dpos)
  195. if not cpos then
  196. player:set_attribute("filler_activated", "false")
  197. return
  198. end -- finished
  199. end
  200. -- check if protected discrete
  201. if minetest.is_protected(cpos, player_name) then
  202. minetest.chat_send_player(player_name,
  203. "Filling Tool:"..minetest.colorize("#ff0000", " Stop! ")..
  204. "This area is protected!")
  205. minetest.sound_play({name = sound_placing_failed}, {pos = cpos})
  206. player:set_attribute("filler_activated", "false")
  207. return
  208. end
  209. if not inv:contains_item("main", node.name) and not filler.endless_placeable[node.name] and
  210. not minetest.setting_getbool("creative_mode") then
  211. minetest.chat_send_player(player_name,
  212. "Filling Tool:"..minetest.colorize("#ff0000", " Stop! ").."You are out of "..
  213. '"'..minetest.registered_nodes[node.name].description..'"!')
  214. minetest.sound_play({name = sound_placing_failed}, {pos = cpos})
  215. player:set_attribute("filler_activated", "false")
  216. return
  217. end
  218. -- place node
  219. if not filler.endless_placeable[node.name] and not minetest.setting_getbool("creative_mode") then
  220. inv:remove_item("main", node.name)
  221. end
  222. -- works perfect but bad performance
  223. minetest.item_place_node(ItemStack(node.name), player, {type="node", under=cpos, above=cpos}, node.param2)
  224. -- alternatives
  225. --minetest.add_node(cpos, node)
  226. --minetest.place_node(cpos, node)
  227. add_rollback_storage(player_name, cpos, node)
  228. local node_sounds = minetest.registered_nodes[node.name].sounds
  229. if node_sounds and node_sounds.place then
  230. minetest.sound_play(minetest.registered_nodes[node.name].sounds.place, {pos = cpos})
  231. else
  232. --minetest.sound_play("", {pos = cpos})
  233. end
  234. cpos = get_next_pos(cpos, bpos, epos, dpos)
  235. if not cpos then
  236. player:set_attribute("filler_activated", "false")
  237. return
  238. end -- finished
  239. minetest.after(speed, fill_area, cpos, bpos, epos, node, player, dpos, inv)
  240. end
  241. local function set_wear(itemstack, level, max_level)
  242. local temp
  243. if level == 0 then
  244. temp = 0
  245. else
  246. temp = 65536 - math.floor(level / max_level * 65535)
  247. if temp > 65535 then temp = 65535 end
  248. if temp < 1 then temp = 1 end
  249. end
  250. itemstack:set_wear(temp)
  251. end
  252. local function get_wear(itemstack)
  253. if itemstack:get_metadata() == "" then
  254. return 100
  255. else
  256. return tonumber(itemstack:get_metadata())
  257. end
  258. end
  259. minetest.register_tool("filler:filler", {
  260. description = "Filling Tool",
  261. inventory_image = "filler_filler.png",
  262. light_source = 10,
  263. on_place = function(itemstack, placer, pointed_thing)
  264. if not pointed_thing.type == "node" then
  265. return itemstack
  266. end
  267. if placer:get_player_control().sneak == true then
  268. local node = minetest.get_node(pointed_thing.under)
  269. if not minetest.registered_nodes[node.name] then
  270. return itemstack
  271. end
  272. itemstack:get_meta():set_string("node", minetest.serialize(node))
  273. minetest.chat_send_player(placer:get_player_name(),
  274. "Filling Tool: Node set to "..
  275. '"'..minetest.registered_nodes[node.name].description..'"')
  276. minetest.sound_play({name = sound_scan_node}, {pos = pointed_thing.under})
  277. else
  278. return set_pos(itemstack, pointed_thing.above, placer)
  279. end
  280. return itemstack
  281. end,
  282. on_secondary_use = function(itemstack, user)
  283. local pos = vector.round(user:get_pos())
  284. if user:get_player_control().sneak == true then
  285. local node = minetest.get_node(pos)
  286. if not minetest.registered_nodes[node.name] then
  287. return itemstack
  288. end
  289. itemstack:get_meta():set_string("node", minetest.serialize(node))
  290. minetest.chat_send_player(user:get_player_name(),
  291. "Filling Tool: Node set to "..
  292. '"'..minetest.registered_nodes[node.name].description..'"')
  293. minetest.sound_play({name = sound_scan_node}, {pos = pos})
  294. else
  295. return set_pos(itemstack, pos, user)
  296. end
  297. return itemstack
  298. end,
  299. on_use = function(itemstack, user, pointed_thing)
  300. local uses = get_wear(itemstack)
  301. if uses == 0 then
  302. itemstack:set_name('')
  303. return itemstack end
  304. local player_name = user:get_player_name()
  305. if user:get_attribute("filler_activated") == "true" then
  306. minetest.chat_send_player(player_name, "Filling Tool: The filling tool is currently working."..
  307. " (You can hold sneak and left click to deactivate it.)")
  308. if user:get_player_control().sneak == true then
  309. user:set_attribute("filler_deactivate", "true")
  310. end
  311. return
  312. end
  313. local meta = itemstack:get_meta()
  314. local pos1 = minetest.string_to_pos(meta:get_string("pos1"))
  315. local pos2 = minetest.string_to_pos(meta:get_string("pos2"))
  316. local node = minetest.deserialize(meta:get_string("node"))
  317. local volume = get_volume(pos1, pos2)
  318. if not user then return end
  319. local inv = user:get_inventory()
  320. -- Ownercheck added here.
  321. if ownercheck and not check_owner(itemstack,player_name) then
  322. return itemstack
  323. end
  324. -- End Ownercheck
  325. if not node then
  326. minetest.chat_send_player(player_name, "Filling Tool: Hold sneak and right click to select a node.")
  327. return
  328. end
  329. if not pos1 or not pos2 then
  330. minetest.chat_send_player(player_name, "Filling Tool: Right click to set coordinates.")
  331. return
  332. end
  333. if volume > max_volume then
  334. minetest.chat_send_player(player_name, "Filling Tool: This area is too big.")
  335. return
  336. end
  337. if filler.blacklist[node.name] == true then
  338. minetest.chat_send_player(player_name, 'Filling Tool: "'..
  339. minetest.registered_nodes[node.name].description..'"'.." can't be placed with the filling tool.")
  340. return
  341. end
  342. local bpos = table.copy(pos1)
  343. local epos = table.copy(pos2)
  344. local cdpos = 1
  345. if filler.placing_from_top_to_bottom[node.name] == true then
  346. cdpos = -1
  347. if pos1.y < pos2.y then
  348. bpos = table.copy(pos2)
  349. epos = table.copy(pos1)
  350. end
  351. else
  352. if pos1.y > pos2.y then
  353. bpos = table.copy(pos2)
  354. epos = table.copy(pos1)
  355. end
  356. end
  357. local cpos = table.copy(bpos)
  358. local dpos = vector.direction(bpos, epos)
  359. dpos.x = make_it_one(dpos.x)
  360. dpos.y = cdpos
  361. dpos.z = make_it_one(dpos.z)
  362. refresh_rollback_storage(player_name)
  363. user:set_attribute("filler_activated", "true")
  364. fill_area(cpos, bpos, epos, node, user, dpos, inv)
  365. uses = uses - 1
  366. itemstack:set_metadata(tostring(uses))
  367. set_wear(itemstack, uses, 100)
  368. return itemstack
  369. end,
  370. })
  371. minetest.register_chatcommand("rsq", {
  372. description = "Revert the last filling action",
  373. func = function(name, param)
  374. local player = minetest.get_player_by_name(name)
  375. if player:get_attribute("filler_activated") == "true" then
  376. minetest.chat_send_player(name, "Filling Tool: The filling tool is currently working."..
  377. " (You can hold sneak and left click to deactivate it.)")
  378. return
  379. end
  380. player:set_attribute("filler_activated", "true")
  381. rollback_filling(player)
  382. end
  383. })
  384. minetest.register_on_joinplayer(function(player)
  385. player:set_attribute("filler_activated", "false")
  386. end)
  387. if recipe_on then -- this tool is a bit dangerous on multiplayer servers
  388. minetest.register_craft({
  389. output = 'filler:filler',
  390. recipe = {
  391. {'', 'default:mese_post_light', 'default:diamond'},
  392. {'', 'default:steel_ingot', 'default:mese_post_light'},
  393. {'group:stick', '', ''},
  394. }
  395. })
  396. end