pipes.lua 17 KB

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