--[[ Hyperloop Mod ============= Copyright (C) 2017-2019 Joachim Stolberg LGPLv2.1+ See LICENSE.txt for more information ]]-- -- for lazy programmers local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end local P = minetest.string_to_pos local M = minetest.get_meta -- Load support for intllib. local S = hyperloop.S local NS = hyperloop.NS -- To store elevator floors and formspecs local Cache = {} local PlayerNameTags = {} local kPLAYER_OVER_GROUND = 0.5 ------------------------------------------------------------------------------- -- Elevator Shaft ------------------------------------------------------------------------------- -- Down, Up local dirs_to_check = {5,6} -- vertical only if hyperloop.free_tube_placement_enabled then dirs_to_check = {1,2,3,4,5,6} -- all directions end local Shaft = tubelib2.Tube:new({ dirs_to_check = dirs_to_check, max_tube_length = 1000, show_infotext = true, primary_node_names = {"hyperloop:shaft", "hyperloop:shaft2", "hyperloop:shaftA", "hyperloop:shaftA2"}, secondary_node_names = {"hyperloop:elevator_bottom", "hyperloop:elevator_top"}, after_place_tube = function(pos, param2, tube_type, num_tubes) if tube_type == "S" then if num_tubes == 2 then minetest.swap_node(pos, {name = "hyperloop:shaft2", param2 = param2}) else minetest.swap_node(pos, {name = "hyperloop:shaft", param2 = param2}) end else if num_tubes == 2 then minetest.swap_node(pos, {name = "hyperloop:shaftA2", param2 = param2}) else minetest.swap_node(pos, {name = "hyperloop:shaftA", param2 = param2}) end end end, }) hyperloop.Shaft = Shaft local Elevators = hyperloop.Elevators Shaft:register_on_tube_update(function(node, pos, out_dir, peer_pos, peer_in_dir) if out_dir == 6 then -- to the top? -- switch to elevator_bottom node pos = Shaft:get_pos(pos, 5) elseif peer_pos then local _,node = Shaft:get_node(peer_pos) if node.name == "hyperloop:elevator_top" then peer_pos = Shaft:get_pos(peer_pos, 5) end end Elevators:update_connections(pos, out_dir, peer_pos) end) minetest.register_node("hyperloop:shaft", { description = S("Hyperloop Elevator Shaft"), inventory_image = 'hyperloop_shaft_inv.png', tiles = { -- up, down, right, left, back, front "hyperloop_tube_closed.png^[transformR90]", "hyperloop_tube_closed.png^[transformR90]", "hyperloop_tube_closed.png", "hyperloop_tube_closed.png", 'hyperloop_tube.png', 'hyperloop_tube.png', }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, 7/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, }, }, selection_box = { type = "fixed", fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, }, after_place_node = function(pos, placer, itemstack, pointed_thing) if not Shaft:after_place_tube(pos, placer, pointed_thing) then minetest.remove_node(pos) return true end return false end, after_dig_node = function(pos, oldnode, oldmetadata, digger) Shaft:after_dig_tube(pos, oldnode, oldmetadata) end, climbable = true, paramtype2 = "facedir", on_rotate = screwdriver.disallow, paramtype = "light", light_source = 2, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 1}, sounds = default.node_sound_metal_defaults(), }) minetest.register_node("hyperloop:shaftA", { description = S("Hyperloop Elevator Shaft"), tiles = { -- up, down, right, left, back, front "hyperloop_tube_closed.png^[transformR90]", 'hyperloop_tube.png', "hyperloop_tube_closed.png", "hyperloop_tube_closed.png", "hyperloop_tube_closed.png", 'hyperloop_tube.png', }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, 7/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, -8/16, 8/16, -7/16, -7/16}, }, }, selection_box = { type = "fixed", fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, }, after_place_node = function(pos, placer, itemstack, pointed_thing) if not Shaft:after_place_tube(pos, placer, pointed_thing) then minetest.remove_node(pos) return true end return false end, after_dig_node = function(pos, oldnode, oldmetadata, digger) Shaft:after_dig_tube(pos, oldnode, oldmetadata) end, climbable = true, paramtype2 = "facedir", on_rotate = screwdriver.disallow, paramtype = "light", light_source = 2, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 1, not_in_creative_inventory=1}, drop = "hyperloop:shaft", sounds = default.node_sound_metal_defaults(), }) minetest.register_node("hyperloop:shaft2", { description = S("Hyperloop Elevator Shaft"), tiles = { -- up, down, right, left, back, front "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR270]", "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR90]", "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR180]", "hyperloop_tube_locked.png^hyperloop_elogo.png", 'hyperloop_tube.png', 'hyperloop_tube.png', }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, 7/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, }, }, selection_box = { type = "fixed", fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, }, after_dig_node = function(pos, oldnode, oldmetadata, digger) Shaft:after_dig_tube(pos, oldnode, oldmetadata) end, climbable = true, paramtype2 = "facedir", on_rotate = screwdriver.disallow, paramtype = "light", light_source = 2, sunlight_propagates = true, is_ground_content = false, ----- To be unbreakable ----- on_blast = function() end, on_destruct = function () end, can_dig = function() return false end, diggable = false, groups = {cracky = 1, not_in_creative_inventory=1, unbreakable=1}, sounds = default.node_sound_metal_defaults(), }) minetest.register_node("hyperloop:shaftA2", { description = S("Hyperloop Elevator Shaft"), tiles = { -- up, down, right, left, back, front "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR270]", 'hyperloop_tube.png', "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR180]", "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR180]", "hyperloop_tube_locked.png^hyperloop_elogo.png^[transformR90]", 'hyperloop_tube.png', }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, 7/16, -8/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, {-8/16, -8/16, -8/16, 8/16, -7/16, -7/16}, }, }, selection_box = { type = "fixed", fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16}, }, after_dig_node = function(pos, oldnode, oldmetadata, digger) Shaft:after_dig_tube(pos, oldnode, oldmetadata) end, climbable = true, paramtype2 = "facedir", on_rotate = screwdriver.disallow, paramtype = "light", light_source = 2, sunlight_propagates = true, is_ground_content = false, ----- To be unbreakable ----- on_blast = function() end, on_destruct = function () end, can_dig = function() return false end, diggable = false, groups = {cracky = 1, not_in_creative_inventory=1, unbreakable=1}, sounds = default.node_sound_metal_defaults(), }) ------------------------------------------------------------------------------- -- Elevator Car ------------------------------------------------------------------------------- -- Form spec for the floor list local function formspec(pos, lFloors) local tRes = {"size[6,10]label[0.5,0; "..S("Select your destination").."]"} tRes[2] = "label[0.5,0.6;"..S("Destination").."]label[2,0.6;"..S("Floor").."]" if #lFloors == 0 then tRes[#tRes+1] = "button_exit[1,3;3,1;button;Update]" elseif #lFloors < 10 then for idx,floor in ipairs(lFloors) do local ypos = 0.5 + idx*0.8 local ypos2 = ypos - 0.2 tRes[#tRes+1] = "button_exit[0.5,"..ypos2..";1,1;button;"..(#lFloors-idx).."]" if vector.equals(floor.pos, pos) then tRes[#tRes+1] = "label[2,"..ypos..";"..S("(current position)").."]" else tRes[#tRes+1] = "label[2,"..ypos..";"..(floor.name or "").."]" end end else tRes[3] = "scrollbaroptions[smallstep=100;largestep=200]" tRes[4] = "scrollbar[5.3,1.5;0.4,8.2;vertical;floors;0]" tRes[5] = "scroll_container[0.5,2;5,9.5;floors;vertical;0.02]" for idx,floor in ipairs(lFloors) do local ypos = idx*0.8 - 0.5 local ypos2 = ypos - 0.2 tRes[#tRes+1] = "button_exit[0,"..ypos2..";1,1;button;"..(#lFloors-idx).."]" if vector.equals(floor.pos, pos) then tRes[#tRes+1] = "label[1.5,"..ypos..";"..S("(current position)").."]" else tRes[#tRes+1] = "label[1.5,"..ypos..";"..(floor.name or "").."]" end end tRes[#tRes+1] = "scroll_container_end[]" end return table.concat(tRes) end local function update_formspec(pos) local meta = M(pos) local counter = meta:get_int("change_counter") or 0 local changed, newcounter = Elevators:changed(counter) local sKey = SP(pos) if changed or not Cache[sKey] then local lFloors = Elevators:station_list(pos, pos, "level") Cache[sKey] = {} Cache[sKey].lFloors = lFloors Cache[sKey].formspec = formspec(pos, lFloors) meta:set_int("change_counter", newcounter) end M(pos):set_string("formspec", Cache[sKey].formspec) end -- Open/close/darken the elevator door -- floor_pos: position of elevator floor -- cmnd: "close", "open", or "darken" local function door_command(floor_pos, facedir, cmnd, sound) -- one step up local door_pos1 = hyperloop.new_pos(floor_pos, facedir, "1B", 0) local door_pos2 = hyperloop.new_pos(floor_pos, facedir, "1B", 1) local meta = M(floor_pos) local owner = meta:contains("owner") and meta:get_string("owner") if owner and (minetest.is_protected(door_pos1, owner) or minetest.is_protected(door_pos2, owner)) then return end local node1 = minetest.get_node(door_pos1) local node2 = minetest.get_node(door_pos2) if sound then minetest.sound_play("ele_door", { pos = floor_pos, gain = 0.8, max_hear_distance = 10, }) end if cmnd == "open" then node1.name = "air" minetest.swap_node(door_pos1, node1) node2.name = "air" minetest.swap_node(door_pos2, node2) elseif cmnd == "close" then M(door_pos1):set_string("floor_pos", SP(floor_pos)) M(door_pos2):set_string("floor_pos", SP(floor_pos)) node1.name = "hyperloop:elevator_door" node1.param2 = facedir minetest.swap_node(door_pos1, node1) node2.name = "hyperloop:elevator_door_top" node2.param2 = facedir minetest.swap_node(door_pos2, node2) elseif cmnd == "darken" then node1.name = "hyperloop:elevator_door_dark" node1.param2 = facedir minetest.swap_node(door_pos1, node1) node2.name = "hyperloop:elevator_door_dark_top" node2.param2 = facedir minetest.swap_node(door_pos2, node2) end end local function on_final_close_door(tArrival) -- close the door and play sound if no player is around if hyperloop.is_player_around(tArrival.pos) then -- try again later minetest.after(3.0, on_final_close_door, tArrival) else door_command(tArrival.pos, tArrival.facedir, "close", true) end end local function on_open_door(tArrival) door_command(tArrival.pos, tArrival.facedir, "open", true) minetest.after(5.0, on_final_close_door, tArrival) tArrival.busy = false end local function on_arrival_floor(tDeparture, tArrival, player_name, snd) local player = minetest.get_player_by_name(player_name) door_command(tDeparture.pos, tDeparture.facedir, "close", false) door_command(tArrival.pos, tArrival.facedir, "close", false) tDeparture.busy = false if player ~= nil then tArrival.pos.y = tArrival.pos.y - kPLAYER_OVER_GROUND player:set_pos(tArrival.pos) if PlayerNameTags[player_name] then player:set_nametag_attributes(PlayerNameTags[player_name]) PlayerNameTags[player_name] = nil end tArrival.pos.y = tArrival.pos.y + kPLAYER_OVER_GROUND end minetest.sound_stop(snd) minetest.after(1.0, on_open_door, tArrival) end local function on_travel(tDeparture, tArrival, player_name, seconds) local player = minetest.get_player_by_name(player_name) door_command(tDeparture.pos, tDeparture.facedir, "darken", false) door_command(tArrival.pos, tArrival.facedir, "darken", false) if player ~= nil then PlayerNameTags[player_name] = player:get_nametag_attributes() player:set_nametag_attributes({text = " "}) end local snd = minetest.sound_play("ele_norm", { pos = tDeparture.pos, gain = 0.5, max_hear_distance = 3, loop = true, }) minetest.after(seconds, on_arrival_floor, tDeparture, tArrival, player_name, snd) end minetest.register_node("hyperloop:elevator_bottom", { description = S("Hyperloop Elevator"), tiles = { "hyperloop_elevator_bottom.png", "hyperloop_elevator_bottom.png", "hyperloop_elevator.png", "hyperloop_elevator.png", "hyperloop_elevator.png", "hyperloop_elevator.png", }, node_box = { type = "fixed", fixed = { { -8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, { -7/16, -8/16, 7/16, 7/16, 8/16, 8/16}, { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, }, }, selection_box = { type = "fixed", fixed = { -8/16, -8/16, -8/16, 8/16, 23/16, 8/16 }, }, inventory_image = "hyperloop_elevator_inventory.png", on_rotate = screwdriver.disallow, drawtype = "nodebox", paramtype = 'light', light_source = 6, paramtype2 = "facedir", is_ground_content = false, groups = {snappy = 3}, after_place_node = function(pos, placer, itemstack, pointed_thing) local _,node = Shaft:get_node(pos, 6) if node.name == "air" then local facedir = hyperloop.get_facedir(placer) Elevators:set(pos, "", {facedir=facedir, busy=false}) Shaft:after_place_node(pos, {5}) -- formspec local meta = minetest.get_meta(pos) local formspec = "size[6,4]".. "label[0,0;"..S("Please insert floor name").."]" .. "field[0.5,1.5;5,1;floor;"..S("Floor name")..";"..S("Base").."]" .. "button_exit[2,3;2,1;exit;"..S("Save").."]" meta:set_string("formspec", formspec) meta:set_string("owner", placer:get_player_name()) -- add upper part of the car pos = Shaft:get_pos(pos, 6) minetest.add_node(pos, {name="hyperloop:elevator_top", param2=facedir}) Shaft:after_place_node(pos, {6}) else minetest.remove_node(pos) return true end end, on_receive_fields = function(pos, formname, fields, player) -- floor name entered? if fields.floor ~= nil then local floor = string.trim(fields.floor) if floor == "" then return end Elevators:update(pos, {name=floor}) update_formspec(pos) elseif fields.button ~= nil then -- destination selected? update_formspec(pos) local floor = Elevators:get(pos) if floor then floor = table.copy(floor) floor.pos = pos local sKey = SP(pos) local idx = tonumber(fields.button) if idx then local lFloors = Cache[sKey].lFloors local dest = lFloors[#lFloors-idx] if dest and dest.pos and floor.pos then local dist = hyperloop.distance(dest.pos, floor.pos) if dist ~= 0 and floor.busy ~= true then if player ~= nil then pos.y = pos.y - kPLAYER_OVER_GROUND player:set_pos(pos) pos.y = pos.y + kPLAYER_OVER_GROUND end -- due to the missing display, a trip needs 20 sec maximum local seconds = math.min(1 + math.floor(dist/30), 20) floor.busy = true door_command(floor.pos, floor.facedir, "close", true) door_command(dest.pos, dest.facedir, "close", true) minetest.after(1.0, on_travel, floor, dest, player:get_player_name(), seconds) end end end end end end, on_punch = function(pos, node, puncher, pointed_thing) update_formspec(pos) local floor = Elevators:get(pos) if floor and floor.busy ~= true then door_command(pos, floor.facedir, "open", true) end end, after_dig_node = function(pos, oldnode, oldmetadata, digger) Shaft:after_dig_node(pos, {5}) Elevators:delete(pos) -- remove the bottom also pos = Shaft:get_pos(pos, 6) minetest.remove_node(pos) Shaft:after_dig_node(pos, {6}) end, }) minetest.register_node("hyperloop:elevator_top", { description = S("Hyperloop Elevator"), tiles = { -- up, down, right, left, back, front "hyperloop_elevator_bottom.png", "hyperloop_elevator_bottom.png", "hyperloop_elevator_top.png", "hyperloop_elevator.png", "hyperloop_elevator.png", "hyperloop_elevator.png", }, node_box = { type = "fixed", fixed = { { -8/16, 7/16, -8/16, 8/16, 8/16, 8/16}, { -8/16, -8/16, -8/16, -7/16, 8/16, 8/16}, { 7/16, -8/16, -8/16, 8/16, 8/16, 8/16}, { -7/16, -8/16, 7/16, 7/16, 8/16, 8/16}, }, }, on_rotate = screwdriver.disallow, drawtype = "nodebox", paramtype = 'light', light_source = 6, paramtype2 = "facedir", is_ground_content = false, diggable = false, groups = {not_in_creative_inventory=1}, drop = "", }) minetest.register_node("hyperloop:elevator_door_top", { description = "Hyperloop Elevator Door", tiles = { -- up, down, right, left, back, front "hyperloop_elevator_door_top.png", }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { { -8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, }, }, on_rotate = screwdriver.disallow, drop = "", paramtype = 'light', paramtype2 = "facedir", use_texture_alpha = hyperloop.CLIP, is_ground_content = false, groups = {snappy = 3, not_in_creative_inventory=1}, }) minetest.register_node("hyperloop:elevator_door", { description = "Hyperloop Elevator Door", tiles = { -- up, down, right, left, back, front "hyperloop_elevator_door.png", }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { { -8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, }, }, selection_box = { type = "fixed", fixed = { -8/16, -8/16, 6.5/16, 8/16, 24/16, 8/16 }, }, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) local floor_pos = P(M(pos):get_string("floor_pos")) if floor_pos ~= nil then update_formspec(floor_pos) local floor = Elevators:get(floor_pos) if floor and floor.busy ~= true then door_command(floor_pos, floor.facedir, "open", true) end end end, on_rotate = screwdriver.disallow, drop = "", paramtype = 'light', paramtype2 = "facedir", use_texture_alpha = hyperloop.CLIP, is_ground_content = false, groups = {snappy = 3, not_in_creative_inventory=1}, }) minetest.register_node("hyperloop:elevator_door_dark_top", { description = "Hyperloop Elevator Door", tiles = { -- up, down, right, left, back, front "hyperloop_elevator_dark_top.png", }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { { -8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, }, }, on_rotate = screwdriver.disallow, drop = "", paramtype = 'light', paramtype2 = "facedir", is_ground_content = false, groups = {snappy = 3, not_in_creative_inventory=1}, }) minetest.register_node("hyperloop:elevator_door_dark", { description = "Hyperloop Elevator Door", tiles = { -- up, down, right, left, back, front "hyperloop_elevator_dark.png", }, drawtype = "nodebox", node_box = { type = "fixed", fixed = { { -8/16, -8/16, 7/16, 8/16, 8/16, 8/16}, }, }, selection_box = { type = "fixed", fixed = { -8/16, -8/16, 7/16, 8/16, 24/16, 8/16 }, }, on_rotate = screwdriver.disallow, drop = "", paramtype = 'light', paramtype2 = "facedir", is_ground_content = false, groups = {snappy = 3, not_in_creative_inventory=1}, })