--- /dev/null
+local cart_entity = {
+ physical = false, -- otherwise going uphill breaks
+ collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+ visual = "mesh",
+ mesh = "carts_cart.b3d",
+ visual_size = {x=1, y=1},
+ textures = {"carts_cart.png"},
+
+ driver = nil,
+ punched = false, -- used to re-send velocity and position
+ velocity = {x=0, y=0, z=0}, -- only used on punch
+ old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
+ old_pos = nil,
+ old_switch = 0,
+ railtype = nil,
+ attached_items = {}
+}
+
+function cart_entity:on_rightclick(clicker)
+ if not clicker or not clicker:is_player() then
+ return
+ end
+ local player_name = clicker:get_player_name()
+ if self.driver and player_name == self.driver then
+ self.driver = nil
+ carts:manage_attachment(clicker, nil)
+ elseif not self.driver then
+ self.driver = player_name
+ carts:manage_attachment(clicker, self.object)
+ end
+end
+
+function cart_entity:on_activate(staticdata, dtime_s)
+ self.object:set_armor_groups({immortal=1})
+ if string.sub(staticdata, 1, string.len("return")) ~= "return" then
+ return
+ end
+ local data = minetest.deserialize(staticdata)
+ if not data or type(data) ~= "table" then
+ return
+ end
+ self.railtype = data.railtype
+ if data.old_dir then
+ self.old_dir = data.old_dir
+ end
+ if data.old_vel then
+ self.old_vel = data.old_vel
+ end
+end
+
+function cart_entity:get_staticdata()
+ return minetest.serialize({
+ railtype = self.railtype,
+ old_dir = self.old_dir,
+ old_vel = self.old_vel
+ })
+end
+
+function cart_entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
+ local pos = self.object:getpos()
+ if not self.railtype then
+ local node = minetest.get_node(pos).name
+ self.railtype = minetest.get_item_group(node, "connect_to_raillike")
+ end
+
+ if not puncher or not puncher:is_player() then
+ local cart_dir = carts:get_rail_direction(pos, self.old_dir, nil, nil, self.railtype)
+ if vector.equals(cart_dir, {x=0, y=0, z=0}) then
+ return
+ end
+ self.velocity = vector.multiply(cart_dir, 3)
+ self.punched = true
+ return
+ end
+
+ if puncher:get_player_control().sneak then
+ if self.sound_handle then
+ minetest.sound_stop(self.sound_handle)
+ end
+ -- Pick up cart: Drop all attachments
+ if self.driver then
+ if self.old_pos then
+ self.object:setpos(self.old_pos)
+ end
+ local player = minetest.get_player_by_name(self.driver)
+ carts:manage_attachment(player, nil)
+ end
+ for _,obj_ in ipairs(self.attached_items) do
+ if obj_ then
+ obj_:set_detach()
+ end
+ end
+
+ local leftover = puncher:get_inventory():add_item("main", "carts:cart")
+ if not leftover:is_empty() then
+ minetest.add_item(self.object:getpos(), leftover)
+ end
+ self.object:remove()
+ return
+ end
+
+ local vel = self.object:getvelocity()
+ if puncher:get_player_name() == self.driver then
+ if math.abs(vel.x + vel.z) > carts.punch_speed_max then
+ return
+ end
+ end
+
+ local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
+ punch_dir.y = 0
+ local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
+ if vector.equals(cart_dir, {x=0, y=0, z=0}) then
+ return
+ end
+
+ local punch_interval = 1
+ if tool_capabilities and tool_capabilities.full_punch_interval then
+ punch_interval = tool_capabilities.full_punch_interval
+ end
+ time_from_last_punch = math.min(time_from_last_punch or punch_interval, punch_interval)
+ local f = 2 * (time_from_last_punch / punch_interval)
+
+ self.velocity = vector.multiply(cart_dir, f)
+ self.old_dir = cart_dir
+ self.punched = true
+end
+
+local function rail_on_step_event(handler, obj, dtime)
+ if handler then
+ handler(obj, dtime)
+ end
+end
+
+-- sound refresh interval = 1.0sec
+local function rail_sound(self, dtime)
+ if not self.sound_ttl then
+ self.sound_ttl = 1.0
+ return
+ elseif self.sound_ttl > 0 then
+ self.sound_ttl = self.sound_ttl - dtime
+ return
+ end
+ self.sound_ttl = 1.0
+ if self.sound_handle then
+ local handle = self.sound_handle
+ self.sound_handle = nil
+ minetest.after(0.2, minetest.sound_stop, handle)
+ end
+ local vel = self.object:getvelocity()
+ local speed = vector.length(vel)
+ if speed > 0 then
+ self.sound_handle = minetest.sound_play(
+ "carts_cart_moving", {
+ object = self.object,
+ gain = (speed / carts.speed_max) / 2,
+ loop = true,
+ })
+ end
+end
+
+local function rail_on_step(self, dtime)
+ local pos = self.object:getpos()
+ local node = minetest.get_node(pos)
+ local railparams = carts.railparams[node.name] or {}
+
+ local vel = self.object:getvelocity()
+ local update = {}
+ if self.punched then
+ vel = vector.add(vel, self.velocity)
+ self.object:setvelocity(vel)
+ self.old_dir.y = 0
+ elseif vector.equals(vel, {x=0, y=0, z=0}) then
+ return
+ end
+
+ -- stop cart if velocity vector flips
+ if self.old_vel and (((self.old_vel.x * vel.x) < 0) or
+ ((self.old_vel.z * vel.z) < 0)) and
+ (self.old_vel.y == 0) then
+ self.old_dir = {x = 0, y = 0, z = 0}
+ self.old_vel = {x = 0, y = 0, z = 0}
+ self.velocity = {x = 0, y = 0, z = 0}
+ self.old_pos = pos
+ self.object:setvelocity(vector.new())
+ self.object:setacceleration(vector.new())
+ rail_on_step_event(railparams.on_step, self, dtime)
+ return
+ end
+ self.old_vel = vector.new(vel)
+
+ if self.old_pos and not self.punched then
+ local flo_pos = vector.round(pos)
+ local flo_old = vector.round(self.old_pos)
+ if vector.equals(flo_pos, flo_old) then
+ -- Do not check one node multiple times
+ rail_on_step_event(railparams.on_step, self, dtime)
+ return
+ end
+ end
+
+ local ctrl, player
+
+ -- Get player controls
+ if self.driver then
+ player = minetest.get_player_by_name(self.driver)
+ if player then
+ ctrl = player:get_player_control()
+ end
+ end
+
+ if self.old_pos then
+ -- Detection for "skipping" nodes
+ local expected_pos = vector.add(self.old_pos, self.old_dir)
+ local found_path = carts:pathfinder(
+ pos, expected_pos, self.old_dir, ctrl, self.old_switch, self.railtype
+ )
+
+ if not found_path then
+ -- No rail found: reset back to the expected position
+ pos = vector.new(self.old_pos)
+ update.pos = true
+ end
+ end
+
+ local cart_dir = carts:velocity_to_dir(vel)
+
+ -- dir: New moving direction of the cart
+ -- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
+ local dir, switch_keys = carts:get_rail_direction(
+ pos, cart_dir, ctrl, self.old_switch, self.railtype
+ )
+
+ local new_acc = {x=0, y=0, z=0}
+ if vector.equals(dir, {x=0, y=0, z=0}) then
+ vel = {x=0, y=0, z=0}
+ pos = vector.round(pos)
+ update.pos = true
+ update.vel = true
+ else
+ -- If the direction changed
+ if dir.x ~= 0 and self.old_dir.z ~= 0 then
+ vel.x = dir.x * math.abs(vel.z)
+ vel.z = 0
+ pos.z = math.floor(pos.z + 0.5)
+ update.pos = true
+ end
+ if dir.z ~= 0 and self.old_dir.x ~= 0 then
+ vel.z = dir.z * math.abs(vel.x)
+ vel.x = 0
+ pos.x = math.floor(pos.x + 0.5)
+ update.pos = true
+ end
+ -- Up, down?
+ if dir.y ~= self.old_dir.y then
+ vel.y = dir.y * math.abs(vel.x + vel.z)
+ pos = vector.round(pos)
+ update.pos = true
+ end
+
+ -- Slow down or speed up..
+ local acc = dir.y * -4.0
+
+ -- no need to check for railparams == nil since we always make it exist.
+ local speed_mod = railparams.acceleration
+ if speed_mod and speed_mod ~= 0 then
+ -- Try to make it similar to the original carts mod
+ acc = acc + speed_mod
+ else
+ -- Handbrake
+ if ctrl and ctrl.down then
+ acc = acc - 1.6
+ else
+ acc = acc - 0.4
+ end
+ end
+
+ new_acc = vector.multiply(dir, acc)
+ end
+
+ -- Limits
+ local max_vel = carts.speed_max
+ for _,v in ipairs({"x","y","z"}) do
+ if math.abs(vel[v]) > max_vel then
+ vel[v] = carts:get_sign(vel[v]) * max_vel
+ new_acc[v] = 0
+ update.vel = true
+ end
+ end
+
+ self.object:setacceleration(new_acc)
+ self.old_pos = vector.new(pos)
+ if not vector.equals(dir, {x=0, y=0, z=0}) then
+ self.old_dir = vector.new(dir)
+ end
+ self.old_switch = switch_keys
+
+ if self.punched then
+ -- Collect dropped items
+ for _,obj_ in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
+ if not obj_:is_player() and
+ obj_:get_luaentity() and
+ not obj_:get_luaentity().physical_state and
+ obj_:get_luaentity().name == "__builtin:item" then
+
+ obj_:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
+ self.attached_items[#self.attached_items + 1] = obj_
+ end
+ end
+ self.punched = false
+ update.vel = true
+ end
+
+ if not (update.vel or update.pos) then
+ rail_on_step_event(railparams.on_step, self, dtime)
+ return
+ end
+
+ local yaw = 0
+ if self.old_dir.x < 0 then
+ yaw = 0.5
+ elseif self.old_dir.x > 0 then
+ yaw = 1.5
+ elseif self.old_dir.z < 0 then
+ yaw = 1
+ end
+ self.object:setyaw(yaw * math.pi)
+
+ local anim = {x=0, y=0}
+ if dir.y == -1 then
+ anim = {x=1, y=1}
+ elseif dir.y == 1 then
+ anim = {x=2, y=2}
+ end
+ self.object:set_animation(anim, 1, 0)
+
+ self.object:setvelocity(vel)
+ if update.pos then
+ self.object:setpos(pos)
+ end
+
+ -- call event handler
+ rail_on_step_event(railparams.on_step, self, dtime)
+end
+
+function cart_entity:on_step(dtime)
+ rail_on_step(self, dtime)
+ rail_sound(self, dtime)
+end
+
+minetest.register_entity("carts:cart", cart_entity)
+
+minetest.register_craftitem("carts:cart", {
+ description = "Cart (Sneak+Click to pick up)",
+ inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png", "carts_cart_side.png"),
+ wield_image = "carts_cart_side.png",
+ on_place = function(itemstack, placer, pointed_thing)
+ if not pointed_thing.type == "node" then
+ return
+ end
+ if carts:is_rail(pointed_thing.under) then
+ minetest.add_entity(pointed_thing.under, "carts:cart")
+ elseif carts:is_rail(pointed_thing.above) then
+ minetest.add_entity(pointed_thing.above, "carts:cart")
+ else
+ return
+ end
+
+ minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
+ {pos = pointed_thing.above})
+
+ if not minetest.setting_getbool("creative_mode") then
+ itemstack:take_item()
+ end
+ return itemstack
+ end,
+})
+
+minetest.register_craft({
+ output = "carts:cart",
+ recipe = {
+ {"default:steel_ingot", "", "default:steel_ingot"},
+ {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
+ },
+})
default.player_attached = {}
end
-local cart_entity = {
- physical = false, -- otherwise going uphill breaks
- collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
- visual = "mesh",
- mesh = "carts_cart.b3d",
- visual_size = {x=1, y=1},
- textures = {"carts_cart.png"},
-
- driver = nil,
- punched = false, -- used to re-send velocity and position
- velocity = {x=0, y=0, z=0}, -- only used on punch
- old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
- old_pos = nil,
- old_switch = 0,
- railtype = nil,
- attached_items = {}
-}
-
-function cart_entity:on_rightclick(clicker)
- if not clicker or not clicker:is_player() then
- return
- end
- local player_name = clicker:get_player_name()
- if self.driver and player_name == self.driver then
- self.driver = nil
- carts:manage_attachment(clicker, nil)
- elseif not self.driver then
- self.driver = player_name
- carts:manage_attachment(clicker, self.object)
- end
-end
-
-function cart_entity:on_activate(staticdata, dtime_s)
- self.object:set_armor_groups({immortal=1})
- if string.sub(staticdata, 1, string.len("return")) ~= "return" then
- return
- end
- local data = minetest.deserialize(staticdata)
- if not data or type(data) ~= "table" then
- return
- end
- self.railtype = data.railtype
- if data.old_dir then
- self.old_dir = data.old_dir
- end
- if data.old_vel then
- self.old_vel = data.old_vel
- end
-end
-
-function cart_entity:get_staticdata()
- return minetest.serialize({
- railtype = self.railtype,
- old_dir = self.old_dir,
- old_vel = self.old_vel
- })
-end
-
-function cart_entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
- local pos = self.object:getpos()
- if not self.railtype then
- local node = minetest.get_node(pos).name
- self.railtype = minetest.get_item_group(node, "connect_to_raillike")
- end
-
- if not puncher or not puncher:is_player() then
- local cart_dir = carts:get_rail_direction(pos, self.old_dir, nil, nil, self.railtype)
- if vector.equals(cart_dir, {x=0, y=0, z=0}) then
- return
- end
- self.velocity = vector.multiply(cart_dir, 3)
- self.punched = true
- return
- end
-
- if puncher:get_player_control().sneak then
- if self.sound_handle then
- minetest.sound_stop(self.sound_handle)
- end
- -- Pick up cart: Drop all attachments
- if self.driver then
- if self.old_pos then
- self.object:setpos(self.old_pos)
- end
- local player = minetest.get_player_by_name(self.driver)
- carts:manage_attachment(player, nil)
- end
- for _,obj_ in ipairs(self.attached_items) do
- if obj_ then
- obj_:set_detach()
- end
- end
-
- local leftover = puncher:get_inventory():add_item("main", "carts:cart")
- if not leftover:is_empty() then
- minetest.add_item(self.object:getpos(), leftover)
- end
- self.object:remove()
- return
- end
-
- local vel = self.object:getvelocity()
- if puncher:get_player_name() == self.driver then
- if math.abs(vel.x + vel.z) > carts.punch_speed_max then
- return
- end
- end
-
- local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
- punch_dir.y = 0
- local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
- if vector.equals(cart_dir, {x=0, y=0, z=0}) then
- return
- end
-
- local punch_interval = 1
- if tool_capabilities and tool_capabilities.full_punch_interval then
- punch_interval = tool_capabilities.full_punch_interval
- end
- time_from_last_punch = math.min(time_from_last_punch or punch_interval, punch_interval)
- local f = 2 * (time_from_last_punch / punch_interval)
-
- self.velocity = vector.multiply(cart_dir, f)
- self.old_dir = cart_dir
- self.punched = true
-end
-
-local function rail_on_step_event(handler, obj, dtime)
- if handler then
- handler(obj, dtime)
- end
-end
-
--- sound refresh interval = 1.0sec
-local function rail_sound(self, dtime)
- if not self.sound_ttl then
- self.sound_ttl = 1.0
- return
- elseif self.sound_ttl > 0 then
- self.sound_ttl = self.sound_ttl - dtime
- return
- end
- self.sound_ttl = 1.0
- if self.sound_handle then
- local handle = self.sound_handle
- self.sound_handle = nil
- minetest.after(0.2, minetest.sound_stop, handle)
- end
- local vel = self.object:getvelocity()
- local speed = vector.length(vel)
- if speed > 0 then
- self.sound_handle = minetest.sound_play(
- "carts_cart_moving", {
- object = self.object,
- gain = (speed / carts.speed_max) / 2,
- loop = true,
- })
- end
-end
-
-local function rail_on_step(self, dtime)
- local pos = self.object:getpos()
- local node = minetest.get_node(pos)
- local railparams = carts.railparams[node.name] or {}
-
- local vel = self.object:getvelocity()
- local update = {}
- if self.punched then
- vel = vector.add(vel, self.velocity)
- self.object:setvelocity(vel)
- self.old_dir.y = 0
- elseif vector.equals(vel, {x=0, y=0, z=0}) then
- return
- end
-
- -- stop cart if velocity vector flips
- if self.old_vel and (((self.old_vel.x * vel.x) < 0) or
- ((self.old_vel.z * vel.z) < 0)) and
- (self.old_vel.y == 0) then
- self.old_dir = {x = 0, y = 0, z = 0}
- self.old_vel = {x = 0, y = 0, z = 0}
- self.velocity = {x = 0, y = 0, z = 0}
- self.old_pos = pos
- self.object:setvelocity(vector.new())
- self.object:setacceleration(vector.new())
- rail_on_step_event(railparams.on_step, self, dtime)
- return
- end
- self.old_vel = vector.new(vel)
-
- if self.old_pos and not self.punched then
- local flo_pos = vector.round(pos)
- local flo_old = vector.round(self.old_pos)
- if vector.equals(flo_pos, flo_old) then
- -- Do not check one node multiple times
- rail_on_step_event(railparams.on_step, self, dtime)
- return
- end
- end
-
- local ctrl, player
-
- -- Get player controls
- if self.driver then
- player = minetest.get_player_by_name(self.driver)
- if player then
- ctrl = player:get_player_control()
- end
- end
-
- if self.old_pos then
- -- Detection for "skipping" nodes
- local expected_pos = vector.add(self.old_pos, self.old_dir)
- local found_path = carts:pathfinder(
- pos, expected_pos, self.old_dir, ctrl, self.old_switch, self.railtype
- )
-
- if not found_path then
- -- No rail found: reset back to the expected position
- pos = vector.new(self.old_pos)
- update.pos = true
- end
- end
-
- local cart_dir = carts:velocity_to_dir(vel)
-
- -- dir: New moving direction of the cart
- -- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
- local dir, switch_keys = carts:get_rail_direction(
- pos, cart_dir, ctrl, self.old_switch, self.railtype
- )
-
- local new_acc = {x=0, y=0, z=0}
- if vector.equals(dir, {x=0, y=0, z=0}) then
- vel = {x=0, y=0, z=0}
- pos = vector.round(pos)
- update.pos = true
- update.vel = true
- else
- -- If the direction changed
- if dir.x ~= 0 and self.old_dir.z ~= 0 then
- vel.x = dir.x * math.abs(vel.z)
- vel.z = 0
- pos.z = math.floor(pos.z + 0.5)
- update.pos = true
- end
- if dir.z ~= 0 and self.old_dir.x ~= 0 then
- vel.z = dir.z * math.abs(vel.x)
- vel.x = 0
- pos.x = math.floor(pos.x + 0.5)
- update.pos = true
- end
- -- Up, down?
- if dir.y ~= self.old_dir.y then
- vel.y = dir.y * math.abs(vel.x + vel.z)
- pos = vector.round(pos)
- update.pos = true
- end
-
- -- Slow down or speed up..
- local acc = dir.y * -4.0
-
- -- no need to check for railparams == nil since we always make it exist.
- local speed_mod = railparams.acceleration
- if speed_mod and speed_mod ~= 0 then
- -- Try to make it similar to the original carts mod
- acc = acc + speed_mod
- else
- -- Handbrake
- if ctrl and ctrl.down then
- acc = acc - 1.6
- else
- acc = acc - 0.4
- end
- end
-
- new_acc = vector.multiply(dir, acc)
- end
-
- -- Limits
- local max_vel = carts.speed_max
- for _,v in ipairs({"x","y","z"}) do
- if math.abs(vel[v]) > max_vel then
- vel[v] = carts:get_sign(vel[v]) * max_vel
- new_acc[v] = 0
- update.vel = true
- end
- end
-
- self.object:setacceleration(new_acc)
- self.old_pos = vector.new(pos)
- if not vector.equals(dir, {x=0, y=0, z=0}) then
- self.old_dir = vector.new(dir)
- end
- self.old_switch = switch_keys
-
- if self.punched then
- -- Collect dropped items
- for _,obj_ in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
- if not obj_:is_player() and
- obj_:get_luaentity() and
- not obj_:get_luaentity().physical_state and
- obj_:get_luaentity().name == "__builtin:item" then
-
- obj_:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
- self.attached_items[#self.attached_items + 1] = obj_
- end
- end
- self.punched = false
- update.vel = true
- end
-
- if not (update.vel or update.pos) then
- rail_on_step_event(railparams.on_step, self, dtime)
- return
- end
-
- local yaw = 0
- if self.old_dir.x < 0 then
- yaw = 0.5
- elseif self.old_dir.x > 0 then
- yaw = 1.5
- elseif self.old_dir.z < 0 then
- yaw = 1
- end
- self.object:setyaw(yaw * math.pi)
-
- local anim = {x=0, y=0}
- if dir.y == -1 then
- anim = {x=1, y=1}
- elseif dir.y == 1 then
- anim = {x=2, y=2}
- end
- self.object:set_animation(anim, 1, 0)
-
- self.object:setvelocity(vel)
- if update.pos then
- self.object:setpos(pos)
- end
-
- -- call event handler
- rail_on_step_event(railparams.on_step, self, dtime)
-end
-
-function cart_entity:on_step(dtime)
- rail_on_step(self, dtime)
- rail_sound(self, dtime)
-end
-
-minetest.register_entity("carts:cart", cart_entity)
-
-minetest.register_craftitem("carts:cart", {
- description = "Cart (Sneak+Click to pick up)",
- inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png", "carts_cart_side.png"),
- wield_image = "carts_cart_side.png",
- on_place = function(itemstack, placer, pointed_thing)
- if not pointed_thing.type == "node" then
- return
- end
- if carts:is_rail(pointed_thing.under) then
- minetest.add_entity(pointed_thing.under, "carts:cart")
- elseif carts:is_rail(pointed_thing.above) then
- minetest.add_entity(pointed_thing.above, "carts:cart")
- else
- return
- end
-
- minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
- {pos = pointed_thing.above})
-
- if not minetest.setting_getbool("creative_mode") then
- itemstack:take_item()
- end
- return itemstack
- end,
-})
-
-minetest.register_craft({
- output = "carts:cart",
- recipe = {
- {"default:steel_ingot", "", "default:steel_ingot"},
- {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
- },
-})
+dofile(carts.modpath.."/cart_entity.lua")