internal2.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. --[[
  2. Tube Library 2
  3. ==============
  4. Copyright (C) 2018-2021 Joachim Stolberg
  5. LGPLv2.1+
  6. See LICENSE.txt for more information
  7. internal.lua
  8. ]]--
  9. -- for lazy programmers
  10. local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
  11. local S2P = minetest.string_to_pos
  12. local M = minetest.get_meta
  13. -- Load support for I18n.
  14. local S = tubelib2.S
  15. local Tube = {}
  16. local Turn180Deg = {[0]=0,3,4,1,2,6,5}
  17. -- To calculate param2 based on dir6d information
  18. local DirToParam2 = {
  19. -- dir1 / dir2 ==> param2 / type (Angled/Straight)
  20. [12] = {11, "A"},
  21. [13] = {12, "S"},
  22. [14] = {14, "A"},
  23. [15] = { 8, "A"},
  24. [16] = {10, "A"},
  25. [23] = { 7, "A"},
  26. [24] = {21, "S"},
  27. [25] = { 3, "A"},
  28. [26] = {19, "A"},
  29. [34] = { 5, "A"},
  30. [35] = { 0, "A"},
  31. [36] = {20, "A"},
  32. [45] = {15, "A"},
  33. [46] = {13, "A"},
  34. [56] = { 4, "S"},
  35. }
  36. -- To retrieve dir6d values from the nodes param2
  37. local Param2ToDir = {}
  38. for k,item in pairs(DirToParam2) do
  39. Param2ToDir[item[1]] = k
  40. end
  41. -- For neighbour position calculation
  42. local Dir6dToVector = {[0] =
  43. {x=0, y=0, z=0},
  44. {x=0, y=0, z=1},
  45. {x=1, y=0, z=0},
  46. {x=0, y=0, z=-1},
  47. {x=-1, y=0, z=0},
  48. {x=0, y=-1, z=0},
  49. {x=0, y=1, z=0},
  50. }
  51. tubelib2.Tube = Tube
  52. tubelib2.Turn180Deg = Turn180Deg
  53. tubelib2.DirToParam2 = DirToParam2
  54. tubelib2.Param2ToDir = Param2ToDir
  55. tubelib2.Dir6dToVector = Dir6dToVector
  56. --
  57. -- Tubelib2 Methods
  58. --
  59. function Tube: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:index(pos.x, pos.y, pos.z)
  70. node = {
  71. name = minetest.get_name_from_content_id(data[idx]),
  72. param2 = param2_data[idx]
  73. }
  74. return node
  75. end
  76. -- Read param2 from a primary node at the given position.
  77. -- If dir == nil then node_pos = pos
  78. -- Function returns param2, new_pos or nil
  79. function Tube:get_primary_node_param2(pos, dir)
  80. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  81. self.node = self:get_node_lvm(npos)
  82. if self.primary_node_names[self.node.name] then
  83. return self.node.param2, npos
  84. end
  85. end
  86. -- Check if node at given position is a tube node
  87. -- If dir == nil then node_pos = pos
  88. -- Function returns true/false
  89. function Tube:is_primary_node(pos, dir)
  90. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  91. local node = self:get_node_lvm(npos)
  92. return self.primary_node_names[node.name]
  93. end
  94. -- Get secondary node at given position
  95. -- If dir == nil then node_pos = pos
  96. -- Function returns node and new_pos or nil
  97. function Tube:get_secondary_node(pos, dir)
  98. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  99. local node = self:get_node_lvm(npos)
  100. if self.secondary_node_names[node.name] then
  101. return node, npos
  102. end
  103. end
  104. -- Get special registered nodes at given position
  105. -- If dir == nil then node_pos = pos
  106. -- Function returns node and new_pos or nil
  107. function Tube:get_special_node(pos, dir)
  108. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  109. local node = self:get_node_lvm(npos)
  110. if self.special_node_names[node.name] then
  111. return node, npos
  112. end
  113. end
  114. -- Check if node at given position is a secondary node
  115. -- If dir == nil then node_pos = pos
  116. -- Function returns true/false
  117. function Tube:is_secondary_node(pos, dir)
  118. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  119. local node = self:get_node_lvm(npos)
  120. return self.secondary_node_names[node.name]
  121. end
  122. -- Check if node at given position is a special node
  123. -- If dir == nil then node_pos = pos
  124. -- Function returns true/false
  125. function Tube:is_special_node(pos, dir)
  126. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  127. local node = self:get_node_lvm(npos)
  128. return self.special_node_names[node.name]
  129. end
  130. -- Check if node has a connection on the given dir
  131. function Tube:connected(pos, dir)
  132. return self:is_primary_node(pos, dir) or self:is_secondary_node(pos, dir)
  133. end
  134. function Tube:get_next_tube(pos, dir)
  135. local param2, npos = self:get_primary_node_param2(pos, dir)
  136. if param2 then
  137. local val = Param2ToDir[param2 % 32] or 0
  138. local dir1, dir2 = math.floor(val / 10), val % 10
  139. local num_conn = math.floor(param2 / 32) or 0
  140. local odir = Turn180Deg[dir]
  141. if odir == dir1 then
  142. return npos, dir2, num_conn
  143. elseif odir == dir2 then
  144. return npos, dir1, num_conn
  145. end
  146. return
  147. end
  148. return self:get_next_teleport_node(pos, dir)
  149. end
  150. -- Return param2 and tube type ("A"/"S")
  151. function Tube:encode_param2(dir1, dir2, num_conn)
  152. if dir1 and dir2 and num_conn then
  153. if dir1 > dir2 then
  154. dir1, dir2 = dir2, dir1
  155. end
  156. local param2, _type = unpack(DirToParam2[dir1 * 10 + dir2] or {0, "S"})
  157. return (num_conn * 32) + param2, _type
  158. end
  159. return 0, "S"
  160. end
  161. -- Return dir1, dir2, num_conn
  162. function Tube:decode_param2(pos, param2)
  163. local val = Param2ToDir[param2 % 32]
  164. if val then
  165. local dir1, dir2 = math.floor(val / 10), val % 10
  166. local num_conn = math.floor(param2 / 32)
  167. return dir1, dir2, num_conn
  168. end
  169. end
  170. function Tube:repair_tube(pos, dir)
  171. local param2, npos = self:get_primary_node_param2(pos, dir)
  172. if param2 then
  173. local dir1, dir2 = self:decode_param2(npos, param2)
  174. local param2, tube_type = self:encode_param2(dir1, dir2, 2)
  175. self.clbk_after_place_tube(npos, param2, tube_type, 2)
  176. end
  177. end
  178. -- Return node next to pos in direction 'dir'
  179. function Tube:get_node(pos, dir)
  180. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  181. return npos, self:get_node_lvm(npos)
  182. end
  183. -- format and return given data as table
  184. function Tube:get_tube_data(pos, dir1, dir2, num_tubes, state)
  185. local param2, tube_type = self:encode_param2(dir1, dir2, num_tubes)
  186. return pos, param2, tube_type, num_tubes, state
  187. end
  188. -- Return pos for a primary_node and true if num_conn < 2, else false
  189. function Tube:friendly_primary_node(pos, dir)
  190. local param2, npos = self:get_primary_node_param2(pos, dir)
  191. if param2 then
  192. local _,_,num_conn = self:decode_param2(npos, param2)
  193. -- tube node with max one connection?
  194. return npos, (num_conn or 2) < 2
  195. end
  196. end
  197. function Tube:vector_to_dir(v)
  198. if v.y > 0 then
  199. return 6
  200. elseif v.y < 0 then
  201. return 5
  202. else
  203. return minetest.dir_to_facedir(v) + 1
  204. end
  205. end
  206. -- Check all 6 possible positions for known nodes considering preferred_pos
  207. -- and the players fdir and return dir1, dir2 and the number of tubes to connect to (0..2).
  208. function Tube:determine_tube_dirs(pos, preferred_pos, fdir)
  209. local tbl = {}
  210. local allowed = table.copy(self.valid_dirs)
  211. -- If the node at players "prefered position" is a tube,
  212. -- then the other side of the new tube shall point to the player.
  213. if preferred_pos then
  214. local _, friendly = self:friendly_primary_node(preferred_pos)
  215. if friendly then
  216. local v = vector.direction(pos, preferred_pos)
  217. local dir1 = self:vector_to_dir(v)
  218. local dir2 = Turn180Deg[fdir]
  219. return dir1, dir2, 1
  220. end
  221. end
  222. -- Check for primary nodes (tubes)
  223. for dir = 1,6 do
  224. if allowed[dir] then
  225. local npos, friendly = self:friendly_primary_node(pos, dir)
  226. if npos then
  227. if not friendly then
  228. allowed[dir] = false
  229. else
  230. if preferred_pos and vector.equals(npos, preferred_pos) then
  231. preferred_pos = nil
  232. table.insert(tbl, 1, dir)
  233. else
  234. table.insert(tbl, dir)
  235. end
  236. end
  237. end
  238. end
  239. end
  240. -- If no tube around the pointed pos and player prefers a position,
  241. -- then the new tube shall point to the player.
  242. if #tbl == 0 and preferred_pos and fdir and allowed[Turn180Deg[fdir]] then
  243. tbl[1] = Turn180Deg[fdir]
  244. -- Already 2 dirs found?
  245. elseif #tbl >= 2 then
  246. return tbl[1], tbl[2], 2
  247. end
  248. -- Check for secondary nodes (chests and so on)
  249. for dir = 1,6 do
  250. if allowed[dir] then
  251. local node,npos = self:get_secondary_node(pos, dir)
  252. if npos then
  253. if self:is_valid_dir(node, Turn180Deg[dir]) == false then
  254. allowed[dir] = false
  255. else
  256. if preferred_pos and vector.equals(npos, preferred_pos) then
  257. preferred_pos = nil
  258. table.insert(tbl, 2, dir)
  259. else
  260. table.insert(tbl, dir)
  261. end
  262. end
  263. end
  264. end
  265. end
  266. -- player pointed to an unknown node to force the tube orientation?
  267. if preferred_pos and fdir then
  268. if tbl[1] == Turn180Deg[fdir] and allowed[fdir] then
  269. tbl[2] = fdir
  270. elseif allowed[Turn180Deg[fdir]] then
  271. tbl[2] = Turn180Deg[fdir]
  272. end
  273. end
  274. -- dir1, dir2 still unknown?
  275. if fdir then
  276. if #tbl == 0 and allowed[Turn180Deg[fdir]] then
  277. tbl[1] = Turn180Deg[fdir]
  278. end
  279. if #tbl == 1 and allowed[Turn180Deg[tbl[1]]] then
  280. tbl[2] = Turn180Deg[tbl[1]]
  281. elseif #tbl == 1 and tbl[1] ~= Turn180Deg[fdir] and allowed[Turn180Deg[fdir]] then
  282. tbl[2] = Turn180Deg[fdir]
  283. end
  284. end
  285. if #tbl >= 2 and tbl[1] ~= tbl[2] then
  286. local num_tubes = (self:connected(pos, tbl[1]) and 1 or 0) +
  287. (self:connected(pos, tbl[2]) and 1 or 0)
  288. return tbl[1], tbl[2], math.min(2, num_tubes)
  289. end
  290. end
  291. -- Determine a tube side without connection, increment the number of connections
  292. -- and return the new data to be able to update the node:
  293. -- new_pos, dir1, dir2, num_connections (1, 2)
  294. function Tube:add_tube_dir(pos, dir)
  295. local param2, npos = self:get_primary_node_param2(pos, dir)
  296. if param2 then
  297. local d1, d2, num = self:decode_param2(npos, param2)
  298. if not num then return end
  299. -- if invalid face, do nothing
  300. if self:is_valid_dir_pos(pos, dir) == false then return end
  301. -- not already connected to the new tube?
  302. dir = Turn180Deg[dir]
  303. if d1 ~= dir and dir ~= d2 then
  304. if num == 0 then
  305. d1 = dir
  306. elseif num == 1 then
  307. -- determine, which of d1, d2 has already a connection
  308. if self:connected(npos, d1) then
  309. d2 = dir
  310. else
  311. d1 = dir
  312. end
  313. end
  314. end
  315. return npos, d1, d2, num
  316. end
  317. end
  318. -- Decrement the number of tube connections
  319. -- and return the new data to be able to update the node:
  320. -- new_pos, dir1, dir2, num_connections (0, 1)
  321. function Tube:del_tube_dir(pos, dir)
  322. local param2, npos = self:get_primary_node_param2(pos, dir)
  323. if param2 then
  324. local d1, d2, num = self:decode_param2(npos, param2)
  325. -- check if node is connected to the given pos
  326. dir = Turn180Deg[dir]
  327. if d1 == dir or dir == d2 then
  328. return npos, d1, d2, math.max((num or 1) - 1, 0)
  329. end
  330. end
  331. end
  332. -- Pairing helper function
  333. function Tube:store_teleport_data(pos, peer_pos)
  334. local meta = M(pos)
  335. meta:set_string("tele_pos", P2S(peer_pos))
  336. meta:set_string("channel", nil)
  337. meta:set_string("formspec", nil)
  338. meta:set_string("infotext", S("Connected to @1", P2S(peer_pos)))
  339. return meta:get_int("tube_dir")
  340. end
  341. -- Jump over the teleport nodes to the next tube node
  342. function Tube:get_next_teleport_node(pos, dir)
  343. if pos then
  344. local npos = vector.add(pos, Dir6dToVector[dir or 0])
  345. if self:is_valid_dir_pos(npos, Turn180Deg[dir]) == false then
  346. return
  347. end
  348. local meta = M(npos)
  349. local s = meta:get_string("tele_pos")
  350. if s ~= "" then
  351. local tele_pos = S2P(s)
  352. local tube_dir = M(tele_pos):get_int("tube_dir")
  353. if tube_dir ~= 0 then
  354. return tele_pos, tube_dir
  355. end
  356. end
  357. end
  358. end
  359. function Tube:dbg_out()
  360. for pos1,item1 in pairs(self.connCache) do
  361. for dir1,item2 in pairs(item1) do
  362. print("pos1="..pos1..", dir1="..dir1..", pos2="..P2S(item2.pos2)..", dir2="..item2.dir2)
  363. end
  364. end
  365. end
  366. -- Walk to the end of the tube line and return pos and outdir of both head tube nodes.
  367. -- If no tube is available, return nil
  368. function Tube:walk_tube_line(pos, dir)
  369. local cnt = 0
  370. if dir then
  371. while cnt <= self.max_tube_length do
  372. local new_pos, new_dir, num = self:get_next_tube(pos, dir)
  373. if not new_pos then break end
  374. if cnt > 0 and num ~= 2 and self:is_primary_node(new_pos, new_dir) then
  375. self:repair_tube(new_pos, new_dir)
  376. end
  377. pos, dir = new_pos, new_dir
  378. cnt = cnt + 1
  379. end
  380. if cnt > 0 then
  381. return pos, dir, cnt
  382. end
  383. end
  384. return table.copy(pos), dir, 0
  385. end