tube_api.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. --[[
  2. Tube Library 2
  3. ==============
  4. Copyright (C) 2017-2021 Joachim Stolberg
  5. LGPLv2.1+
  6. See LICENSE.txt for more information
  7. tube_api.lua
  8. ]]--
  9. -- Version for compatibility checks, see readme.md/history
  10. tubelib2.version = 2.2
  11. -- for lazy programmers
  12. local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
  13. local M = minetest.get_meta
  14. -- Load support for I18n.
  15. local S = tubelib2.S
  16. -- Cardinal directions, regardless of orientation
  17. local Dir2Str = {"north", "east", "south", "west", "down", "up"}
  18. function tubelib2.dir_to_string(dir)
  19. return Dir2Str[dir]
  20. end
  21. -- Relative directions, dependant on orientation (param2)
  22. local DirToSide = {
  23. -- param2 (0 to 23)
  24. {[0]="B","L","F","R", "U","U","U","U", "D","D","D","D", "B","L","F","R", "B","L","F","R", "B","L","F","R",}, -- dir = 1
  25. {[0]="R","B","L","F", "R","B","L","F", "R","B","L","F", "U","U","U","U", "D","D","D","D", "L","F","R","B",}, -- dir = 2
  26. {[0]="F","R","B","L", "D","D","D","D", "U","U","U","U", "F","R","B","L", "F","R","B","L", "F","R","B","L",}, -- dir = 3
  27. {[0]="L","F","R","B", "L","F","R","B", "L","F","R","B", "D","D","D","D", "U","U","U","U", "R","B","L","F",}, -- dir = 4
  28. {[0]="D","D","D","D", "B","L","F","R", "F","R","B","L", "R","B","L","F", "L","F","R","B", "U","U","U","U",}, -- dir = 5
  29. {[0]="U","U","U","U", "F","R","B","L", "B","L","F","R", "L","F","R","B", "R","B","L","F", "D","D","D","D",}, -- dir = 6
  30. }
  31. local SideToDir = {B={}, R={}, F={}, L={}, D={}, U={}}
  32. for param2 = 0,23 do
  33. for dir = 1,6 do
  34. local side = DirToSide[dir][param2]
  35. SideToDir[side][param2] = dir
  36. end
  37. end
  38. function tubelib2.dir_to_side(dir, param2)
  39. return DirToSide[dir][param2]
  40. end
  41. function tubelib2.side_to_dir(side, param2)
  42. return SideToDir[side][param2]
  43. end
  44. local function Tbl(list)
  45. local tbl = {}
  46. for _,item in ipairs(list) do
  47. tbl[item] = true
  48. end
  49. return tbl
  50. end
  51. -- Tubelib2 Class
  52. local Tube = tubelib2.Tube
  53. local Turn180Deg = tubelib2.Turn180Deg
  54. local Dir6dToVector = tubelib2.Dir6dToVector
  55. local function get_pos(pos, dir)
  56. return vector.add(pos, Dir6dToVector[dir or 0])
  57. end
  58. tubelib2.get_pos = get_pos
  59. function tubelib2.get_node_lvm(pos)
  60. local node = minetest.get_node_or_nil(pos)
  61. if node then
  62. return node
  63. end
  64. local vm = minetest.get_voxel_manip()
  65. local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
  66. local data = vm:get_data()
  67. local param2_data = vm:get_param2_data()
  68. local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
  69. local idx = area:indexp(pos)
  70. node = {
  71. name = minetest.get_name_from_content_id(data[idx]),
  72. param2 = param2_data[idx]
  73. }
  74. return node
  75. end
  76. local function update1(self, pos, dir)
  77. local fpos,fdir = self:walk_tube_line(pos, dir)
  78. self:infotext(get_pos(pos, dir), fpos)
  79. self:infotext(fpos, get_pos(pos, dir))
  80. -- Translate pos/dir pointing to the secondary node into
  81. -- spos/sdir of the secondary node pointing to the tube.
  82. if fpos and fdir then
  83. local spos, sdir = get_pos(fpos,fdir), Turn180Deg[fdir]
  84. self:del_from_cache(spos, sdir)
  85. self:add_to_cache(pos, dir, spos, sdir)
  86. self:add_to_cache(spos, sdir, pos, dir)
  87. self:update_secondary_node(pos, dir, spos, sdir)
  88. self:update_secondary_node(spos, sdir, pos, dir)
  89. end
  90. end
  91. local function update2(self, pos1, dir1, pos2, dir2)
  92. local fpos1,fdir1,cnt1 = self:walk_tube_line(pos1, dir1)
  93. local fpos2,fdir2,cnt2 = self:walk_tube_line(pos2, dir2)
  94. if cnt1 + cnt2 >= self.max_tube_length then -- line to long?
  95. -- reset next tube(s) to head tube(s) again
  96. local param2 = self:encode_param2(dir1, dir2, 2)
  97. self:update_after_dig_tube(pos1, param2)
  98. M(get_pos(pos1, dir1)):set_string("infotext", S("Maximum length reached!"))
  99. M(get_pos(pos1, dir2)):set_string("infotext", S("Maximum length reached!"))
  100. return false
  101. end
  102. self:infotext(fpos1, fpos2)
  103. self:infotext(fpos2, fpos1)
  104. -- Translate fpos/fdir pointing to the secondary node into
  105. -- spos/sdir of the secondary node pointing to the tube.
  106. local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1]
  107. local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2]
  108. self:del_from_cache(spos1, sdir1)
  109. self:del_from_cache(spos2, sdir2)
  110. self:add_to_cache(spos1, sdir1, spos2, sdir2)
  111. self:add_to_cache(spos2, sdir2, spos1, sdir1)
  112. self:update_secondary_node(spos1, sdir1, spos2, sdir2)
  113. self:update_secondary_node(spos2, sdir2, spos1, sdir1)
  114. return true
  115. end
  116. local function update3(self, pos, dir1, dir2)
  117. if pos and dir1 and dir2 then
  118. local fpos1,fdir1,cnt1 = self:walk_tube_line(pos, dir1)
  119. local fpos2,fdir2,cnt2 = self:walk_tube_line(pos, dir2)
  120. self:infotext(fpos1, fpos2)
  121. self:infotext(fpos2, fpos1)
  122. -- Translate fpos/fdir pointing to the secondary node into
  123. -- spos/sdir of the secondary node pointing to the tube.
  124. if fpos1 and fpos2 and fdir1 and fdir2 then
  125. local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1]
  126. local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2]
  127. self:del_from_cache(spos1, sdir1)
  128. self:del_from_cache(spos2, sdir2)
  129. self:add_to_cache(spos1, sdir1, spos2, sdir2)
  130. self:add_to_cache(spos2, sdir2, spos1, sdir1)
  131. self:update_secondary_node(spos1, sdir1, spos2, sdir2)
  132. self:update_secondary_node(spos2, sdir2, spos1, sdir1)
  133. return dir1, dir2, fpos1, fpos2, fdir1, fdir2, cnt1 or 0, cnt2 or 0
  134. end
  135. return dir1, dir2, pos, pos, dir1, dir2, cnt1 or 0, cnt2 or 0
  136. end
  137. end
  138. local function update_secondary_nodes_after_node_placed(self, pos, dirs)
  139. dirs = dirs or self.dirs_to_check
  140. -- check surrounding for secondary nodes
  141. for _,dir in ipairs(dirs) do
  142. local tmp, npos
  143. if self.force_to_use_tubes then
  144. tmp, npos = self:get_special_node(pos, dir)
  145. else
  146. tmp, npos = self:get_secondary_node(pos, dir)
  147. end
  148. if npos then
  149. self:update_secondary_node(npos, Turn180Deg[dir], pos, dir)
  150. self:update_secondary_node(pos, dir, npos, Turn180Deg[dir])
  151. end
  152. end
  153. end
  154. local function update_secondary_nodes_after_node_dug(self, pos, dirs)
  155. dirs = dirs or self.dirs_to_check
  156. -- check surrounding for secondary nodes
  157. for _,dir in ipairs(dirs) do
  158. local tmp, npos
  159. if self.force_to_use_tubes then
  160. tmp, npos = self:get_special_node(pos, dir)
  161. else
  162. tmp, npos = self:get_secondary_node(pos, dir)
  163. end
  164. if npos then
  165. self:del_from_cache(npos, Turn180Deg[dir])
  166. self:del_from_cache(pos, dir)
  167. self:update_secondary_node(npos, Turn180Deg[dir])
  168. self:update_secondary_node(pos, dir)
  169. end
  170. end
  171. end
  172. --
  173. -- API Functions
  174. --
  175. function Tube:new(attr)
  176. local o = {
  177. dirs_to_check = attr.dirs_to_check or {1,2,3,4,5,6},
  178. max_tube_length = attr.max_tube_length or 1000,
  179. primary_node_names = Tbl(attr.primary_node_names or {}),
  180. secondary_node_names = Tbl(attr.secondary_node_names or {}),
  181. valid_node_contact_sides = {},
  182. show_infotext = attr.show_infotext or false,
  183. force_to_use_tubes = attr.force_to_use_tubes or false,
  184. clbk_after_place_tube = attr.after_place_tube,
  185. tube_type = attr.tube_type or "unknown",
  186. pairingList = {}, -- teleporting nodes
  187. connCache = {}, -- connection cache {pos1 = {dir1 = {pos2 = pos2, dir2 = dir2},...}
  188. special_node_names = {}, -- use add_special_node_names() to register nodes
  189. debug_info = attr.debug_info, -- debug_info(pos, text)
  190. }
  191. o.valid_dirs = Tbl(o.dirs_to_check)
  192. setmetatable(o, self)
  193. self.__index = self
  194. if attr.valid_node_contact_sides then
  195. o:set_valid_sides_multiple(attr.valid_node_contact_sides)
  196. end
  197. return o
  198. end
  199. -- Register (foreign) tubelib compatible nodes.
  200. function Tube:add_secondary_node_names(names)
  201. for _,name in ipairs(names) do
  202. self.secondary_node_names[name] = true
  203. end
  204. end
  205. -- Defaults for valid sides configuration
  206. local function invert_booleans(tab)
  207. local inversion = {}
  208. for key, value in pairs(tab) do
  209. inversion[key] = not value
  210. end
  211. return inversion
  212. end
  213. local valid_sides_default_true = Tbl({"B", "R", "F", "L", "D", "U"})
  214. local valid_sides_default_false = invert_booleans(valid_sides_default_true)
  215. local function complete_valid_sides(valid_sides, existing_defaults)
  216. local valid_sides_complete = {}
  217. for side, default_value in pairs(existing_defaults) do
  218. local new_value = valid_sides[side]
  219. if new_value == nil then
  220. valid_sides_complete[side] = default_value
  221. else
  222. valid_sides_complete[side] = new_value
  223. end
  224. end
  225. return valid_sides_complete
  226. end
  227. -- Set sides which are valid
  228. -- with a table of name = valid_sides pairs
  229. function Tube:set_valid_sides_multiple(names)
  230. for name, valid_sides in pairs(names) do
  231. self:set_valid_sides(name, valid_sides)
  232. end
  233. end
  234. -- Set sides which are invalid
  235. -- with a table of name = valid_sides pairs
  236. function Tube:set_invalid_sides_multiple(names)
  237. for name, invalid_sides in pairs(names) do
  238. self:set_invalid_sides(name, invalid_sides)
  239. end
  240. end
  241. -- Set sides which are valid
  242. -- will assume all sides not given are invalid
  243. -- Only sets new sides, existing sides will remain
  244. function Tube:set_valid_sides(name, valid_sides)
  245. local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_false
  246. self.valid_node_contact_sides[name] = complete_valid_sides(Tbl(valid_sides), existing_defaults)
  247. end
  248. -- Set sides which are invalid
  249. -- will assume all sides not given are valid
  250. -- Only sets new sides, existing sides will remain
  251. function Tube:set_invalid_sides(name, invalid_sides)
  252. local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_true
  253. self.valid_node_contact_sides[name] = complete_valid_sides(invert_booleans(Tbl(invalid_sides)), existing_defaults)
  254. end
  255. -- Checks the list of valid node connection sides to
  256. -- see if a given side can be connected to
  257. function Tube:is_valid_side(name, side)
  258. local valid_sides = self.valid_node_contact_sides[name]
  259. if valid_sides then
  260. return valid_sides[side] or false
  261. end
  262. end
  263. -- Checks if a particular node can be connected to
  264. -- from a particular direction, taking into account orientation
  265. function Tube:is_valid_dir(node, dir)
  266. if node and dir ~= nil and self.valid_node_contact_sides[node.name] then
  267. local side = tubelib2.dir_to_side(dir, node.param2)
  268. return self:is_valid_side(node.name, side)
  269. end
  270. end
  271. -- Checks if a node at a particular position can be connected to
  272. -- from a particular direction, taking into account orientation
  273. function Tube:is_valid_dir_pos(pos, dir)
  274. local node = self:get_node_lvm(pos)
  275. return self:is_valid_dir(node, dir)
  276. end
  277. -- Register further nodes, which should be updated after
  278. -- a node/tube is placed/dug
  279. function Tube:add_special_node_names(names)
  280. for _,name in ipairs(names) do
  281. self.special_node_names[name] = true
  282. end
  283. end
  284. -- Called for each connected node when the tube connection has been changed.
  285. -- func(node, pos, out_dir, peer_pos, peer_in_dir)
  286. function Tube:register_on_tube_update(update_secondary_node)
  287. self.clbk_update_secondary_node = update_secondary_node
  288. end
  289. -- Called for each connected node when the tube connection has been changed.
  290. -- func(pos1, out_dir, self, node)
  291. function Tube:register_on_tube_update2(update_secondary_node2)
  292. self.clbk_update_secondary_node2 = update_secondary_node2
  293. end
  294. function Tube:get_pos(pos, dir)
  295. return vector.add(pos, Dir6dToVector[dir or 0])
  296. end
  297. -- To be called after a secondary node is placed.
  298. -- dirs is a list with valid dirs, like: {1,2,3,4}
  299. function Tube:after_place_node(pos, dirs)
  300. -- [s][f]----[n] x
  301. -- s..secondary, f..far, n..near, x..node to be placed
  302. for _,dir in ipairs(self:update_after_place_node(pos, dirs)) do
  303. if pos and dir then
  304. update1(self, pos, dir)
  305. end
  306. end
  307. update_secondary_nodes_after_node_placed(self, pos, dirs)
  308. end
  309. -- To be called after a tube/primary node is placed.
  310. -- Returns false, if placing is not allowed
  311. function Tube:after_place_tube(pos, placer, pointed_thing)
  312. -- [s1][f1]----[n1] x [n2]-----[f2][s2]
  313. -- s..secondary, f..far, n..near, x..node to be placed
  314. local res,dir1,dir2 = self:update_after_place_tube(pos, placer, pointed_thing)
  315. if res then -- node placed?
  316. return update2(self, pos, dir1, pos, dir2)
  317. end
  318. return res
  319. end
  320. function Tube:after_dig_node(pos, dirs)
  321. -- [s][f]----[n] x
  322. -- s..secondary, f..far, n..near, x..node to be removed
  323. for _,dir in ipairs(self:update_after_dig_node(pos, dirs)) do
  324. update1(self, pos, dir)
  325. end
  326. update_secondary_nodes_after_node_dug(self, pos, dirs)
  327. end
  328. -- To be called after a tube/primary node is removed.
  329. function Tube:after_dig_tube(pos, oldnode)
  330. -- [s1][f1]----[n1] x [n2]-----[f2][s2]
  331. -- s..secondary, f..far, n..near, x..node to be removed
  332. -- update tubes
  333. if oldnode and oldnode.param2 then
  334. local dir1, dir2 = self:update_after_dig_tube(pos, oldnode.param2)
  335. if dir1 then update1(self, pos, dir1) end
  336. if dir2 then update1(self, pos, dir2) end
  337. -- Update secondary nodes, if right beside
  338. dir1, dir2 = self:decode_param2(pos, oldnode.param2)
  339. local npos1,ndir1 = get_pos(pos, dir1),Turn180Deg[dir1]
  340. local npos2,ndir2 = get_pos(pos, dir2),Turn180Deg[dir2]
  341. self:del_from_cache(npos1,ndir1)
  342. self:update_secondary_node(npos1,ndir1)
  343. self:update_secondary_node(npos2,ndir2)
  344. end
  345. end
  346. -- From source node to destination node via tubes.
  347. -- pos is the source node position, dir the output dir
  348. -- The returned pos is the destination position, dir
  349. -- is the direction into the destination node.
  350. function Tube:get_connected_node_pos(pos, dir)
  351. local key = P2S(pos)
  352. if self.connCache[key] and self.connCache[key][dir] then
  353. local item = self.connCache[key][dir]
  354. return item.pos2, Turn180Deg[item.dir2]
  355. end
  356. local fpos,fdir = self:walk_tube_line(pos, dir)
  357. local spos = get_pos(fpos,fdir)
  358. self:add_to_cache(pos, dir, spos, Turn180Deg[fdir])
  359. self:add_to_cache(spos, Turn180Deg[fdir], pos, dir)
  360. return spos, fdir
  361. end
  362. -- Check if node at given position is a tubelib2 compatible node,
  363. -- able to receive and/or deliver items.
  364. -- If dir == nil then node_pos = pos
  365. -- Function returns the result (true/false), new pos, and the node
  366. function Tube:compatible_node(pos, dir)
  367. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  368. local node = self:get_node_lvm(npos)
  369. return self.secondary_node_names[node.name], npos, node
  370. end
  371. -- To be called from a repair tool in the case of a "WorldEdit" or with
  372. -- legacy nodes corrupted tube line.
  373. function Tube:tool_repair_tube(pos)
  374. local param2 = self:get_primary_node_param2(pos)
  375. if param2 then
  376. local dir1, dir2 = self:decode_param2(pos, param2)
  377. return update3(self, pos, dir1, dir2)
  378. end
  379. end
  380. -- To be called from a repair tool in the case, tube nodes are "unbreakable".
  381. function Tube:tool_remove_tube(pos, sound)
  382. if self:is_primary_node(pos) then
  383. local _,node = self:get_node(pos)
  384. minetest.sound_play({name=sound},{
  385. pos=pos,
  386. gain=1,
  387. max_hear_distance=5,
  388. loop=false})
  389. minetest.remove_node(pos)
  390. self:after_dig_tube(pos, node)
  391. return true
  392. end
  393. return false
  394. end
  395. function Tube:prepare_pairing(pos, tube_dir, sFormspec)
  396. local meta = M(pos)
  397. if meta:get_int("tube_dir") ~= 0 then -- already prepared?
  398. -- update tube_dir only
  399. meta:set_int("tube_dir", tube_dir)
  400. elseif tube_dir then
  401. meta:set_int("tube_dir", tube_dir)
  402. meta:set_string("channel", nil)
  403. meta:set_string("infotext", S("Pairing is missing"))
  404. meta:set_string("formspec", sFormspec)
  405. else
  406. meta:set_string("infotext", S("Connection to a tube is missing!"))
  407. end
  408. end
  409. function Tube:pairing(pos, channel)
  410. if self.pairingList[channel] and not vector.equals(pos, self.pairingList[channel]) then
  411. -- store peer position on both nodes
  412. local peer_pos = self.pairingList[channel]
  413. local tube_dir1 = self:store_teleport_data(pos, peer_pos)
  414. local tube_dir2 = self:store_teleport_data(peer_pos, pos)
  415. update2(self, pos, tube_dir1, peer_pos, tube_dir2)
  416. self.pairingList[channel] = nil
  417. return true
  418. else
  419. self.pairingList[channel] = pos
  420. local meta = M(pos)
  421. meta:set_string("channel", channel)
  422. meta:set_string("infotext", S("Pairing is missing (@1)", channel))
  423. return false
  424. end
  425. end
  426. function Tube:stop_pairing(pos, oldmetadata, sFormspec)
  427. -- unpair peer node
  428. if oldmetadata and oldmetadata.fields then
  429. if oldmetadata.fields.tele_pos then
  430. local tele_pos = minetest.string_to_pos(oldmetadata.fields.tele_pos)
  431. local peer_meta = M(tele_pos)
  432. if peer_meta then
  433. self:after_dig_node(tele_pos, {peer_meta:get_int("tube_dir")})
  434. peer_meta:set_string("channel", nil)
  435. peer_meta:set_string("tele_pos", nil)
  436. peer_meta:set_string("formspec", sFormspec)
  437. peer_meta:set_string("infotext", S("Pairing is missing"))
  438. end
  439. elseif oldmetadata.fields.channel then
  440. self.pairingList[oldmetadata.fields.channel] = nil
  441. end
  442. end
  443. end
  444. -- Used by chat commands, when tubes are placed e.g. via WorldEdit
  445. function Tube:replace_tube_line(pos1, pos2)
  446. if pos1 and pos2 and not vector.equals(pos1, pos2) then
  447. local check = (((pos1.x == pos2.x) and 1) or 0) +
  448. (((pos1.y == pos2.y) and 1) or 0) +
  449. (((pos1.z == pos2.z) and 1) or 0)
  450. if check == 2 then
  451. local v = vector.direction(pos1, pos2)
  452. local dir1 = self:vector_to_dir(v)
  453. local dir2 = Turn180Deg[dir1]
  454. self:replace_nodes(pos1, pos2, dir1, dir2)
  455. update3(self, pos1, dir1, dir2)
  456. end
  457. end
  458. end
  459. -- Used to change the tube nodes texture (e.g. on/off state)
  460. function Tube:switch_tube_line(pos, dir, state)
  461. self:switch_nodes(pos, dir, state)
  462. end
  463. -- Generator function to iterate over a tube line
  464. -- Returns for each tube: i , pos, node
  465. function Tube:get_tube_line(pos, dir)
  466. if pos and dir then
  467. self.ref = {pos = pos, dir = dir}
  468. return function(self, i)
  469. if i < self.max_tube_length then
  470. local new_pos, new_dir, num = self:get_next_tube(self.ref.pos, self.ref.dir)
  471. if new_pos then
  472. self.ref.pos, self.ref.dir = new_pos, new_dir
  473. i = i + 1
  474. return i, self.ref.pos, self.node
  475. end
  476. end
  477. end, self, 0
  478. end
  479. end