Minecarts: Save _railtype in staticdata
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_minecarts / init.lua
blob25e2183f79074f6822cd53a6870395ce63379e9f
1 mcl_minecarts = {}
2 mcl_minecarts.modpath = minetest.get_modpath("mcl_minecarts")
3 mcl_minecarts.speed_max = 10
5 dofile(mcl_minecarts.modpath.."/functions.lua")
6 dofile(mcl_minecarts.modpath.."/rails.lua")
8 -- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
9 local entity_mapping = {}
11 local function register_entity(entity_id, mesh, textures, drop, on_rightclick)
12 local cart = {
13 physical = false,
14 collisionbox = {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
15 visual = "mesh",
16 mesh = mesh,
17 visual_size = {x=1, y=1},
18 textures = textures,
20 on_rightclick = on_rightclick,
22 _driver = nil, -- player who sits in and controls the minecart (only for minecart!)
23 _punched = false, -- used to re-send _velocity and position
24 _velocity = {x=0, y=0, z=0}, -- only used on punch
25 _start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
26 _old_dir = {x=0, y=0, z=0},
27 _old_pos = nil,
28 _old_vel = {x=0, y=0, z=0},
29 _old_switch = 0,
30 _railtype = nil,
33 function cart:on_activate(staticdata, dtime_s)
34 local data = minetest.deserialize(staticdata)
35 if type(data) == "table" then
36 self._railtype = data._railtype
37 end
38 self.object:set_armor_groups({immortal=1})
39 end
41 function cart:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
42 local pos = self.object:getpos()
43 if not self._railtype then
44 local node = minetest.get_node(vector.floor(pos)).name
45 self._railtype = minetest.get_item_group(node, "connect_to_raillike")
46 end
48 if not puncher or not puncher:is_player() then
49 local cart_dir = mcl_minecarts:get_rail_direction(pos, {x=1, y=0, z=0}, nil, nil, self._railtype)
50 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
51 return
52 end
53 self._velocity = vector.multiply(cart_dir, 3)
54 self._old_pos = nil
55 self._punched = true
56 return
57 end
59 if puncher:get_player_control().sneak then
60 if self._driver then
61 if self._old_pos then
62 self.object:setpos(self._old_pos)
63 end
64 mcl_player.player_attached[self._driver] = nil
65 local player = minetest.get_player_by_name(self._driver)
66 if player then
67 player:set_detach()
68 end
69 end
71 -- Disable detector rail
72 local rou_pos = vector.round(pos)
73 local node = minetest.get_node(rou_pos)
74 if node.name == "mcl_minecarts:detector_rail_on" then
75 local newnode = {name="mcl_minecarts:detector_rail", param2 = node.param2}
76 minetest.swap_node(rou_pos, newnode)
77 mesecon.receptor_off(rou_pos)
78 end
80 -- Drop items and remove cart entity
81 if not minetest.settings:get_bool("creative_mode") then
82 for d=1, #drop do
83 minetest.add_item(self.object:getpos(), drop[d])
84 end
85 end
87 self.object:remove()
88 return
89 end
91 local vel = self.object:getvelocity()
92 if puncher:get_player_name() == self._driver then
93 if math.abs(vel.x + vel.z) > 7 then
94 return
95 end
96 end
98 local punch_dir = mcl_minecarts:velocity_to_dir(puncher:get_look_dir())
99 punch_dir.y = 0
100 local cart_dir = mcl_minecarts:get_rail_direction(pos, punch_dir, nil, nil, self._railtype)
101 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
102 return
105 time_from_last_punch = math.min(time_from_last_punch, tool_capabilities.full_punch_interval)
106 local f = 3 * (time_from_last_punch / tool_capabilities.full_punch_interval)
108 self._velocity = vector.multiply(cart_dir, f)
109 self._old_pos = nil
110 self._punched = true
113 function cart:on_step(dtime)
114 local vel = self.object:getvelocity()
115 local update = {}
116 if self._punched then
117 vel = vector.add(vel, self._velocity)
118 self.object:setvelocity(vel)
119 self._old_dir.y = 0
120 elseif vector.equals(vel, {x=0, y=0, z=0}) then
121 return
124 local dir, last_switch = nil, nil
125 local pos = self.object:getpos()
126 if self._old_pos and not self._punched then
127 local flo_pos = vector.floor(pos)
128 local flo_old = vector.floor(self._old_pos)
129 if vector.equals(flo_pos, flo_old) then
130 return
131 -- Prevent querying the same node over and over again
134 -- Update detector rails
135 local rou_pos = vector.round(pos)
136 local rou_old = vector.round(self._old_pos)
137 local node = minetest.get_node(rou_pos)
138 local node_old = minetest.get_node(rou_old)
140 if node.name == "mcl_minecarts:detector_rail" then
141 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
142 minetest.swap_node(rou_pos, newnode)
143 mesecon.receptor_on(rou_pos)
145 if node_old.name == "mcl_minecarts:detector_rail_on" then
146 local newnode = {name="mcl_minecarts:detector_rail", param2 = node_old.param2}
147 minetest.swap_node(rou_old, newnode)
148 mesecon.receptor_off(rou_old)
152 local ctrl, player = nil, nil
153 if self._driver then
154 player = minetest.get_player_by_name(self._driver)
155 if player then
156 ctrl = player:get_player_control()
160 -- Stop cart if velocity vector flips
161 if self._old_vel and self._old_vel.y == 0 and
162 (self._old_vel.x * vel.x < 0 or self._old_vel.z * vel.z < 0) then
163 self._old_vel = {x = 0, y = 0, z = 0}
164 self._old_pos = pos
165 self.object:setvelocity(vector.new())
166 self.object:setacceleration(vector.new())
167 return
169 self._old_vel = vector.new(vel)
171 if self._old_pos then
172 local diff = vector.subtract(self._old_pos, pos)
173 for _,v in ipairs({"x","y","z"}) do
174 if math.abs(diff[v]) > 1.1 then
175 local expected_pos = vector.add(self._old_pos, self._old_dir)
176 dir, last_switch = mcl_minecarts:get_rail_direction(pos, self._old_dir, ctrl, self._old_switch, self._railtype)
177 if vector.equals(dir, {x=0, y=0, z=0}) then
178 dir = false
179 pos = vector.new(expected_pos)
180 update.pos = true
182 break
187 if vel.y == 0 then
188 for _,v in ipairs({"x", "z"}) do
189 if vel[v] ~= 0 and math.abs(vel[v]) < 0.9 then
190 vel[v] = 0
191 update.vel = true
196 local cart_dir = mcl_minecarts:velocity_to_dir(vel)
197 local max_vel = mcl_minecarts.speed_max
198 if not dir then
199 dir, last_switch = mcl_minecarts:get_rail_direction(pos, cart_dir, ctrl, self._old_switch, self._railtype)
202 local new_acc = {x=0, y=0, z=0}
203 if vector.equals(dir, {x=0, y=0, z=0}) then
204 vel = {x=0, y=0, z=0}
205 update.vel = true
206 else
207 -- If the direction changed
208 if dir.x ~= 0 and self._old_dir.z ~= 0 then
209 vel.x = dir.x * math.abs(vel.z)
210 vel.z = 0
211 pos.z = math.floor(pos.z + 0.5)
212 update.pos = true
214 if dir.z ~= 0 and self._old_dir.x ~= 0 then
215 vel.z = dir.z * math.abs(vel.x)
216 vel.x = 0
217 pos.x = math.floor(pos.x + 0.5)
218 update.pos = true
220 -- Up, down?
221 if dir.y ~= self._old_dir.y then
222 vel.y = dir.y * math.abs(vel.x + vel.z)
223 pos = vector.round(pos)
224 update.pos = true
227 -- Slow down or speed up
228 local acc = dir.y * -1.8
230 local speed_mod = minetest.registered_nodes[minetest.get_node(pos).name]._rail_acceleration
231 if speed_mod and speed_mod ~= 0 then
232 acc = acc + speed_mod
233 else
234 acc = acc - 0.4
237 new_acc = vector.multiply(dir, acc)
240 self.object:setacceleration(new_acc)
241 self._old_pos = vector.new(pos)
242 self._old_dir = vector.new(dir)
243 self._old_switch = last_switch
245 -- Limits
246 for _,v in ipairs({"x","y","z"}) do
247 if math.abs(vel[v]) > max_vel then
248 vel[v] = mcl_minecarts:get_sign(vel[v]) * max_vel
249 new_acc[v] = 0
250 update.vel = true
254 -- Give achievement when player reached a distance of 1000 nodes from the start position
255 if self._driver and (vector.distance(self._start_pos, pos) >= 1000) then
256 awards.unlock(self._driver, "mcl:onARail")
260 if update.pos or self._punched then
261 local yaw = 0
262 if dir.x < 0 then
263 yaw = 0.5
264 elseif dir.x > 0 then
265 yaw = 1.5
266 elseif dir.z < 0 then
267 yaw = 1
269 self.object:setyaw(yaw * math.pi)
272 if self._punched then
273 self._punched = false
276 if not (update.vel or update.pos) then
277 return
281 local anim = {x=0, y=0}
282 if dir.y == -1 then
283 anim = {x=1, y=1}
284 elseif dir.y == 1 then
285 anim = {x=2, y=2}
287 self.object:set_animation(anim, 1, 0)
289 self.object:setvelocity(vel)
290 if update.pos then
291 self.object:setpos(pos)
293 update = nil
296 function cart:get_staticdata()
297 return minetest.serialize({_railtype = self._railtype})
300 minetest.register_entity(entity_id, cart)
303 -- Place a minecart at pointed_thing
304 mcl_minecarts.place_minecart = function(itemstack, pointed_thing)
305 if not pointed_thing.type == "node" then
306 return
309 local railpos, node
310 if mcl_minecarts:is_rail(pointed_thing.under) then
311 railpos = pointed_thing.under
312 node = minetest.get_node(pointed_thing.under)
313 elseif mcl_minecarts:is_rail(pointed_thing.above) then
314 railpos = pointed_thing.above
315 node = minetest.get_node(pointed_thing.above)
316 else
317 return
320 -- Activate detector rail
321 if node.name == "mcl_minecarts:detector_rail" then
322 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
323 minetest.swap_node(railpos, newnode)
324 mesecon.receptor_on(railpos)
327 local entity_id = entity_mapping[itemstack:get_name()]
328 local cart = minetest.add_entity(railpos, entity_id)
329 local railtype = minetest.get_item_group(node.name, "connect_to_raillike")
330 local le = cart:get_luaentity()
331 if le ~= nil then
332 le._railtype = railtype
334 local cart_dir = mcl_minecarts:get_rail_direction(railpos, {x=1, y=0, z=0}, nil, nil, railtype)
335 cart:setyaw(minetest.dir_to_yaw(cart_dir))
337 if not minetest.settings:get_bool("creative_mode") then
338 itemstack:take_item()
340 return itemstack
344 local register_craftitem = function(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
345 entity_mapping[itemstring] = entity_id
347 local groups = { minecart = 1, transport = 1 }
348 if creative == false then
349 groups.not_in_creative_inventory = 1
351 local def = {
352 stack_max = 1,
353 on_place = function(itemstack, placer, pointed_thing)
354 if not pointed_thing.type == "node" then
355 return
358 -- Call on_rightclick if the pointed node defines it
359 local node = minetest.get_node(pointed_thing.under)
360 if placer and not placer:get_player_control().sneak then
361 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
362 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
366 return mcl_minecarts.place_minecart(itemstack, pointed_thing)
367 end,
368 _on_dispense = function(stack, pos, droppos, dropnode, dropdir)
369 -- Place minecart as entity on rail. If there's no rail, just drop it.
370 local placed
371 if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
372 -- FIXME: This places minecarts even if the spot is already occupied
373 local pointed_thing = { under = droppos, above = { x=droppos.x, y=droppos.y+1, z=droppos.z } }
374 placed = mcl_minecarts.place_minecart(stack, pointed_thing)
376 if placed == nil then
377 -- Drop item
378 minetest.add_item(droppos, stack)
380 end,
381 groups = groups,
383 def.description = description
384 def._doc_items_longdesc = longdesc
385 def._doc_items_usagehelp = usagehelp
386 def.inventory_image = icon
387 def.wield_image = icon
388 minetest.register_craftitem(itemstring, def)
391 --[[
392 Register a minecart
393 * itemstring: Itemstring of minecart item
394 * entity_id: ID of minecart entity
395 * description: Item name / description
396 * longdesc: Long help text
397 * usagehelp: Usage help text
398 * mesh: Minecart mesh
399 * textures: Minecart textures table
400 * icon: Item icon
401 * drop: Dropped items after destroying minecart
402 * on_rightclick: Called after rightclick
403 * on_activate_by_rail: Called when above activator rail
404 * creative: If false, don't show in Creative Inventory
406 local function register_minecart(itemstring, entity_id, description, longdesc, usagehelp, mesh, textures, icon, drop, on_rightclick, on_activate_by_rail, creative)
407 register_entity(entity_id, mesh, textures, drop, on_rightclick)
408 register_craftitem(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
409 if minetest.get_modpath("doc_identifier") ~= nil then
410 doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
414 -- Minecart
415 register_minecart(
416 "mcl_minecarts:minecart",
417 "mcl_minecarts:minecart",
418 "Minecart",
419 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
420 "Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type.",
421 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
422 "To obtain the minecart, punch it while holding down the sneak key.",
423 "mcl_minecarts_minecart.b3d",
424 {"mcl_minecarts_minecart.png"},
425 "mcl_minecarts_minecart_normal.png",
426 {"mcl_minecarts:minecart"},
427 function(self, clicker)
428 local name = clicker:get_player_name()
429 if not clicker or not clicker:is_player() then
430 return
432 local player_name = clicker:get_player_name()
433 if self._driver and player_name == self._driver then
434 self._driver = nil
435 self._start_pos = nil
436 clicker:set_detach()
437 local player = minetest.get_player_by_name(name)
438 player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
439 elseif not self._driver then
440 self._driver = player_name
441 self._start_pos = self.object:getpos()
442 mcl_player.player_attached[player_name] = true
443 clicker:set_attach(self.object, "", {x=0, y=8.25, z=-2}, {x=0, y=0, z=0})
444 mcl_player.player_attached[name] = true
445 minetest.after(0.2, function(name)
446 local player = minetest.get_player_by_name(name)
447 if player then
448 mcl_player.player_set_animation(player, "sit" , 30)
449 player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
451 end, name)
456 -- Minecart with Chest
457 register_minecart(
458 "mcl_minecarts:chest_minecart",
459 "mcl_minecarts:chest_minecart",
460 "Minecart with Chest",
461 nil, nil,
462 "mcl_minecarts_minecart_chest.b3d",
463 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
464 "mcl_minecarts_minecart_chest.png",
465 {"mcl_minecarts:minecart", "mcl_chests:chest"},
466 nil, nil, false)
468 -- Minecart with Furnace
469 register_minecart(
470 "mcl_minecarts:furnace_minecart",
471 "mcl_minecarts:furnace_minecart",
472 "Minecart with Furnace",
473 nil, nil,
474 "mcl_minecarts_minecart_block.b3d",
476 "default_furnace_top.png",
477 "default_furnace_top.png",
478 "default_furnace_front.png",
479 "default_furnace_side.png",
480 "default_furnace_side.png",
481 "default_furnace_side.png",
482 "mcl_minecarts_minecart.png",
484 "mcl_minecarts_minecart_furnace.png",
485 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
486 -- Feed furnace with coal
487 function(self, clicker)
488 if not clicker or not clicker:is_player() then
489 return
491 if not self._fueltime then
492 self._fueltime = 0
494 local held = clicker:get_wielded_item()
495 if minetest.get_item_group(held:get_name(), "coal") == 1 then
496 self._fueltime = self._fueltime + 180
498 if not minetest.settings:get_bool("creative_mode") then
499 held:take_item()
500 local index = clicker:get_wielded_index()
501 local inv = clicker:get_inventory()
502 inv:set_stack("main", index, held)
505 -- DEBUG
506 minetest.chat_send_player(clicker:get_player_name(), "Fuel: " .. tostring(self._fueltime))
508 end, nil, false
511 -- Minecart with Command Block
512 register_minecart(
513 "mcl_minecarts:command_block_minecart",
514 "mcl_minecarts:command_block_minecart",
515 "Minecart with Command Block",
516 nil, nil,
517 "mcl_minecarts_minecart_block.b3d",
519 "jeija_commandblock_off.png^[verticalframe:2:0",
520 "jeija_commandblock_off.png^[verticalframe:2:0",
521 "jeija_commandblock_off.png^[verticalframe:2:0",
522 "jeija_commandblock_off.png^[verticalframe:2:0",
523 "jeija_commandblock_off.png^[verticalframe:2:0",
524 "jeija_commandblock_off.png^[verticalframe:2:0",
525 "mcl_minecarts_minecart.png",
527 "mcl_minecarts_minecart_command_block.png",
528 {"mcl_minecarts:minecart"},
529 nil, nil, false
532 -- Minecart with Hopper
533 register_minecart(
534 "mcl_minecarts:hopper_minecart",
535 "mcl_minecarts:hopper_minecart",
536 "Minecart with Hopper",
537 nil, nil,
538 "mcl_minecarts_minecart_hopper.b3d",
540 "mcl_hoppers_hopper_inside.png",
541 "mcl_minecarts_minecart.png",
542 "mcl_hoppers_hopper_outside.png",
543 "mcl_hoppers_hopper_top.png",
545 "mcl_minecarts_minecart_hopper.png",
546 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
547 nil, nil, false
550 -- Minecart with TNT
551 register_minecart(
552 "mcl_minecarts:tnt_minecart",
553 "mcl_minecarts:tnt_minecart",
554 "Minecart with TNT",
555 nil, nil,
556 "mcl_minecarts_minecart_block.b3d",
558 "default_tnt_top.png",
559 "default_tnt_bottom.png",
560 "default_tnt_side.png",
561 "default_tnt_side.png",
562 "default_tnt_side.png",
563 "default_tnt_side.png",
564 "mcl_minecarts_minecart.png",
566 "mcl_minecarts_minecart_tnt.png",
567 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
568 nil, nil, false
572 minetest.register_craft({
573 output = "mcl_minecarts:minecart",
574 recipe = {
575 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
576 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
580 -- TODO: Re-enable crafting of special minecarts when they have been implemented
581 if false then
582 minetest.register_craft({
583 output = "mcl_minecarts:hopper_minecart",
584 recipe = {
585 {"mcl_hoppers:hopper"},
586 {"mcl_minecarts:minecart"},
590 minetest.register_craft({
591 output = "mcl_minecarts:chest_minecart",
592 recipe = {
593 {"mcl_chests:chest"},
594 {"mcl_minecarts:minecart"},
598 minetest.register_craft({
599 output = "mcl_minecarts:tnt_minecart",
600 recipe = {
601 {"mcl_tnt:tnt"},
602 {"mcl_minecarts:minecart"},
606 minetest.register_craft({
607 output = "mcl_minecarts:furnace_minecart",
608 recipe = {
609 {"mcl_furnaces:furnace"},
610 {"mcl_minecarts:minecart"},