pipeline.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. -- pipelines are like pipes except much larger and more performant
  2. -- pipelines are also only point-to-point; there are no joints
  3. -- pipelines need pumps to force the fluid through, even downhill
  4. local networks = {}
  5. local net_members = {}
  6. local storage = {}
  7. local netname = 1
  8. --local mod_storage = minetest.get_mod_storage()
  9. local mod_storage = bitumen.mod_storage -- minetest.get_mod_storage()
  10. networks = minetest.deserialize(mod_storage:get_string("pl_networks")) or {}
  11. net_members = minetest.deserialize(mod_storage:get_string("pl_net_members")) or {}
  12. storage = minetest.deserialize(mod_storage:get_string("pl_storage")) or {}
  13. netname = mod_storage:get_int("pl_netname") or 1
  14. local function save_data()
  15. --print("saving")
  16. mod_storage:set_string("pl_networks", minetest.serialize(networks))
  17. mod_storage:set_string("pl_net_members", minetest.serialize(net_members))
  18. mod_storage:set_string("pl_storage", minetest.serialize(storage))
  19. mod_storage:set_int("pl_netname", netname)
  20. end
  21. -- centralized network creation for consistency
  22. local function new_network(pos)
  23. local hash = minetest.hash_node_position(pos)
  24. print("new pipeline network: hash: ".. hash .." name: " ..netname);
  25. networks[hash] = {
  26. hash = hash,
  27. pos = {x=pos.x, y=pos.y, z=pos.z},
  28. fluid = 'air',
  29. name = netname,
  30. count = 1,
  31. inputs = {
  32. [hash] = 1,
  33. },
  34. outputs = {},
  35. buffer = 0,
  36. in_pressure = -32000,
  37. storage = {
  38. --[[
  39. [entry_hash] = < storage_center_hash >
  40. ]]
  41. }
  42. }
  43. net_members[hash] = hash
  44. netname = netname + 1
  45. return networks[hash], hash
  46. end
  47. local function pnet_for(pos)
  48. local hash = minetest.hash_node_position(pos)
  49. local ph = net_members[hash]
  50. if ph == nil then
  51. return nil, hash
  52. end
  53. return networks[ph], hash
  54. end
  55. -- merge a list of networks, if the are multiple nets in the list
  56. local function try_merge(merge_list)
  57. if #merge_list > 1 then
  58. print("\n merging "..#merge_list.." networks")
  59. local biggest = {count = 0}
  60. local mlookup = {}
  61. for _,n in ipairs(merge_list) do
  62. mlookup[n.hash] = 1
  63. if n.count > biggest.count then
  64. biggest = n
  65. end
  66. end
  67. mlookup[biggest.hash] = 0
  68. for k,v in pairs(net_members) do
  69. if mlookup[v] == 1 then
  70. net_members[k] = biggest.hash
  71. end
  72. end
  73. for _,n in ipairs(merge_list) do
  74. if n.hash ~= biggest.hash then
  75. biggest.count = biggest.count + n.count
  76. networks[n.hash] = nil -- delete old networks
  77. end
  78. end
  79. return biggest
  80. end
  81. return merge_list[1]
  82. end
  83. -- check specific nearby nodes for existing networks
  84. local function check_merge(pos, npos1, npos2)
  85. local hash = minetest.hash_node_position(pos)
  86. local merge_list = {}
  87. local current_net = nil
  88. local found_net = 0
  89. local check_net = function(npos)
  90. local nhash = minetest.hash_node_position(npos)
  91. local nphash = net_members[nhash]
  92. if nphash ~= nil then
  93. local pnet = networks[nphash]
  94. if nil == current_net then
  95. print("joining existing network: ".. pnet.name)
  96. net_members[hash] = nphash
  97. current_net = nphash
  98. pnet.count = pnet.count + 1
  99. pnet.inputs[hash] = 1
  100. table.insert(merge_list, pnet)
  101. elseif current_net == nphash then
  102. print("alternate connection to existing network")
  103. else
  104. print("found seconday network: "..pnet.name)
  105. table.insert(merge_list, pnet)
  106. end
  107. found_net = 1
  108. end
  109. end
  110. check_net(npos1)
  111. check_net(npos2)
  112. return found_net, merge_list
  113. end
  114. bitumen.pipelines = {}
  115. -- used by external machines to find the network for a node
  116. bitumen.pipelines.get_net = function(pos)
  117. local hash = minetest.hash_node_position(pos)
  118. local phash = net_members[hash]
  119. if phash == nil then
  120. return nil, nil, hash
  121. end
  122. return networks[phash], phash, hash
  123. end
  124. bitumen.pipes.after_change = function(pos, opos1, opos2, npos1, npos2)
  125. local o1net, o1phash, o1hash = bitumen.pipelines.get_net(opos1)
  126. local o2net, o2phash, o2hash = bitumen.pipelines.get_net(opos2)
  127. local n1net, n1phash, n1hash = bitumen.pipelines.get_net(npos1)
  128. local n2net, n2phash, n2hash = bitumen.pipelines.get_net(npos2)
  129. local check_o1 = true
  130. local check_o2 = true
  131. local check_n1 = true
  132. local check_n2 = true
  133. -- check if one of the nodes is still in the network. this will often be the case
  134. if vector.equals(opos1, npos1) then
  135. print("old pos 1 is new pos 1")
  136. check_o1 = false
  137. check_n1 = false
  138. elseif vector.equals(opos1, npos2) then
  139. print("old pos 1 is new pos 1")
  140. check_o1 = false
  141. check_n2 = false
  142. end
  143. if vector.equals(opos2, npos1) then
  144. print("old pos 1 is new pos 1")
  145. check_o2 = false
  146. check_n1 = false
  147. elseif vector.equals(opos2, npos2) then
  148. print("old pos 1 is new pos 1")
  149. check_o2 = false
  150. check_n2 = false
  151. end
  152. -- remove o1 from the network
  153. if check_o1 then
  154. end
  155. -- merge with n1's network
  156. if check_o1 then
  157. end
  158. local hash = minetest.hash_node_position(pos)
  159. local phash = net_members[hash]
  160. if phash == nil then
  161. print("wtf: pipe has no network in after_destruct")
  162. return
  163. end
  164. local pnet = networks[phash]
  165. if pnet == nil then
  166. print("wtf: no network in after_destruct for pipe")
  167. return
  168. end
  169. -- remove this node from the network
  170. net_members[hash] = nil
  171. pnet.count = pnet.count - 1
  172. -- neighboring nodes
  173. local check_pos = {
  174. {x=pos.x+1, y=pos.y, z=pos.z},
  175. {x=pos.x-1, y=pos.y, z=pos.z},
  176. {x=pos.x, y=pos.y+1, z=pos.z},
  177. {x=pos.x, y=pos.y-1, z=pos.z},
  178. {x=pos.x, y=pos.y, z=pos.z+1},
  179. {x=pos.x, y=pos.y, z=pos.z-1},
  180. }
  181. local stack = {}
  182. local found = 0
  183. -- check neighbors for network membership
  184. for _,p in ipairs(check_pos) do
  185. local h = minetest.hash_node_position(p)
  186. local lphash = net_members[h]
  187. if lphash ~= nil then
  188. local lpnet = networks[lphash]
  189. -- only process pipes/fixtures on the same network as the destroyed pipe
  190. if lpnet and lpnet.name == pnet.name then
  191. stack[h] = vector.new(p)
  192. found = found + 1
  193. --print("check stack: "..p.x..","..p.y..","..p.z)
  194. else
  195. print("no lpnet")
  196. end
  197. else
  198. print("no lphash "..p.x..","..p.y..","..p.z)
  199. end
  200. end
  201. -- don't need to split the network if this was just on the end
  202. if found > 1 then
  203. --print("check to split the network")
  204. for h,p in pairs(stack) do
  205. print(dump(p))
  206. print(dump(h))
  207. -- BUG: spouts and intakes can be counted as pipes when walking the network
  208. -- just rename the net
  209. local new_pnet = rebase_network(p)
  210. -- print("split off pnet ".. new_pnet.name .. " at " .. minetest.pos_to_string(p))
  211. -- all fluid is lost in the network atm
  212. -- some networks might get orphaned, for example, the first
  213. -- net to be rebased in a loop
  214. end
  215. end
  216. save_data()
  217. end
  218. minetest.register_node("bitumen:pipeline", {
  219. description = "Petroleum pipeline segment",
  220. drawtype = "nodebox",
  221. node_box = {
  222. type = "fixed",
  223. fixed = {
  224. {-.3, -.35, -.5, .3, .35, .5},
  225. {-.35, -.3, -.5, .35, .3, .5},
  226. -- {-.4, -.4, -.4, .4, .4, .4},
  227. -- {-.4, -.4, -.4, .4, .4, .4},
  228. },
  229. },
  230. paramtype = "light",
  231. is_ground_content = false,
  232. paramtype2 = "facedir",
  233. is_ground_content = false,
  234. tiles = { "default_gold_block.png" },
  235. walkable = true,
  236. groups = { cracky = 3, petroleum_pipeline = 1, },
  237. on_place = minetest.rotate_node,
  238. on_construct = function(pos)
  239. print("\npipeline placed at "..pos.x..","..pos.y..","..pos.z)
  240. local node = minetest.get_node(pos)
  241. local back_dir = minetest.facedir_to_dir(node.param2)
  242. local backpos = vector.add(pos, back_dir)
  243. local frontpos = vector.subtract(pos, back_dir)
  244. -- minetest.set_node(backpos, {name="default:dirt"})
  245. -- local found_net, merge_list = check_merge(pos)
  246. --if found_net == 0 then
  247. -- local net = new_network(pos)
  248. --end
  249. -- try_merge(merge_list)
  250. --save_data()
  251. end,
  252. --after_destruct = bitumen.pipes.after_destruct,
  253. })
  254. minetest.register_node("bitumen:pipeline_elbow", {
  255. description = "Petroleum pipeline elbow",
  256. drawtype = "nodebox",
  257. node_box = {
  258. type = "fixed",
  259. fixed = {
  260. {-.3, -.35, -.5, .3, .35, .3},
  261. {-.35, -.3, -.5, .35, .3, .3},
  262. {-.3, -.3, -.35, .3, .5, .35},
  263. {-.35, -.3, -.3, .35, .5, .3},
  264. -- {-.4, -.4, -.4, .4, .4, .4},
  265. -- {-.4, -.4, -.4, .4, .4, .4},
  266. },
  267. },
  268. paramtype = "light",
  269. is_ground_content = false,
  270. paramtype2 = "facedir",
  271. is_ground_content = false,
  272. tiles = { "default_gold_block.png" },
  273. walkable = true,
  274. groups = { cracky = 3, petroleum_pipeline = 1, },
  275. on_place = minetest.rotate_node,
  276. on_rotate = function(pos, node, player, mode, new_param2)
  277. local oldback_dir = minetest.facedir_to_dir(node.param2)
  278. local oldfrontpos = vector.subtract(pos, oldback_dir)
  279. local oldtop_dir = ({[0]={x=0, y=1, z=0},
  280. {x=0, y=0, z=1},
  281. {x=0, y=0, z=-1},
  282. {x=1, y=0, z=0},
  283. {x=-1, y=0, z=0},
  284. {x=0, y=-1, z=0}})[math.floor(node.param2/4)]
  285. local oldtoppos = vector.add(pos, oldtop_dir)
  286. minetest.set_node(oldfrontpos, {name="default:glass"})
  287. minetest.set_node(oldtoppos, {name="default:glass"})
  288. local back_dir = minetest.facedir_to_dir(new_param2)
  289. local frontpos = vector.subtract(pos, back_dir)
  290. local top_dir = ({[0]={x=0, y=1, z=0},
  291. {x=0, y=0, z=1},
  292. {x=0, y=0, z=-1},
  293. {x=1, y=0, z=0},
  294. {x=-1, y=0, z=0},
  295. {x=0, y=-1, z=0}})[math.floor(new_param2/4)]
  296. local toppos = vector.add(pos, top_dir)
  297. minetest.set_node(frontpos, {name="default:dirt"})
  298. minetest.set_node(toppos, {name="default:cobble"})
  299. end,
  300. on_construct = function(pos)
  301. print("\npipeline elbow placed at "..pos.x..","..pos.y..","..pos.z)
  302. local node = minetest.get_node(pos)
  303. local back_dir = minetest.facedir_to_dir(node.param2)
  304. local frontpos = vector.subtract(pos, back_dir)
  305. local top_dir = ({[0]={x=0, y=1, z=0},
  306. {x=0, y=0, z=1},
  307. {x=0, y=0, z=-1},
  308. {x=1, y=0, z=0},
  309. {x=-1, y=0, z=0},
  310. {x=0, y=-1, z=0}})[math.floor(node.param2/4)]
  311. local toppos = vector.add(pos, top_dir)
  312. minetest.set_node(frontpos, {name="default:dirt"})
  313. minetest.set_node(toppos, {name="default:cobble"})
  314. -- local found_net, merge_list = check_merge(pos)
  315. --if found_net == 0 then
  316. -- local net = new_network(pos)
  317. --end
  318. -- try_merge(merge_list)
  319. --save_data()
  320. end,
  321. --after_destruct = bitumen.pipelines.after_destruct,
  322. })
  323. local storage_tank_builder_formspec =
  324. "size[10,8;]" ..
  325. default.gui_bg ..
  326. default.gui_bg_img ..
  327. default.gui_slots ..
  328. "list[context;main;0,0.3;4,3;]" ..
  329. "button[5,1;1,4;build;Build]" ..
  330. "list[current_player;main;0,3.85;8,1;]" ..
  331. "list[current_player;main;0,5.08;8,3;8]" ..
  332. "listring[context;main]" ..
  333. "listring[current_player;main]" ..
  334. default.get_hotbar_bg(0, 3.85)
  335. minetest.register_node("bitumen:storage_tank_constructor", {
  336. description = "Storage Tank Constructor",
  337. drawtype = "normal",
  338. paramtype2 = "facedir",
  339. on_rotate = screwdriver.rotate_simple,
  340. groups = {cracky=1},
  341. tiles = {
  342. "default_copper_block.png","default_tin_block.png",
  343. },
  344. on_construct = function(pos)
  345. local meta = minetest.get_meta(pos)
  346. local inv = meta:get_inventory()
  347. inv:set_size("main", 12)
  348. meta:set_string("formspec", storage_tank_builder_formspec);
  349. end,
  350. on_receive_fields = function(pos, form, fields, player)
  351. local meta = minetest.get_meta(pos)
  352. if fields.build then
  353. -- tanks can only be built on thick foundations
  354. local ret = bitumen.check_foundation(
  355. {x = pos.x - 15, y = pos.y - 3, z = pos.z - 15},
  356. {x = pos.x + 15, y = pos.y - 1, z = pos.z + 15},
  357. {
  358. ["default:steelblock"] = 1,
  359. ["bitumen:concrete"] = 1,
  360. }
  361. )
  362. if ret == false then
  363. minetest.chat_send_player(player:get_player_name(), "Foundation is incomplete: 30x3x30")
  364. return
  365. else
  366. minetest.chat_send_player(player:get_player_name(), "Foundation is complete.")
  367. end
  368. -- tanks need room
  369. local ret = bitumen.check_foundation(
  370. {x = pos.x - 16, y = pos.y , z = pos.z - 16},
  371. {x = pos.x + 16, y = pos.y + 12, z = pos.z + 16},
  372. {
  373. ["air"] = 1,
  374. ["bitumen:storage_tank_constructor"] = 1,
  375. }
  376. )
  377. if ret == false then
  378. minetest.chat_send_player(player:get_player_name(), "Area is not clear: 32x12x32")
  379. return
  380. else
  381. minetest.chat_send_player(player:get_player_name(), "Area is clear.")
  382. end
  383. local inv = meta:get_inventory();
  384. -- should be ~1500 sheets
  385. if (inv:contains_item("main", "bitumen:galv_steel_sheet 99") and
  386. inv:contains_item("main", "default:coal_lump 20")) then
  387. inv:remove_item("main", "bitumen:galv_steel_sheet 99")
  388. inv:remove_item("main", "default:coal_lump 20")
  389. else
  390. minetest.chat_send_player(player:get_player_name(), "Not enough materials: 99x Galvanized Steel Sheet, 20x Coal Lump")
  391. return
  392. end
  393. -- ready to go
  394. minetest.chat_send_player(player:get_player_name(), "Clear area, construction starting...")
  395. for i = 9,1,-1 do
  396. minetest.after(10-i, (function(n)
  397. return function()
  398. minetest.chat_send_player(
  399. player:get_player_name(),
  400. "Storage Tank construction in "..n.."..."
  401. )
  402. end
  403. end)(i))
  404. end
  405. minetest.after(10, function()
  406. minetest.set_node(pos, {name="bitumen:storage_tank"})
  407. end)
  408. end
  409. end,
  410. })
  411. bitumen.register_blueprint({
  412. name = "bitumen:storage_tank",
  413. })
  414. minetest.register_node("bitumen:storage_tank", {
  415. paramtype = "light",
  416. drawtype = "mesh",
  417. mesh = "storage_tank.obj",
  418. description = "Storage Tank",
  419. tiles = {
  420. "default_sand.png",
  421. },
  422. inventory_image = "default_sand.png",
  423. selection_box = {
  424. type = "fixed",
  425. fixed = {
  426. { -.5, -.5, -.5, .5, 1.5, .5 },
  427. -- { -8.2, -.5, -.2, -7.8, 10, .2 },
  428. -- { -.2, -.5, -8.2, .2, 10, -7.8 },
  429. -- { 8.2, -.5, -.2, 7.8, 10, .2 },
  430. -- { -.2, -.5, 8.2, .2, 10, 7.8 },
  431. },
  432. },
  433. collision_box = {
  434. type = "fixed",
  435. fixed = {
  436. { -.5, -.5, -.5, .5, 1.5, .5 },
  437. }
  438. },
  439. paramtype2 = "facedir",
  440. groups = {choppy=1, petroleum_fixture=1, bitumen_magic_proof=1 },
  441. sounds = default.node_sound_wood_defaults(),
  442. on_construct = function(pos)
  443. local meta = minetest.get_meta(pos)
  444. if placer then
  445. local owner = placer:get_player_name()
  446. meta:set_string("owner", owner)
  447. end
  448. -- meta:set_float("fluid_level", 0)
  449. -- meta:set_float("capacity", math.floor(3.14159 * .75 * 9 * 9 * 9 * 64))
  450. -- meta:set_string("infotext", "0%")
  451. bitumen.magic.set_collision_nodes(pos, bitumen.magic.gencylinder({0, 0, 0}, 14.99, 9))
  452. -- bitumen.pipes.on_construct(pos)
  453. end,
  454. on_destruct = bitumen.magic.on_destruct,
  455. can_dig = function(pos, player)
  456. -- local meta = minetest.get_meta(pos);
  457. -- local owner = meta:get_string("owner")
  458. -- local fluid_level = meta:get_float("fluid_level") or 0
  459. -- return player:get_player_name() ~= owner and fluid_level <= 0.01
  460. return true
  461. end,
  462. })