2 physical = false, -- otherwise going uphill breaks
3 collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
5 mesh = "carts_cart.b3d",
6 visual_size = {x=1, y=1},
7 textures = {"carts_cart.png"},
10 punched = false, -- used to re-send velocity and position
11 velocity = {x=0, y=0, z=0}, -- only used on punch
12 old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
19 function cart_entity:on_rightclick(clicker)
20 if not clicker or not clicker:is_player() then
23 local player_name = clicker:get_player_name()
24 if self.driver and player_name == self.driver then
26 carts:manage_attachment(clicker, nil)
27 elseif not self.driver then
28 self.driver = player_name
29 carts:manage_attachment(clicker, self.object)
33 function cart_entity:on_activate(staticdata, dtime_s)
34 self.object:set_armor_groups({immortal=1})
35 if string.sub(staticdata, 1, string.len("return")) ~= "return" then
38 local data = minetest.deserialize(staticdata)
39 if not data or type(data) ~= "table" then
42 self.railtype = data.railtype
44 self.old_dir = data.old_dir
47 self.old_vel = data.old_vel
51 function cart_entity:get_staticdata()
52 return minetest.serialize({
53 railtype = self.railtype,
54 old_dir = self.old_dir,
55 old_vel = self.old_vel
59 function cart_entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
60 local pos = self.object:getpos()
61 local vel = self.object:getvelocity()
62 if not self.railtype or vector.equals(vel, {x=0, y=0, z=0}) then
63 local node = minetest.get_node(pos).name
64 self.railtype = minetest.get_item_group(node, "connect_to_raillike")
66 -- Punched by non-player
67 if not puncher or not puncher:is_player() then
68 local cart_dir = carts:get_rail_direction(pos, self.old_dir, nil, nil, self.railtype)
69 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
72 self.velocity = vector.multiply(cart_dir, 2)
76 -- Player digs cart by sneak-punch
77 if puncher:get_player_control().sneak then
78 if self.sound_handle then
79 minetest.sound_stop(self.sound_handle)
81 -- Detach driver and items
84 self.object:setpos(self.old_pos)
86 local player = minetest.get_player_by_name(self.driver)
87 carts:manage_attachment(player, nil)
89 for _,obj_ in ipairs(self.attached_items) do
95 local inv = puncher:get_inventory()
96 if not (creative and creative.is_enabled_for
97 and creative.is_enabled_for(puncher:get_player_name()))
98 or not inv:contains_item("main", "carts:cart") then
99 local leftover = inv:add_item("main", "carts:cart")
100 -- If no room in inventory add a replacement cart to the world
101 if not leftover:is_empty() then
102 minetest.add_item(self.object:getpos(), leftover)
108 -- Player punches cart to alter velocity
109 if puncher:get_player_name() == self.driver then
110 if math.abs(vel.x + vel.z) > carts.punch_speed_max then
115 local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
117 local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
118 if vector.equals(cart_dir, {x=0, y=0, z=0}) then
122 local punch_interval = 1
123 if tool_capabilities and tool_capabilities.full_punch_interval then
124 punch_interval = tool_capabilities.full_punch_interval
126 time_from_last_punch = math.min(time_from_last_punch or punch_interval, punch_interval)
127 local f = 2 * (time_from_last_punch / punch_interval)
129 self.velocity = vector.multiply(cart_dir, f)
130 self.old_dir = cart_dir
134 local function rail_on_step_event(handler, obj, dtime)
140 -- sound refresh interval = 1.0sec
141 local function rail_sound(self, dtime)
142 if not self.sound_ttl then
145 elseif self.sound_ttl > 0 then
146 self.sound_ttl = self.sound_ttl - dtime
150 if self.sound_handle then
151 local handle = self.sound_handle
152 self.sound_handle = nil
153 minetest.after(0.2, minetest.sound_stop, handle)
155 local vel = self.object:getvelocity()
156 local speed = vector.length(vel)
158 self.sound_handle = minetest.sound_play(
159 "carts_cart_moving", {
160 object = self.object,
161 gain = (speed / carts.speed_max) / 2,
167 local function get_railparams(pos)
168 local node = minetest.get_node(pos)
169 return carts.railparams[node.name] or {}
172 local function rail_on_step(self, dtime)
173 local vel = self.object:getvelocity()
175 vel = vector.add(vel, self.velocity)
176 self.object:setvelocity(vel)
178 elseif vector.equals(vel, {x=0, y=0, z=0}) then
182 local pos = self.object:getpos()
185 -- stop cart if velocity vector flips
186 if self.old_vel and self.old_vel.y == 0 and
187 (self.old_vel.x * vel.x < 0 or self.old_vel.z * vel.z < 0) then
188 self.old_vel = {x = 0, y = 0, z = 0}
190 self.object:setvelocity(vector.new())
191 self.object:setacceleration(vector.new())
192 rail_on_step_event(get_railparams(pos).on_step, self, dtime)
195 self.old_vel = vector.new(vel)
197 if self.old_pos and not self.punched then
198 local flo_pos = vector.round(pos)
199 local flo_old = vector.round(self.old_pos)
200 if vector.equals(flo_pos, flo_old) then
201 -- Do not check one node multiple times
208 -- Get player controls
210 player = minetest.get_player_by_name(self.driver)
212 ctrl = player:get_player_control()
217 -- Detection for "skipping" nodes
218 local found_path = carts:pathfinder(
219 pos, self.old_pos, self.old_dir, ctrl, self.old_switch, self.railtype
222 if not found_path then
223 -- No rail found: reset back to the expected position
224 pos = vector.new(self.old_pos)
229 local cart_dir = carts:velocity_to_dir(vel)
232 -- dir: New moving direction of the cart
233 -- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
234 local dir, switch_keys = carts:get_rail_direction(
235 pos, cart_dir, ctrl, self.old_switch, self.railtype
238 local new_acc = {x=0, y=0, z=0}
239 if vector.equals(dir, {x=0, y=0, z=0}) then
240 vel = {x = 0, y = 0, z = 0}
241 pos = vector.round(pos)
245 -- Direction change detected
246 if not vector.equals(dir, self.old_dir) then
247 vel = vector.multiply(dir, math.abs(vel.x + vel.z))
249 if dir.y ~= self.old_dir.y then
250 pos = vector.round(pos)
254 -- Center on the rail
255 if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
256 pos.x = math.floor(pos.x + 0.5)
259 if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
260 pos.z = math.floor(pos.z + 0.5)
264 -- Slow down or speed up..
265 local acc = dir.y * -4.0
267 -- Get rail for corrected position
268 railparams = get_railparams(pos)
270 -- no need to check for railparams == nil since we always make it exist.
271 local speed_mod = railparams.acceleration
272 if speed_mod and speed_mod ~= 0 then
273 -- Try to make it similar to the original carts mod
274 acc = acc + speed_mod
276 -- Handbrake or coast
277 if ctrl and ctrl.down then
284 new_acc = vector.multiply(dir, acc)
288 local max_vel = carts.speed_max
289 for _, v in pairs({"x","y","z"}) do
290 if math.abs(vel[v]) > max_vel then
291 vel[v] = carts:get_sign(vel[v]) * max_vel
297 self.object:setacceleration(new_acc)
298 self.old_pos = vector.new(pos)
299 if not vector.equals(dir, {x=0, y=0, z=0}) then
300 self.old_dir = vector.new(dir)
302 self.old_switch = switch_keys
305 -- Collect dropped items
306 for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do
307 if not obj_:is_player() and
308 obj_:get_luaentity() and
309 not obj_:get_luaentity().physical_state and
310 obj_:get_luaentity().name == "__builtin:item" then
312 obj_:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
313 self.attached_items[#self.attached_items + 1] = obj_
320 railparams = railparams or get_railparams(pos)
322 if not (update.vel or update.pos) then
323 rail_on_step_event(railparams.on_step, self, dtime)
328 if self.old_dir.x < 0 then
330 elseif self.old_dir.x > 0 then
332 elseif self.old_dir.z < 0 then
335 self.object:setyaw(yaw * math.pi)
337 local anim = {x=0, y=0}
340 elseif dir.y == 1 then
343 self.object:set_animation(anim, 1, 0)
345 self.object:setvelocity(vel)
347 self.object:setpos(pos)
350 -- call event handler
351 rail_on_step_event(railparams.on_step, self, dtime)
354 function cart_entity:on_step(dtime)
355 rail_on_step(self, dtime)
356 rail_sound(self, dtime)
359 minetest.register_entity("carts:cart", cart_entity)
361 minetest.register_craftitem("carts:cart", {
362 description = "Cart (Sneak+Click to pick up)",
363 inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png", "carts_cart_side.png"),
364 wield_image = "carts_cart_side.png",
365 on_place = function(itemstack, placer, pointed_thing)
366 local under = pointed_thing.under
367 local node = minetest.get_node(under)
368 local udef = minetest.registered_nodes[node.name]
369 if udef and udef.on_rightclick and
370 not (placer and placer:is_player() and
371 placer:get_player_control().sneak) then
372 return udef.on_rightclick(under, node, placer, itemstack,
373 pointed_thing) or itemstack
376 if not pointed_thing.type == "node" then
379 if carts:is_rail(pointed_thing.under) then
380 minetest.add_entity(pointed_thing.under, "carts:cart")
381 elseif carts:is_rail(pointed_thing.above) then
382 minetest.add_entity(pointed_thing.above, "carts:cart")
387 minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
388 {pos = pointed_thing.above})
390 if not (creative and creative.is_enabled_for
391 and creative.is_enabled_for(placer:get_player_name())) then
392 itemstack:take_item()
398 minetest.register_craft({
399 output = "carts:cart",
401 {"default:steel_ingot", "", "default:steel_ingot"},
402 {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},