summaryrefslogtreecommitdiff
path: root/new_flow_logic/abms.lua
blob: eb551d8acf765f6db7ff674ec7050a5ec2135a84 (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
-- reimplementation of new_flow_logic branch: processing functions
-- written 2017 by thetaepsilon



local flowlogic = {}
flowlogic.helpers = {}
pipeworks.flowlogic = flowlogic



-- borrowed from above: might be useable to replace the above coords tables
local make_coords_offsets = function(pos, include_base)
	local coords = {
		{x=pos.x,y=pos.y-1,z=pos.z},
		{x=pos.x,y=pos.y+1,z=pos.z},
		{x=pos.x-1,y=pos.y,z=pos.z},
		{x=pos.x+1,y=pos.y,z=pos.z},
		{x=pos.x,y=pos.y,z=pos.z-1},
		{x=pos.x,y=pos.y,z=pos.z+1},
	}
	if include_base then table.insert(coords, pos) end
	return coords
end



-- local debuglog = function(msg) print("## "..msg) end



--local formatvec = function(vec) local sep="," return "("..tostring(vec.x)..sep..tostring(vec.y)..sep..tostring(vec.z)..")" end

-- new version of liquid check
-- accepts a limit parameter to only delete water blocks that the receptacle can accept,
-- and returns it so that the receptacle can update it's pressure values.
local check_for_liquids_v2 = function(pos, limit)
	local coords = make_coords_offsets(pos, false)
	local total = 0
	for index, tpos in ipairs(coords) do
		if total >= limit then break end
		local name = minetest.get_node(tpos).name
		if name == "default:water_source" then
			minetest.remove_node(tpos)
			total = total + 1
		end
	end
	--pipeworks.logger("check_for_liquids_v2@"..formatvec(pos).." total "..total)
	return total
end
flowlogic.check_for_liquids_v2 = check_for_liquids_v2



local label_pressure = "pipeworks.water_pressure"
local get_pressure_access = function(pos)
	local metaref = minetest.get_meta(pos)
	return {
		get = function()
			return metaref:get_float(label_pressure)
		end,
		set = function(v)
			metaref:set_float(label_pressure, v)
		end
	}
end


-- logging is unreliable when something is crashing...
local nilexplode = function(caller, label, value)
	if value == nil then
		error(caller..": "..label.." was nil")
	end
end



flowlogic.run = function(pos, node)
	local nodename = node.name
	-- get the current pressure value.
	local nodepressure = get_pressure_access(pos)
	local currentpressure = nodepressure.get()

	-- if node is an input: run intake phase
	local inputdef = pipeworks.flowables.inputs.list[nodename]
	if inputdef then
		currentpressure = flowlogic.run_input(pos, node, currentpressure, inputdef)
		--debuglog("post-intake currentpressure is "..currentpressure)
		--nilexplode("run()", "currentpressure", currentpressure)
	end

	-- balance pressure with neighbours
	currentpressure = flowlogic.balance_pressure(pos, node, currentpressure)

	-- if node is an output: run output phase
	local output = pipeworks.flowables.outputs.list[nodename]
	if output then
		currentpressure = flowlogic.run_output(
			pos,
			node,
			currentpressure,
			output.upper,
			output.lower,
			output.outputfn)
	end

	-- set the new pressure
	nodepressure.set(currentpressure)
end



flowlogic.balance_pressure = function(pos, node, currentpressure)
	-- debuglog("balance_pressure() "..node.name.." at "..pos.x.." "..pos.y.." "..pos.z)
	-- check the pressure of all nearby flowable nodes, and average it out.

	-- pressure handles to average over
	local connections = {}
	-- unconditionally include self in nodes to average over.
	-- result of averaging will be returned as new pressure for main flow logic callback
	local totalv = currentpressure
	local totalc = 1

	-- then handle neighbours, but if not a pressure node don't consider them at all
	for _, npos in ipairs(make_coords_offsets(pos, false)) do
		local nodename = minetest.get_node(npos).name
		-- for now, just check if it's in the simple table.
		-- TODO: the "can flow from" logic in flowable_node_registry.lua
		local haspressure = (pipeworks.flowables.list.simple[nodename])
		if haspressure then
			local neighbour = get_pressure_access(npos)
			--pipeworks.logger("balance_pressure @ "..formatvec(pos).." "..nodename.." "..formatvec(npos).." added to neighbour set")
			local n = neighbour.get()
			table.insert(connections, neighbour)
			totalv = totalv + n
			totalc = totalc + 1
		end
	end

	local average = totalv / totalc
	for _, target in ipairs(connections) do
		target.set(average)
	end

	return average
end



flowlogic.run_input = function(pos, node, currentpressure, inputdef)
	-- intakefn allows a given input node to define it's own intake logic.
	-- this function will calculate the maximum amount of water that can be taken in;
	-- the intakefn will be given this and is expected to return the actual absorption amount.

	local maxpressure = inputdef.maxpressure
	local intake_limit = maxpressure - currentpressure
	if intake_limit <= 0 then return currentpressure end

	local actual_intake = inputdef.intakefn(pos, intake_limit)
	--pipeworks.logger("run_input@"..formatvec(pos).." oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake)
	if actual_intake <= 0 then return currentpressure end

	local newpressure = actual_intake + currentpressure
	--debuglog("run_input() end, oldpressure "..currentpressure.." intake_limit "..intake_limit.." actual_intake "..actual_intake.." newpressure "..newpressure)
	return newpressure
end



-- flowlogic output helper implementation:
-- outputs water by trying to place water nodes nearby in the world.
-- neighbours is a list of node offsets to try placing water in.
-- this is a constructor function, returning another function which satisfies the output helper requirements.
-- note that this does *not* take rotation into account.
flowlogic.helpers.make_neighbour_output_fixed = function(neighbours)
	return function(pos, node, currentpressure)
		local taken = 0
		for _, offset in pairs(neighbours) do
			local npos = vector.add(pos, offset)
			local name = minetest.get_node(npos).name
			if (name == "air") or (name == "default:water_flowing") then
				minetest.swap_node(npos, {name="default:water_source"})
				taken = taken + 1
			end
		end
		return taken
	end
end

-- complementary function to the above when using non-finite mode:
-- removes water sources from neighbor positions when the output is "off" due to lack of pressure.
flowlogic.helpers.make_neighbour_cleanup_fixed = function(neighbours)
	return function(pos, node, currentpressure)
		-- FIXME - this would indiscriminately take blocks while under-pressure, not just one time?
		for _, offset in pairs(neighbours) do
			local npos = vector.add(pos, offset)
			local name = minetest.get_node(npos).name
			if (name == "default:water_source") then
				minetest.remove_node(pos)
			end
		end
	end
end



flowlogic.run_output = function(pos, node, currentpressure, upper, lower, outputfn)
	-- processing step for water output devices.
	-- takes care of checking a minimum pressure value and updating the resulting pressure level
	-- the outputfn is provided the current pressure and returns the pressure "taken".
	-- as an example, using this with the above spigot function,
	-- the spigot function tries to output a water source if it will fit in the world.
	local result = currentpressure
	if currentpressure > lower then
		local takenpressure = outputfn(pos, node, currentpressure)
		local newpressure = currentpressure - takenpressure
		if newpressure < 0 then newpressure = 0 end
		result = newpressure
	end
	return result
end