abms.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. -- reimplementation of new_flow_logic branch: processing functions
  2. -- written 2017 by thetaepsilon
  3. local flowlogic = {}
  4. flowlogic.helpers = {}
  5. pipeworks.flowlogic = flowlogic
  6. -- borrowed from above: might be useable to replace the above coords tables
  7. local make_coords_offsets = function(pos, include_base)
  8. local coords = {
  9. {x=pos.x,y=pos.y-1,z=pos.z},
  10. {x=pos.x,y=pos.y+1,z=pos.z},
  11. {x=pos.x-1,y=pos.y,z=pos.z},
  12. {x=pos.x+1,y=pos.y,z=pos.z},
  13. {x=pos.x,y=pos.y,z=pos.z-1},
  14. {x=pos.x,y=pos.y,z=pos.z+1},
  15. }
  16. if include_base then table.insert(coords, pos) end
  17. return coords
  18. end
  19. -- local debuglog = function(msg) print("## "..msg) end
  20. local formatvec = function(vec) local sep="," return "("..tostring(vec.x)..sep..tostring(vec.y)..sep..tostring(vec.z)..")" end
  21. -- new version of liquid check
  22. -- accepts a limit parameter to only delete water blocks that the receptacle can accept,
  23. -- and returns it so that the receptacle can update it's pressure values.
  24. local check_for_liquids_v2 = function(pos, limit)
  25. local coords = make_coords_offsets(pos, false)
  26. local total = 0
  27. for index, tpos in ipairs(coords) do
  28. if total >= limit then break end
  29. local name = minetest.get_node(tpos).name
  30. if name == "default:water_source" then
  31. minetest.remove_node(tpos)
  32. total = total + 1
  33. end
  34. end
  35. --pipeworks.logger("check_for_liquids_v2@"..formatvec(pos).." total "..total)
  36. return total
  37. end
  38. flowlogic.check_for_liquids_v2 = check_for_liquids_v2
  39. local label_pressure = "pipeworks.water_pressure"
  40. local get_pressure_access = function(pos)
  41. local metaref = minetest.get_meta(pos)
  42. return {
  43. get = function()
  44. return metaref:get_float(label_pressure)
  45. end,
  46. set = function(v)
  47. metaref:set_float(label_pressure, v)
  48. end
  49. }
  50. end
  51. -- logging is unreliable when something is crashing...
  52. local nilexplode = function(caller, label, value)
  53. if value == nil then
  54. error(caller..": "..label.." was nil")
  55. end
  56. end
  57. local finitemode = pipeworks.toggles.finite_water
  58. flowlogic.run = function(pos, node)
  59. local nodename = node.name
  60. -- get the current pressure value.
  61. local nodepressure = get_pressure_access(pos)
  62. local currentpressure = nodepressure.get()
  63. local oldpressure = currentpressure
  64. -- if node is an input: run intake phase
  65. local inputdef = pipeworks.flowables.inputs.list[nodename]
  66. if inputdef then
  67. currentpressure = flowlogic.run_input(pos, node, currentpressure, inputdef)
  68. --debuglog("post-intake currentpressure is "..currentpressure)
  69. --nilexplode("run()", "currentpressure", currentpressure)
  70. end
  71. -- balance pressure with neighbours
  72. currentpressure = flowlogic.balance_pressure(pos, node, currentpressure)
  73. -- if node is an output: run output phase
  74. local outputdef = pipeworks.flowables.outputs.list[nodename]
  75. if outputdef then
  76. currentpressure = flowlogic.run_output(
  77. pos,
  78. node,
  79. currentpressure,
  80. oldpressure,
  81. outputdef,
  82. finitemode)
  83. end
  84. -- if node has pressure transitions: determine new node
  85. if pipeworks.flowables.transitions.list[nodename] then
  86. local newnode = flowlogic.run_transition(node, currentpressure)
  87. --pipeworks.logger("flowlogic.run()@"..formatvec(pos).." transition, new node name = "..dump(newnode).." pressure "..tostring(currentpressure))
  88. minetest.swap_node(pos, newnode)
  89. flowlogic.run_transition_post(pos, newnode)
  90. end
  91. -- set the new pressure
  92. nodepressure.set(currentpressure)
  93. end
  94. local simple_neighbour_offsets = {
  95. {x=0, y=-1,z= 0},
  96. {x=0, y= 1,z= 0},
  97. {x=-1,y= 0,z= 0},
  98. {x= 1,y= 0,z= 0},
  99. {x= 0,y= 0,z=-1},
  100. {x= 0,y= 0,z= 1},
  101. }
  102. local get_neighbour_positions = function(pos, node)
  103. -- local dname = "get_neighbour_positions@"..formatvec(pos).." "
  104. -- get list of node neighbours.
  105. -- if this node is directional and only flows on certain sides,
  106. -- invoke the callback to retrieve the set.
  107. -- for simple flowables this is just an auto-gen'd list of all six possible neighbours.
  108. local candidates = {}
  109. if pipeworks.flowables.list.simple[node.name] then
  110. candidates = simple_neighbour_offsets
  111. else
  112. -- directional flowables: call the callback to get the list
  113. local directional = pipeworks.flowables.list.directional[node.name]
  114. if directional then
  115. --pipeworks.logger(dname.."invoking neighbourfn")
  116. local offsets = directional.neighbourfn(node)
  117. candidates = offsets
  118. end
  119. end
  120. -- then, check each possible neighbour to see if they can be reached from this node.
  121. local connections = {}
  122. for index, offset in ipairs(candidates) do
  123. local npos = vector.add(pos, offset)
  124. local neighbour = minetest.get_node(npos)
  125. local nodename = neighbour.name
  126. local is_simple = (pipeworks.flowables.list.simple[nodename])
  127. if is_simple then
  128. local n = get_pressure_access(npos)
  129. table.insert(connections, n)
  130. else
  131. -- if target node is also directional, check if it agrees it can flow in that direction
  132. local directional = pipeworks.flowables.list.directional[nodename]
  133. if directional then
  134. --pipeworks.logger(dname.."directionality test for offset "..formatvec(offset))
  135. local towards_origin = vector.multiply(offset, -1)
  136. --pipeworks.logger(dname.."vector passed to directionfn: "..formatvec(towards_origin))
  137. local result = directional.directionfn(neighbour, towards_origin)
  138. --pipeworks.logger(dname.."result: "..tostring(result))
  139. if result then
  140. local n = get_pressure_access(npos)
  141. table.insert(connections, n)
  142. end
  143. end
  144. end
  145. end
  146. return connections
  147. end
  148. flowlogic.balance_pressure = function(pos, node, currentpressure)
  149. -- local dname = "flowlogic.balance_pressure()@"..formatvec(pos).." "
  150. -- check the pressure of all nearby flowable nodes, and average it out.
  151. -- pressure handles to average over
  152. local connections = {}
  153. -- unconditionally include self in nodes to average over.
  154. -- result of averaging will be returned as new pressure for main flow logic callback
  155. local totalv = currentpressure
  156. local totalc = 1
  157. local connections = get_neighbour_positions(pos, node)
  158. -- for each neighbour, add neighbour's pressure to the total to balance out
  159. for _, neighbour in ipairs(connections) do
  160. local n = neighbour.get()
  161. totalv = totalv + n
  162. totalc = totalc + 1
  163. end
  164. local average = totalv / totalc
  165. for _, target in ipairs(connections) do
  166. target.set(average)
  167. end
  168. return average
  169. end
  170. flowlogic.run_input = function(pos, node, currentpressure, inputdef)
  171. -- intakefn allows a given input node to define it's own intake logic.
  172. -- this function will calculate the maximum amount of water that can be taken in;
  173. -- the intakefn will be given this and is expected to return the actual absorption amount.
  174. local maxpressure = inputdef.maxpressure
  175. local intake_limit = maxpressure - currentpressure
  176. if intake_limit <= 0 then return currentpressure end
  177. local actual_intake = inputdef.intakefn(pos, intake_limit)
  178. --pipeworks.logger("run_input@"..formatvec(pos).." oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake)
  179. if actual_intake <= 0 then return currentpressure end
  180. local newpressure = actual_intake + currentpressure
  181. --debuglog("run_input() end, oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure)
  182. return newpressure
  183. end
  184. -- flowlogic output helper implementation:
  185. -- outputs water by trying to place water nodes nearby in the world.
  186. -- neighbours is a list of node offsets to try placing water in.
  187. -- this is a constructor function, returning another function which satisfies the output helper requirements.
  188. -- note that this does *not* take rotation into account.
  189. flowlogic.helpers.make_neighbour_output_fixed = function(neighbours)
  190. return function(pos, node, currentpressure, finitemode)
  191. local taken = 0
  192. for _, offset in pairs(neighbours) do
  193. local npos = vector.add(pos, offset)
  194. local name = minetest.get_node(npos).name
  195. if currentpressure < 1 then break end
  196. -- take pressure anyway in non-finite mode, even if node is water source already.
  197. -- in non-finite mode, pressure has to be sustained to keep the sources there.
  198. -- so in non-finite mode, placing water is dependent on the target node;
  199. -- draining pressure is not.
  200. local canplace = (name == "air") or (name == "default:water_flowing")
  201. if canplace then
  202. minetest.swap_node(npos, {name="default:water_source"})
  203. end
  204. if (not finitemode) or canplace then
  205. taken = taken + 1
  206. currentpressure = currentpressure - 1
  207. end
  208. end
  209. return taken
  210. end
  211. end
  212. -- complementary function to the above when using non-finite mode:
  213. -- removes water sources from neighbor positions when the output is "off" due to lack of pressure.
  214. flowlogic.helpers.make_neighbour_cleanup_fixed = function(neighbours)
  215. return function(pos, node, currentpressure)
  216. --pipeworks.logger("neighbour_cleanup_fixed@"..formatvec(pos))
  217. for _, offset in pairs(neighbours) do
  218. local npos = vector.add(pos, offset)
  219. local name = minetest.get_node(npos).name
  220. if (name == "default:water_source") then
  221. --pipeworks.logger("neighbour_cleanup_fixed removing "..formatvec(npos))
  222. minetest.remove_node(npos)
  223. end
  224. end
  225. end
  226. end
  227. flowlogic.run_output = function(pos, node, currentpressure, oldpressure, outputdef, finitemode)
  228. -- processing step for water output devices.
  229. -- takes care of checking a minimum pressure value and updating the resulting pressure level
  230. -- the outputfn is provided the current pressure and returns the pressure "taken".
  231. -- as an example, using this with the above spigot function,
  232. -- the spigot function tries to output a water source if it will fit in the world.
  233. --pipeworks.logger("flowlogic.run_output() pos "..formatvec(pos).." old -> currentpressure "..tostring(oldpressure).." "..tostring(currentpressure).." finitemode "..tostring(finitemode))
  234. local upper = outputdef.upper
  235. local lower = outputdef.lower
  236. local result = currentpressure
  237. local threshold = nil
  238. if finitemode then threshold = lower else threshold = upper end
  239. if currentpressure > threshold then
  240. local takenpressure = outputdef.outputfn(pos, node, currentpressure, finitemode)
  241. local newpressure = currentpressure - takenpressure
  242. if newpressure < 0 then newpressure = 0 end
  243. result = newpressure
  244. end
  245. if (not finitemode) and (currentpressure < lower) and (oldpressure < lower) then
  246. --pipeworks.logger("flowlogic.run_output() invoking cleanup currentpressure="..tostring(currentpressure))
  247. outputdef.cleanupfn(pos, node, currentpressure)
  248. end
  249. return result
  250. end
  251. -- determine which node to switch to based on current pressure
  252. flowlogic.run_transition = function(node, currentpressure)
  253. local simplesetdef = pipeworks.flowables.transitions.simple[node.name]
  254. local result = node
  255. local found = false
  256. -- simple transition sets: assumes all nodes in the set share param values.
  257. if simplesetdef then
  258. -- assumes that the set has been checked to contain at least one element...
  259. local nodename_prev = simplesetdef[1].nodename
  260. local result_nodename = node.name
  261. for index, element in ipairs(simplesetdef) do
  262. -- find the highest element that is below the current pressure.
  263. local threshold = element.threshold
  264. if threshold > currentpressure then
  265. result_nodename = nodename_prev
  266. found = true
  267. break
  268. end
  269. nodename_prev = element.nodename
  270. end
  271. -- use last element if no threshold is greater than current pressure
  272. if not found then
  273. result_nodename = nodename_prev
  274. found = true
  275. end
  276. -- preserve param1/param2 values
  277. result = { name=result_nodename, param1=node.param1, param2=node.param2 }
  278. end
  279. if not found then
  280. pipeworks.logger("flowlogic.run_transition() BUG no transition definitions found! nodename="..nodename.." currentpressure="..tostring(currentpressure))
  281. end
  282. return result
  283. end
  284. -- post-update hook for run_transition
  285. -- among other things, updates mesecons if present.
  286. -- node here means the new node, returned from run_transition() above
  287. flowlogic.run_transition_post = function(pos, node)
  288. local mesecons_def = minetest.registered_nodes[node.name].mesecons
  289. local mesecons_rules = pipeworks.flowables.transitions.mesecons[node.name]
  290. if minetest.global_exists("mesecon") and (mesecons_def ~= nil) and mesecons_rules then
  291. if type(mesecons_def) ~= "table" then
  292. pipeworks.logger("flowlogic.run_transition_post() BUG mesecons def for "..node.name.."not a table: got "..tostring(mesecons_def))
  293. else
  294. local receptor = mesecons_def.receptor
  295. if receptor then
  296. local state = receptor.state
  297. if state == mesecon.state.on then
  298. mesecon.receptor_on(pos, mesecons_rules)
  299. elseif state == mesecon.state.off then
  300. mesecon.receptor_off(pos, mesecons_rules)
  301. end
  302. end
  303. end
  304. end
  305. end