Minecarts: Init _railtype on placement
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_minecarts / init.lua
blob5aac4f0a68a22caa62eac74956ca06ef5542a7ee
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 le = cart:get_luaentity()
327 if le ~= nil then
328 le._railtype = railtype
330 local cart_dir = mcl_minecarts:get_rail_direction(railpos, {x=1, y=0, z=0}, nil, nil, railtype)
331 cart:setyaw(minetest.dir_to_yaw(cart_dir))
333 if not minetest.settings:get_bool("creative_mode") then
334 itemstack:take_item()
336 return itemstack
340 local register_craftitem = function(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
341 entity_mapping[itemstring] = entity_id
343 local groups = { minecart = 1, transport = 1 }
344 if creative == false then
345 groups.not_in_creative_inventory = 1
347 local def = {
348 stack_max = 1,
349 on_place = function(itemstack, placer, pointed_thing)
350 if not pointed_thing.type == "node" then
351 return
354 -- Call on_rightclick if the pointed node defines it
355 local node = minetest.get_node(pointed_thing.under)
356 if placer and not placer:get_player_control().sneak then
357 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
358 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
362 return mcl_minecarts.place_minecart(itemstack, pointed_thing)
363 end,
364 _on_dispense = function(stack, pos, droppos, dropnode, dropdir)
365 -- Place minecart as entity on rail. If there's no rail, just drop it.
366 local placed
367 if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
368 -- FIXME: This places minecarts even if the spot is already occupied
369 local pointed_thing = { under = droppos, above = { x=droppos.x, y=droppos.y+1, z=droppos.z } }
370 placed = mcl_minecarts.place_minecart(stack, pointed_thing)
372 if placed == nil then
373 -- Drop item
374 minetest.add_item(droppos, stack)
376 end,
377 groups = groups,
379 def.description = description
380 def._doc_items_longdesc = longdesc
381 def._doc_items_usagehelp = usagehelp
382 def.inventory_image = icon
383 def.wield_image = icon
384 minetest.register_craftitem(itemstring, def)
387 --[[
388 Register a minecart
389 * itemstring: Itemstring of minecart item
390 * entity_id: ID of minecart entity
391 * description: Item name / description
392 * longdesc: Long help text
393 * usagehelp: Usage help text
394 * mesh: Minecart mesh
395 * textures: Minecart textures table
396 * icon: Item icon
397 * drop: Dropped items after destroying minecart
398 * on_rightclick: Called after rightclick
399 * on_activate_by_rail: Called when above activator rail
400 * creative: If false, don't show in Creative Inventory
402 local function register_minecart(itemstring, entity_id, description, longdesc, usagehelp, mesh, textures, icon, drop, on_rightclick, on_activate_by_rail, creative)
403 register_entity(entity_id, mesh, textures, drop, on_rightclick)
404 register_craftitem(itemstring, entity_id, description, longdesc, usagehelp, icon, creative)
405 if minetest.get_modpath("doc_identifier") ~= nil then
406 doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
410 -- Minecart
411 register_minecart(
412 "mcl_minecarts:minecart",
413 "mcl_minecarts:minecart",
414 "Minecart",
415 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
416 "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.",
417 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
418 "To obtain the minecart, punch it while holding down the sneak key.",
419 "mcl_minecarts_minecart.b3d",
420 {"mcl_minecarts_minecart.png"},
421 "mcl_minecarts_minecart_normal.png",
422 {"mcl_minecarts:minecart"},
423 function(self, clicker)
424 local name = clicker:get_player_name()
425 if not clicker or not clicker:is_player() then
426 return
428 local player_name = clicker:get_player_name()
429 if self._driver and player_name == self._driver then
430 self._driver = nil
431 self._start_pos = nil
432 clicker:set_detach()
433 local player = minetest.get_player_by_name(name)
434 player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
435 elseif not self._driver then
436 self._driver = player_name
437 self._start_pos = self.object:getpos()
438 mcl_player.player_attached[player_name] = true
439 clicker:set_attach(self.object, "", {x=0, y=8.25, z=-2}, {x=0, y=0, z=0})
440 mcl_player.player_attached[name] = true
441 minetest.after(0.2, function(name)
442 local player = minetest.get_player_by_name(name)
443 if player then
444 mcl_player.player_set_animation(player, "sit" , 30)
445 player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
447 end, name)
452 -- Minecart with Chest
453 register_minecart(
454 "mcl_minecarts:chest_minecart",
455 "mcl_minecarts:chest_minecart",
456 "Minecart with Chest",
457 nil, nil,
458 "mcl_minecarts_minecart_chest.b3d",
459 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
460 "mcl_minecarts_minecart_chest.png",
461 {"mcl_minecarts:minecart", "mcl_chests:chest"},
462 nil, nil, false)
464 -- Minecart with Furnace
465 register_minecart(
466 "mcl_minecarts:furnace_minecart",
467 "mcl_minecarts:furnace_minecart",
468 "Minecart with Furnace",
469 nil, nil,
470 "mcl_minecarts_minecart_block.b3d",
472 "default_furnace_top.png",
473 "default_furnace_top.png",
474 "default_furnace_front.png",
475 "default_furnace_side.png",
476 "default_furnace_side.png",
477 "default_furnace_side.png",
478 "mcl_minecarts_minecart.png",
480 "mcl_minecarts_minecart_furnace.png",
481 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
482 -- Feed furnace with coal
483 function(self, clicker)
484 if not clicker or not clicker:is_player() then
485 return
487 if not self._fueltime then
488 self._fueltime = 0
490 local held = clicker:get_wielded_item()
491 if minetest.get_item_group(held:get_name(), "coal") == 1 then
492 self._fueltime = self._fueltime + 180
494 if not minetest.settings:get_bool("creative_mode") then
495 held:take_item()
496 local index = clicker:get_wielded_index()
497 local inv = clicker:get_inventory()
498 inv:set_stack("main", index, held)
501 -- DEBUG
502 minetest.chat_send_player(clicker:get_player_name(), "Fuel: " .. tostring(self._fueltime))
504 end, nil, false
507 -- Minecart with Command Block
508 register_minecart(
509 "mcl_minecarts:command_block_minecart",
510 "mcl_minecarts:command_block_minecart",
511 "Minecart with Command Block",
512 nil, nil,
513 "mcl_minecarts_minecart_block.b3d",
515 "jeija_commandblock_off.png^[verticalframe:2:0",
516 "jeija_commandblock_off.png^[verticalframe:2:0",
517 "jeija_commandblock_off.png^[verticalframe:2:0",
518 "jeija_commandblock_off.png^[verticalframe:2:0",
519 "jeija_commandblock_off.png^[verticalframe:2:0",
520 "jeija_commandblock_off.png^[verticalframe:2:0",
521 "mcl_minecarts_minecart.png",
523 "mcl_minecarts_minecart_command_block.png",
524 {"mcl_minecarts:minecart"},
525 nil, nil, false
528 -- Minecart with Hopper
529 register_minecart(
530 "mcl_minecarts:hopper_minecart",
531 "mcl_minecarts:hopper_minecart",
532 "Minecart with Hopper",
533 nil, nil,
534 "mcl_minecarts_minecart_hopper.b3d",
536 "mcl_hoppers_hopper_inside.png",
537 "mcl_minecarts_minecart.png",
538 "mcl_hoppers_hopper_outside.png",
539 "mcl_hoppers_hopper_top.png",
541 "mcl_minecarts_minecart_hopper.png",
542 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
543 nil, nil, false
546 -- Minecart with TNT
547 register_minecart(
548 "mcl_minecarts:tnt_minecart",
549 "mcl_minecarts:tnt_minecart",
550 "Minecart with TNT",
551 nil, nil,
552 "mcl_minecarts_minecart_block.b3d",
554 "default_tnt_top.png",
555 "default_tnt_bottom.png",
556 "default_tnt_side.png",
557 "default_tnt_side.png",
558 "default_tnt_side.png",
559 "default_tnt_side.png",
560 "mcl_minecarts_minecart.png",
562 "mcl_minecarts_minecart_tnt.png",
563 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
564 nil, nil, false
568 minetest.register_craft({
569 output = "mcl_minecarts:minecart",
570 recipe = {
571 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
572 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
576 -- TODO: Re-enable crafting of special minecarts when they have been implemented
577 if false then
578 minetest.register_craft({
579 output = "mcl_minecarts:hopper_minecart",
580 recipe = {
581 {"mcl_hoppers:hopper"},
582 {"mcl_minecarts:minecart"},
586 minetest.register_craft({
587 output = "mcl_minecarts:chest_minecart",
588 recipe = {
589 {"mcl_chests:chest"},
590 {"mcl_minecarts:minecart"},
594 minetest.register_craft({
595 output = "mcl_minecarts:tnt_minecart",
596 recipe = {
597 {"mcl_tnt:tnt"},
598 {"mcl_minecarts:minecart"},
602 minetest.register_craft({
603 output = "mcl_minecarts:furnace_minecart",
604 recipe = {
605 {"mcl_furnaces:furnace"},
606 {"mcl_minecarts:minecart"},