summaryrefslogtreecommitdiff
path: root/technic/helpers.lua
blob: 5780f274e0649f1e83cae74c11b8cbf1fd5ae375 (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
local digit_sep_esc
do
	local sep = technic.config:get("digit_separator")
	sep = tonumber(sep) and string.char(sep) or sep or " "
	-- Escape for gsub
	for magic in ("().%+-*?[^$"):gmatch(".") do
		if sep == magic then
			sep = "%"..sep
		end
	end
	digit_sep_esc = sep
end


function technic.pretty_num(num)
	local str, k = tostring(num), nil
	repeat
		str, k = str:gsub("^(-?%d+)(%d%d%d)", "%1"..digit_sep_esc.."%2")
	until k == 0
	return str
end


--- Same as minetest.swap_node, but only changes name
-- and doesn't re-set if already set.
function technic.swap_node(pos, name)
	local node = minetest.get_node(pos)
	if node.name ~= name then
		node.name = name
		minetest.swap_node(pos, node)
	end
end


--- Fully charge RE chargeable item.
-- Must be defined early to reference in item definitions.
function technic.refill_RE_charge(stack)
	local max_charge = technic.power_tools[stack:get_name()]
	if not max_charge then return stack end
	technic.set_RE_wear(stack, max_charge, max_charge)
	local meta = minetest.deserialize(stack:get_metadata()) or {}
	meta.charge = max_charge
	stack:set_metadata(minetest.serialize(meta))
	return stack
end


-- If the node is loaded, returns it.  If it isn't loaded, load it and return nil.
function technic.get_or_load_node(pos)
	local node = minetest.get_node_or_nil(pos)
	if node then return node end
	local vm = VoxelManip()
	local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
	return nil
end


technic.tube_inject_item = pipeworks.tube_inject_item or function(pos, start_pos, velocity, item)
	local tubed = pipeworks.tube_item(vector.new(pos), item)
	tubed:get_luaentity().start_pos = vector.new(start_pos)
	tubed:setvelocity(velocity)
	tubed:setacceleration(vector.new(0, 0, 0))
end


--- Iterates over the node positions along the specified ray.
-- The returned positions will not include the starting position.
function technic.trace_node_ray(pos, dir, range)
	local x_step = dir.x > 0 and 1 or -1
	local y_step = dir.y > 0 and 1 or -1
	local z_step = dir.z > 0 and 1 or -1

	local i = 1
	return function(p)
		-- Approximation of where we should be if we weren't rounding
		-- to nodes.  This moves forward a bit faster then we do.
		-- A correction is done below.
		local real_x = pos.x + (dir.x * i)
		local real_y = pos.y + (dir.y * i)
		local real_z = pos.z + (dir.z * i)

		-- How far off we've gotten from where we should be.
		local dx = math.abs(real_x - p.x)
		local dy = math.abs(real_y - p.y)
		local dz = math.abs(real_z - p.z)

		-- If the real position moves ahead too fast, stop it so we
		-- can catch up.  If it gets too far ahead it will smooth
		-- out our movement too much and we won't turn fast enough.
		if dx + dy + dz < 2 then
			i = i + 1
		end

		-- Step in whichever direction we're most off course in.
		if dx > dy then
			if dx > dz then
				p.x = p.x + x_step
			else
				p.z = p.z + z_step
			end
		elseif dy > dz then
			p.y = p.y + y_step
		else
			p.z = p.z + z_step
		end
		if vector.distance(pos, p) > range then
			return nil
		end
		return p
	end, vector.round(pos)
end


--- Like trace_node_ray, but includes extra positions close to the ray.
function technic.trace_node_ray_fat(pos, dir, range)
	local x_step = dir.x > 0 and 1 or -1
	local y_step = dir.y > 0 and 1 or -1
	local z_step = dir.z > 0 and 1 or -1

	local next_poses = {}

	local i = 1
	return function(p)
		local ni, np = next(next_poses)
		if np then
			next_poses[ni] = nil
			return np
		end

		-- Approximation of where we should be if we weren't rounding
		-- to nodes.  This moves forward a bit faster then we do.
		-- A correction is done below.
		local real_x = pos.x + (dir.x * i)
		local real_y = pos.y + (dir.y * i)
		local real_z = pos.z + (dir.z * i)

		-- How far off we've gotten from where we should be.
		local dx = math.abs(real_x - p.x)
		local dy = math.abs(real_y - p.y)
		local dz = math.abs(real_z - p.z)

		-- If the real position moves ahead too fast, stop it so we
		-- can catch up.  If it gets too far ahead it will smooth
		-- out our movement too much and we won't turn fast enough.
		if dx + dy + dz < 2 then
			i = i + 1
		end

		-- Step in whichever direction we're most off course in.
		local sx, sy, sz  -- Whether we've already stepped along each axis
		if dx > dy then
			if dx > dz then
				sx = true
				p.x = p.x + x_step
			else
				sz = true
				p.z = p.z + z_step
			end
		elseif dy > dz then
			sy = true
			p.y = p.y + y_step
		else
			sz = true
			p.z = p.z + z_step
		end

		if vector.distance(pos, p) > range then
			return nil
		end

		-- Add other positions that we're significantly off on.
		-- We can just use fixed integer keys here because the
		-- table will be completely cleared before we reach this
		-- code block again.
		local dlen = math.sqrt(dx*dx + dy*dy + dz*dz)
		-- Normalized axis deltas
		local dxn, dyn, dzn = dx / dlen, dy / dlen, dz / dlen
		if not sx and dxn > 0.5 then
			next_poses[1] = vector.new(p.x + x_step, p.y, p.z)
		end
		if not sy and dyn > 0.5 then
			next_poses[2] = vector.new(p.x, p.y + y_step, p.z)
		end
		if not sz and dzn > 0.5 then
			next_poses[3] = vector.new(p.x, p.y, p.z + z_step)
		end

		return p
	end, vector.round(pos)
end