flowable_node_registry_install.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. -- flowable node registry: add entries and install ABMs if new flow logic is enabled
  2. -- written 2017 by thetaepsilon
  3. -- use for hooking up ABMs as nodes are registered
  4. local abmregister = pipeworks.flowlogic.abmregister
  5. -- registration functions
  6. pipeworks.flowables.register = {}
  7. local register = pipeworks.flowables.register
  8. -- some sanity checking for passed args, as this could potentially be made an external API eventually
  9. local checkexists = function(nodename)
  10. if type(nodename) ~= "string" then error("pipeworks.flowables nodename must be a string!") end
  11. return pipeworks.flowables.list.all[nodename]
  12. end
  13. local insertbase = function(nodename)
  14. if checkexists(nodename) then error("pipeworks.flowables duplicate registration!") end
  15. pipeworks.flowables.list.all[nodename] = true
  16. -- table.insert(pipeworks.flowables.list.nodenames, nodename)
  17. if pipeworks.toggles.pipe_mode == "pressure" then
  18. abmregister.flowlogic(nodename)
  19. end
  20. end
  21. local regwarning = function(kind, nodename)
  22. local tail = ""
  23. if pipeworks.toggles.pipe_mode ~= "pressure" then tail = " but pressure logic not enabled" end
  24. --pipeworks.logger(kind.." flow logic registry requested for "..nodename..tail)
  25. end
  26. -- Register a node as a simple flowable.
  27. -- Simple flowable nodes have no considerations for direction of flow;
  28. -- A cluster of adjacent simple flowables will happily average out in any direction.
  29. register.simple = function(nodename)
  30. insertbase(nodename)
  31. pipeworks.flowables.list.simple[nodename] = true
  32. table.insert(pipeworks.flowables.list.simple_nodenames, nodename)
  33. regwarning("simple", nodename)
  34. end
  35. -- Register a node as a directional flowable:
  36. -- has a helper function which determines which nodes to consider valid neighbours.
  37. register.directional = function(nodename, neighbourfn, directionfn)
  38. insertbase(nodename)
  39. pipeworks.flowables.list.directional[nodename] = {
  40. neighbourfn = neighbourfn,
  41. directionfn = directionfn
  42. }
  43. regwarning("directional", nodename)
  44. end
  45. -- register a node as a directional flowable that can only flow through either the top or bottom side.
  46. -- used for fountainheads (bottom side) and pumps (top side).
  47. -- this is in world terms, not facedir relative!
  48. register.directional_vertical_fixed = function(nodename, topside)
  49. local y
  50. if topside then y = 1 else y = -1 end
  51. local side = { x=0, y=y, z=0 }
  52. local neighbourfn = function(node) return { side } end
  53. local directionfn = function(node, direction)
  54. return vector.equals(direction, side)
  55. end
  56. register.directional(nodename, neighbourfn, directionfn)
  57. end
  58. -- register a node as a directional flowable whose accepting sides depends upon param2 rotation.
  59. -- used for entry panels, valves, flow sensors and spigots.
  60. -- this is mostly for legacy reasons and SHOULD NOT BE USED IN NEW CODE.
  61. register.directional_horizonal_rotate = function(nodename, doubleended)
  62. local rotations = {
  63. {x= 0,y= 0,z= 1},
  64. {x= 1,y= 0,z= 0},
  65. {x= 0,y= 0,z=-1},
  66. {x=-1,y= 0,z= 0},
  67. }
  68. local getends = function(node)
  69. --local dname = "horizontal rotate getends() "
  70. local param2 = node.param2
  71. -- the pipeworks nodes use a fixed value for vertical facing nodes
  72. -- if that is detected, just return that directly.
  73. if param2 == 17 then
  74. return {{x=0,y=1,z=0}, {x=0,y=-1,z=0}}
  75. end
  76. -- the sole end of the spigot points in the direction the rotation bits suggest
  77. -- also note to self: lua arrays start at one...
  78. local mainend = (param2 % 4) + 1
  79. -- use modulus wrap-around to find other end for straight-run devices like the valve
  80. local otherend = ((param2 + 2) % 4) + 1
  81. local mainrot = rotations[mainend]
  82. --pipeworks.logger(dname.."mainrot: "..dump(mainrot))
  83. local result
  84. if doubleended then
  85. result = { mainrot, rotations[otherend] }
  86. else
  87. result = { mainrot }
  88. end
  89. --pipeworks.logger(dname.."result: "..dump(result))
  90. return result
  91. end
  92. local neighbourfn = function(node)
  93. return getends(node)
  94. end
  95. local directionfn = function(node, direction)
  96. local result = false
  97. for index, endvec in ipairs(getends(node)) do
  98. if vector.equals(direction, endvec) then result = true end
  99. end
  100. return result
  101. end
  102. register.directional(nodename, neighbourfn, directionfn)
  103. end
  104. local checkbase = function(nodename)
  105. if not checkexists(nodename) then error("pipeworks.flowables node doesn't exist as a flowable!") end
  106. end
  107. local duplicateerr = function(kind, nodename) error(kind.." duplicate registration for "..nodename) end
  108. -- Registers a node as a fluid intake.
  109. -- intakefn is used to determine the water that can be taken in a node-specific way.
  110. -- Expects node to be registered as a flowable (is present in flowables.list.all),
  111. -- so that water can move out of it.
  112. -- maxpressure is the maximum pipeline pressure that this node can drive;
  113. -- if the input's node exceeds this the callback is not run.
  114. -- possible WISHME here: technic-driven high-pressure pumps
  115. register.intake = function(nodename, maxpressure, intakefn)
  116. -- check for duplicate registration of this node
  117. local list = pipeworks.flowables.inputs.list
  118. checkbase(nodename)
  119. if list[nodename] then duplicateerr("pipeworks.flowables.inputs", nodename) end
  120. list[nodename] = { maxpressure=maxpressure, intakefn=intakefn }
  121. regwarning("intake", nodename)
  122. end
  123. -- Register a node as a simple intake:
  124. -- tries to absorb water source nodes from it's surroundings.
  125. -- may exceed limit slightly due to needing to absorb whole nodes.
  126. register.intake_simple = function(nodename, maxpressure)
  127. register.intake(nodename, maxpressure, pipeworks.flowlogic.check_for_liquids_v2)
  128. end
  129. -- Register a node as an output.
  130. -- Expects node to already be a flowable.
  131. -- upper and lower thresholds have different meanings depending on whether finite liquid mode is in effect.
  132. -- if not (the default unless auto-detected),
  133. -- nodes above their upper threshold have their outputfn invoked (and pressure deducted),
  134. -- nodes between upper and lower are left idle,
  135. -- and nodes below lower have their cleanup fn invoked (to say remove water sources).
  136. -- the upper and lower difference acts as a hysteresis to try and avoid "gaps" in the flow.
  137. -- if finite mode is on, upper is ignored and lower is used to determine whether to run outputfn;
  138. -- cleanupfn is ignored in this mode as finite mode assumes something causes water to move itself.
  139. register.output = function(nodename, upper, lower, outputfn, cleanupfn)
  140. if pipeworks.flowables.outputs.list[nodename] then
  141. error("pipeworks.flowables.outputs duplicate registration!")
  142. end
  143. checkbase(nodename)
  144. pipeworks.flowables.outputs.list[nodename] = {
  145. upper=upper,
  146. lower=lower,
  147. outputfn=outputfn,
  148. cleanupfn=cleanupfn,
  149. }
  150. -- output ABM now part of main flow logic ABM to preserve ordering.
  151. -- note that because outputs have to be a flowable first
  152. -- (and the installation of the flow logic ABM is conditional),
  153. -- registered output nodes for new_flow_logic is also still conditional on the enable flag.
  154. regwarning("output node", nodename)
  155. end
  156. -- register a simple output:
  157. -- drains pressure by attempting to place water in nearby nodes,
  158. -- which can be set by passing a list of offset vectors.
  159. -- will attempt to drain as many whole nodes as there are positions in the offset list.
  160. -- for meanings of upper and lower, see register.output() above.
  161. -- non-finite mode:
  162. -- above upper pressure: places water sources as appropriate, keeps draining pressure.
  163. -- below lower presssure: removes it's neighbour water sources.
  164. -- finite mode:
  165. -- same as for above pressure in non-finite mode,
  166. -- but only drains pressure when water source nodes are actually placed.
  167. register.output_simple = function(nodename, upper, lower, neighbours)
  168. local outputfn = pipeworks.flowlogic.helpers.make_neighbour_output_fixed(neighbours)
  169. local cleanupfn = pipeworks.flowlogic.helpers.make_neighbour_cleanup_fixed(neighbours)
  170. register.output(nodename, upper, lower, outputfn, cleanupfn)
  171. end
  172. -- common base checking for transition nodes
  173. -- ensures the node has only been registered once as a transition.
  174. local transition_list = pipeworks.flowables.transitions.list
  175. local insert_transition_base = function(nodename)
  176. checkbase(nodename)
  177. if transition_list[nodename] then duplicateerr("base transition", nodename) end
  178. transition_list[nodename] = true
  179. end
  180. -- register a simple transition set.
  181. -- expects a table with nodenames as keys and threshold pressures as values.
  182. -- internally, the table is sorted by value, and when one of these nodes needs to transition,
  183. -- the table is searched starting from the lowest (even if it's value is non-zero),
  184. -- until a value is found which is higher than or equal to the current node pressure.
  185. -- ex. nodeset = { ["mod:level_0"] = 0, ["mod:level_1"] = 1, --[[ ... ]] }
  186. local simpleseterror = function(msg)
  187. error("register.transition_simple_set(): "..msg)
  188. end
  189. local simple_transitions = pipeworks.flowables.transitions.simple
  190. register.transition_simple_set = function(nodeset, extras)
  191. local set = {}
  192. if extras == nil then extras = {} end
  193. local length = #nodeset
  194. if length < 2 then simpleseterror("nodeset needs at least two elements!") end
  195. for index, element in ipairs(nodeset) do
  196. if type(element) ~= "table" then simpleseterror("element "..tostring(index).." in nodeset was not table!") end
  197. local nodename = element[1]
  198. local value = element[2]
  199. if type(nodename) ~= "string" then simpleseterror("nodename "..tostring(nodename).."was not a string!") end
  200. if type(value) ~= "number" then simpleseterror("pressure value "..tostring(value).."was not a number!") end
  201. insert_transition_base(nodename)
  202. if simple_transitions[nodename] then duplicateerr("simple transition set", nodename) end
  203. -- assigning set to table is done separately below
  204. table.insert(set, { nodename=nodename, threshold=value })
  205. end
  206. -- sort pressure values, smallest first
  207. local smallest_first = function(a, b)
  208. return a.threshold < b.threshold
  209. end
  210. table.sort(set, smallest_first)
  211. -- individual registration of each node, all sharing this set,
  212. -- so each node in the set will transition to the correct target node.
  213. for _, element in ipairs(set) do
  214. --pipeworks.logger("register.transition_simple_set() after sort: nodename "..element.nodename.." value "..tostring(element.threshold))
  215. simple_transitions[element.nodename] = set
  216. end
  217. -- handle extra options
  218. -- if mesecons rules table was passed, set for each node
  219. if extras.mesecons then
  220. local mesecons_rules = pipeworks.flowables.transitions.mesecons
  221. for _, element in ipairs(set) do
  222. mesecons_rules[element.nodename] = extras.mesecons
  223. end
  224. end
  225. end