Drop minecart as item if its not on a rail anymore
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_minecarts / init.lua
blobcdc140f35bf25170c4b1e9221c6f023e0d896207
1 mcl_minecarts = {}
2 mcl_minecarts.modpath = minetest.get_modpath("mcl_minecarts")
3 mcl_minecarts.speed_max = 10
4 mcl_minecarts.check_float_time = 10
6 dofile(mcl_minecarts.modpath.."/functions.lua")
7 dofile(mcl_minecarts.modpath.."/rails.lua")
9 -- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
10 local entity_mapping = {}
12 local function register_entity(entity_id, mesh, textures, drop, on_rightclick)
13 local cart = {
14 physical = false,
15 collisionbox = {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
16 visual = "mesh",
17 mesh = mesh,
18 visual_size = {x=1, y=1},
19 textures = textures,
21 on_rightclick = on_rightclick,
23 _driver = nil, -- player who sits in and controls the minecart (only for minecart!)
24 _punched = false, -- used to re-send _velocity and position
25 _velocity = {x=0, y=0, z=0}, -- only used on punch
26 _start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
27 _last_float_check = nil, -- timestamp of last time the cart was checked to be still on a rail
28 _old_dir = {x=0, y=0, z=0},
29 _old_pos = nil,
30 _old_vel = {x=0, y=0, z=0},
31 _old_switch = 0,
32 _railtype = nil,
35 function cart:on_activate(staticdata, dtime_s)
36 local data = minetest.deserialize(staticdata)
37 if type(data) == "table" then
38 self._railtype = data._railtype
39 end
40 self.object:set_armor_groups({immortal=1})
41 end
43 function cart:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
44 local pos = self.object:getpos()
45 if not self._railtype then
46 local node = minetest.get_node(vector.floor(pos)).name
47 self._railtype = minetest.get_item_group(node, "connect_to_raillike")
48 end
50 if not puncher or not puncher:is_player() then
51 local cart_dir = mcl_minecarts:get_rail_direction(pos, {x=1, y=0, z=0}, nil, nil, self._railtype)
52 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
53 return
54 end
55 self._velocity = vector.multiply(cart_dir, 3)
56 self._old_pos = nil
57 self._punched = true
58 return
59 end
61 if puncher:get_player_control().sneak then
62 if self._driver then
63 if self._old_pos then
64 self.object:setpos(self._old_pos)
65 end
66 mcl_player.player_attached[self._driver] = nil
67 local player = minetest.get_player_by_name(self._driver)
68 if player then
69 player:set_detach()
70 player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
71 end
72 end
74 -- Disable detector rail
75 local rou_pos = vector.round(pos)
76 local node = minetest.get_node(rou_pos)
77 if node.name == "mcl_minecarts:detector_rail_on" then
78 local newnode = {name="mcl_minecarts:detector_rail", param2 = node.param2}
79 minetest.swap_node(rou_pos, newnode)
80 mesecon.receptor_off(rou_pos)
81 end
83 -- Drop items and remove cart entity
84 if not minetest.settings:get_bool("creative_mode") then
85 for d=1, #drop do
86 minetest.add_item(self.object:getpos(), drop[d])
87 end
88 end
90 self.object:remove()
91 return
92 end
94 local vel = self.object:getvelocity()
95 if puncher:get_player_name() == self._driver then
96 if math.abs(vel.x + vel.z) > 7 then
97 return
98 end
99 end
101 local punch_dir = mcl_minecarts:velocity_to_dir(puncher:get_look_dir())
102 punch_dir.y = 0
103 local cart_dir = mcl_minecarts:get_rail_direction(pos, punch_dir, nil, nil, self._railtype)
104 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
105 return
108 time_from_last_punch = math.min(time_from_last_punch, tool_capabilities.full_punch_interval)
109 local f = 3 * (time_from_last_punch / tool_capabilities.full_punch_interval)
111 self._velocity = vector.multiply(cart_dir, f)
112 self._old_pos = nil
113 self._punched = true
116 function cart:on_step(dtime)
117 local vel = self.object:getvelocity()
118 local update = {}
119 if self._last_float_check == nil then
120 self._last_float_check = 0
121 else
122 self._last_float_check = self._last_float_check + dtime
124 local pos, rou_pos, node
125 -- Drop minecart if it isn't on a rail anymore
126 if self._last_float_check >= mcl_minecarts.check_float_time then
127 pos = self.object:getpos()
128 rou_pos = vector.round(pos)
129 node = minetest.get_node(rou_pos)
130 local g = minetest.get_item_group(node.name, "connect_to_raillike")
131 if g ~= self._railtype and self._railtype ~= nil then
132 -- Detach driver
133 if self._driver then
134 if self._old_pos then
135 self.object:setpos(self._old_pos)
137 mcl_player.player_attached[self._driver] = nil
138 local player = minetest.get_player_by_name(self._driver)
139 if player then
140 player:set_detach()
141 player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
145 -- Drop items and remove cart entity
146 if not minetest.settings:get_bool("creative_mode") then
147 for d=1, #drop do
148 minetest.add_item(self.object:getpos(), drop[d])
152 self.object:remove()
153 return
155 self._last_float_check = 0
158 if self._punched then
159 vel = vector.add(vel, self._velocity)
160 self.object:setvelocity(vel)
161 self._old_dir.y = 0
162 elseif vector.equals(vel, {x=0, y=0, z=0}) then
163 return
166 local dir, last_switch = nil, nil
167 if not pos then
168 pos = self.object:getpos()
170 if self._old_pos and not self._punched then
171 local flo_pos = vector.floor(pos)
172 local flo_old = vector.floor(self._old_pos)
173 if vector.equals(flo_pos, flo_old) then
174 return
175 -- Prevent querying the same node over and over again
178 if not rou_pos then
179 rou_pos = vector.round(pos)
181 rou_old = vector.round(self._old_pos)
182 if not node then
183 node = minetest.get_node(rou_pos)
185 local node_old = minetest.get_node(rou_old)
187 -- Update detector rails
188 if node.name == "mcl_minecarts:detector_rail" then
189 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
190 minetest.swap_node(rou_pos, newnode)
191 mesecon.receptor_on(rou_pos)
193 if node_old.name == "mcl_minecarts:detector_rail_on" then
194 local newnode = {name="mcl_minecarts:detector_rail", param2 = node_old.param2}
195 minetest.swap_node(rou_old, newnode)
196 mesecon.receptor_off(rou_old)
200 local ctrl, player = nil, nil
201 if self._driver then
202 player = minetest.get_player_by_name(self._driver)
203 if player then
204 ctrl = player:get_player_control()
208 -- Stop cart if velocity vector flips
209 if self._old_vel and self._old_vel.y == 0 and
210 (self._old_vel.x * vel.x < 0 or self._old_vel.z * vel.z < 0) then
211 self._old_vel = {x = 0, y = 0, z = 0}
212 self._old_pos = pos
213 self.object:setvelocity(vector.new())
214 self.object:setacceleration(vector.new())
215 return
217 self._old_vel = vector.new(vel)
219 if self._old_pos then
220 local diff = vector.subtract(self._old_pos, pos)
221 for _,v in ipairs({"x","y","z"}) do
222 if math.abs(diff[v]) > 1.1 then
223 local expected_pos = vector.add(self._old_pos, self._old_dir)
224 dir, last_switch = mcl_minecarts:get_rail_direction(pos, self._old_dir, ctrl, self._old_switch, self._railtype)
225 if vector.equals(dir, {x=0, y=0, z=0}) then
226 dir = false
227 pos = vector.new(expected_pos)
228 update.pos = true
230 break
235 if vel.y == 0 then
236 for _,v in ipairs({"x", "z"}) do
237 if vel[v] ~= 0 and math.abs(vel[v]) < 0.9 then
238 vel[v] = 0
239 update.vel = true
244 local cart_dir = mcl_minecarts:velocity_to_dir(vel)
245 local max_vel = mcl_minecarts.speed_max
246 if not dir then
247 dir, last_switch = mcl_minecarts:get_rail_direction(pos, cart_dir, ctrl, self._old_switch, self._railtype)
250 local new_acc = {x=0, y=0, z=0}
251 if vector.equals(dir, {x=0, y=0, z=0}) then
252 vel = {x=0, y=0, z=0}
253 update.vel = true
254 else
255 -- If the direction changed
256 if dir.x ~= 0 and self._old_dir.z ~= 0 then
257 vel.x = dir.x * math.abs(vel.z)
258 vel.z = 0
259 pos.z = math.floor(pos.z + 0.5)
260 update.pos = true
262 if dir.z ~= 0 and self._old_dir.x ~= 0 then
263 vel.z = dir.z * math.abs(vel.x)
264 vel.x = 0
265 pos.x = math.floor(pos.x + 0.5)
266 update.pos = true
268 -- Up, down?
269 if dir.y ~= self._old_dir.y then
270 vel.y = dir.y * math.abs(vel.x + vel.z)
271 pos = vector.round(pos)
272 update.pos = true
275 -- Slow down or speed up
276 local acc = dir.y * -1.8
278 local speed_mod = minetest.registered_nodes[minetest.get_node(pos).name]._rail_acceleration
279 if speed_mod and speed_mod ~= 0 then
280 acc = acc + speed_mod
281 else
282 acc = acc - 0.4
285 new_acc = vector.multiply(dir, acc)
288 self.object:setacceleration(new_acc)
289 self._old_pos = vector.new(pos)
290 self._old_dir = vector.new(dir)
291 self._old_switch = last_switch
293 -- Limits
294 for _,v in ipairs({"x","y","z"}) do
295 if math.abs(vel[v]) > max_vel then
296 vel[v] = mcl_minecarts:get_sign(vel[v]) * max_vel
297 new_acc[v] = 0
298 update.vel = true
302 -- Give achievement when player reached a distance of 1000 nodes from the start position
303 if self._driver and (vector.distance(self._start_pos, pos) >= 1000) then
304 awards.unlock(self._driver, "mcl:onARail")
308 if update.pos or self._punched then
309 local yaw = 0
310 if dir.x < 0 then
311 yaw = 0.5
312 elseif dir.x > 0 then
313 yaw = 1.5
314 elseif dir.z < 0 then
315 yaw = 1
317 self.object:setyaw(yaw * math.pi)
320 if self._punched then
321 self._punched = false
324 if not (update.vel or update.pos) then
325 return
329 local anim = {x=0, y=0}
330 if dir.y == -1 then
331 anim = {x=1, y=1}
332 elseif dir.y == 1 then
333 anim = {x=2, y=2}
335 self.object:set_animation(anim, 1, 0)
337 self.object:setvelocity(vel)
338 if update.pos then
339 self.object:setpos(pos)
341 update = nil
344 function cart:get_staticdata()
345 return minetest.serialize({_railtype = self._railtype})
348 minetest.register_entity(entity_id, cart)
351 -- Place a minecart at pointed_thing
352 mcl_minecarts.place_minecart = function(itemstack, pointed_thing)
353 if not pointed_thing.type == "node" then
354 return
357 local railpos, node
358 if mcl_minecarts:is_rail(pointed_thing.under) then
359 railpos = pointed_thing.under
360 node = minetest.get_node(pointed_thing.under)
361 elseif mcl_minecarts:is_rail(pointed_thing.above) then
362 railpos = pointed_thing.above
363 node = minetest.get_node(pointed_thing.above)
364 else
365 return
368 -- Activate detector rail
369 if node.name == "mcl_minecarts:detector_rail" then
370 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
371 minetest.swap_node(railpos, newnode)
372 mesecon.receptor_on(railpos)
375 local entity_id = entity_mapping[itemstack:get_name()]
376 local cart = minetest.add_entity(railpos, entity_id)
377 local railtype = minetest.get_item_group(node.name, "connect_to_raillike")
378 local le = cart:get_luaentity()
379 if le ~= nil then
380 le._railtype = railtype
382 local cart_dir = mcl_minecarts:get_rail_direction(railpos, {x=1, y=0, z=0}, nil, nil, railtype)
383 cart:setyaw(minetest.dir_to_yaw(cart_dir))
385 if not minetest.settings:get_bool("creative_mode") then
386 itemstack:take_item()
388 return itemstack
392 local register_craftitem = function(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
393 entity_mapping[itemstring] = entity_id
395 local groups = { minecart = 1, transport = 1 }
396 if creative == false then
397 groups.not_in_creative_inventory = 1
399 local def = {
400 stack_max = 1,
401 on_place = function(itemstack, placer, pointed_thing)
402 if not pointed_thing.type == "node" then
403 return
406 -- Call on_rightclick if the pointed node defines it
407 local node = minetest.get_node(pointed_thing.under)
408 if placer and not placer:get_player_control().sneak then
409 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
410 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
414 return mcl_minecarts.place_minecart(itemstack, pointed_thing)
415 end,
416 _on_dispense = function(stack, pos, droppos, dropnode, dropdir)
417 -- Place minecart as entity on rail. If there's no rail, just drop it.
418 local placed
419 if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
420 -- FIXME: This places minecarts even if the spot is already occupied
421 local pointed_thing = { under = droppos, above = { x=droppos.x, y=droppos.y+1, z=droppos.z } }
422 placed = mcl_minecarts.place_minecart(stack, pointed_thing)
424 if placed == nil then
425 -- Drop item
426 minetest.add_item(droppos, stack)
428 end,
429 groups = groups,
431 def.description = description
432 def._doc_items_longdesc = longdesc
433 def._doc_items_usagehelp = usagehelp
434 def.inventory_image = icon
435 def.wield_image = icon
436 minetest.register_craftitem(itemstring, def)
439 --[[
440 Register a minecart
441 * itemstring: Itemstring of minecart item
442 * entity_id: ID of minecart entity
443 * description: Item name / description
444 * longdesc: Long help text
445 * usagehelp: Usage help text
446 * mesh: Minecart mesh
447 * textures: Minecart textures table
448 * icon: Item icon
449 * drop: Dropped items after destroying minecart
450 * on_rightclick: Called after rightclick
451 * on_activate_by_rail: Called when above activator rail
452 * creative: If false, don't show in Creative Inventory
454 local function register_minecart(itemstring, entity_id, description, longdesc, usagehelp, mesh, textures, icon, drop, on_rightclick, on_activate_by_rail, creative)
455 register_entity(entity_id, mesh, textures, drop, on_rightclick)
456 register_craftitem(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
457 if minetest.get_modpath("doc_identifier") ~= nil then
458 doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
462 -- Minecart
463 register_minecart(
464 "mcl_minecarts:minecart",
465 "mcl_minecarts:minecart",
466 "Minecart",
467 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
468 "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.",
469 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
470 "To obtain the minecart, punch it while holding down the sneak key.",
471 "mcl_minecarts_minecart.b3d",
472 {"mcl_minecarts_minecart.png"},
473 "mcl_minecarts_minecart_normal.png",
474 {"mcl_minecarts:minecart"},
475 function(self, clicker)
476 local name = clicker:get_player_name()
477 if not clicker or not clicker:is_player() then
478 return
480 local player_name = clicker:get_player_name()
481 if self._driver and player_name == self._driver then
482 self._driver = nil
483 self._start_pos = nil
484 clicker:set_detach()
485 clicker:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
486 elseif not self._driver then
487 self._driver = player_name
488 self._start_pos = self.object:getpos()
489 mcl_player.player_attached[player_name] = true
490 clicker:set_attach(self.object, "", {x=0, y=8.25, z=-2}, {x=0, y=0, z=0})
491 mcl_player.player_attached[name] = true
492 minetest.after(0.2, function(name)
493 local player = minetest.get_player_by_name(name)
494 if player then
495 mcl_player.player_set_animation(player, "sit" , 30)
496 player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
498 end, name)
503 -- Minecart with Chest
504 register_minecart(
505 "mcl_minecarts:chest_minecart",
506 "mcl_minecarts:chest_minecart",
507 "Minecart with Chest",
508 nil, nil,
509 "mcl_minecarts_minecart_chest.b3d",
510 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
511 "mcl_minecarts_minecart_chest.png",
512 {"mcl_minecarts:minecart", "mcl_chests:chest"},
513 nil, nil, false)
515 -- Minecart with Furnace
516 register_minecart(
517 "mcl_minecarts:furnace_minecart",
518 "mcl_minecarts:furnace_minecart",
519 "Minecart with Furnace",
520 nil, nil,
521 "mcl_minecarts_minecart_block.b3d",
523 "default_furnace_top.png",
524 "default_furnace_top.png",
525 "default_furnace_front.png",
526 "default_furnace_side.png",
527 "default_furnace_side.png",
528 "default_furnace_side.png",
529 "mcl_minecarts_minecart.png",
531 "mcl_minecarts_minecart_furnace.png",
532 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
533 -- Feed furnace with coal
534 function(self, clicker)
535 if not clicker or not clicker:is_player() then
536 return
538 if not self._fueltime then
539 self._fueltime = 0
541 local held = clicker:get_wielded_item()
542 if minetest.get_item_group(held:get_name(), "coal") == 1 then
543 self._fueltime = self._fueltime + 180
545 if not minetest.settings:get_bool("creative_mode") then
546 held:take_item()
547 local index = clicker:get_wielded_index()
548 local inv = clicker:get_inventory()
549 inv:set_stack("main", index, held)
552 -- DEBUG
553 minetest.chat_send_player(clicker:get_player_name(), "Fuel: " .. tostring(self._fueltime))
555 end, nil, false
558 -- Minecart with Command Block
559 register_minecart(
560 "mcl_minecarts:command_block_minecart",
561 "mcl_minecarts:command_block_minecart",
562 "Minecart with Command Block",
563 nil, nil,
564 "mcl_minecarts_minecart_block.b3d",
566 "jeija_commandblock_off.png^[verticalframe:2:0",
567 "jeija_commandblock_off.png^[verticalframe:2:0",
568 "jeija_commandblock_off.png^[verticalframe:2:0",
569 "jeija_commandblock_off.png^[verticalframe:2:0",
570 "jeija_commandblock_off.png^[verticalframe:2:0",
571 "jeija_commandblock_off.png^[verticalframe:2:0",
572 "mcl_minecarts_minecart.png",
574 "mcl_minecarts_minecart_command_block.png",
575 {"mcl_minecarts:minecart"},
576 nil, nil, false
579 -- Minecart with Hopper
580 register_minecart(
581 "mcl_minecarts:hopper_minecart",
582 "mcl_minecarts:hopper_minecart",
583 "Minecart with Hopper",
584 nil, nil,
585 "mcl_minecarts_minecart_hopper.b3d",
587 "mcl_hoppers_hopper_inside.png",
588 "mcl_minecarts_minecart.png",
589 "mcl_hoppers_hopper_outside.png",
590 "mcl_hoppers_hopper_top.png",
592 "mcl_minecarts_minecart_hopper.png",
593 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
594 nil, nil, false
597 -- Minecart with TNT
598 register_minecart(
599 "mcl_minecarts:tnt_minecart",
600 "mcl_minecarts:tnt_minecart",
601 "Minecart with TNT",
602 nil, nil,
603 "mcl_minecarts_minecart_block.b3d",
605 "default_tnt_top.png",
606 "default_tnt_bottom.png",
607 "default_tnt_side.png",
608 "default_tnt_side.png",
609 "default_tnt_side.png",
610 "default_tnt_side.png",
611 "mcl_minecarts_minecart.png",
613 "mcl_minecarts_minecart_tnt.png",
614 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
615 nil, nil, false
619 minetest.register_craft({
620 output = "mcl_minecarts:minecart",
621 recipe = {
622 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
623 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
627 -- TODO: Re-enable crafting of special minecarts when they have been implemented
628 if false then
629 minetest.register_craft({
630 output = "mcl_minecarts:hopper_minecart",
631 recipe = {
632 {"mcl_hoppers:hopper"},
633 {"mcl_minecarts:minecart"},
637 minetest.register_craft({
638 output = "mcl_minecarts:chest_minecart",
639 recipe = {
640 {"mcl_chests:chest"},
641 {"mcl_minecarts:minecart"},
645 minetest.register_craft({
646 output = "mcl_minecarts:tnt_minecart",
647 recipe = {
648 {"mcl_tnt:tnt"},
649 {"mcl_minecarts:minecart"},
653 minetest.register_craft({
654 output = "mcl_minecarts:furnace_minecart",
655 recipe = {
656 {"mcl_furnaces:furnace"},
657 {"mcl_minecarts:minecart"},