helpers.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. --advtrains by orwell96, see readme.txt
  2. local dir_trans_tbl={
  3. [0]={x=0, z=1, y=0},
  4. [1]={x=1, z=2, y=0},
  5. [2]={x=1, z=1, y=0},
  6. [3]={x=2, z=1, y=0},
  7. [4]={x=1, z=0, y=0},
  8. [5]={x=2, z=-1, y=0},
  9. [6]={x=1, z=-1, y=0},
  10. [7]={x=1, z=-2, y=0},
  11. [8]={x=0, z=-1, y=0},
  12. [9]={x=-1, z=-2, y=0},
  13. [10]={x=-1, z=-1, y=0},
  14. [11]={x=-2, z=-1, y=0},
  15. [12]={x=-1, z=0, y=0},
  16. [13]={x=-2, z=1, y=0},
  17. [14]={x=-1, z=1, y=0},
  18. [15]={x=-1, z=2, y=0},
  19. }
  20. local dir_angle_tbl={}
  21. for d,v in pairs(dir_trans_tbl) do
  22. local uvec = vector.normalize(v)
  23. dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
  24. end
  25. function advtrains.dir_to_angle(dir)
  26. return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
  27. end
  28. function advtrains.dirCoordSet(coord, dir)
  29. return vector.add(coord, advtrains.dirToCoord(dir))
  30. end
  31. advtrains.pos_add_dir = advtrains.dirCoordSet
  32. function advtrains.pos_add_angle(pos, ang)
  33. -- 0 is +Z -> meaning of sin/cos swapped
  34. return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
  35. end
  36. function advtrains.dirToCoord(dir)
  37. return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
  38. end
  39. advtrains.dir_to_vector = advtrains.dirToCoord
  40. function advtrains.maxN(list, expectstart)
  41. local n=expectstart or 0
  42. while list[n] do
  43. n=n+1
  44. end
  45. return n-1
  46. end
  47. function advtrains.minN(list, expectstart)
  48. local n=expectstart or 0
  49. while list[n] do
  50. n=n-1
  51. end
  52. return n+1
  53. end
  54. function atround(number)
  55. return math.floor(number+0.5)
  56. end
  57. atfloor = math.floor
  58. function advtrains.round_vector_floor_y(vec)
  59. return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
  60. end
  61. function advtrains.yawToDirection(yaw, conn1, conn2)
  62. if not conn1 or not conn2 then
  63. error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
  64. end
  65. local yaw1 = advtrains.dir_to_angle(conn1)
  66. local yaw2 = advtrains.dir_to_angle(conn2)
  67. local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
  68. local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
  69. if math.abs(adiff2)<math.abs(adiff1) then
  70. return conn2
  71. else
  72. return conn1
  73. end
  74. end
  75. function advtrains.yawToAnyDir(yaw)
  76. local min_conn, min_diff=0, 10
  77. for conn, vec in pairs(advtrains.dir_trans_tbl) do
  78. local yaw1 = advtrains.dir_to_angle(conn)
  79. local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
  80. if diff < min_diff then
  81. min_conn = conn
  82. min_diff = diff
  83. end
  84. end
  85. return min_conn
  86. end
  87. function advtrains.yawToClosestConn(yaw, conns)
  88. local min_connid, min_diff=1, 10
  89. for connid, conn in ipairs(conns) do
  90. local yaw1 = advtrains.dir_to_angle(conn.c)
  91. local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
  92. if diff < min_diff then
  93. min_connid = connid
  94. min_diff = diff
  95. end
  96. end
  97. return min_connid
  98. end
  99. local pi, pi2 = math.pi, 2*math.pi
  100. function advtrains.minAngleDiffRad(r1, r2)
  101. while r1>pi2 do
  102. r1=r1-pi2
  103. end
  104. while r1<0 do
  105. r1=r1+pi2
  106. end
  107. while r2>pi2 do
  108. r2=r2-pi2
  109. end
  110. while r1<0 do
  111. r2=r2+pi2
  112. end
  113. local try1=r2-r1
  114. local try2=r2+pi2-r1
  115. local try3=r2-pi2-r1
  116. local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
  117. if minabs==math.abs(try1) then
  118. return try1
  119. end
  120. if minabs==math.abs(try2) then
  121. return try2
  122. end
  123. if minabs==math.abs(try3) then
  124. return try3
  125. end
  126. end
  127. -- Takes 2 connections (0...AT_CMAX) as argument
  128. -- Returns the angle median of those 2 positions from the pov
  129. -- of standing on the cdir1 side and looking towards cdir2
  130. -- cdir1 - >NODE> - cdir2
  131. function advtrains.conn_angle_median(cdir1, cdir2)
  132. local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
  133. local ang2 = advtrains.dir_to_angle(cdir2)
  134. return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
  135. end
  136. function advtrains.merge_tables(a, ...)
  137. local new={}
  138. for _,t in ipairs({a,...}) do
  139. for k,v in pairs(t) do new[k]=v end
  140. end
  141. return new
  142. end
  143. function advtrains.save_keys(tbl, keys)
  144. local new={}
  145. for _,key in ipairs(keys) do
  146. new[key] = tbl[key]
  147. end
  148. return new
  149. end
  150. function advtrains.get_real_index_position(path, index)
  151. if not path or not index then return end
  152. local first_pos=path[math.floor(index)]
  153. local second_pos=path[math.floor(index)+1]
  154. if not first_pos or not second_pos then return nil end
  155. local factor=index-math.floor(index)
  156. local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
  157. return actual_pos
  158. end
  159. function advtrains.pos_median(pos1, pos2)
  160. return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
  161. end
  162. function advtrains.abs_ceil(i)
  163. return math.ceil(math.abs(i))*math.sign(i)
  164. end
  165. function advtrains.serialize_inventory(inv)
  166. local ser={}
  167. local liszts=inv:get_lists()
  168. for lisztname, liszt in pairs(liszts) do
  169. ser[lisztname]={}
  170. for idx, item in ipairs(liszt) do
  171. local istring=item:to_string()
  172. if istring~="" then
  173. ser[lisztname][idx]=istring
  174. end
  175. end
  176. end
  177. return minetest.serialize(ser)
  178. end
  179. function advtrains.deserialize_inventory(sers, inv)
  180. local ser=minetest.deserialize(sers)
  181. if ser then
  182. inv:set_lists(ser)
  183. return true
  184. end
  185. return false
  186. end
  187. --is_protected wrapper that checks for protection_bypass privilege
  188. function advtrains.is_protected(pos, name)
  189. if not name then
  190. error("advtrains.is_protected() called without name parameter!")
  191. end
  192. if minetest.check_player_privs(name, {protection_bypass=true}) then
  193. --player can bypass protection
  194. return false
  195. end
  196. return minetest.is_protected(pos, name)
  197. end
  198. function advtrains.is_creative(name)
  199. if not name then
  200. error("advtrains.is_creative() called without name parameter!")
  201. end
  202. if minetest.check_player_privs(name, {creative=true}) then
  203. return true
  204. end
  205. return minetest.settings:get_bool("creative_mode")
  206. end
  207. function advtrains.is_damage_enabled(name)
  208. if not name then
  209. error("advtrains.is_damage_enabled() called without name parameter!")
  210. end
  211. if minetest.check_player_privs(name, "train_admin") then
  212. return false
  213. end
  214. return minetest.settings:get_bool("enable_damage")
  215. end
  216. function advtrains.ms_to_kmh(speed)
  217. return speed * 3.6
  218. end
  219. -- 4 possible inputs:
  220. -- integer: just do that modulo calculation
  221. -- table with c set: rotate c
  222. -- table with tables: rotate each
  223. -- table with integers: rotate each (probably no use case)
  224. function advtrains.rotate_conn_by(conn, rotate)
  225. if tonumber(conn) then
  226. return (conn+rotate)%AT_CMAX
  227. elseif conn.c then
  228. return { c = (conn.c+rotate)%AT_CMAX, y = conn.y}
  229. end
  230. local tmp={}
  231. for connid, data in ipairs(conn) do
  232. tmp[connid]=advtrains.rotate_conn_by(data, rotate)
  233. end
  234. return tmp
  235. end
  236. function advtrains.oppd(dir)
  237. return advtrains.rotate_conn_by(dir, AT_CMAX/2)
  238. end
  239. --conn_to_match like rotate_conn_by
  240. --other_conns have to be a table of conn tables!
  241. function advtrains.conn_matches_to(conn, other_conns)
  242. if tonumber(conn) then
  243. for connid, data in ipairs(other_conns) do
  244. if advtrains.oppd(conn) == data.c then return connid end
  245. end
  246. return false
  247. elseif conn.c then
  248. for connid, data in ipairs(other_conns) do
  249. local cmp = advtrains.oppd(conn)
  250. if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end
  251. end
  252. return false
  253. end
  254. local tmp={}
  255. for connid, data in ipairs(conn) do
  256. local backmatch = advtrains.conn_matches_to(data, other_conns)
  257. if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail>
  258. end
  259. return false
  260. end
  261. -- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid
  262. -- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
  263. -- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided.
  264. function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on)
  265. local this_pos = advtrains.round_vector_floor_y(this_posnr)
  266. local this_conns = this_conns_p
  267. if not this_conns then
  268. _, this_conns = advtrains.get_rail_info_at(this_pos)
  269. end
  270. if not conn_idx then
  271. for coni, _ in ipairs(this_conns) do
  272. local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
  273. if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
  274. end
  275. return nil
  276. end
  277. local conn = this_conns[conn_idx]
  278. local conn_y = conn.y or 0
  279. local adj_pos = advtrains.dirCoordSet(this_pos, conn.c);
  280. while conn_y>=1 do
  281. conn_y = conn_y - 1
  282. adj_pos.y = adj_pos.y + 1
  283. end
  284. local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
  285. if not nextnode_ok then
  286. adj_pos.y = adj_pos.y - 1
  287. conn_y = conn_y + 1
  288. nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
  289. if not nextnode_ok then
  290. return nil
  291. end
  292. end
  293. local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
  294. if adj_connid then
  295. return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
  296. end
  297. return nil
  298. end
  299. -- when a train enters a rail on connid 'conn', which connid will it go out?
  300. -- nconns: number of connections in connection table:
  301. -- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub)
  302. -- returns: connid_out
  303. local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}}
  304. function advtrains.get_matching_conn(conn, nconns)
  305. return connlku[nconns][conn]
  306. end
  307. function advtrains.random_id()
  308. local idst=""
  309. for i=0,5 do
  310. idst=idst..(math.random(0,9))
  311. end
  312. return idst
  313. end
  314. -- Shorthand for pos_to_string and round_vector_floor_y
  315. function advtrains.roundfloorpts(pos)
  316. return minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
  317. end
  318. -- insert an element into a table if it does not yet exist there
  319. -- equalfunc is a function to compare equality, defaults to ==
  320. -- returns true if the element was inserted
  321. function advtrains.insert_once(tab, elem, equalfunc)
  322. for _,e in pairs(tab) do
  323. if equalfunc and equalfunc(elem, e) or e==elem then return false end
  324. end
  325. tab[#tab+1] = elem
  326. return true
  327. end
  328. local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"}
  329. local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15}
  330. local f = atfloor
  331. local function hex(i)
  332. local x=i+32768
  333. local c4 = x % 16
  334. x = f(x / 16)
  335. local c3 = x % 16
  336. x = f(x / 16)
  337. local c2 = x % 16
  338. x = f(x / 16)
  339. local c1 = x % 16
  340. return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4])
  341. end
  342. local function c(s,i) return dect[string.sub(s,i,i)] end
  343. local function dec(s)
  344. return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768
  345. end
  346. -- Takes a position vector and outputs a encoded value suitable as table index
  347. -- This is essentially a hexadecimal representation of the position (+32768)
  348. -- Order (YYY)YXXXXZZZZ
  349. function advtrains.encode_pos(pos)
  350. return hex(pos.y) .. hex(pos.x) .. hex(pos.z)
  351. end
  352. -- decodes a position encoded with encode_pos
  353. function advtrains.decode_pos(pts)
  354. if not pts or not #pts==6 then return nil end
  355. local stry = string.sub(pts, 1,4)
  356. local strx = string.sub(pts, 5,8)
  357. local strz = string.sub(pts, 9,12)
  358. return vector.new(dec(strx), dec(stry), dec(strz))
  359. end
  360. --[[ Benchmarking code
  361. local tdt = {}
  362. local tlt = {}
  363. local tet = {}
  364. for i=1,1000000 do
  365. tdt[i] = vector.new(math.random(-65536, 65535), math.random(-65536, 65535), math.random(-65536, 65535))
  366. if i%1000 == 0 then
  367. tlt[#tlt+1] = tdt[i]
  368. end
  369. end
  370. local t1=os.clock()
  371. for i=1,1000000 do
  372. local pe = advtrains.encode_pos(tdt[i])
  373. local pb = advtrains.decode_pos(pe)
  374. tet[pe] = i
  375. end
  376. for i,v in ipairs(tlt) do
  377. local lk = tet[advtrains.encode_pos(v)]
  378. end
  379. atdebug("endec",os.clock()-t1,"s")
  380. tet = {}
  381. t1=os.clock()
  382. for i=1,1000000 do
  383. local pe = minetest.pos_to_string(tdt[i])
  384. local pb = minetest.string_to_pos(pe)
  385. tet[pe] = i
  386. end
  387. for i,v in ipairs(tlt) do
  388. local lk = tet[minetest.pos_to_string(v)]
  389. end
  390. atdebug("pts",os.clock()-t1,"s")
  391. --Results:
  392. --2018-11-29 16:57:08: ACTION[Main]: [advtrains]endec 1.786451 s
  393. --2018-11-29 16:57:10: ACTION[Main]: [advtrains]pts 2.566377 s
  394. ]]
  395. -- Function to check whether a position is near (within range of) any player
  396. function advtrains.position_in_range(pos, range)
  397. if not pos then
  398. return true
  399. end
  400. for _,p in pairs(minetest.get_connected_players()) do
  401. if vector.distance(p:get_pos(),pos)<=range then
  402. return true
  403. end
  404. end
  405. return false
  406. end
  407. local active_node_range = tonumber(minetest.settings:get("active_block_range"))*16 + 16
  408. -- Function to check whether node at position(pos) is "loaded"/"active"
  409. -- That is, whether it is within the active_block_range to a player
  410. if minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not
  411. advtrains.is_node_loaded = minetest.is_block_active
  412. else
  413. function advtrains.is_node_loaded(pos)
  414. if advtrains.position_in_range(pos, active_node_range) then
  415. return true
  416. end
  417. end
  418. end