Make player sit in minecarts
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_minecarts / init.lua
blob33014228a8efd98a622a838d8d4b8ebc7852975d
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 self.object:set_armor_groups({immortal=1})
35 end
37 function cart:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
38 local pos = self.object:getpos()
39 if not self._railtype then
40 local node = minetest.get_node(vector.floor(pos)).name
41 self._railtype = minetest.get_item_group(node, "connect_to_raillike")
42 end
44 if not puncher or not puncher:is_player() then
45 local cart_dir = mcl_minecarts:get_rail_direction(pos, {x=1, y=0, z=0}, nil, nil, self._railtype)
46 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
47 return
48 end
49 self._velocity = vector.multiply(cart_dir, 3)
50 self._old_pos = nil
51 self._punched = true
52 return
53 end
55 if puncher:get_player_control().sneak then
56 if self._driver then
57 if self._old_pos then
58 self.object:setpos(self._old_pos)
59 end
60 mcl_player.player_attached[self._driver] = nil
61 local player = minetest.get_player_by_name(self._driver)
62 if player then
63 player:set_detach()
64 end
65 end
67 -- Disable detector rail
68 local rou_pos = vector.round(pos)
69 local node = minetest.get_node(rou_pos)
70 if node.name == "mcl_minecarts:detector_rail_on" then
71 local newnode = {name="mcl_minecarts:detector_rail", param2 = node.param2}
72 minetest.swap_node(rou_pos, newnode)
73 mesecon.receptor_off(rou_pos)
74 end
76 -- Drop items and remove cart entity
77 if not minetest.settings:get_bool("creative_mode") then
78 for d=1, #drop do
79 minetest.add_item(self.object:getpos(), drop[d])
80 end
81 end
83 self.object:remove()
84 return
85 end
87 local vel = self.object:getvelocity()
88 if puncher:get_player_name() == self._driver then
89 if math.abs(vel.x + vel.z) > 7 then
90 return
91 end
92 end
94 local punch_dir = mcl_minecarts:velocity_to_dir(puncher:get_look_dir())
95 punch_dir.y = 0
96 local cart_dir = mcl_minecarts:get_rail_direction(pos, punch_dir, nil, nil, self._railtype)
97 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
98 return
99 end
101 time_from_last_punch = math.min(time_from_last_punch, tool_capabilities.full_punch_interval)
102 local f = 3 * (time_from_last_punch / tool_capabilities.full_punch_interval)
104 self._velocity = vector.multiply(cart_dir, f)
105 self._old_pos = nil
106 self._punched = true
109 function cart:on_step(dtime)
110 local vel = self.object:getvelocity()
111 local update = {}
112 if self._punched then
113 vel = vector.add(vel, self._velocity)
114 self.object:setvelocity(vel)
115 self._old_dir.y = 0
116 elseif vector.equals(vel, {x=0, y=0, z=0}) then
117 return
120 local dir, last_switch = nil, nil
121 local pos = self.object:getpos()
122 if self._old_pos and not self._punched then
123 local flo_pos = vector.floor(pos)
124 local flo_old = vector.floor(self._old_pos)
125 if vector.equals(flo_pos, flo_old) then
126 return
127 -- Prevent querying the same node over and over again
130 -- Update detector rails
131 local rou_pos = vector.round(pos)
132 local rou_old = vector.round(self._old_pos)
133 local node = minetest.get_node(rou_pos)
134 local node_old = minetest.get_node(rou_old)
136 if node.name == "mcl_minecarts:detector_rail" then
137 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
138 minetest.swap_node(rou_pos, newnode)
139 mesecon.receptor_on(rou_pos)
141 if node_old.name == "mcl_minecarts:detector_rail_on" then
142 local newnode = {name="mcl_minecarts:detector_rail", param2 = node_old.param2}
143 minetest.swap_node(rou_old, newnode)
144 mesecon.receptor_off(rou_old)
148 local ctrl, player = nil, nil
149 if self._driver then
150 player = minetest.get_player_by_name(self._driver)
151 if player then
152 ctrl = player:get_player_control()
156 -- Stop cart if velocity vector flips
157 if self._old_vel and self._old_vel.y == 0 and
158 (self._old_vel.x * vel.x < 0 or self._old_vel.z * vel.z < 0) then
159 self._old_vel = {x = 0, y = 0, z = 0}
160 self._old_pos = pos
161 self.object:setvelocity(vector.new())
162 self.object:setacceleration(vector.new())
163 return
165 self._old_vel = vector.new(vel)
167 if self._old_pos then
168 local diff = vector.subtract(self._old_pos, pos)
169 for _,v in ipairs({"x","y","z"}) do
170 if math.abs(diff[v]) > 1.1 then
171 local expected_pos = vector.add(self._old_pos, self._old_dir)
172 dir, last_switch = mcl_minecarts:get_rail_direction(pos, self._old_dir, ctrl, self._old_switch, self._railtype)
173 if vector.equals(dir, {x=0, y=0, z=0}) then
174 dir = false
175 pos = vector.new(expected_pos)
176 update.pos = true
178 break
183 if vel.y == 0 then
184 for _,v in ipairs({"x", "z"}) do
185 if vel[v] ~= 0 and math.abs(vel[v]) < 0.9 then
186 vel[v] = 0
187 update.vel = true
192 local cart_dir = mcl_minecarts:velocity_to_dir(vel)
193 local max_vel = mcl_minecarts.speed_max
194 if not dir then
195 dir, last_switch = mcl_minecarts:get_rail_direction(pos, cart_dir, ctrl, self._old_switch, self._railtype)
198 local new_acc = {x=0, y=0, z=0}
199 if vector.equals(dir, {x=0, y=0, z=0}) then
200 vel = {x=0, y=0, z=0}
201 update.vel = true
202 else
203 -- If the direction changed
204 if dir.x ~= 0 and self._old_dir.z ~= 0 then
205 vel.x = dir.x * math.abs(vel.z)
206 vel.z = 0
207 pos.z = math.floor(pos.z + 0.5)
208 update.pos = true
210 if dir.z ~= 0 and self._old_dir.x ~= 0 then
211 vel.z = dir.z * math.abs(vel.x)
212 vel.x = 0
213 pos.x = math.floor(pos.x + 0.5)
214 update.pos = true
216 -- Up, down?
217 if dir.y ~= self._old_dir.y then
218 vel.y = dir.y * math.abs(vel.x + vel.z)
219 pos = vector.round(pos)
220 update.pos = true
223 -- Slow down or speed up
224 local acc = dir.y * -1.8
226 local speed_mod = minetest.registered_nodes[minetest.get_node(pos).name]._rail_acceleration
227 if speed_mod and speed_mod ~= 0 then
228 acc = acc + speed_mod
229 else
230 acc = acc - 0.4
233 new_acc = vector.multiply(dir, acc)
236 self.object:setacceleration(new_acc)
237 self._old_pos = vector.new(pos)
238 self._old_dir = vector.new(dir)
239 self._old_switch = last_switch
241 -- Limits
242 for _,v in ipairs({"x","y","z"}) do
243 if math.abs(vel[v]) > max_vel then
244 vel[v] = mcl_minecarts:get_sign(vel[v]) * max_vel
245 new_acc[v] = 0
246 update.vel = true
250 -- Give achievement when player reached a distance of 1000 nodes from the start position
251 if self._driver and (vector.distance(self._start_pos, pos) >= 1000) then
252 awards.unlock(self._driver, "mcl:onARail")
256 if update.pos or self._punched then
257 local yaw = 0
258 if dir.x < 0 then
259 yaw = 0.5
260 elseif dir.x > 0 then
261 yaw = 1.5
262 elseif dir.z < 0 then
263 yaw = 1
265 self.object:setyaw(yaw * math.pi)
268 if self._punched then
269 self._punched = false
272 if not (update.vel or update.pos) then
273 return
277 local anim = {x=0, y=0}
278 if dir.y == -1 then
279 anim = {x=1, y=1}
280 elseif dir.y == 1 then
281 anim = {x=2, y=2}
283 self.object:set_animation(anim, 1, 0)
285 self.object:setvelocity(vel)
286 if update.pos then
287 self.object:setpos(pos)
289 update = nil
292 function cart:get_staticdata()
293 return ""
296 minetest.register_entity(entity_id, cart)
299 -- Place a minecart at pointed_thing
300 mcl_minecarts.place_minecart = function(itemstack, pointed_thing)
301 if not pointed_thing.type == "node" then
302 return
305 local railpos, node
306 if mcl_minecarts:is_rail(pointed_thing.under) then
307 railpos = pointed_thing.under
308 node = minetest.get_node(pointed_thing.under)
309 elseif mcl_minecarts:is_rail(pointed_thing.above) then
310 railpos = pointed_thing.above
311 node = minetest.get_node(pointed_thing.above)
312 else
313 return
316 -- Activate detector rail
317 if node.name == "mcl_minecarts:detector_rail" then
318 local newnode = {name="mcl_minecarts:detector_rail_on", param2 = node.param2}
319 minetest.swap_node(railpos, newnode)
320 mesecon.receptor_on(railpos)
323 local entity_id = entity_mapping[itemstack:get_name()]
324 local cart = minetest.add_entity(railpos, entity_id)
325 local railtype = minetest.get_item_group(node.name, "connect_to_raillike")
326 local cart_dir = mcl_minecarts:get_rail_direction(railpos, {x=1, y=0, z=0}, nil, nil, railtype)
327 cart:setyaw(minetest.dir_to_yaw(cart_dir))
329 if not minetest.settings:get_bool("creative_mode") then
330 itemstack:take_item()
332 return itemstack
336 local register_craftitem = function(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
337 entity_mapping[itemstring] = entity_id
339 local groups = { minecart = 1, transport = 1 }
340 if creative == false then
341 groups.not_in_creative_inventory = 1
343 local def = {
344 stack_max = 1,
345 on_place = function(itemstack, placer, pointed_thing)
346 if not pointed_thing.type == "node" then
347 return
350 -- Call on_rightclick if the pointed node defines it
351 local node = minetest.get_node(pointed_thing.under)
352 if placer and not placer:get_player_control().sneak then
353 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
354 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
358 return mcl_minecarts.place_minecart(itemstack, pointed_thing)
359 end,
360 _on_dispense = function(stack, pos, droppos, dropnode, dropdir)
361 -- Place minecart as entity on rail. If there's no rail, just drop it.
362 local placed
363 if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
364 -- FIXME: This places minecarts even if the spot is already occupied
365 local pointed_thing = { under = droppos, above = { x=droppos.x, y=droppos.y+1, z=droppos.z } }
366 placed = mcl_minecarts.place_minecart(stack, pointed_thing)
368 if placed == nil then
369 -- Drop item
370 minetest.add_item(droppos, stack)
372 end,
373 groups = groups,
375 def.description = description
376 def._doc_items_longdesc = longdesc
377 def._doc_items_usagehelp = usagehelp
378 def.inventory_image = icon
379 def.wield_image = icon
380 minetest.register_craftitem(itemstring, def)
383 --[[
384 Register a minecart
385 * itemstring: Itemstring of minecart item
386 * entity_id: ID of minecart entity
387 * description: Item name / description
388 * longdesc: Long help text
389 * usagehelp: Usage help text
390 * mesh: Minecart mesh
391 * textures: Minecart textures table
392 * icon: Item icon
393 * drop: Dropped items after destroying minecart
394 * on_rightclick: Called after rightclick
395 * on_activate_by_rail: Called when above activator rail
396 * creative: If false, don't show in Creative Inventory
398 local function register_minecart(itemstring, entity_id, description, longdesc, usagehelp, mesh, textures, icon, drop, on_rightclick, on_activate_by_rail, creative)
399 register_entity(entity_id, mesh, textures, drop, on_rightclick)
400 register_craftitem(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
401 if minetest.get_modpath("doc_identifier") ~= nil then
402 doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
406 -- Minecart
407 register_minecart(
408 "mcl_minecarts:minecart",
409 "mcl_minecarts:minecart",
410 "Minecart",
411 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
412 "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.",
413 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
414 "To obtain the minecart, punch it while holding down the sneak key.",
415 "mcl_minecarts_minecart.b3d",
416 {"mcl_minecarts_minecart.png"},
417 "mcl_minecarts_minecart_normal.png",
418 {"mcl_minecarts:minecart"},
419 function(self, clicker)
420 local name = clicker:get_player_name()
421 if not clicker or not clicker:is_player() then
422 return
424 local player_name = clicker:get_player_name()
425 if self._driver and player_name == self._driver then
426 self._driver = nil
427 self._start_pos = nil
428 clicker:set_detach()
429 local player = minetest.get_player_by_name(name)
430 player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
431 elseif not self._driver then
432 self._driver = player_name
433 self._start_pos = self.object:getpos()
434 mcl_player.player_attached[player_name] = true
435 clicker:set_attach(self.object, "", {x=0, y=8.25, z=-2}, {x=0, y=0, z=0})
436 mcl_player.player_attached[name] = true
437 minetest.after(0.2, function(name)
438 local player = minetest.get_player_by_name(name)
439 if player then
440 mcl_player.player_set_animation(player, "sit" , 30)
441 player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
443 end, name)
448 -- Minecart with Chest
449 register_minecart(
450 "mcl_minecarts:chest_minecart",
451 "mcl_minecarts:chest_minecart",
452 "Minecart with Chest",
453 nil, nil,
454 "mcl_minecarts_minecart_chest.b3d",
455 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
456 "mcl_minecarts_minecart_chest.png",
457 {"mcl_minecarts:minecart", "mcl_chests:chest"},
458 nil, nil, false)
460 -- Minecart with Furnace
461 register_minecart(
462 "mcl_minecarts:furnace_minecart",
463 "mcl_minecarts:furnace_minecart",
464 "Minecart with Furnace",
465 nil, nil,
466 "mcl_minecarts_minecart_block.b3d",
468 "default_furnace_top.png",
469 "default_furnace_top.png",
470 "default_furnace_front.png",
471 "default_furnace_side.png",
472 "default_furnace_side.png",
473 "default_furnace_side.png",
474 "mcl_minecarts_minecart.png",
476 "mcl_minecarts_minecart_furnace.png",
477 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
478 -- Feed furnace with coal
479 function(self, clicker)
480 if not clicker or not clicker:is_player() then
481 return
483 if not self._fueltime then
484 self._fueltime = 0
486 local held = clicker:get_wielded_item()
487 if minetest.get_item_group(held:get_name(), "coal") == 1 then
488 self._fueltime = self._fueltime + 180
490 if not minetest.settings:get_bool("creative_mode") then
491 held:take_item()
492 local index = clicker:get_wielded_index()
493 local inv = clicker:get_inventory()
494 inv:set_stack("main", index, held)
497 -- DEBUG
498 minetest.chat_send_player(clicker:get_player_name(), "Fuel: " .. tostring(self._fueltime))
500 end, nil, false
503 -- Minecart with Command Block
504 register_minecart(
505 "mcl_minecarts:command_block_minecart",
506 "mcl_minecarts:command_block_minecart",
507 "Minecart with Command Block",
508 nil, nil,
509 "mcl_minecarts_minecart_block.b3d",
511 "jeija_commandblock_off.png^[verticalframe:2:0",
512 "jeija_commandblock_off.png^[verticalframe:2:0",
513 "jeija_commandblock_off.png^[verticalframe:2:0",
514 "jeija_commandblock_off.png^[verticalframe:2:0",
515 "jeija_commandblock_off.png^[verticalframe:2:0",
516 "jeija_commandblock_off.png^[verticalframe:2:0",
517 "mcl_minecarts_minecart.png",
519 "mcl_minecarts_minecart_command_block.png",
520 {"mcl_minecarts:minecart"},
521 nil, nil, false
524 -- Minecart with Hopper
525 register_minecart(
526 "mcl_minecarts:hopper_minecart",
527 "mcl_minecarts:hopper_minecart",
528 "Minecart with Hopper",
529 nil, nil,
530 "mcl_minecarts_minecart_hopper.b3d",
532 "mcl_hoppers_hopper_inside.png",
533 "mcl_minecarts_minecart.png",
534 "mcl_hoppers_hopper_outside.png",
535 "mcl_hoppers_hopper_top.png",
537 "mcl_minecarts_minecart_hopper.png",
538 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
539 nil, nil, false
542 -- Minecart with TNT
543 register_minecart(
544 "mcl_minecarts:tnt_minecart",
545 "mcl_minecarts:tnt_minecart",
546 "Minecart with TNT",
547 nil, nil,
548 "mcl_minecarts_minecart_block.b3d",
550 "default_tnt_top.png",
551 "default_tnt_bottom.png",
552 "default_tnt_side.png",
553 "default_tnt_side.png",
554 "default_tnt_side.png",
555 "default_tnt_side.png",
556 "mcl_minecarts_minecart.png",
558 "mcl_minecarts_minecart_tnt.png",
559 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
560 nil, nil, false
564 minetest.register_craft({
565 output = "mcl_minecarts:minecart",
566 recipe = {
567 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
568 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
572 -- TODO: Re-enable crafting of special minecarts when they have been implemented
573 if false then
574 minetest.register_craft({
575 output = "mcl_minecarts:hopper_minecart",
576 recipe = {
577 {"mcl_hoppers:hopper"},
578 {"mcl_minecarts:minecart"},
582 minetest.register_craft({
583 output = "mcl_minecarts:chest_minecart",
584 recipe = {
585 {"mcl_chests:chest"},
586 {"mcl_minecarts:minecart"},
590 minetest.register_craft({
591 output = "mcl_minecarts:tnt_minecart",
592 recipe = {
593 {"mcl_tnt:tnt"},
594 {"mcl_minecarts:minecart"},
598 minetest.register_craft({
599 output = "mcl_minecarts:furnace_minecart",
600 recipe = {
601 {"mcl_furnaces:furnace"},
602 {"mcl_minecarts:minecart"},