net2.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. if not minetest.global_exists("net2") then net2 = {} end
  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. ["breeder:inactive"] = "leaf",
  208. ["breeder:active"] = "leaf",
  209. ["windy:winder"] = "leaf",
  210. ["tide:tide"] = "leaf",
  211. ["breeder:inactive"] = "leaf",
  212. ["breeder:active"] = "leaf",
  213. ["conv2:converter"] = "leaf",
  214. ["grind2:mv_inactive"] = "leaf",
  215. ["grind2:mv_active"] = "leaf",
  216. ["ecfurn2:mv_inactive"] = "leaf",
  217. ["ecfurn2:mv_active"] = "leaf",
  218. ["extract2:mv_active"] = "leaf",
  219. ["extract2:mv_inactive"] = "leaf",
  220. ["comp2:mv_active"] = "leaf",
  221. ["comp2:mv_inactive"] = "leaf",
  222. ["alloyf2:mv_active"] = "leaf",
  223. ["alloyf2:mv_inactive"] = "leaf",
  224. ["cent2:mv_active"] = "leaf",
  225. ["cent2:mv_inactive"] = "leaf",
  226. ["distrib2:mv_machine"] = "leaf",
  227. }
  228. net2.traversable.hv = {
  229. ["stat2:hv"] = attached_hubs_and_nodes,
  230. ["gen2:hv_inactive"] = "leaf",
  231. ["gen2:hv_active"] = "leaf",
  232. ["solar:hv"] = "leaf",
  233. ["reactor:inactive"] = "leaf",
  234. ["reactor:active"] = "leaf",
  235. ["conv2:converter"] = "leaf",
  236. ["ecfurn2:hv_inactive"] = "leaf",
  237. ["ecfurn2:hv_active"] = "leaf",
  238. ["distrib2:hv_machine"] = "leaf",
  239. ["leecher:leecher"] = "leaf",
  240. }
  241. -- All machines capable of producing and buffering EUs.
  242. net2.generators = {
  243. "gen2:hv_inactive",
  244. "gen2:hv_active",
  245. "solar:hv",
  246. "breeder:inactive",
  247. "breeder:active",
  248. "reactor:inactive",
  249. "reactor:active",
  250. "gen2:mv_inactive",
  251. "gen2:mv_active",
  252. "solar:mv",
  253. "windy:winder",
  254. "tide:tide",
  255. "gen2:lv_inactive",
  256. "gen2:lv_active",
  257. "geo2:lv_inactive",
  258. "geo2:lv_active",
  259. "wat2:lv_inactive",
  260. "wat2:lv_active",
  261. "solar:lv",
  262. }
  263. local function is_generator(name)
  264. for k, v in ipairs(net2.generators) do
  265. if v == name then
  266. return true
  267. end
  268. end
  269. end
  270. local function is_converter(name)
  271. if name == "conv2:converter" then
  272. return true
  273. end
  274. end
  275. -- Register batteries as traversable nodes.
  276. for k, v in ipairs({
  277. {tier="lv"},
  278. {tier="mv"},
  279. {tier="hv"},
  280. }) do
  281. -- Batteries are added to the traversability table of the same tier.
  282. local tb = net2.traversable[v.tier]
  283. for i = 0, 12, 1 do
  284. tb["bat2:bt" .. i .. "_" .. v.tier] = "leaf"
  285. end
  286. end
  287. -- Get a network of a voltage tier. Obtains a cached table, if possible.
  288. -- The returned table shall contain the positions of all nodes that are
  289. -- visible from the position of the inital node doing the scan.
  290. function net2.get_network(pos, owner, tier)
  291. local hash = minetest.hash_node_position(pos)
  292. if not net2.networks[tier][owner] then
  293. net2.networks[tier][owner] = {}
  294. end
  295. local owner_cache = net2.networks[tier][owner]
  296. if owner_cache[hash] then
  297. return owner_cache[hash].nodes
  298. end
  299. local donodes = net2.traversable[tier]
  300. local trash, allnodes = floodfill(pos, donodes, stat2.chain_limit(tier)+3, owner)
  301. -- Determine network radius. This allows us to use a cool optimization.
  302. local rad = 0
  303. for k, v in ipairs(allnodes) do
  304. local d = vector_distance(pos, v.pos)
  305. if d > rad then
  306. rad = d
  307. end
  308. end
  309. -- Plus a little extra.
  310. rad = math.ceil(rad+2)
  311. local cache = {}
  312. cache.nodes = {}
  313. cache.nodes.allnodes = allnodes
  314. local batteries = {}
  315. for k, v in ipairs(allnodes) do
  316. if string.find(v.name, "^bat2:bt") then
  317. batteries[#batteries+1] = v
  318. end
  319. end
  320. local generators = {}
  321. for k, v in ipairs(allnodes) do
  322. if is_generator(v.name) then
  323. generators[#generators+1] = v
  324. end
  325. end
  326. local converters = {}
  327. for k, v in ipairs(allnodes) do
  328. if is_converter(v.name) then
  329. converters[#converters+1] = v
  330. end
  331. end
  332. cache.nodes.batteries = batteries
  333. cache.nodes.generators = generators
  334. cache.nodes.converters = converters
  335. cache.pos = pos
  336. cache.radius = rad
  337. owner_cache[hash] = cache
  338. return cache.nodes
  339. end
  340. -- Idea: If networks kept track of who owns them, we could keep networks
  341. -- from different players seperate. Energy sharing would have to
  342. -- be done using a special node. Thus, modifying a network belonging
  343. -- to one player would not drop caches for a network owned by another.
  344. -- Clear caches which may be dirty.
  345. -- This is needed whenever a node of that tier is added or removed.
  346. -- Pass the position of the added/removed node, its owner, and tier.
  347. -- These 3 things are used to optimize which caches are cleared.
  348. function net2.clear_caches(pos, owner, tier)
  349. local tbrm = {}
  350. local hash = minetest.hash_node_position(pos)
  351. if not net2.networks[tier][owner] then
  352. goto done
  353. end
  354. -- If the node itself has a cache, we always clear it.
  355. net2.networks[tier][owner][hash] = nil
  356. for k, v in pairs(net2.networks[tier][owner]) do
  357. -- Any caches closer than their calculated radius could be dirty.
  358. if vector_distance(v.pos, pos) <= v.radius then
  359. tbrm[#tbrm+1] = k
  360. end
  361. end
  362. -- Actually remove caches which could be dirty.
  363. for k, v in ipairs(tbrm) do
  364. net2.networks[tier][owner][v] = nil
  365. end
  366. ::done::
  367. end
  368. if not net2.run_once then
  369. local c = "net2:core"
  370. local f = net2.modpath .. "/net2.lua"
  371. reload.register_file(c, f, false)
  372. net2.run_once = true
  373. end