pipes.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. local networks = {}
  2. local net_members = {}
  3. local netname = 1
  4. bitumen.mod_storage = minetest.get_mod_storage()
  5. local mod_storage = bitumen.mod_storage
  6. networks = minetest.deserialize(mod_storage:get_string("networks")) or {}
  7. net_members = minetest.deserialize(mod_storage:get_string("net_members")) or {}
  8. netname = mod_storage:get_int("netname") or 1
  9. local function save_data()
  10. --print("saving")
  11. mod_storage:set_string("networks", minetest.serialize(networks))
  12. mod_storage:set_string("net_members", minetest.serialize(net_members))
  13. mod_storage:set_int("netname", netname)
  14. end
  15. -- centralized network creation for consistency
  16. local function new_network(pos)
  17. local hash = minetest.hash_node_position(pos)
  18. print("new network: hash: ".. hash .." name: " ..netname);
  19. networks[hash] = {
  20. hash = hash,
  21. pos = {x=pos.x, y=pos.y, z=pos.z},
  22. fluid = 'air',
  23. name = netname,
  24. count = 1,
  25. inputs = {
  26. [hash] = 1,
  27. },
  28. outputs = {},
  29. buffer = 0,
  30. in_pressure = -32000,
  31. }
  32. net_members[hash] = hash
  33. netname = netname + 1
  34. return networks[hash], hash
  35. end
  36. -- check nearby nodes for existing networks
  37. local function check_merge(pos)
  38. local hash = minetest.hash_node_position(pos)
  39. local merge_list = {}
  40. local current_net = nil
  41. local found_net = 0
  42. local check_net = function(npos)
  43. local nhash = minetest.hash_node_position(npos)
  44. local nphash = net_members[nhash]
  45. if nphash ~= nil then
  46. local pnet = networks[nphash]
  47. if nil == current_net then
  48. print("joining existing network: ".. pnet.name)
  49. net_members[hash] = nphash
  50. current_net = nphash
  51. pnet.count = pnet.count + 1
  52. pnet.inputs[hash] = 1
  53. table.insert(merge_list, pnet)
  54. elseif current_net == nphash then
  55. print("alternate connection to existing network")
  56. else
  57. print("found seconday network: "..pnet.name)
  58. table.insert(merge_list, pnet)
  59. end
  60. found_net = 1
  61. end
  62. end
  63. check_net({x=pos.x, y=pos.y - 1, z=pos.z})
  64. check_net({x=pos.x, y=pos.y + 1, z=pos.z})
  65. check_net({x=pos.x + 1, y=pos.y, z=pos.z})
  66. check_net({x=pos.x - 1, y=pos.y, z=pos.z})
  67. check_net({x=pos.x, y=pos.y, z=pos.z + 1})
  68. check_net({x=pos.x, y=pos.y, z=pos.z - 1})
  69. return found_net, merge_list
  70. end
  71. -- merge a list of networks, if the are multiple nets in the list
  72. local function try_merge(merge_list)
  73. if #merge_list > 1 then
  74. print("\n merging "..#merge_list.." networks")
  75. local biggest = {count = 0}
  76. local mlookup = {}
  77. for _,n in ipairs(merge_list) do
  78. mlookup[n.hash] = 1
  79. if n.count > biggest.count then
  80. biggest = n
  81. end
  82. end
  83. mlookup[biggest.hash] = 0
  84. for k,v in pairs(net_members) do
  85. if mlookup[v] == 1 then
  86. net_members[k] = biggest.hash
  87. end
  88. end
  89. for _,n in ipairs(merge_list) do
  90. if n.hash ~= biggest.hash then
  91. biggest.count = biggest.count + n.count
  92. networks[n.hash] = nil -- delete old networks
  93. end
  94. end
  95. return biggest
  96. end
  97. return merge_list[1]
  98. end
  99. local function pnet_for(pos)
  100. local hash = minetest.hash_node_position(pos)
  101. local ph = net_members[hash]
  102. if ph == nil then
  103. return nil, hash
  104. end
  105. return networks[ph], hash
  106. end
  107. local function walk_net(opos)
  108. local members = {}
  109. local count = 0
  110. local opnet = pnet_for(opos)
  111. if opnet == nil then
  112. return nil, 0, nil
  113. end
  114. local stack = {}
  115. table.insert(stack, opos)
  116. while #stack > 0 do
  117. local pos = table.remove(stack)
  118. local pnet, hash = pnet_for(pos)
  119. if pnet ~= nil and members[hash] == nil then
  120. -- only count members of the original node's network
  121. if pnet.name == opnet.name then
  122. members[hash] = pos
  123. count = count + 1
  124. -- print(" found net node: " .. minetest.pos_to_string(pos))
  125. table.insert(stack, {x=pos.x-1, y=pos.y, z=pos.z})
  126. table.insert(stack, {x=pos.x+1, y=pos.y, z=pos.z})
  127. table.insert(stack, {x=pos.x, y=pos.y-1, z=pos.z})
  128. table.insert(stack, {x=pos.x, y=pos.y+1, z=pos.z})
  129. table.insert(stack, {x=pos.x, y=pos.y, z=pos.z-1})
  130. table.insert(stack, {x=pos.x, y=pos.y, z=pos.z+1})
  131. end
  132. end
  133. end
  134. return members, count, opnet
  135. end
  136. -- crawls the network and assigns found nodes to the new network
  137. local function rebase_network(pos)
  138. local net, phash, hash = bitumen.pipes.get_net(pos)
  139. -- print(dump(pos))
  140. if hash == phash then
  141. print("trying to rebase network to the same spot")
  142. return {name = 9999999}
  143. end
  144. local pipes = walk_net(pos)
  145. -- print(dump(pipes))
  146. local new_net, new_phash = new_network(pos)
  147. -- switch all the members
  148. for h,p in pairs(pipes) do
  149. --print("reassigning " .. minetest.pos_to_string(p))
  150. net_members[h] = new_phash
  151. new_net.count = new_net.count + 1
  152. end
  153. return new_net, new_phash
  154. end
  155. bitumen.pipes = {}
  156. -- used by external machines to connect to a network in their on_construct callback
  157. bitumen.pipes.on_construct = function(pos)
  158. local found_net, merge_list = check_merge(pos)
  159. if found_net == 0 then
  160. local hash = minetest.hash_node_position(pos)
  161. local net = new_network(pos)
  162. end
  163. local pnet = try_merge(merge_list)
  164. --pnet.count = pnet.count + 1 -- TODO: this might be implicit somewhere else
  165. save_data()
  166. end
  167. bitumen.pipes.after_destruct = function(pos)
  168. local hash = minetest.hash_node_position(pos)
  169. local phash = net_members[hash]
  170. if phash == nil then
  171. print("wtf: pipe has no network in after_destruct")
  172. return
  173. end
  174. local pnet = networks[phash]
  175. if pnet == nil then
  176. print("wtf: no network in after_destruct for pipe")
  177. return
  178. end
  179. -- remove this node from the network
  180. net_members[hash] = nil
  181. pnet.count = pnet.count - 1
  182. -- neighboring nodes
  183. local check_pos = {
  184. {x=pos.x+1, y=pos.y, z=pos.z},
  185. {x=pos.x-1, y=pos.y, z=pos.z},
  186. {x=pos.x, y=pos.y+1, z=pos.z},
  187. {x=pos.x, y=pos.y-1, z=pos.z},
  188. {x=pos.x, y=pos.y, z=pos.z+1},
  189. {x=pos.x, y=pos.y, z=pos.z-1},
  190. }
  191. local stack = {}
  192. local found = 0
  193. -- check neighbors for network membership
  194. for _,p in ipairs(check_pos) do
  195. local h = minetest.hash_node_position(p)
  196. local lphash = net_members[h]
  197. if lphash ~= nil then
  198. local lpnet = networks[lphash]
  199. -- only process pipes/fixtures on the same network as the destroyed pipe
  200. if lpnet and lpnet.name == pnet.name then
  201. stack[h] = vector.new(p)
  202. found = found + 1
  203. --print("check stack: "..p.x..","..p.y..","..p.z)
  204. else
  205. print("no lpnet")
  206. end
  207. else
  208. print("no lphash "..p.x..","..p.y..","..p.z)
  209. end
  210. end
  211. -- don't need to split the network if this was just on the end
  212. if found > 1 then
  213. --print("check to split the network")
  214. for h,p in pairs(stack) do
  215. print(dump(p))
  216. print(dump(h))
  217. -- BUG: spouts and intakes can be counted as pipes when walking the network
  218. -- just rename the net
  219. local new_pnet = rebase_network(p)
  220. -- print("split off pnet ".. new_pnet.name .. " at " .. minetest.pos_to_string(p))
  221. -- all fluid is lost in the network atm
  222. -- some networks might get orphaned, for example, the first
  223. -- net to be rebased in a loop
  224. end
  225. end
  226. save_data()
  227. end
  228. -- used by external machines to find the network for a node
  229. bitumen.pipes.get_net = function(pos)
  230. local hash = minetest.hash_node_position(pos)
  231. local phash = net_members[hash]
  232. if phash == nil then
  233. return nil, nil, hash
  234. end
  235. return networks[phash], phash, hash
  236. end
  237. -- used by external machines to add fluid into the pipe network
  238. -- returns amount of fluid successfully pushed into the network
  239. bitumen.pipes.push_fluid = function(pos, fluid, amount, extra_pressure)
  240. local hash = minetest.hash_node_position(pos)
  241. local phash = net_members[hash]
  242. if phash == nil then
  243. --print("no network to push to")
  244. return 0 -- no network
  245. end
  246. local pnet = networks[phash]
  247. --print("fluid: "..pnet.fluid.. ", buf: "..pnet.buffer)
  248. if pnet.fluid == 'air' or pnet.buffer == 0 then
  249. if minetest.registered_nodes[fluid]
  250. and minetest.registered_nodes[fluid].groups.petroleum ~= nil then
  251. -- BUG: check for "full" nodes
  252. pnet.fluid = fluid
  253. else
  254. --print("here "..fluid.." ".. dump(minetest.registered_nodes[fluid]))
  255. return 0 -- no available liquids
  256. end
  257. else -- only suck in existing fluid
  258. if fluid ~= pnet.fluid and fluid ~= pnet.fluid.."_full" then
  259. --print("wrong fluid")
  260. return 0
  261. end
  262. end
  263. if amount < 0 then
  264. print("!!!!!!!!!!!! push amount less than zero?")
  265. return 0
  266. end
  267. local input_pres = math.floor(pos.y + extra_pressure + .5)
  268. pnet.in_pressure = pnet.in_pressure or -32000
  269. if math.floor(pnet.in_pressure + .5) > input_pres then
  270. --print("backflow at intake: " .. pnet.in_pressure.. " > " ..input_pres )
  271. return 0
  272. end
  273. pnet.in_pressure = math.max(pnet.in_pressure, input_pres)
  274. --print("net pressure: ".. pnet.in_pressure)
  275. local rate = amount --math.max(1, math.ceil(ulevel / 2))
  276. local cap = 64
  277. local take = math.max(0, math.min(amount, cap - pnet.buffer))
  278. pnet.buffer = pnet.buffer + take
  279. return take
  280. end
  281. -- used by external machines to remove fluid from the pipe network
  282. -- returns amount and fluid type
  283. bitumen.pipes.take_fluid = function(pos, max_amount, backpressure)
  284. local hash = minetest.hash_node_position(pos)
  285. local phash = net_members[hash]
  286. if phash == nil then
  287. return 0, "air" -- no network
  288. end
  289. local pnet = networks[phash]
  290. if pnet.buffer <= 0 then
  291. --print("spout: no water in pipe")
  292. return 0, "air" -- no water in the pipe
  293. end
  294. -- hack
  295. pnet.in_pressure = pnet.in_pressure or -32000
  296. if pnet.in_pressure <= pos.y then
  297. --print("insufficient pressure at spout: ".. pnet.in_pressure .. " < " ..pos.y )
  298. return 0, "air"
  299. end
  300. local take = math.min(pnet.buffer, max_amount)
  301. pnet.buffer = math.max(pnet.buffer - take, 0)
  302. if pnet.buffer == 0 then
  303. -- print("pipe drained " .. pnet.name) -- BUG: there might be a bug where a low pressure input can add fluid to the network with a higher spout, then a higher intake with low flow can raise the pressure of the previous fluid to the level of the comparatively lower spout
  304. pnet.in_pressure = backpressure or pos.y
  305. end
  306. return take, pnet.fluid
  307. end
  308. -- take or push into the network, based on given pressure
  309. -- returns change in fluid and fluid type
  310. -- negative if fluid was pushed into the network
  311. -- positive if fluid was taken from the network
  312. bitumen.pipes.buffer = function(pos, fluid, my_pres, avail_push, cap_take, can_change_fluid)
  313. local hash = minetest.hash_node_position(pos)
  314. local phash = net_members[hash]
  315. if phash == nil then
  316. --print("no net")
  317. return 0, "air" -- no network
  318. end
  319. local pnet = networks[phash]
  320. -- print("pressure ["..pnet.name.."] ".. pnet.in_pressure .. " - " .. my_pres)
  321. if pnet.in_pressure <= my_pres then
  322. -- push into the network
  323. -- print("push")
  324. return -bitumen.pipes.push_fluid(pos, fluid, avail_push, my_pres), fluid
  325. else
  326. -- print("take")
  327. if pnet.fluid == fluid or can_change_fluid then
  328. -- print("can take " .. cap_take)
  329. return bitumen.pipes.take_fluid(pos, cap_take, my_pres)
  330. else
  331. -- print("wrong type ".. pnet.fluid .. " - ".. fluid)
  332. return 0, "air" -- wrong fluid type
  333. end
  334. end
  335. end
  336. minetest.register_node("bitumen:intake", {
  337. description = "Petroleum Intake",
  338. drawtype = "nodebox",
  339. node_box = {
  340. type = "connected",
  341. fixed = {{-.1, -.1, -.1, .1, .1, .1}},
  342. -- connect_bottom =
  343. connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
  344. connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
  345. connect_back = {{-.1, -.1, .1, .1, .1, .5}},
  346. connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
  347. connect_bottom = {{ -.1, -.5, -.1, .1, .1, .1}},
  348. },
  349. connects_to = { "group:petroleum_pipe"--[[, "group:petroleum_fixture"]]},
  350. paramtype = "light",
  351. is_ground_content = false,
  352. tiles = { "default_tin_block.png" },
  353. walkable = true,
  354. groups = { cracky = 3, petroleum_fixture = 1, },
  355. on_construct = function(pos)
  356. print("\nintake placed at "..pos.x..","..pos.y..","..pos.z)
  357. local found_net, merge_list = check_merge(pos)
  358. if found_net == 0 then
  359. local hash = minetest.hash_node_position(pos)
  360. local net = new_network(pos)
  361. net.in_pressure = pos.y
  362. net.inputs[hash] = 1
  363. end
  364. try_merge(merge_list)
  365. save_data()
  366. end,
  367. after_destruct = bitumen.pipes.after_destruct,
  368. })
  369. minetest.register_abm({
  370. nodenames = {"bitumen:intake"},
  371. neighbors = {"group:petroleum"},
  372. interval = 1,
  373. chance = 1,
  374. action = function(pos)
  375. local hash = minetest.hash_node_position(pos)
  376. pos.y = pos.y + 1
  377. local unode = minetest.get_node(pos)
  378. local phash = net_members[hash]
  379. local pnet = networks[phash]
  380. if pnet.fluid == 'air' or pnet.buffer == 0 then
  381. if minetest.registered_nodes[unode.name].groups.petroleum ~= nil then
  382. -- BUG: check for "full" nodes
  383. pnet.fluid = minetest.registered_nodes[unode.name].nonfull_name
  384. --print("intake: here".. (pnet.fluid or "<nil>"))
  385. else
  386. --print("intake: no fluid available ".. pos.x.. ",".. pos.y..",".. pos.z)
  387. return -- no available liquids
  388. end
  389. else -- only suck in existing fluid
  390. if unode.name ~= pnet.fluid and unode.name ~= pnet.fluid.."_full" then
  391. --print("bitumen: no fluid near intake: " .. unode.name .. " != " .. pnet.fluid)
  392. return
  393. end
  394. end
  395. local ulevel = minetest.get_node_level(pos)
  396. if ulevel < 1 then
  397. print("!!!!!!!!!!!! intake level less than one?")
  398. return
  399. end
  400. pnet.in_pressure = pnet.in_pressure or -32000
  401. if pnet.in_pressure > pos.y - 1 then
  402. --print("petroleum backflow at intake: " .. pnet.in_pressure.. " > " ..(pos.y - 1) )
  403. return
  404. end
  405. pnet.in_pressure = math.max(pnet.in_pressure, pos.y - 1)
  406. local rate = math.max(1, math.ceil(ulevel / 2))
  407. local cap = 64
  408. local take = math.max(0, math.min(ulevel, cap - pnet.buffer))
  409. pnet.buffer = pnet.buffer + take
  410. --print("intake took "..take.. " water")
  411. local nl = math.floor(ulevel - take + 0.5)
  412. if nl > 0 then
  413. minetest.set_node_level(pos, nl)
  414. else
  415. minetest.set_node(pos, {name = "air"})
  416. end
  417. end
  418. })
  419. minetest.register_node("bitumen:spout", {
  420. description = "Petroleum Spout",
  421. drawtype = "nodebox",
  422. node_box = {
  423. type = "connected",
  424. fixed = {{-.1, -.1, -.1, .1, .1, .1}},
  425. -- connect_bottom =
  426. connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
  427. connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
  428. connect_back = {{-.1, -.1, .1, .1, .1, .5}},
  429. connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
  430. connect_top = {{ -.1, -.1, -.1, .1, .5, .1}},
  431. },
  432. connects_to = { "group:petroleum_pipe",--[[ "group:petroleum_fixture" ]]},
  433. paramtype = "light",
  434. is_ground_content = false,
  435. tiles = { "default_copper_block.png" },
  436. walkable = true,
  437. groups = { cracky = 3, petroleum_fixture = 1, },
  438. on_construct = function(pos)
  439. print("\nspout placed at "..pos.x..","..pos.y..","..pos.z)
  440. local found_net, merge_list = check_merge(pos)
  441. if found_net == 0 then
  442. local hash = minetest.hash_node_position(pos)
  443. local pnet = new_network(pos)
  444. pnet.outputs[hash] = 1
  445. end
  446. try_merge(merge_list)
  447. save_data()
  448. end,
  449. after_destruct = bitumen.pipes.after_destruct,
  450. })
  451. minetest.register_abm({
  452. nodenames = {"bitumen:spout"},
  453. -- neighbors = {"group:fresh_water"},
  454. interval = 1,
  455. chance = 1,
  456. action = function(pos)
  457. local hash = minetest.hash_node_position(pos)
  458. local phash = net_members[hash]
  459. local pnet = networks[phash]
  460. if pnet.buffer <= 0 then
  461. --print("spout: no water in pipe")
  462. return -- no water in the pipe
  463. end
  464. -- hack
  465. pnet.in_pressure = pnet.in_pressure or -32000
  466. if pnet.in_pressure <= pos.y then
  467. --print("insufficient pressure at spout: ".. pnet.in_pressure .. " < " ..pos.y )
  468. return
  469. end
  470. pos.y = pos.y - 1
  471. local bnode = minetest.get_node(pos)
  472. local avail = math.min(16, pnet.buffer) -- pnet.buffer / #pnet.outputs
  473. if bnode.name == pnet.fluid then
  474. local blevel = minetest.get_node_level(pos)
  475. local cap = 64 - blevel
  476. local out = math.min(cap, math.min(avail, cap))
  477. --print("cap: ".. cap .." avail: ".. avail .. " out: "..out)
  478. pnet.buffer = pnet.buffer - out
  479. minetest.set_node_level(pos, blevel + out)
  480. elseif bnode.name == "air" or bnode.name == "bitumen:vapor_1" or bnode.name == "bitumen:vapor_2" then
  481. local out = math.min(64, math.max(0, avail))
  482. pnet.buffer = pnet.buffer - out
  483. minetest.set_node(pos, {name = pnet.fluid})
  484. minetest.set_node_level(pos, out)
  485. end
  486. end
  487. })
  488. minetest.register_node("bitumen:pipe", {
  489. description = "petroleum pipe",
  490. drawtype = "nodebox",
  491. node_box = {
  492. type = "connected",
  493. fixed = {{-.1, -.1, -.1, .1, .1, .1}},
  494. -- connect_bottom =
  495. connect_front = {{-.1, -.1, -.5, .1, .1, .1}},
  496. connect_left = {{-.5, -.1, -.1, -.1, .1, .1}},
  497. connect_back = {{-.1, -.1, .1, .1, .1, .5}},
  498. connect_right = {{ .1, -.1, -.1, .5, .1, .1}},
  499. connect_top = {{ -.1, -.1, -.1, .1, .5, .1}},
  500. connect_bottom = {{ -.1, -.5, -.1, .1, .1, .1}},
  501. },
  502. connects_to = { "group:petroleum_pipe", "group:petroleum_fixture" },
  503. paramtype = "light",
  504. is_ground_content = false,
  505. tiles = { "default_steel_block.png" },
  506. walkable = true,
  507. groups = { cracky = 3, petroleum_pipe = 1, },
  508. on_construct = function(pos)
  509. print("\npipe placed at "..pos.x..","..pos.y..","..pos.z)
  510. local found_net, merge_list = check_merge(pos)
  511. if found_net == 0 then
  512. local net = new_network(pos)
  513. end
  514. try_merge(merge_list)
  515. save_data()
  516. end,
  517. after_destruct = bitumen.pipes.after_destruct,
  518. })