diff --git a/mods/carts/cart_entity.lua b/mods/carts/cart_entity.lua new file mode 100644 index 00000000..ea5f4bca --- /dev/null +++ b/mods/carts/cart_entity.lua @@ -0,0 +1,384 @@ +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"}, + }, +}) diff --git a/mods/carts/init.lua b/mods/carts/init.lua index 7cfee74f..53b33cc2 100644 --- a/mods/carts/init.lua +++ b/mods/carts/init.lua @@ -17,387 +17,4 @@ if not default.player_attached then 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")