From cf9b82de379b030fd184b1f8cd0ea754e7c792f4 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 22 Nov 2016 20:32:17 +0100 Subject: [PATCH] Huge rewrite Add sounds Change Y acceleration to -2.5 Add partwise compatibility for MTG-registered rails --- README.txt | 7 +- cart_entity.lua | 380 +++++++++++++++++++++++++++++++++++++++++ depends.txt | 3 +- init.lua | 353 ++------------------------------------ rails.lua | 20 +-- sounds/cart_rail.1.ogg | Bin 0 -> 7543 bytes sounds/cart_rail.2.ogg | Bin 0 -> 8080 bytes sounds/cart_rail.3.ogg | Bin 0 -> 7232 bytes 8 files changed, 402 insertions(+), 361 deletions(-) create mode 100644 cart_entity.lua create mode 100644 sounds/cart_rail.1.ogg create mode 100644 sounds/cart_rail.2.ogg create mode 100644 sounds/cart_rail.3.ogg diff --git a/README.txt b/README.txt index 3a5d295..93dd58f 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,7 @@ Minetest mod: boost_cart ========================== Based on (and fully compatible with) the mod "carts" by PilzAdam +Also compatible with the carts mod in the subgame "minetest_game". Target: Run smoothly as possible even on laggy server @@ -16,7 +17,7 @@ Target: Run smoothly as possible even on laggy server License for everything ------------------------ -CC-0 +CC-0, if not specified otherwise below Authors @@ -36,6 +37,10 @@ kddekadenz cart_side.png cart_top.png +klankbeeld (CC-BY 3.0) + http://freesound.org/people/klankbeeld/sounds/174042/ + cart_rail.*.ogg + numberZero carts_rail_dtc.png carts_rail_dtc_on.png diff --git a/cart_entity.lua b/cart_entity.lua new file mode 100644 index 0000000..6c31a05 --- /dev/null +++ b/cart_entity.lua @@ -0,0 +1,380 @@ + +local HAVE_MESECONS_ENABLED = minetest.global_exists("mesecon") + +function boost_cart:on_rail_step(pos) + -- Play rail sound + if self.sound_counter <= 0 then + minetest.sound_play("cart_rail", { + pos = pos, + max_hear_distance = 40, + gain = 0.5 + }) + self.sound_counter = math.random(4, 15) + end + self.sound_counter = self.sound_counter - 1 + + if HAVE_MESECONS_ENABLED then + boost_cart:signal_detector_rail(pos) + end +end + +local cart_entity = { + physical = false, + collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + visual = "mesh", + mesh = "cart.x", + visual_size = {x=1, y=1}, + textures = {"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, + sound_counter = 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 + boost_cart:manage_attachment(clicker, nil) + elseif not self.driver then + self.driver = player_name + boost_cart:manage_attachment(clicker, self.object) + end +end + +function cart_entity:on_activate(staticdata, dtime_s) + self.object:set_armor_groups({immortal=1}) + self.sound_counter = math.random(4, 15) + + 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 +end + +function cart_entity:get_staticdata() + return minetest.serialize({ + railtype = self.railtype, + old_dir = self.old_dir + }) +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 = boost_cart: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 + -- 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) + boost_cart:manage_attachment(player, nil) + end + for _, obj_ in pairs(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) > boost_cart.punch_speed_max then + return + end + end + + local punch_dir = boost_cart:velocity_to_dir(puncher:get_look_dir()) + punch_dir.y = 0 + local cart_dir = boost_cart: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 = 3 * (time_from_last_punch / punch_interval) + + self.velocity = vector.multiply(cart_dir, f) + self.old_dir = cart_dir + self.punched = true +end + +function cart_entity:on_step(dtime) + 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 + + local pos = self.object:getpos() + + 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 + 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 = boost_cart: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 = boost_cart: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 = boost_cart: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 + -- Direction change detected + if not vector.equals(dir, self.old_dir) then + vel = vector.multiply(dir, math.abs(vel.x + vel.z)) + update.vel = true + if dir.y ~= self.old_dir.y then + pos = vector.round(pos) + update.pos = true + end + end + -- Center on the rail + if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then + pos.x = math.floor(pos.x + 0.5) + update.pos = true + end + if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then + pos.z = math.floor(pos.z + 0.5) + update.pos = true + end + + -- Calculate current cart acceleration + local acc = nil + + local acc_meta = minetest.get_meta(pos):get_string("cart_acceleration") + if acc_meta == "halt" then + -- Stop rail + vel = {x=0, y=0, z=0} + acc = false + pos = vector.round(pos) + update.pos = true + update.vel = true + mod_found = true + end + if acc == nil then + -- Meta speed modifier + local speed_mod = tonumber(acc_meta) + if speed_mod and speed_mod ~= 0 then + -- Try to make it similar to the original carts mod + acc = speed_mod * 10 + end + end + if acc == nil and boost_cart.mtg_compat then + -- MTG Cart API adaption + local rail_node = minetest.get_node(vector.round(pos)) + local railparam = carts.railparams[rail_node.name] + if railparam and railparam.acceleration then + acc = railparam.acceleration + end + end + if acc == nil then + -- Handbrake + if ctrl and ctrl.down then + acc = -2 + else + acc = -0.4 + end + end + + -- Slow down or speed up, depending on Y direction + if acc then + acc = acc + dir.y * -2.5 + else + acc = 0 + end + + if self.old_dir.y ~= 1 and not self.punched then + -- Stop the cart swing between two rail parts (handbrake) + if vector.equals(vector.multiply(self.old_dir, -1), dir) then + vel = {x=0, y=0, z=0} + acc = 0 + if self.old_pos then + pos = vector.new(self.old_pos) + update.pos = true + end + dir = vector.new(self.old_dir) + update.vel = true + end + end + + new_acc = vector.multiply(dir, acc) + end + boost_cart.on_rail_step(self, vector.round(pos)) + + -- Limits + local max_vel = boost_cart.speed_max + for _,v in pairs({"x","y","z"}) do + if math.abs(vel[v]) > max_vel then + vel[v] = boost_cart:get_sign(vel[v]) * max_vel + new_acc[v] = 0 + update.vel = true + end + end + + self.object:setacceleration(new_acc) + self.old_pos = pos + if not vector.equals(dir, {x=0, y=0, z=0}) then + self.old_dir = dir + end + self.old_switch = switch_keys + + + if self.punched then + -- Collect dropped items + for _, obj_ in pairs(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 + 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 + update = nil +end + +minetest.register_entity(":carts:cart", cart_entity) +minetest.register_craftitem(":carts:cart", { + description = "Cart (Sneak+Click to pick up)", + inventory_image = minetest.inventorycube("cart_top.png", "cart_side.png", "cart_side.png"), + wield_image = "cart_side.png", + on_place = function(itemstack, placer, pointed_thing) + if not pointed_thing.type == "node" then + return + end + if boost_cart:is_rail(pointed_thing.under) then + minetest.add_entity(pointed_thing.under, "carts:cart") + elseif boost_cart:is_rail(pointed_thing.above) then + minetest.add_entity(pointed_thing.above, "carts:cart") + else + return + end + + 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/depends.txt b/depends.txt index bfb43df..705a8eb 100644 --- a/depends.txt +++ b/depends.txt @@ -1,3 +1,4 @@ default mesecons? -moreores? \ No newline at end of file +moreores? +carts? \ No newline at end of file diff --git a/init.lua b/init.lua index 2538822..7dd5c44 100644 --- a/init.lua +++ b/init.lua @@ -13,354 +13,23 @@ if not boost_cart.modpath then "See also: http://dev.minetest.net/Installing_Mods") end -dofile(boost_cart.modpath.."/functions.lua") -dofile(boost_cart.modpath.."/rails.lua") - -local HAVE_MESECONS_ENABLED = minetest.global_exists("mesecon") -if HAVE_MESECONS_ENABLED then - dofile(boost_cart.modpath.."/detector.lua") -end - -- Support for non-default games if not default.player_attached then default.player_attached = {} end -boost_cart.cart = { - physical = false, - collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, - visual = "mesh", - mesh = "cart.x", - visual_size = {x=1, y=1}, - textures = {"cart.png"}, +dofile(boost_cart.modpath.."/functions.lua") +dofile(boost_cart.modpath.."/rails.lua") - 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 boost_cart.cart: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 - boost_cart:manage_attachment(clicker, nil) - elseif not self.driver then - self.driver = player_name - boost_cart:manage_attachment(clicker, self.object) - end +if minetest.global_exists("mesecon") then + dofile(boost_cart.modpath.."/detector.lua") +--else +-- minetest.register_alias("carts:powerrail", "boost_cart:detectorrail") +-- minetest.register_alias("carts:powerrail", "boost_cart:detectorrail_on") end -function boost_cart.cart: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 +boost_cart.mtg_compat = minetest.global_exists("carts") and carts.pathfinder +if boost_cart.mtg_compat then + minetest.log("action", "[boost_cart] Overwriting definitions of similar carts mod") end - -function boost_cart.cart:get_staticdata() - return minetest.serialize({ - railtype = self.railtype, - old_dir = self.old_dir - }) -end - -function boost_cart.cart: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 = boost_cart: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 - -- 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) - boost_cart: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) > boost_cart.punch_speed_max then - return - end - end - - local punch_dir = boost_cart:velocity_to_dir(puncher:get_look_dir()) - punch_dir.y = 0 - local cart_dir = boost_cart: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 = 3 * (time_from_last_punch / punch_interval) - - self.velocity = vector.multiply(cart_dir, f) - self.old_dir = cart_dir - self.punched = true -end - -function boost_cart.cart:on_step(dtime) - 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 - - local pos = self.object:getpos() - - 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 - 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 = boost_cart: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 = boost_cart: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 = boost_cart: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 - -- Direction change detected - if not vector.equals(dir, self.old_dir) then - vel = vector.multiply(dir, math.abs(vel.x + vel.z)) - update.vel = true - if dir.y ~= self.old_dir.y then - pos = vector.round(pos) - update.pos = true - end - end - -- Center on the rail - if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then - pos.x = math.floor(pos.x + 0.5) - update.pos = true - end - if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then - pos.z = math.floor(pos.z + 0.5) - update.pos = true - end - - -- Slow down or speed up.. - local acc = dir.y * -1.8 - - local speed_mod_string = minetest.get_meta(pos):get_string("cart_acceleration") - local speed_mod = tonumber(speed_mod_string) - if speed_mod_string == "halt" then - vel = {x=0, y=0, z=0} - acc = 0 - pos = vector.round(pos) - update.pos = true - update.vel = true - elseif speed_mod and speed_mod ~= 0 then - -- Try to make it similar to the original carts mod - acc = acc + (speed_mod * 10) - else - -- Handbrake - if ctrl and ctrl.down then - acc = acc - 1.6 - else - acc = acc - 0.4 - end - end - - if self.old_dir.y ~= 1 and not self.punched then - -- Stop the cart swing between two rail parts (handbrake) - if vector.equals(vector.multiply(self.old_dir, -1), dir) then - vel = {x=0, y=0, z=0} - acc = 0 - if self.old_pos then - pos = vector.new(self.old_pos) - update.pos = true - end - dir = vector.new(self.old_dir) - update.vel = true - end - end - - new_acc = vector.multiply(dir, acc) - end - - if HAVE_MESECONS_ENABLED then - boost_cart:signal_detector_rail(vector.round(pos)) - end - - -- Limits - local max_vel = boost_cart.speed_max - for _,v in ipairs({"x","y","z"}) do - if math.abs(vel[v]) > max_vel then - vel[v] = boost_cart: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 - 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 - update = nil -end - -minetest.register_entity(":carts:cart", boost_cart.cart) -minetest.register_craftitem(":carts:cart", { - description = "Cart (Sneak+Click to pick up)", - inventory_image = minetest.inventorycube("cart_top.png", "cart_side.png", "cart_side.png"), - wield_image = "cart_side.png", - on_place = function(itemstack, placer, pointed_thing) - if not pointed_thing.type == "node" then - return - end - if boost_cart:is_rail(pointed_thing.under) then - minetest.add_entity(pointed_thing.under, "carts:cart") - elseif boost_cart:is_rail(pointed_thing.above) then - minetest.add_entity(pointed_thing.above, "carts:cart") - else - return - end - - 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(boost_cart.modpath.."/cart_entity.lua") diff --git a/rails.lua b/rails.lua index 04d86b8..e5b2f42 100644 --- a/rails.lua +++ b/rails.lua @@ -1,21 +1,10 @@ -minetest.register_node(":default:rail", { +boost_cart:register_rail(":default:rail", { description = "Rail", - drawtype = "raillike", tiles = { "default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png" }, - inventory_image = "default_rail.png", - wield_image = "default_rail.png", - paramtype = "light", - sunlight_propagates = true, - is_ground_content = true, - walkable = false, - selection_box = { - type = "fixed", - fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2}, - }, - groups = boost_cart:get_rail_groups(), + groups = boost_cart:get_rail_groups() }) if minetest.get_modpath("moreores") then @@ -28,7 +17,7 @@ else "carts_rail_cp.png", "carts_rail_curved_cp.png", "carts_rail_t_junction_cp.png", "carts_rail_crossing_cp.png" }, - groups = boost_cart:get_rail_groups(), + groups = boost_cart:get_rail_groups() }) minetest.register_craft({ @@ -62,7 +51,6 @@ boost_cart:register_rail(":carts:powerrail", { action_on = function(pos, node) boost_cart:boost_rail(pos, 0.5) end, - action_off = function(pos, node) minetest.get_meta(pos):set_string("cart_acceleration", "0") end, @@ -98,7 +86,6 @@ boost_cart:register_rail(":carts:brakerail", { action_on = function(pos, node) minetest.get_meta(pos):set_string("cart_acceleration", "-0.3") end, - action_off = function(pos, node) minetest.get_meta(pos):set_string("cart_acceleration", "0") end, @@ -134,7 +121,6 @@ boost_cart:register_rail("boost_cart:startstoprail", { action_on = function(pos, node) boost_cart:boost_rail(pos, 0.5) end, - action_off = function(pos, node) minetest.get_meta(pos):set_string("cart_acceleration", "halt") end, diff --git a/sounds/cart_rail.1.ogg b/sounds/cart_rail.1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ba2d9ef8c7bd373a2d927dc3bed66ed1d16f69ea GIT binary patch literal 7543 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 literal 0 HcmV?d00001 diff --git a/sounds/cart_rail.2.ogg b/sounds/cart_rail.2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1b930f03e1fcd489c0a29a43f25faf2e828ae394 GIT binary patch literal 8080 zcmcI}c{r5c-~T;!lF$$$lx>8JrHmz8wqe3Bgiyx5WlxG^3)y1qJ2PZ2TSQW_l{Jht zOJVGN3K6O29zK1Z&+q$O*Yo`KyRS3ndf(@L?(^PW@Ao<98hr-`BY*<<`w?I~BIJ9& z&p^&V{BC*KIC+zYAXmzdI8Q4eheQKJpZw)N5BW8uzSeu)x}?& z<;5WSBrk+Ps*5jFPih&TbX9)&NnxD)ib(B%+A;Ck4@f=uhzYjxC5`1h?>+=6>+g>K z&<;LO7d0AATV^#n^74_eqBJjXsUx%afMC!}KB4hTAuzU6|`8pmyDLTOsBUZ*tC4E zbmR1TxAac@2?9Cn0H6ZrZujKw2EBn6AM%nl^;R17Mva25AbaG0e;yp-1vG>|+djoP zm`*z3=Am1H!DTt}>C_pIB!Lqm^XK_81~N8#5lk5ic_q-yq1REynZk`FX_bYzF{ z1;)|XK>3UKlg{|WGbQE0yI}Y;v2mKnl28LQr!c`MsUL}WJp&2{<$oMmWkSFsOXC z392-lhe;BV=JgI%3}(f@1NWCeKMT*`zgquSZbMczlh4B~ z$9!i-17}8KtxrY#C$Rp>8~{w3u*00h*~Gv*vqG<`(H{l)Pv&?+J0Ej*KNirf7C?Ov z>-{F9zbP|#Qo~RNWyEGV$mTo5ZfA7Sa!A&02xB+mXE$DNXYoAHtp4wWIZB(EQQv=M z4mm~6BH})4CsO@)=H!XQeUghir;{jPmMC;5#UU!ABt2)jq=4Z+GRHBxI4!z3GMW?_ zClZz76qQlhSm64ua`nak+5X8KEiVDEftjP>CGfAzX@zsEgV|IqhT1%`QE3o#sE@{} ze+K{nS`!#`59^2_T6_d8JAxL+7%Tnn83S@hWKcsgpktE(fD-`Jm1sEcF*AiS>)ujw zWPsCFY~N6&A7=$zp48PGu@9Agmk-1D<=x^!%aXDnRn12eT+XP#kE9xYh* zr~pF%2!?mS-*R~M>2&8^>4Vc}<{@osDP3 zMpb|x0kq)nSpGl)8$<;P05*(h23R-_-3MDtK)-`8aHElk;w+;+1RYOUG@K?olo>|g zHtK_sxWjr>7qUVJR7C)QuN4CR)I?-#H2`K|U^aofKS@RxDyVx>Lmz54dJ<)tBGU_% zH8jTbp2Un8VAn0i6fNtEuEG3KNOh7B{6g2L3<^~>0e8)Ga_VMglhCfS2bLD)#6 z#@R6=MAS%~?`TS(WxbnanoO?@c_|bWGvb4pya-N~HT1z4V(iR}F=i%q<3x-pF;EwZ z88t$UvtcmxnDK2lOp5P}55~gO&g_(l8G8^|&1Z;#g-=TB-CtBzl~u3pSG%8XTHn{s zu+PX4DJ>O>E_E&~b;~SunM?s+pOjXwrI(%u=dG5WpKJlS=Sxe)9+ir|FBRK=A=>mp zqO{b#tXk}6(fQSu_5GMhqVJrsrKPFe_zuSGd*I|w@*L6k=C0mQJ<4>%*T^^UBhmMB zx|_9}CT1iMlM;S2*=}w$koV&b3kEX^D(wf3S)cNA7!S0DtzMiX2KH_2bpL3YGP4VNFRiFFgmW{(_mtaF=d477IIzeu_r5hia3Vmg=-Pzk2D z%R2-`7661Mct^wNl5Lpf>0rLh=u(c*1cb04mI)~$NMJ@fvtgN~It6U|bm#?Zp-88a z&;+yyq0Rv5Qc}ld$w!@VBu)GqlylqO)uF% zm%4fmm)K{-GcLV|Ig7v|F+x&3tCPVo;;?eLl*8P-tk8`tb^U6LcGJ= z2nj#bs@>dWy|He>%UPVrjs<0yV=G>Ea@`dJ&t!**7A9rj*o6f67=U2DD=-pXh7PgF zO`zgm%qlcS(By}uh%WMlC7P46!cuJ3xIqdufM`=*hr^l2)UgAA!7O;dyAfLS_7e~u zQBDOEfUj6)8%DLz7<7!PBO^K*LH8IP1BMtK4JYAk;}F99gct-V4+m8PBc7s0N5JFE z!80f|6L^Ze=!g_!0u_oTD+EZ=z#}-+2jDQU{HO{ECJo5Z!hCU3NWxoItY*nu6PuVk zZ=4#Elfa5~f)^UwFg^o|jvuIy-wl2!S2Zip1!6D85R{uGO$T29cWBNqD0!QPVnGeE zyrbbA{RLa$qc@*0R&i$nAMiDyqUoL%cl=P%OA!pPs$1f&w$`blT8lX)5k=r z5wEok^7+q0f)zoCa}csZ4UmOUEHhH41QZ9FWQ?HA2VGP~W*{qk$&5M<1&}dp9z(#9 zH^fl`q^@|!HE%GK9NxVM0ti1@(scmX2%!YV9>ku_JQ+j9_Qwcj(0Lsk>J0FXG;AL~ z2(lRXp%I|!NN@~P{jei(9~l-0C)qxffW%14SGa&FAN4!F>vBFumLfo ztVW-IG#QN)$p>G-$p&EM4MT%4en76&EIx^bb}Sb&Jmw(a7Ay#;&;Y?Iv9jOUU2Jh) z3CQudT;ieDpyr)H_L3(&NOH{4(V zH~|3NG|Vh4z1&(4J888eyXbVHy6KUi8sGzIr3F+HDXv_(BHcMS_Z7l=R!R+R;}ybs z#0GmRDlkL9hkU!HqB^`TQ-!G?4guurGI?BXz%}5s*yq3IzN}mj6PFMZzo4u-H`dcX zFf>nEnH^`F-@Uj>U2W-^M;&R&`yk?Vs((N;Tz0Fok6p0m|OGoo8AMPN?CL6p_5cKL4mRG!Q+D}zGl0WahahkuW{ob?FEJ+#R{ufJ-^fE=N za;=a?osz#eZZoLr#^*GEiH4$B2J%Y~&wtf1G$m6tx`K+hebC+8cHlu%zRLs z2Ve8;{Gm~6KYO3GOkl(K8KvCx#H~H7(8hXe#BJExi^VPW*F9FJU+A`FDBhkM1!V!-y7fB(c@K$$Z()ev8^`ayKk zgIl+yp1JRsq_~r>6|4N@Hc7WKv+hmpm4n9P&o4V(J@f7o7Cxdl@N3k_8gh`aRJrsa zt_6Egy~^ityy8ZUeT%R^#gQuL#14O0VF6de=C_0i9_3EZYxtKx48n&C!~D_s@a*^uEejZM(&sg zQ|&_Max!Eudw)!p@;VleCFgXQgNJkM*#{wZlTAISsUOK9o1Jl8vHHYsw{3<$ieTe! zc|-F6c1J4FW1nczmy1UH7^gFTuC3>o@c4=HTWGDm{1Jq1X9rG7QCDaDoTPu;O)Bh@ z_&q;kE$5w9r1{0g;=G38>S9s-Ty1V2V|GuIhm}I?fI6jgy-eh<%>4&O*Pq4Yx?|*o z3JRQ#Gn5OhrrQ`j%>y)xQC*IymwZM6hjXJN<|=8m#GvUieiS|73%uu0bEAvB$OZp#{)O zjKFNNe*R;RzVqJ?`hW3^h0XA~3TcFnKljR;|Jg}OSCN0b~ zdw9FK-GJ*7FB!ICl^xSv_vf_v{LbY4xIC2AM5V;R3ACPI#2Vp3<{l@`?j@ z8DiSi#uqbBF`A|ec@cMp@4%|}E08r&0@gJwZKqHp%;j|f}c_C%3I{~xyGWpWI=6(k^ zqoxPtYYR_(I;J2O=x3dOl(6~8t|iaxDb57AlfDgmkN;J$RZ#e|rRVy4!@3K8;OX}s z5>i_@y_U~5`j_%tq)15p?a-!au5rc}ZOwq({+-3NpJ%drE!V214+`QpX;72T;ou4Sg)_AZ&9A$&P6b>WRXZF-`$$sD37$}z;bYoY1%~f@qB7x0n<0Xz zpB+hC@$Y?wOqO*y9uO4u$ZT>lJXTu6_#TEppZ@~tI(#flpwI{y_Go!BZ_Y|BY5%xQ ze@Vki;X}Z;8cgPyofBMD4Y+4>+j~E|2M5b_$%$leeP~MB$U+eWE04lNcJgUgWr6QZ zx7Fs7ptUzYUp8G~yR{ZAOyyE9L5v+65DC(Pen$zGXFK{8wahSTCj}*!ZkH)4ZKUp$ zOsS=gX^-(YC_woQ{L&niFU&N$Nf2&R_~Dpx?xKc^AN>v{3fs3GxbAuB*JXywm;p-1 z27XpLP;G%5_ny11*6Qczo!_ZS`vs3ibxpqz>cksw9~31{y*RHGi=)!{mYnmkly9uL z*|16UyBAGjTd>+Tf#ZE$2&p5qx#3f~$;nBCR6}*vRlNENN}{lG8D85#)jt!@D~9T5 zIpIh@qQIMaEP3}mySCKB+t+el&cRq|bN1W>HCF=8jV&b2N=2_N^008)+A5=7w{%Gz z*!$6V!+6zRl43{oiyKDtbk8x_Fj*}m^)*&dg?-sMxCcf)N7T1#;rs6htFrKM59OvC zH-Gv+Al|ja$s1Dvl0eVB^n=v9&GHvMTI42PI{m7idNP+io9P{I>7s$Xfx)YB1<81e z#_!V=3*sFu7fcv=NrUMT%^KC0tef~b<(>uRiD(6>-ZU@2t?&}Ndb*}gUG)P4yT#Hg zcweZbsJ~e$-B5~K5tV^}6yvzxfy83%(xa`j&0ZSQs)eb)D!O&P`#qH_#yHw+bDODW zu-=QyHTUbaFKai8LNc_4OfsU4Kd1{o4d?vqzehX)XaG5TF~bfk_bH8llaEiR^E_Uc z`pos91qkThY-XSZD4;55n61tFN4Yx#qSp71TfW*qtNam9N%~bY?dz%cGEJ<;ymR*4 zV*;O1^)zc$ec^#llN5UC(rD#+lhz=`WjIH3k?Xki#jq#C4n^(>C{^$KHTLTv7PZnv zpH{yXy%e%U)cNt3*q#6OqMOA*WRW^0QihGDpQiZUDG%P%UB9Gt0jm@nPPAa@0)F}j z;Bc4a-XF`-7UK2u{iKF9p3nDIboJm0Ujp9ujXD)++pP6f9P6mBTk2k=xpoKDj zx_`zB;H_+|`mp7h#hVew*IT^zXAf+@T`|b2EXrr|{cH6)-ji<5$axpwcFvW1Nf~Rx zdt5rEl5XX;)PcAH$Bu7+Eq+<9I(-pqY9R8cg%b289n-C+L)sKpCAM#`1s%}ZyUAoc z;jM4Vy|`q1!!L?>Hy5a_W_8sDN*kx$)H6T#C$#4J7uQd#O3$oMcWXqs-0r6AEe&FmuBDON`ZcU%T}=f(xzOpQgSa$|l=P-OgvHPK+Zzox z?@YvTe;zBr{W4v~&rNsUX~btigvQp=p1*zvIH`+MC@sI*Z*2UPy-B!gLcgRglPw!a zm$uw=Ra7={s2aWi6OK$bw{IDxUYhAvTu9)RO1$OyI$w;0=UYcW< ztN+h!sNfBDzzSC0itn^lD~Ly?W@z&No*nGsWRG0kt}3o^^dX7`ATlA{4E8 z>Tvk1oUPGKu%>l5Um0OCtr+v5Nr8htM>VxJ{r#7+_UpC;L2MVd^^0Ra7KE_Pq9TrFM-g1OtTp!8xqZ-JVhJg zXbv39OVwD9=!*yzM$+J#Ui8Pv#LXlMzdC+z+gfq={7%4a{fS-Ew+hnmX%=Tr(YHcL zTGo^e&LU=W?WSJq0aBWx*|=u>_3IlhE?shryD>e20f5&~mb2uYa4G}Wbfl@lqm_m! z8q0IkPgWjNhy#guz+J-qcbSVO%A46pC>uoy{gG{c*JQjSOr7l+UNv*VAOA*PFT)`J6HVQ5#Ftw z9SMvwC||xe85xywsdrGM(uMI3HLx23;eK@xz(Yv`G1+t0G5Pf4tDT}7uOVgdxgeY8 z=Z&t1>QcgLWKOfhIhok~VyMdcx*I7?%L7~%Z0258Iz?4-m&?Y=|B{3+XGLQ^?gpGX zI4o&p#XtPE=VaQguZGHPJHETC@CL5c74^nGlIy~pLznAw@R-18tf#W zi=|0e-MzNj>9H7w_}w<;(a+1yMC& zF~#?+;?XwM1jS>K%R}g~@OJKhG4)wjfc!Kke@0 zcF@Gv6?x9x@zj=hBo3*lpm110RZ)>4jfri0GFn&f6cfY=MleRmrtqF5A_M^)XC=`* zDK0oPm69(VoK88xpqdoo(kXFGLL@S#`A>&bcH)O1Rw!JWE3t4I=RwE13P~mU=3(7W zs@*|y#G8#{xn8UsAbA&87khfs(J0PcR%{T6g!T#XC^&)9ImU`d3xO^M;oB;HC);<2 z%-y_eI}}@y5k`u0MJXrM=k69Js(+NO?$O;TU)_f{!VH`uRXkz0yW&U3&Sd}Hac%j5 z1h(k13p>GdIT_+((Z%V$K-9KdNGKe*2^rjk<0|1Dm58ohNs}cxvpp)kO2^DCjEUe0 zu(J$w9tsQ?3JkGIkFt3bV)ZD>W;n`jHOfIK`tSB5WNekuwp>RtL3#VJX_M#z36X;L z*aDWw=yGPri4hVcSF!{;xe!(x;u*HOj2H3ejmm0=B(=bl;oKbnhG zn)CnNwB1@|AZ-xKmJ5iM3zEkOk}bY+99t1?g+NZV335%o$_9SQEq)rH8TjkBQH;92 z8SVa)1V-8+NE?G_x`1c{(ICk8`>I&_X%6`r4}nlHBJ#gKSGVv291+iPN%IKjR7!H# z3QIT;E0oWv$GxoyOo+|@fXwX4T<*g1WKQLkz_a=r;w-YH>PphHwla8*E<6cxiEhtm z%FF1<^rPdrGpXBDFkco2t)vf(3D0EnGz&X`dG39g93>;C!M|2LnbUXc;DCywWJ%T3 zS!~Pu8x|#Jvgr-j3GheU>nzVYE;~T|bjn`r;$n>bCw#O)-W-O7nscrpLlW5?;o`;O zDPsONcvA8(t>_}Lgv8_ck_c0RuoTt*+Lq@?2#SPlq4-a?1?6oN7bVBbv`ateRP2yr zbmfh7_4)gaFgh(y^8YYN}5CqqGh0$)vbWhhf#LJ0b@fUV?zn{ zg3rG|5T2FuOw)oa*p{*{;P7HV-R|vnyTcCm$!Y?>;(b!KPLF^1VGU9ByPj4 zb;OJyKR{3!AjlIfH2?RC0o(y)<9=lj*i;A-h9EsncHt{9o(P!Xc}=$+7!KOXSsixj z2Sj8OOplD)E#lvU!(-jZ*bj&g;~35i(zOc-7=u0-n{G>Pjm+9fM#}(0CG}J4M+LjxyYH%mnT{w8z*oO}PuMVrD_?+Cv<$AdU(c z6Khe%dyEagSrGReBk~jb(~OBV&fUt+BNjwELE=EI^Qb_mC4h~R@rVF%V92f9^oevYl$N( zUc|J3F@K_srL&dbDJy|cu$qqzh7{f{t@VCfaj&fE(|VQn-ui|06PYfVnbM`D2jWXT zN=v=6NrB z98#SphC=qeUF9Vbhk((6V9Z`H&~+rl9zCx*F&IK$T&GOL9zxXB(hv{4_jtdxOdI=w z?oiUu?5B8{69eA*+r51lVDr<<&XrvHalTY;nP~YvYE)3B{`1~rTRKAQYn(~LA!8Q< z9olNV93F*;jtr(g{rX|Oxwpn^qAtYVeO+}XC^}Nz8DKh`1=0@Za?aZVMK(BUB>Bao zIa3{B>YV5R7@<@sA_*&nBJtp*QB)Y-gP#ObY>{-L^c%7tvMt32FRPKcvXUO8T;H4i#=F&y-)sTJ}}s2mjY?84SC ztYR=|wQn4|>Vp2;^HU_?IYW8sur7|F+)KXsk8*UgfDKK)+1ebB*|Mt1+H;|<=C!Yf z{Gb2{=&&Zyp0tQumIG(9twM`bG<9pmL&zOaI8q-yQ>4s^okmbu#iz3iEwJqQk!dp1 z$jD^tne50k$4>}A0S5*h%WEhUYjTYM1euP51K!z);+OBCsOWN5r~rJDV2<3n5o7{c z$Bmm1kLA2cAcGVW;xRKtPKj74aVi-*lShH;f{dr>a#D*Z*5C}v#sf}~s&05W9$-*B z!yrh39TP32*MmWW1jW9a=aN&e*2mBw#_dUp%Inv1?C*PKQ&R!9Yg# zV8Lnx)1A%s;{p4ZKOM^<9tl&Q$&LVLKycFJR|9+cuyhr+;e=_vxGYn+27tH_H9Nu- zUkE3`@VFA74!C52<;VviDq}G4v;YR9M(7A&ys{=!DU1zq$P{lV-+bB+q*BPQ3rhv? zQ<<@apv7w}(D2m+iL5@A#I|FJ z7=};S7DhLdO=5I@bPxr`CpbL)4<~%U+RZtuwl(1}k0T8nA3SL)zNVhrR0-pgF=o8Q z`2XKQN#~ML`I%s{JmXb^`L^CMSNDG1ot!CJ99$7>o0I!5d(kS52 zbw+s6Xh4xMgQvl|0~p|50_4%QZYXIKAcaBIfDE3)G>ecD*dU+7YLK?%uv#O&&uYTW zXA2KW03YI8u$l!pLN_sT!+?+kRfrLNB)}FURRC{*YGsUVQJgt{#yIo;83XX!o(HD< znI|6E^UoNc`u4vJQGX~?{2vOYEB!Zv?+-2l?7PbT1{^T8*WYzkK1NCJTIRmMuq z>z{1q%&VH|Grecr3c|79Kr?tQJwCf+F{569S7( zTwNp&x~M3v2TM&dqYw;Hk(@g2*k++4%@9}-SRtX(+IdRYi4c2x459 z8RL&*KXvZjzc@EJH8(slF*C6|JNdrz$HC`p<1B6O6*5g!q#n{^`VIT(-}~zlKSmxT zecSx;XeadgfQGbihL~4CR9L(0jb)qtExiFwc4e}nv$%&gk1`t$u-xx5J9OBo^r1;Z zO4mKN#v79Z!Qz&#@3KAfg%%QD|9n;!Sa$`FO!y%lUK}{5u(Cdqsaa&W^zr-Gx#qa% zKZ6G<2hEdqWrlwb9(X*~c-hlMh;CpO}X3n(4i_x{2&mX_rz zJ=UJ&{SJPqx;9tJ?%CHvdkVcCw`niWX%1up1#U|E&tfZ zg*O3p>vW$N{8bJZR<_qyr&PUvX)n(aFQ7l%;I+Huv9jbi?c)ybymY&TVk6{(LdK8y zP|rM?a_^^GDlV8m(CKN(pV3&2$h?E7=PK&B7*;GwuXsA!Ka^-#^=tQ!(eY2t!?)dV zJ{1F(PRf=&S=;f+^j@>wwH4^8bT(G;_D0gzH0C?#nA1DN zTsG@x)*cP5rwyGH3rnkYEeLrMF0!i=W^B@{K_{3fAd+=+ziF=ysddYK*LwBofSa*- z_fxmw>(1R9@bXsG8wbp!12?~aJ{hN@VSZrjw7;u|j2G`xUPlb4zhev{wakY0Wb%r- zaBE$D1}o~SdxNO!ewEGlZrAQ0D>SZjXm%2eYX^_3b~kp-qXeG+W-Xl9-Ot>BnG8wy zN2In6hmgCz9l7&~jjJyBuFB&pVVb^2IetqSI<0ojCM`Q&b7?mWVc(E8igj6wmA;7A zKdOHD*Lp{Bgc&V;w~O*!5M>ZfS7`@5ege{XEs@L2PI;*bzrdieXE zkF-8DC+6g`mDVyIZ3o2V`&RX@*xMs7Sy3 z)9duUR*&e=Jg%G!F^`k9$6UkTPUFM1Sq7#`ck(Vr-b$O1>VownqngUd{;J>oGSpgf zrp-+CbkB&}OycxdtU2uHL&a^<=%1H~Mxk<~y0y(o%BV|K< zf@)+dh0H1@<#p69`#$H*bio*Uu(-VFV&9IS4#F$@X-Ct|RY~Zn$O?>Od5nW?yw}h5 z3ykF$3vKA89Q6ByOA2%Blf$c*i@(JfS&mP1CvF@HJYvtz)8=f^Rj%~*VR}}`@jc=` zw5DE%p9L8QKBzKl1mjf?W;?EX&gVj zPcGoWK$UJB+FoO)7HkX^XmLW9wUCl4j$L&WX>c#|GddbeVt3Oluw=Tw>l8QC)={sz zp;`IN-O|`dRWSRo=ZgvbnYVP?by@$jy%um6(uzLpg7rSN@`^llMiB z*n8cPRXCdK>L(+uIOtew{wP3ZrW*Tu!B1y-RM6pyNWce`WG^1~51c%Qixl^wm4aqt z2AS`xWOu%NM?LB3uruV1(VMoc$8o_nf(9fpJ@bK=aM=MpzCN_C17XqKZMA~0o~2db z#Wboj_S0+M{@k6rW=+L}xaQMWZ>h7YwWi#D^ceo4x!{tA8W$8AXJp#E@~l_7R#81zx|u`gWwZ#Z^{^cop{W$mxpLx3d#|gvR@rUtV%n$*8j#9ZSvfzl5l``@r!< zTbqUDx3V*@zE;{}<~#Q*gctWGi#7fJg|AEXdvY{jE?Xr^R3y`)-mYSA<6XitbnS;r zU+z&h-q*K zzfE8EzMw*d*acUC`4#fwh@Gp`hGv=yL88Xhr7u_3pv((`CKaAvYzhutuSevBt-U>t zbI--x`beyy7p&6f^Q^YsE?d6Faw$jh=Y!=IFbRp(rcaD>2^c)f=#OQ&`miuishO=8p5k;iQh-}+<3!}e zNP(lbho~h@=_cOwN@sCxs#zK2S2vtaD^VmpBVSzaw>9RyJF_nAr$m#K603XcaTw>y zDV1cx?M)mPJ}Hz zUCbC=`syTD>3&3MFypVo?)SBs#H8gSO;)L9M>i+cbI$XuzR4_VV1>~Jt*Hmtn&7c# zradcuc3=JxTK9;ib?n(%W8$0d?U%E(oT95u%Z|xZ$33=pU+L}ErrbKX)*%ABB#ulx5++4k|6GNQ0my%%6hLmkl z=b4l*Hp}PrwX(Z71c_q<4X>{b9td9yUPDKKyoNhoc)$5&Ac{8H@bm4H^~G9~pJ8L6 z{3;Cw3)j1bb*?S_@?g)KuXGN|738(KT{oV-uxq%&wQPxM+ey6gGAelFgJn6ex=s2=}Um1t!KKm@KDY6vgF<0xJoO_B-`ZYq+WLY%h z?%D4vqWRH^R???yvcUz3pneqauRjE7K>r0w5Tx4x literal 0 HcmV?d00001