net2.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. net2 = net2 or {}
  2. net2.modpath = minetest.get_modpath("networks")
  3. -- Localize vector.distance() for performance.
  4. local vector_distance = vector.distance
  5. -- Network cache tables.
  6. -- Caches are sorted first by voltage tier, then by owner,
  7. -- and finally by hashed position.
  8. net2.networks = net2.networks or {}
  9. net2.networks.lv = net2.networks.lv or {}
  10. net2.networks.mv = net2.networks.mv or {}
  11. net2.networks.hv = net2.networks.hv or {}
  12. -- Put energy into the network.
  13. -- Return the amount that didn't fit.
  14. function net2.put_energy(pos, owner, energy, tier)
  15. local nodes = net2.get_network(pos, owner, tier)
  16. local bats = nodes.batteries
  17. local convs = nodes.converters
  18. local allitems = minetest.registered_items
  19. for k, v in ipairs(bats) do
  20. local def = allitems[v.name]
  21. energy = def.on_energy_put(v.pos, energy)
  22. if energy <= 0 then
  23. energy = 0
  24. break
  25. end
  26. end
  27. for k, v in ipairs(convs) do
  28. local def = allitems[v.name]
  29. energy = def.on_energy_put(v.pos, energy, tier)
  30. if energy <= 0 then
  31. energy = 0
  32. break
  33. end
  34. end
  35. return energy
  36. end
  37. -- Get energy from the network.
  38. -- Return the amount actually gotten.
  39. function net2.get_energy(pos, owner, energy, tier)
  40. local nodes = net2.get_network(pos, owner, tier)
  41. local bats = nodes.batteries or {}
  42. local gens = nodes.generators or {}
  43. local total = 0
  44. local needed = energy
  45. local allitems = minetest.registered_items
  46. for k, v in ipairs(bats) do
  47. local def = allitems[v.name]
  48. if def.on_energy_get then
  49. local gotten = def.on_energy_get(v.pos, energy)
  50. energy = energy - gotten
  51. total = total + gotten
  52. if total >= needed then
  53. return total
  54. end
  55. end
  56. end
  57. for k, v in ipairs(gens) do
  58. local def = allitems[v.name]
  59. if def.on_energy_get then
  60. local gotten = def.on_energy_get(v.pos, energy)
  61. energy = energy - gotten
  62. total = total + gotten
  63. if total >= needed then
  64. return total
  65. end
  66. end
  67. end
  68. return total
  69. end
  70. -- Queued algorithm.
  71. local function floodfill(startpos, nodelist, maxdepth, netowner)
  72. local traversal = {}
  73. local queue = {}
  74. local output = {}
  75. local curpos, hash, exists, nodename, nodeowner, found, norm, cb, count, depth
  76. local first = true
  77. local get_node_hash = minetest.hash_node_position
  78. local get_node_info = nodestore.get_nodename_and_realowner
  79. startpos.d = 1
  80. queue[#queue+1] = startpos
  81. count = 1
  82. ::continue::
  83. curpos = queue[#queue]
  84. queue[#queue] = nil
  85. depth = curpos.d
  86. curpos.d = nil
  87. hash = get_node_hash(curpos)
  88. exists = false
  89. if traversal[hash] then
  90. exists = true
  91. if depth >= traversal[hash] then
  92. goto next
  93. end
  94. end
  95. if depth >= maxdepth then
  96. goto next
  97. end
  98. count = count + 1
  99. nodename, nodeowner = get_node_info(curpos, hash, netowner)
  100. -- Owner must be correct.
  101. if nodeowner ~= netowner then
  102. goto next
  103. end
  104. found = false
  105. norm = true
  106. cb = nil
  107. for n, m in pairs(nodelist) do
  108. if n == nodename then
  109. found = true
  110. if type(m) == "function" then
  111. cb = m
  112. elseif type(m) == "string" then
  113. if m == "leaf" then
  114. -- The first node scanned musn't be treated as a leaf.
  115. if not first then
  116. norm = false
  117. end
  118. end
  119. end
  120. break
  121. end
  122. end
  123. if not found then
  124. goto next
  125. end
  126. traversal[hash] = depth
  127. if not exists then
  128. output[#output+1] = {pos=curpos, name=nodename}
  129. end
  130. if cb then
  131. -- The node callback can add to the adjacency list.
  132. cb(curpos, queue, depth+1)
  133. elseif norm then
  134. queue[#queue+1] = {x=curpos.x+1, y=curpos.y, z=curpos.z, d=depth+1}
  135. queue[#queue+1] = {x=curpos.x-1, y=curpos.y, z=curpos.z, d=depth+1}
  136. queue[#queue+1] = {x=curpos.x, y=curpos.y+1, z=curpos.z, d=depth+1}
  137. queue[#queue+1] = {x=curpos.x, y=curpos.y-1, z=curpos.z, d=depth+1}
  138. queue[#queue+1] = {x=curpos.x, y=curpos.y, z=curpos.z+1, d=depth+1}
  139. queue[#queue+1] = {x=curpos.x, y=curpos.y, z=curpos.z-1, d=depth+1}
  140. end
  141. ::next::
  142. first = false
  143. if #queue > 0 then
  144. goto continue
  145. end
  146. return count, output
  147. end
  148. -- Called from inside the floodfill algorithm.
  149. -- This lets the floodfill algorithm find new parts of the network.
  150. local function attached_hubs_and_nodes(pos, queue, d)
  151. local info = nodestore.get_hub_info(pos)
  152. for k, v in ipairs({
  153. {mp="np", me="ne", pos={x=pos.x, y=pos.y, z=pos.z+1, d=d}},
  154. {mp="sp", me="se", pos={x=pos.x, y=pos.y, z=pos.z-1, d=d}},
  155. {mp="ep", me="ee", pos={x=pos.x+1, y=pos.y, z=pos.z, d=d}},
  156. {mp="wp", me="we", pos={x=pos.x-1, y=pos.y, z=pos.z, d=d}},
  157. {mp="up", me="ue", pos={x=pos.x, y=pos.y+1, z=pos.z, d=d}},
  158. {mp="dp", me="de", pos={x=pos.x, y=pos.y-1, z=pos.z, d=d}},
  159. }) do
  160. local e = info[v.me]
  161. local got = false
  162. if e == 1 then
  163. local p = info[v.mp]
  164. if p then
  165. p.d = d
  166. queue[#queue+1] = p
  167. got = true
  168. end
  169. end
  170. if not got then
  171. queue[#queue+1] = v.pos
  172. end
  173. end
  174. end
  175. -- Node tables used in the floodfill algorithm.
  176. net2.traversable = {}
  177. net2.traversable.lv = {
  178. ["stat2:lv"] = attached_hubs_and_nodes,
  179. ["gen2:lv_inactive"] = "leaf",
  180. ["gen2:lv_active"] = "leaf",
  181. ["geo2:lv_inactive"] = "leaf",
  182. ["geo2:lv_active"] = "leaf",
  183. ["wat2:lv_inactive"] = "leaf",
  184. ["wat2:lv_active"] = "leaf",
  185. ["solar:lv"] = "leaf",
  186. ["conv2:converter"] = "leaf",
  187. ["grind2:lv_inactive"] = "leaf",
  188. ["grind2:lv_active"] = "leaf",
  189. ["ecfurn2:lv_inactive"] = "leaf",
  190. ["ecfurn2:lv_active"] = "leaf",
  191. ["extract2:lv_active"] = "leaf",
  192. ["extract2:lv_inactive"] = "leaf",
  193. ["comp2:lv_active"] = "leaf",
  194. ["comp2:lv_inactive"] = "leaf",
  195. ["gemcut2:lv_active"] = "leaf",
  196. ["gemcut2:lv_inactive"] = "leaf",
  197. ["distrib2:lv_machine"] = "leaf",
  198. ["charger:charger"] = "leaf",
  199. ["workshop:workshop"] = "leaf",
  200. ["solar:panel"] = "leaf",
  201. }
  202. net2.traversable.mv = {
  203. ["stat2:mv"] = attached_hubs_and_nodes,
  204. ["gen2:mv_inactive"] = "leaf",
  205. ["gen2:mv_active"] = "leaf",
  206. ["solar:mv"] = "leaf",
  207. ["windy:winder"] = "leaf",
  208. ["tide:tide"] = "leaf",
  209. ["breeder:inactive"] = "leaf",
  210. ["breeder:active"] = "leaf",
  211. ["conv2:converter"] = "leaf",
  212. ["grind2:mv_inactive"] = "leaf",
  213. ["grind2:mv_active"] = "leaf",
  214. ["ecfurn2:mv_inactive"] = "leaf",
  215. ["ecfurn2:mv_active"] = "leaf",
  216. ["extract2:mv_active"] = "leaf",
  217. ["extract2:mv_inactive"] = "leaf",
  218. ["comp2:mv_active"] = "leaf",
  219. ["comp2:mv_inactive"] = "leaf",
  220. ["alloyf2:mv_active"] = "leaf",
  221. ["alloyf2:mv_inactive"] = "leaf",
  222. ["cent2:mv_active"] = "leaf",
  223. ["cent2:mv_inactive"] = "leaf",
  224. ["distrib2:mv_machine"] = "leaf",
  225. }
  226. net2.traversable.hv = {
  227. ["stat2:hv"] = attached_hubs_and_nodes,
  228. ["gen2:hv_inactive"] = "leaf",
  229. ["gen2:hv_active"] = "leaf",
  230. ["solar:hv"] = "leaf",
  231. ["reactor:inactive"] = "leaf",
  232. ["reactor:active"] = "leaf",
  233. ["conv2:converter"] = "leaf",
  234. ["ecfurn2:hv_inactive"] = "leaf",
  235. ["ecfurn2:hv_active"] = "leaf",
  236. ["distrib2:hv_machine"] = "leaf",
  237. ["leecher:leecher"] = "leaf",
  238. }
  239. -- All machines capable of producing and buffering EUs.
  240. net2.generators = {
  241. "gen2:hv_inactive",
  242. "gen2:hv_active",
  243. "solar:hv",
  244. "reactor:inactive",
  245. "reactor:active",
  246. "gen2:mv_inactive",
  247. "gen2:mv_active",
  248. "solar:mv",
  249. "windy:winder",
  250. "tide:tide",
  251. "gen2:lv_inactive",
  252. "gen2:lv_active",
  253. "geo2:lv_inactive",
  254. "geo2:lv_active",
  255. "wat2:lv_inactive",
  256. "wat2:lv_active",
  257. "solar:lv",
  258. }
  259. local function is_generator(name)
  260. for k, v in ipairs(net2.generators) do
  261. if v == name then
  262. return true
  263. end
  264. end
  265. end
  266. local function is_converter(name)
  267. if name == "conv2:converter" then
  268. return true
  269. end
  270. end
  271. -- Register batteries as traversable nodes.
  272. for k, v in ipairs({
  273. {tier="lv"},
  274. {tier="mv"},
  275. {tier="hv"},
  276. }) do
  277. -- Batteries are added to the traversability table of the same tier.
  278. local tb = net2.traversable[v.tier]
  279. for i = 0, 12, 1 do
  280. tb["bat2:bt" .. i .. "_" .. v.tier] = "leaf"
  281. end
  282. end
  283. -- Get a network of a voltage tier. Obtains a cached table, if possible.
  284. -- The returned table shall contain the positions of all nodes that are
  285. -- visible from the position of the inital node doing the scan.
  286. function net2.get_network(pos, owner, tier)
  287. local hash = minetest.hash_node_position(pos)
  288. if not net2.networks[tier][owner] then
  289. net2.networks[tier][owner] = {}
  290. end
  291. local owner_cache = net2.networks[tier][owner]
  292. if owner_cache[hash] then
  293. return owner_cache[hash].nodes
  294. end
  295. local donodes = net2.traversable[tier]
  296. local trash, allnodes = floodfill(pos, donodes, stat2.chain_limit(tier)+3, owner)
  297. -- Determine network radius. This allows us to use a cool optimization.
  298. local rad = 0
  299. for k, v in ipairs(allnodes) do
  300. local d = vector_distance(pos, v.pos)
  301. if d > rad then
  302. rad = d
  303. end
  304. end
  305. -- Plus a little extra.
  306. rad = math.ceil(rad+2)
  307. local cache = {}
  308. cache.nodes = {}
  309. cache.nodes.allnodes = allnodes
  310. local batteries = {}
  311. for k, v in ipairs(allnodes) do
  312. if string.find(v.name, "^bat2:bt") then
  313. batteries[#batteries+1] = v
  314. end
  315. end
  316. local generators = {}
  317. for k, v in ipairs(allnodes) do
  318. if is_generator(v.name) then
  319. generators[#generators+1] = v
  320. end
  321. end
  322. local converters = {}
  323. for k, v in ipairs(allnodes) do
  324. if is_converter(v.name) then
  325. converters[#converters+1] = v
  326. end
  327. end
  328. cache.nodes.batteries = batteries
  329. cache.nodes.generators = generators
  330. cache.nodes.converters = converters
  331. cache.pos = pos
  332. cache.radius = rad
  333. owner_cache[hash] = cache
  334. return cache.nodes
  335. end
  336. -- Idea: If networks kept track of who owns them, we could keep networks
  337. -- from different players seperate. Energy sharing would have to
  338. -- be done using a special node. Thus, modifying a network belonging
  339. -- to one player would not drop caches for a network owned by another.
  340. -- Clear caches which may be dirty.
  341. -- This is needed whenever a node of that tier is added or removed.
  342. -- Pass the position of the added/removed node, its owner, and tier.
  343. -- These 3 things are used to optimize which caches are cleared.
  344. function net2.clear_caches(pos, owner, tier)
  345. local tbrm = {}
  346. local hash = minetest.hash_node_position(pos)
  347. if not net2.networks[tier][owner] then
  348. goto done
  349. end
  350. -- If the node itself has a cache, we always clear it.
  351. net2.networks[tier][owner][hash] = nil
  352. for k, v in pairs(net2.networks[tier][owner]) do
  353. -- Any caches closer than their calculated radius could be dirty.
  354. if vector_distance(v.pos, pos) <= v.radius then
  355. tbrm[#tbrm+1] = k
  356. end
  357. end
  358. -- Actually remove caches which could be dirty.
  359. for k, v in ipairs(tbrm) do
  360. net2.networks[tier][owner][v] = nil
  361. end
  362. ::done::
  363. end
  364. if not net2.run_once then
  365. local c = "net2:core"
  366. local f = net2.modpath .. "/net2.lua"
  367. reload.register_file(c, f, false)
  368. net2.run_once = true
  369. end