net2.lua 10 KB

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