tube_api.lua 17 KB

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