summaryrefslogtreecommitdiff
path: root/railcart/railcart.lua
blob: bdd9a052898dae360616259d146736168af6998e (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
RAILCART_ENTITY_UPDATE_TIME = 1
RAILCART_OBJECT_UPDATE_TIME = 5
RAILCART_OBJECT_SAVE_TIME = 10
RAILCART_RELOAD_DISTANCE = 32
RAILCART_SNAP_DISTANCE = 0.5
RAILCART_SPEED_PUSH = 5
RAILCART_SPEED_MIN = 0.1
RAILCART_SPEED_MAX = 20

railcart = {
	timer = 0,
	allcarts = {},
}

railcart.cart = {
	id = nil,
	entity = {},
	pos = nil,
	target = nil,
	prev = nil,
	accel = nil,
	dir = {x=0, y=0, z=0},
	vel = {x=0, y=0, z=0},
	acc = {x=0, y=0, z=0},
	timer = 0,
}

function railcart.cart:new(obj)
	obj = obj or {}
	setmetatable(obj, self)
	self.__index = self
	return obj
end

function railcart.cart:is_loaded()
	for _, player in pairs(minetest.get_connected_players()) do
		local pos = player:getpos()
		if pos then
			local dist = railtrack:get_distance(pos, self.pos)
			if dist <= RAILCART_RELOAD_DISTANCE then
				return true
			end
		end
	end
	return false
end

function railcart.cart:on_step(dtime)
	self.timer = self.timer - dtime
	if self.timer > 0 then
		return
	end
	self.timer = RAILCART_OBJECT_UPDATE_TIME
	local entity = railcart:get_cart_ref(self.id)
	if entity.object then
		return
	end
	if self:is_loaded() then
		local object = minetest.add_entity(self.pos, "railcart:cart_entity")
		if object then
			entity = object:get_luaentity() or {}
			entity.cart = self
			object:setvelocity(self.vel)
			object:setacceleration(self.acc)
		end
	else
		self.timer = railcart:update(self, self.timer)
	end
end

function railcart:save()
	local carts = {}
	for id, cart in pairs(railcart.allcarts) do
		local ref = {}
		for k, v in pairs(cart) do
			ref[k] = v
		end
		ref.entity = nil
		table.insert(carts, ref)
	end
	local output = io.open(minetest.get_worldpath().."/railcart.txt",'w')
	if output then
		output:write(minetest.serialize(carts))
		io.close(output)
	end
end

function railcart:get_cart_ref(id)
	local cart_ref = {}
	for _, ref in pairs(minetest.luaentities) do
		if ref.cart then
			if ref.cart.id == id then
				cart_ref = ref
				break
			end
		end
	end
	return cart_ref
end

function railcart:get_delta_time(vel, acc, dist)
	if vel > 0 then
		if acc == 0 then
			return dist / vel
		end
		local r = math.sqrt(vel * vel + 2 * acc * dist)
		if r > 0 then
			return (-vel + r) / acc
		end
	end
	return 9999 --INF
end

function railcart:velocity_to_dir(v)
	if math.abs(v.x) > math.abs(v.z) then
		return {x=railtrack:get_sign(v.x), y=railtrack:get_sign(v.y), z=0}
	else
		return {x=0, y=railtrack:get_sign(v.y), z=railtrack:get_sign(v.z)}
	end
end

function railcart:velocity_to_speed(vel)
	local speed = math.max(math.abs(vel.x), math.abs(vel.z))
	if speed < RAILCART_SPEED_MIN then
		speed = 0
	elseif speed > RAILCART_SPEED_MAX then
		speed = RAILCART_SPEED_MAX
	end
	return speed
end

function railcart:get_target(pos, vel)
	local meta = minetest.get_meta(vector.round(pos))
	local dir = self:velocity_to_dir(vel)
	local targets = {}
	local rots = RAILTRACK_ROTATIONS
	local contype = meta:get_string("contype") or ""
	local s_junc = meta:get_string("junctions") or ""
	local s_cons = meta:get_string("connections") or ""
	local s_rots = meta:get_string("rotations") or ""
	if contype == "section" then
		local junctions = minetest.deserialize(s_junc) or {}
		for _, p in pairs(junctions) do
			table.insert(targets, p)
		end
	else
		local cons = minetest.deserialize(s_cons) or {}
		for _, p in pairs(cons) do
			table.insert(targets, p)
		end
		if s_rots ~= "" then
			local fwd = false
			for _, p in pairs(cons) do
				if vector.equals(vector.add(pos, dir), p) then
					fwd = true
				end
			end
			if fwd == true or #cons == 1 then
				rots = s_rots
			end
		end
	end
	local rotations = railtrack:get_rotations(rots, dir)
	for _, r in ipairs(rotations) do
		for _, t in pairs(targets) do
			local d = railtrack:get_direction(t, pos)
			if r.x == d.x and r.z == d.z then
				return t
			end
		end
	end
end

function railcart:update(cart, time, object)
	if object then
		cart.pos = object:getpos()
		cart.vel = object:getvelocity()
	end
	if not cart.target then
		cart.pos = vector.new(cart.prev)
		cart.target = railcart:get_target(cart.pos, cart.vel)
		if object then
			object:moveto(cart.pos)
		end
	end
	local speed = railcart:velocity_to_speed(cart.vel)
	if not cart.target then
		speed = 0
	end
	if speed > RAILCART_SPEED_MIN then
		cart.dir = railtrack:get_direction(cart.target, cart.pos)
		local d1 = railtrack:get_distance(cart.prev, cart.target)
		local d2 = railtrack:get_distance(cart.prev, cart.pos)
		local dist = d1 - d2
		if dist > RAILCART_SNAP_DISTANCE then
			local accel = RAILTRACK_ACCEL_FLAT
			if cart.dir.y == -1 then
				accel = RAILTRACK_ACCEL_DOWN
			elseif cart.dir.y == 1 then
				accel = RAILTRACK_ACCEL_UP
			end
			accel = cart.accel or accel
			local d = dist - RAILCART_SNAP_DISTANCE
			local dt = railcart:get_delta_time(speed, accel, d)
			if dt < time then
				time = dt
			end
			local dp = speed * time + 0.5 * accel * time * time
			local vf = speed + accel * time
			if object then
				if vf <= 0 then
					speed = 0
					accel = 0
				end
				cart.vel = vector.multiply(cart.dir, speed)
				cart.acc = vector.multiply(cart.dir, accel)
			elseif dp > 0 then
				cart.vel = vector.multiply(cart.dir, vf)
				cart.pos = vector.add(cart.pos, vector.multiply(cart.dir, dp))
			end
		else
			cart.pos = vector.new(cart.target)
			cart.prev = vector.new(cart.target)
			cart.accel = railtrack:get_acceleration(cart.target)
			cart.target = nil
			return 0
		end
	else
		cart.vel = {x=0, y=0, z=0}
		cart.acc = {x=0, y=0, z=0}
	end
	if object then
		if cart.dir.y == -1 then
			object:set_animation({x=1, y=1}, 1, 0)
		elseif cart.dir.y == 1 then
			object:set_animation({x=2, y=2}, 1, 0)
		else
			object:set_animation({x=0, y=0}, 1, 0)
		end
		if cart.dir.x < 0 then
			object:setyaw(math.pi / 2)
		elseif cart.dir.x > 0 then
			object:setyaw(3 * math.pi / 2)
		elseif cart.dir.z < 0 then
			object:setyaw(math.pi)
		elseif cart.dir.z > 0 then
			object:setyaw(0)
		end
		object:setvelocity(cart.vel)
		object:setacceleration(cart.acc)
	end
	return time
end