-local path = minetest.get_modpath("minecart")
-dofile(path.."/rail.lua")
+local pool = {}
---this begins the minecart library
-local minecart = {}
+local player_pool = {}
+
+
+local dirs = {
+ {x= 1,y= 0,z= 0},
+ {x=-1,y= 0,z= 0},
+ {x= 1,y= 1,z= 0},
+ {x=-1,y= 1,z= 0},
---these are the variables for the minecart
-minecart.max_speed = 15
-minecart.speed = 0
-minecart.rider = nil
+ {x= 1,y=-1,z= 0},
+ {x=-1,y=-1,z= 0},
---binary direction
-minecart.get_dir = function(pos,pos2)
- return(minetest.facedir_to_dir(minetest.dir_to_facedir(vector.direction(pos2,pos))))
+ {x= 0,y= 0,z= 1},
+ {x= 0,y= 0,z=-1},
+
+ {x= 0,y= 1,z= 1},
+ {x= 0,y= 1,z=-1},
+
+ {x= 0,y=-1,z= 1},
+ {x= 0,y=-1,z=-1},
+}
+
+local function coupling_particles(pos,truth)
+ local color = "red"
+ if truth then
+ color = "green"
+ end
+
+ minetest.add_particlespawner({
+ amount = 15,
+ time = 0.001,
+ minpos = pos,
+ maxpos = pos,
+ minvel = vector.new(-10,-10,-10),
+ maxvel = vector.new(10,10,10),
+ minacc = {x=0, y=0, z=0},
+ maxacc = {x=0, y=0, z=0},
+ minexptime = 1.1,
+ maxexptime = 1.5,
+ minsize = 1,
+ maxsize = 2,
+ collisiondetection = false,
+ collision_removal = false,
+ vertical = false,
+ texture = "couple_particle.png^[colorize:"..color..":200",
+ glow = 14,
+ })
end
---this gets the node position that the minecart is in
-minecart.round_pos = function(pos)
- return(vector.round(pos))
+
+local function data_injection(pos,data)
+ if data then
+ pool[minetest.hash_node_position(pos)] = true
+ else
+ pool[minetest.hash_node_position(pos)] = nil
+ end
end
---this is called when a player is standing next to the minecart
---it will take their position and convert it into binary then
---will begin movement
-minecart.set_direction = function(self,dir)
- if not self.goal then
- --reset the y to 0 since we will be checking up and down anyways
- dir.y = 0
- local pos = vector.add(minecart.round_pos(self.object:get_pos()),dir)
- local node = minetest.get_node(pos).name
- local node_above = minetest.get_node(vector.new(pos.x,pos.y+1,pos.z)).name
- local node_under = minetest.get_node(vector.new(pos.x,pos.y-1,pos.z)).name
-
- --next to
- if node == "minecart:rail" and node_under ~= "minecart:rail" then
- self.dir = dir
- self.goal = pos
- --downhill
- elseif node == "air" and node_under == "minecart:rail" then
- self.dir = vector.new(dir.x,dir.y-1,dir.z)
- self.goal = vector.new(pos.x,pos.y-1,pos.z)
- --uphill
- elseif node ~= "minecart:rail" and node_above == "minecart:rail" then
- self.dir = vector.new(dir.x,dir.y+1,dir.z)
- self.goal = vector.new(pos.x,pos.y+1,pos.z)
- --rail not found
- else
- self.goal = nil
- end
+local function speed_limiter(self,speed)
+ local test = self.object:get_velocity()--vector.multiply(self.velocity,new_vel)
+
+ if test.x > speed then
+ test.x = speed
+ elseif test.x < -speed then
+ test.x = -speed
+ end
+ if test.z > speed then
+ test.z = speed
+ elseif test.z < -speed then
+ test.z = -speed
end
+ self.object:set_velocity(test)
end
---this will turn the z into x and x into z
-minecart.flip_direction_axis = function(self)
- self.dir = vector.new(math.abs(self.dir.z),0,math.abs(self.dir.x))
+local function create_axis(pos)
+ local possible_dirs = {}
+ for _,dir in pairs(dirs) do
+ local pos2 = vector.add(pos,dir)
+ if pool[minetest.hash_node_position(pos2)] then
+ table.insert(possible_dirs,dir)
+ end
+ end
+ return(possible_dirs)
end
---this makes the minecart move in "blocks"
---it will go to it's goal then when it has reached the goal, move to another goal
-minecart.movement = function(self)
- if self.dir and self.goal then
- local pos = self.object:get_pos()
- local movement = vector.direction(pos,self.goal)
- self.object:set_velocity(vector.multiply(movement,self.speed))
- local distance_from_goal = vector.distance(pos,self.goal)
-
- --this checks how far the minecart is from the "goal node"
- --aka the node that the minecart was supposed to go to
- if distance_from_goal < 0.2 then
- self.object:set_velocity(vector.new(0,0,0))
- self.goal = nil
- --self.object:move_to(minecart.round_pos(self.object:get_pos()))
-
- --if the minecart is slowed down below 1 nph (node per hour)
- --try to flip direction if pointing up
- --then stop it if failed
- if self.speed < 1 then
- if self.dir.y == 1 then
- self.dir = vector.multiply(self.dir, -1)
- minecart.set_direction(self,self.dir)
- else
- self.speed = 0
- self.dir = nil
- self.goal = nil
- end
-
- --otherwise try to keep going
- else
- --test to see if minecart will keep moving
- minecart.set_direction(self,self.dir)
-
- --if not rail ahead then we'll try to turn
- if not self.goal then
- minecart.flip_direction_axis(self)
-
- minecart.set_direction(self,self.dir)
-
- --if trying to turn that direcion failed we'll try the other
- if not self.goal then
- self.dir = vector.multiply(self.dir, -1)
- minecart.set_direction(self,self.dir)
- end
- end
-
- --and if everything fails, give up and stop
- if not self.goal then
- self.speed = 0
- end
+local function collision_detect(self)
+ if not self.axis_lock then return end
+ local pos = self.object:get_pos()
+ for _,object in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
+ if object:is_player() then
+ local pos2 = object:get_pos()
+ if self.axis_lock == "x" then
+
+ local velocity = (1-vector.distance(vector.new(pos.x,0,0),vector.new(pos2.x,0,0)))*5
+ local dir = vector.direction(vector.new(pos2.x,0,0),vector.new(pos.x,0,0))
+ local new_vel = vector.multiply(dir,velocity)
+ self.object:add_velocity(new_vel)
+ self.dir = dir
+ elseif self.axis_lock == "z" then
+ local velocity = (1-vector.distance(vector.new(0,0,pos.z),vector.new(0,0,pos2.z)))*5
+ local dir = vector.direction(vector.new(0,0,pos2.z),vector.new(0,0,pos.z))
+ local new_vel = vector.multiply(dir,velocity)
+ self.object:add_velocity(new_vel)
+ self.dir = dir
end
+ return
end
-
- --make minecart slow down, but only so much
-
- --speed up going downhill
- if self.dir and (self.dir.y == -1 or self.rider) and self.speed < 10 then
- self.speed = self.speed + 0.05
- --slow down going uphill
- elseif self.dir and self.speed > 1 and self.dir.y == 1 then
- self.speed = self.speed - 0.05
- --normal flat friction slowdown
- elseif self.speed > 1 then
- self.speed = self.speed - 0.01
+ end
+end
+
+local function direction_snap(self)
+ local dir = self.dir
+ local pitch = 0
+ if dir.y == 1 then pitch = math.pi/4 end
+ if dir.y == -1 then pitch = -math.pi/4 end
+ local yaw = minetest.dir_to_yaw(dir)
+ self.object:set_rotation(vector.new(pitch,yaw,0))
+end
+
+local function turn_snap(pos,self,dir,dir2)
+ if self.axis_lock == "x" then
+ if dir.x ~= 0 and dir2.z ~= 0 then
+ local velocity = self.object:get_velocity()
+ local inertia = math.abs(velocity.x)
+ self.object:set_velocity(vector.multiply(dir2,inertia))
+ self.dir = dir2
+ self.axis_lock = "z"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
+ end
+ end
+ if self.axis_lock == "z" then
+ if dir.z ~= 0 and dir2.x ~= 0 then
+ local velocity = self.object:get_velocity()
+ local inertia = math.abs(velocity.z)
+ self.object:set_velocity(vector.multiply(dir2,inertia))
+ self.dir = dir2
+ self.axis_lock = "x"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
+ end
+ end
+ return(false)
+end
+
+local function climb_snap(pos,self,dir,dir2)
+ if self.axis_lock == "x" then
+ if dir.x == dir2.x and dir2.y ~= 0 then
+ local velocity = self.object:get_velocity()
+ local inertia = math.abs(velocity.x)
+ self.object:set_velocity(vector.multiply(dir2,inertia))
+ self.dir = dir2
+ self.axis_lock = "x"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
end
- --stop the minecart from flying off into the distance
- elseif not vector.equals(self.object:get_velocity(), vector.new(0,0,0)) and (self.speed == 0 or not self.speed) then
- self.object:set_velocity(vector.new(0,0,0))
- --this is when the minecart is stopped
- --gotta figure out some way to apply gravity and make it physical
- --without breaking the rest of it
- elseif self.speed == 0 then
- --self.object:add_velocity(vector.new(0,-10,0))
end
+ if self.axis_lock == "z" then
+ if dir.z == dir2.z and dir2.y ~= 0 then
+ local velocity = self.object:get_velocity()
+ local inertia = math.abs(velocity.z)
+ self.object:set_velocity(vector.multiply(dir2,inertia))
+ self.dir = dir2
+ self.axis_lock = "z"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
+ end
+ end
+ return(false)
+end
+
+local function straight_snap(pos,self,dir)
+ if self.axis_lock == "x" then
+ if dir.x ~= 0 and pool[minetest.hash_node_position(vector.add(pos,vector.new(dir.x,0,0)))] then
+ local velocity = self.object:get_velocity()
+ self.object:set_velocity(vector.new(velocity.x,0,0))
+ self.dir = vector.new(dir.x,0,0)
+ self.axis_lock = "x"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
+ end
+ end
+ if self.axis_lock == "z" then
+ if dir.z ~= 0 and pool[minetest.hash_node_position(vector.add(pos,vector.new(0,0,dir.z)))] then
+ local velocity = self.object:get_velocity()
+ self.object:set_velocity(vector.new(0,0,velocity.z))
+ self.dir = vector.new(0,0,dir.z)
+ self.axis_lock = "z"
+ self.object:set_pos(pos)
+ direction_snap(self)
+ return(true)
+ end
+ end
+ return(false)
+end
+
+
+local function coupling_logic(self)
+ if not self.axis_lock then return end
+
+ if not self.coupler1 then return end
+
+ if self.dir.y ~= 0 then return end
+
+ local pos = self.object:get_pos()
+ local pos2 = self.coupler1:get_pos()
+
+ if self.axis_lock == "x" then
+ local velocity_real = self.object:get_velocity()
+ local distance = vector.distance(pos,pos2)
+ local new_vel = vector.new(0,0,0)
+ if distance > 1.5 then
+ local velocity = (distance-1)
+ local dir = vector.direction(vector.new(pos.x,0,0),vector.new(pos2.x,0,0))
+ self.dir = dir
+ new_vel = vector.multiply(dir,velocity)
+ else
+ new_vel = vector.multiply(velocity_real,-1)
+ end
+ self.object:add_velocity(new_vel)
+ elseif self.axis_lock == "z" then
+ local velocity_real = self.object:get_velocity()
+ local distance = vector.distance(pos,pos2)
+ local new_vel = vector.new(0,0,0)
+ if distance > 1.5 then
+ local velocity = (distance-1)
+ local dir = vector.direction(vector.new(0,0,pos.z),vector.new(0,0,pos2.z))
+ self.dir = dir
+ new_vel = vector.multiply(dir,velocity)
+ else
+ new_vel = vector.multiply(velocity_real,-1)
+ end
+ self.object:add_velocity(new_vel)
+ end
+
+ return
end
---this simply sets the mesh based on if the minecart is moving up or down
---this will be replaced by set animation in the future
-minecart.set_mesh = function(self)
- if self.dir and self.dir.y < 0 then
- self.object:set_animation({x=2,y=2}, 15, 0, true)
- elseif self.dir and self.dir.y > 0 then
- self.object:set_animation({x=1,y=1}, 15, 0, true)
+
+local function rail_brain(self,pos)
+
+
+ if not self.dir then self.dir = vector.new(0,0,0) end
+
+ local pos2 = self.object:get_pos()
+
+ local dir = self.dir
+
+ speed_limiter(self,6)
+
+ if not pool[minetest.hash_node_position(vector.add(pos,dir))] then
+
+ if straight_snap(pos,self,dir) then
+ return
+ end
+
+ local possible_dirs = create_axis(pos)
+
+ if table.getn(possible_dirs) == 0 then
+ --stop slow down become physical
+ else
+ for _,dir2 in pairs(possible_dirs) do
+ if turn_snap(pos,self,dir,dir2) then
+ return
+ end
+ if climb_snap(pos,self,dir,dir2) then
+ return
+ end
+ end
+ end
else
- self.object:set_animation({x=0,y=0}, 15, 0, true)
+ coupling_logic(self)
end
+
end
+local minecart = {}
+
minecart.on_step = function(self,dtime)
- local pos = self.object:get_pos()
-
- --get player input (standing next to the minecart)
- for _,object in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
- if self.object ~= object and object:is_player() and object:get_player_name() ~= self.rider then
- local pos2 = object:get_pos()
- minecart.set_direction(self, minecart.get_dir(pos,pos2))
- self.speed = 7
+ if dtime > 0.1 then
+ self.object:set_pos(self.old_pos)
+ end
+ local pos = vector.round(self.object:get_pos())
+ if not self.axis_lock then
+ local possible_dirs = create_axis(pos)
+ for _,dir in pairs(possible_dirs) do
+ if dir.x ~=0 then
+ self.axis_lock = "x"
+ self.dir = dir
+ direction_snap(self)
+ break
+ elseif dir.z ~= 0 then
+ self.axis_lock = "z"
+ self.dir = dir
+ direction_snap(self)
+ break
+ end
end
- end
-
- --this makes the minecart actually move, it is also
- --the begining of it's logic
- minecart.movement(self)
-
- --set the minecart's mesh
- minecart.set_mesh(self)
+ else
+ rail_brain(self,pos)
+ --collision_detect(self)
+ end
+ self.old_pos = self.object:get_pos()
+end
+
+
+minecart.on_punch = function(self, puncher)
+ if not puncher:get_wielded_item():get_name() == "minecart:wrench" then
+ return
+ end
+ if self.furnace then
+ self.object:set_velocity(vector.multiply(self.dir,6))
+ minetest.add_particlespawner({
+ amount = 30,
+ time = 0,
+ minpos = vector.new(0,0.5,0),
+ maxpos = vector.new(0,0.5,0),
+ minvel = vector.new(0,0,0),
+ maxvel = vector.new(0,0,0),
+ minacc = {x=0, y=3, z=0},
+ maxacc = {x=0, y=5, z=0},
+ minexptime = 1.1,
+ maxexptime = 1.5,
+ minsize = 1,
+ maxsize = 2,
+ collisiondetection = false,
+ collision_removal = false,
+ vertical = false,
+ texture = "smoke.png",
+ attached = self.object
+ })
+ end
+
end
---make the player ride the minecart
---or make the player get off
+
minecart.on_rightclick = function(self,clicker)
- if not clicker or not clicker:is_player() then return end
+ local pos = self.object:get_pos()
+ if clicker:get_wielded_item():get_name() == "utility:furnace" then
+ local obj = minetest.add_entity(pos, "minecart:furnace")
+ obj:set_attach(self.object,"",vector.new(0,0,0),vector.new(0,0,0))
+ minetest.sound_play("wrench",{
+ object = self.object,
+ gain = 1.0,
+ max_hear_distance = 64,
+ })
+ coupling_particles(pos,true)
+ self.furnace = true
+ return
+ end
+
+ if not clicker:get_wielded_item():get_name() == "minecart:wrench" then
+ return
+ end
+
local name = clicker:get_player_name()
-
- --get on the minecart
- if not self.rider then
- self.rider = name
- clicker:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
- --get off the minecart
- elseif name == self.rider then
- self.rider = nil
- clicker:set_detach()
+ if not pool[name] then
+ if not self.coupler2 then
+ pool[name] = self.object
+ minetest.sound_play("wrench",{
+ object = self.object,
+ gain = 1.0,
+ max_hear_distance = 64,
+ })
+ coupling_particles(pos,true)
+ else
+ minetest.sound_play("wrench",{
+ object = self.object,
+ gain = 1.0,
+ max_hear_distance = 64,
+ pitch = 0.7,
+ })
+ coupling_particles(pos,false)
+ end
+ else
+ if pool[name] ~= self.object and not (pool[name]:get_luaentity().coupler1 and pool[name]:get_luaentity().coupler1 == self.object or self.coupler2) then
+ self.coupler1 = pool[name]
+ pool[name]:get_luaentity().coupler2 = self.object
+ minetest.sound_play("wrench",{
+ object = self.object,
+ gain = 1.0,
+ max_hear_distance = 64,
+ })
+ coupling_particles(pos,true)
+ else
+ minetest.sound_play("wrench",{
+ object = self.object,
+ gain = 1.0,
+ max_hear_distance = 64,
+ pitch = 0.7,
+ })
+ coupling_particles(pos,false)
+ end
+ pool[name] = nil
end
end
if type(data) ~= "table" then
return
end
- self.dir = data.dir
- self.goal = data.goal
- self.speed = data.speed
-
- --run through if there was a rider then check if they exist and put them back on
- --and if they don't exist then nillify the rider value
- if data.rider then
- if minetest.player_exists(data.rider) then
- self.rider = data.rider
- local player = minetest.get_player_by_name(data.rider)
- player:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
- else
- self.rider = nil
- end
- else
- self.rider = nil
- end
-
-
+ self.old_pos = self.object:get_pos()
+ self.velocity = vector.new(0,0,0)
end
---remember data
+
minecart.get_staticdata = function(self)
return minetest.serialize({
- dir = self.dir,
- goal = self.goal,
- speed = self.speed,
- rider = self.rider
})
end
mesh = "minecart.x",
visual_size = {x=1, y=1},
textures = {"minecart.png"},
- automatic_face_movement_dir = 90.0,
- automatic_face_movement_max_rotation_per_sec = 1200,
}
-
-minecart.on_punch = function(self,puncher, time_from_last_punch, tool_capabilities, dir, damage)
- local obj = minetest.add_item(self.object:getpos(), "minecart:minecart")
- self.object:remove()
-end
-
minetest.register_entity("minecart:minecart", minecart)
return
end
+ local sneak = placer:get_player_control().sneak
+ local noddef = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
+ if not sneak and noddef.on_rightclick then
+ minetest.item_place(itemstack, placer, pointed_thing)
+ return
+ end
+
if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "rail")>0 then
minetest.add_entity(pointed_thing.under, "minecart:minecart")
else
{"main:iron", "main:iron", "main:iron"},
},
})
+
+
+
+
+
+minetest.register_node("minecart:rail",{
+ description = "Rail",
+ wield_image = "rail.png",
+ tiles = {
+ "rail.png", "railcurve.png",
+ "railt.png", "railcross.png"
+ },
+ drawtype = "raillike",
+ paramtype = "light",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ walkable = false,
+ node_placement_prediction = "",
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
+ },
+ sounds = main.stoneSound(),
+ after_place_node = function(pos)
+ data_injection(pos,true)
+ end,
+ after_destruct = function(pos)
+ data_injection(pos)
+ end,
+ groups={stone=1,wood=1,rail=1,attached_node=1},
+})
+
+
+minetest.register_lbm({
+ name = "minecart:rail",
+ nodenames = {"minecart:rail"},
+ run_at_every_load = true,
+ action = function(pos)
+ data_injection(pos,true)
+ end,
+})
+
+minetest.register_craft({
+ output = "minecart:rail 32",
+ recipe = {
+ {"main:iron","","main:iron"},
+ {"main:iron","main:stick","main:iron"},
+ {"main:iron","","main:iron"}
+ }
+})
+
+
+minetest.register_food("minecart:wrench",{
+ description = "Train Wrench",
+ texture = "wrench.png",
+})
+
+minetest.register_craft({
+ output = "minecart:wrench",
+ recipe = {
+ {"main:iron", "", "main:iron"},
+ {"main:iron", "main:lapis", "main:iron"},
+ {"", "main:lapis", ""}
+ }
+})
+
+
+
+minetest.register_entity("minecart:furnace", {
+ initial_properties = {
+ visual = "wielditem",
+ visual_size = {x = 0.6, y = 0.6},
+ textures = {},
+ physical = true,
+ is_visible = false,
+ collide_with_objects = false,
+ pointable=false,
+ collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+ },
+ set_node = function(self)
+ self.object:set_properties({
+ is_visible = true,
+ textures = {"utility:furnace"},
+ })
+ end,
+
+
+ on_activate = function(self, staticdata)
+ self.object:set_armor_groups({immortal = 1})
+
+ self:set_node()
+ end,
+})