summaryrefslogtreecommitdiff
path: root/pipeworks/pressure_logic/flowable_node_registry_install.lua
blob: 0ad00a6ad9963669f9b7f97d8e5a8f3170e62e90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
-- flowable node registry: add entries and install ABMs if new flow logic is enabled
-- written 2017 by thetaepsilon



-- use for hooking up ABMs as nodes are registered
local abmregister = pipeworks.flowlogic.abmregister

-- registration functions
pipeworks.flowables.register = {}
local register = pipeworks.flowables.register

-- some sanity checking for passed args, as this could potentially be made an external API eventually
local checkexists = function(nodename)
	if type(nodename) ~= "string" then error("pipeworks.flowables nodename must be a string!") end
	return pipeworks.flowables.list.all[nodename]
end

local insertbase = function(nodename)
	if checkexists(nodename) then error("pipeworks.flowables duplicate registration!") end
	pipeworks.flowables.list.all[nodename] = true
	-- table.insert(pipeworks.flowables.list.nodenames, nodename)
	if pipeworks.toggles.pipe_mode == "pressure" then
		abmregister.flowlogic(nodename)
	end
end

local regwarning = function(kind, nodename)
	local tail = ""
	if pipeworks.toggles.pipe_mode ~= "pressure" then tail = " but pressure logic not enabled" end
	--pipeworks.logger(kind.." flow logic registry requested for "..nodename..tail)
end

-- Register a node as a simple flowable.
-- Simple flowable nodes have no considerations for direction of flow;
-- A cluster of adjacent simple flowables will happily average out in any direction.
register.simple = function(nodename)
	insertbase(nodename)
	pipeworks.flowables.list.simple[nodename] = true
	table.insert(pipeworks.flowables.list.simple_nodenames, nodename)
	regwarning("simple", nodename)
end

-- Register a node as a directional flowable:
-- has a helper function which determines which nodes to consider valid neighbours.
register.directional = function(nodename, neighbourfn, directionfn)
	insertbase(nodename)
	pipeworks.flowables.list.directional[nodename] = {
		neighbourfn = neighbourfn,
		directionfn = directionfn
	}
	regwarning("directional", nodename)
end

-- register a node as a directional flowable that can only flow through either the top or bottom side.
-- used for fountainheads (bottom side) and pumps (top side).
-- this is in world terms, not facedir relative!
register.directional_vertical_fixed = function(nodename, topside)
	local y
	if topside then y = 1 else y = -1 end
	local side = { x=0, y=y, z=0 }
	local neighbourfn = function(node) return { side } end
	local directionfn = function(node, direction)
		return vector.equals(direction, side)
	end
	register.directional(nodename, neighbourfn, directionfn)
end

-- register a node as a directional flowable whose accepting sides depends upon param2 rotation.
-- used for entry panels, valves, flow sensors and spigots.
-- this is mostly for legacy reasons and SHOULD NOT BE USED IN NEW CODE.
register.directional_horizonal_rotate = function(nodename, doubleended)
	local rotations = {
		{x= 0,y= 0,z= 1},
		{x= 1,y= 0,z= 0},
		{x= 0,y= 0,z=-1},
		{x=-1,y= 0,z= 0},
	}
	local getends = function(node)
		--local dname = "horizontal rotate getends() "
		local param2 = node.param2
		-- the pipeworks nodes use a fixed value for vertical facing nodes
		-- if that is detected, just return that directly.
		if param2 == 17 then
			return {{x=0,y=1,z=0}, {x=0,y=-1,z=0}}
		end

		-- the sole end of the spigot points in the direction the rotation bits suggest
		-- also note to self: lua arrays start at one...
		local mainend = (param2 % 4) + 1
		-- use modulus wrap-around to find other end for straight-run devices like the valve
		local otherend = ((param2 + 2) % 4) + 1
		local mainrot = rotations[mainend]
		--pipeworks.logger(dname.."mainrot: "..dump(mainrot))
		local result
		if doubleended then
			result = { mainrot, rotations[otherend] }
		else
			result = { mainrot }
		end
		--pipeworks.logger(dname.."result: "..dump(result))
		return result
	end
	local neighbourfn = function(node)
		return getends(node)
	end
	local directionfn = function(node, direction)
		local result = false
		for index, endvec in ipairs(getends(node)) do
			if vector.equals(direction, endvec) then result = true end
		end
		return result
	end
	register.directional(nodename, neighbourfn, directionfn)
end



local checkbase = function(nodename)
	if not checkexists(nodename) then error("pipeworks.flowables node doesn't exist as a flowable!") end
end

local duplicateerr = function(kind, nodename) error(kind.." duplicate registration for "..nodename) end



-- Registers a node as a fluid intake.
-- intakefn is used to determine the water that can be taken in a node-specific way.
-- Expects node to be registered as a flowable (is present in flowables.list.all),
-- so that water can move out of it.
-- maxpressure is the maximum pipeline pressure that this node can drive;
-- if the input's node exceeds this the callback is not run.
-- possible WISHME here: technic-driven high-pressure pumps
register.intake = function(nodename, maxpressure, intakefn)
	-- check for duplicate registration of this node
	local list = pipeworks.flowables.inputs.list
	checkbase(nodename)
	if list[nodename] then duplicateerr("pipeworks.flowables.inputs", nodename) end
	list[nodename] = { maxpressure=maxpressure, intakefn=intakefn }
	regwarning("intake", nodename)
end



-- Register a node as a simple intake:
-- tries to absorb water source nodes from it's surroundings.
-- may exceed limit slightly due to needing to absorb whole nodes.
register.intake_simple = function(nodename, maxpressure)
	register.intake(nodename, maxpressure, pipeworks.flowlogic.check_for_liquids_v2)
end



-- Register a node as an output.
-- Expects node to already be a flowable.
-- upper and lower thresholds have different meanings depending on whether finite liquid mode is in effect.
-- if not (the default unless auto-detected),
-- nodes above their upper threshold have their outputfn invoked (and pressure deducted),
-- nodes between upper and lower are left idle,
-- and nodes below lower have their cleanup fn invoked (to say remove water sources).
-- the upper and lower difference acts as a hysteresis to try and avoid "gaps" in the flow.
-- if finite mode is on, upper is ignored and lower is used to determine whether to run outputfn;
-- cleanupfn is ignored in this mode as finite mode assumes something causes water to move itself.
register.output = function(nodename, upper, lower, outputfn, cleanupfn)
	if pipeworks.flowables.outputs.list[nodename] then
		error("pipeworks.flowables.outputs duplicate registration!")
	end
	checkbase(nodename)
	pipeworks.flowables.outputs.list[nodename] = {
		upper=upper,
		lower=lower,
		outputfn=outputfn,
		cleanupfn=cleanupfn,
	}
	-- output ABM now part of main flow logic ABM to preserve ordering.
	-- note that because outputs have to be a flowable first
	-- (and the installation of the flow logic ABM is conditional),
	-- registered output nodes for new_flow_logic is also still conditional on the enable flag.
	regwarning("output node", nodename)
end

-- register a simple output:
-- drains pressure by attempting to place water in nearby nodes,
-- which can be set by passing a list of offset vectors.
-- will attempt to drain as many whole nodes as there are positions in the offset list.
-- for meanings of upper and lower, see register.output() above.
-- non-finite mode:
--	above upper pressure: places water sources as appropriate, keeps draining pressure.
--	below lower presssure: removes it's neighbour water sources.
-- finite mode:
--	same as for above pressure in non-finite mode,
--	but only drains pressure when water source nodes are actually placed.
register.output_simple = function(nodename, upper, lower, neighbours)
	local outputfn = pipeworks.flowlogic.helpers.make_neighbour_output_fixed(neighbours)
	local cleanupfn = pipeworks.flowlogic.helpers.make_neighbour_cleanup_fixed(neighbours)
	register.output(nodename, upper, lower, outputfn, cleanupfn)
end



-- common base checking for transition nodes
-- ensures the node has only been registered once as a transition.
local transition_list = pipeworks.flowables.transitions.list
local insert_transition_base = function(nodename)
	checkbase(nodename)
	if transition_list[nodename] then duplicateerr("base transition", nodename) end
	transition_list[nodename] = true
end



-- register a simple transition set.
-- expects a table with nodenames as keys and threshold pressures as values.
-- internally, the table is sorted by value, and when one of these nodes needs to transition,
-- the table is searched starting from the lowest (even if it's value is non-zero),
-- until a value is found which is higher than or equal to the current node pressure.
-- ex. nodeset = { ["mod:level_0"] = 0, ["mod:level_1"] = 1, --[[ ... ]] }
local simpleseterror = function(msg)
	error("register.transition_simple_set(): "..msg)
end
local simple_transitions = pipeworks.flowables.transitions.simple

register.transition_simple_set = function(nodeset, extras)
	local set = {}
	if extras == nil then extras = {} end

	local length = #nodeset
	if length < 2 then simpleseterror("nodeset needs at least two elements!") end
	for index, element in ipairs(nodeset) do
		if type(element) ~= "table" then simpleseterror("element "..tostring(index).." in nodeset was not table!") end
		local nodename = element[1]
		local value = element[2]
		if type(nodename) ~= "string" then simpleseterror("nodename "..tostring(nodename).."was not a string!") end
		if type(value) ~= "number" then simpleseterror("pressure value "..tostring(value).."was not a number!") end
		insert_transition_base(nodename)
		if simple_transitions[nodename] then duplicateerr("simple transition set", nodename) end
		-- assigning set to table is done separately below

		table.insert(set, { nodename=nodename, threshold=value })
	end

	-- sort pressure values, smallest first
	local smallest_first = function(a, b)
		return a.threshold < b.threshold
	end
	table.sort(set, smallest_first)

	-- individual registration of each node, all sharing this set,
	-- so each node in the set will transition to the correct target node.
	for _, element in ipairs(set) do
		--pipeworks.logger("register.transition_simple_set() after sort: nodename "..element.nodename.." value "..tostring(element.threshold))
		simple_transitions[element.nodename] = set
	end

	-- handle extra options
	-- if mesecons rules table was passed, set for each node
	if extras.mesecons then
		local mesecons_rules = pipeworks.flowables.transitions.mesecons
		for _, element in ipairs(set) do
			mesecons_rules[element.nodename] = extras.mesecons
		end
	end
end