summaryrefslogtreecommitdiff
path: root/railcart/railcart.lua
blob: 8f896490bfe2e73bffa066e96c36aa5b40c430d0 (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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
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,
	pos = nil,
	target = nil,
	prev = nil,
	accel = nil,
	inv = 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_entity(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
		local inv = {}
		if ref.inv then
			local list = ref.inv:get_list("main")
			for i, stack in ipairs(list) do
			  inv[i] = stack:to_string()
			end
		end
		ref.inv = inv
		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:create_detached_inventory(id)
	local inv = minetest.create_detached_inventory("railcart_"..tostring(id), {
		on_put = function(inv, listname, index, stack, player)
			railcart:save()
		end,
		on_take = function(inv, listname, index, stack, player)
			railcart:save()
		end,
		allow_put = function(inv, listname, index, stack, player)
			return 1
		end,
		allow_take = function(inv, listname, index, stack, player)
			return stack:get_count()
		end,
		allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
			return count
		end,
	})
	inv:set_size("main", 32)
	return inv
end

function railcart:remove_cart(id)
	for i, cart in pairs(railcart.allcarts) do
		if cart.id == id then
			railcart.allcarts[i] = nil
			railcart:save()
			break
		end
	end
end

function railcart:get_rail_direction(pos)
	local target = nil
	local cons = railtrack:get_connections(pos)
	local ymax = pos.y
	for _, con in pairs(cons) do
		if con.y >= ymax then
			ymax = con.y
			target = con
		end
	end
	if target then
		return railtrack:get_direction(target, pos)
	end
	return {x=0, y=0, z=0}
end

function railcart:get_new_id()
	local id = 0
	for _, cart in pairs(railcart.allcarts) do
		if cart.id > id then
			id = cart.id
		end
	end
	return id + 1
end

function railcart:get_cart_entity(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_carts_in_radius(pos, rad)
	local carts = {}
	for _, cart in pairs(railcart.allcarts) do
		local px = pos.x - cart.pos.x
		local py = pos.y - cart.pos.y
		local pz = pos.z - cart.pos.z
		if (px * px) + (py * py) + (pz * pz) <= rad * rad then
			table.insert(carts, cart)
		end
	end
	return carts
end

function railcart:get_cart_in_sight(p1, p2)
	local ref = nil
	local dist = railtrack:get_distance(p1, p2) + 1
	local dir = railtrack:get_direction(p2, p1)
	local carts = railcart:get_carts_in_radius(p1, dist)
	for _, cart in pairs(carts) do
		if not vector.equals(p1, cart.pos) then
			local dc = railtrack:get_direction(cart.pos, p1)
			if vector.equals(dc, dir) then
				local d = railtrack:get_distance(p1, cart.pos)
				if d < dist then
					dist = d
					ref = cart
				end
			end
		end
	end
	return 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 cart.target then
		if vector.equals(cart.target, cart.pos) then
			cart.dir = railcart:get_rail_direction(cart.pos)
		else
			cart.dir = railtrack:get_direction(cart.target, cart.pos)
		end
	else
		speed = 0
	end
	if speed > RAILCART_SPEED_MIN then
		local blocked = false
		local cis = railcart:get_cart_in_sight(cart.pos, cart.target)
		if cis then
			if railcart:velocity_to_speed(cis.vel) == 0 then
				cart.target = vector.subtract(cis.pos, cart.dir)
				blocked = true
			end
		end
		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
			if object then
				dist = math.max(dist - RAILCART_SNAP_DISTANCE, 0)
			end
			local dt = railcart:get_delta_time(speed, accel, dist)
			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
			if blocked and vector.equals(cart.target, cart.prev) then
				cart.vel = {x=0, y=0, z=0}
				cart.acc = {x=0, y=0, z=0}
			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
		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