-- =================================================================== -- See "LICENSE" for license information -- See "history.txt" for history -- =================================================================== minetest.register_privilege ("travelnet_attach", { description = "allows to attach travelnet boxes to travelnets of other players", give_to_singleplayer = false}); minetest.register_privilege ("travelnet_remove", { description = "allows to dig travelnet boxes which belog to nets of other players", give_to_singleplayer = false}); -- =================================================================== local modname = minetest.get_current_modname() local modpath = minetest.get_modpath (modname) local worldpath = minetest.get_worldpath() local datapath = worldpath .. "/mod_travelnet.data" -- =================================================================== travelnet = {} travelnet.modname = modname travelnet.modpath = modpath travelnet.worldpath = worldpath travelnet.datapath = datapath travelnet.targets = {} -- =================================================================== dofile (modpath .. "/config.lua") -- =================================================================== travelnet.save_data = function() local data = minetest.serialize (travelnet.targets) local path = travelnet.datapath local success = false if minetest.safe_file_write then success = minetest.safe_file_write (path, data) else local file = io.open (path, "w") if (file) then file:write (data); file:close(); success = true end end if not success then print "[travelnet] Error: Savefile '%s' could not be written" end end -- =================================================================== travelnet.restore_data = function() local path = travelnet.datapath local file = io.open (path, "r") if file then local data = file:read ("*all") travelnet.targets = minetest.deserialize (data) file:close(); else print ("[Mod travelnet] Error: Savefile '" .. tostring (path) .. "' not found.") end end -- =================================================================== travelnet.update_formspec = function (pos, puncher_name) local meta = minetest.get_meta (pos) local this_node = minetest.get_node (pos) local is_elevator = false if this_node ~= nil and this_node.name == 'travelnet:elevator' then is_elevator = true end if not meta then return end if not (travelnet.targets) then travelnet.targets = {} end local owner_name = meta:get_string ("owner") local station_name = meta:get_string ("station_name") local station_network = meta:get_string ("station_network") if not (owner_name ) or not (station_name ) or station_network == '' or not (station_network) then if is_elevator == true then travelnet.add_target (nil, nil, pos, puncher_name, meta, owner_name) return end meta:set_string ("infotext" , "Travelnet-box (unconfigured)"); meta:set_string ("station_name" , ""); meta:set_string ("station_network" , ""); meta:set_string ("owner" , ""); -- Prompt for initial data meta:set_string ("formspec", "size[12,10]".. "field[0.3,5.6;6,0.7;station_name;Name of this station:;]" .. "field[0.3,6.6;6,0.7;station_network;Assign to Network:;]" .. "field[0.3,7.6;6,0.7;owner_name;(optional) owned by:;]" .. "button_exit[6.3,5.4;1.7,0.7;station_dig;Remove]" .. "button_exit[6.3,6.4;1.7,0.7;station_set;Store]") minetest.chat_send_player (puncher_name, "Error: Update failed! Resetting this box on the travelnet.") return end -- if the station got lost from the network for some reason (savefile corrupted?) then add it again if not (travelnet.targets [owner_name]) or not (travelnet.targets [owner_name][station_network]) or not (travelnet.targets [owner_name][station_network][station_name]) then -- first one by this player? if not (travelnet.targets [owner_name]) then travelnet.targets [owner_name] = {} end -- first station on this network? if not (travelnet.targets [owner_name][station_network]) then travelnet.targets [owner_name][station_network] = {} end local zeit = meta:get_int ("timestamp") if not (zeit) or type (zeit) ~= "number" or zeit < 100000 then zeit = os.time() end -- add this station travelnet.targets [owner_name][station_network][station_name] = { pos=pos, timestamp=zeit } minetest.chat_send_player (owner_name, "Station '" .. station_name .. "' has been reattached to the network '" .. station_network .. "'.") end -- add name of station + network + owner + update-button local zusatzstr = "" local trheight = "10" if this_node and this_node.name == "locked_travelnet:travelnet" then zusatzstr = "field[0.3,11;6,0.7;locks_sent_lock_command;Locked travelnet. Type /help for help:;]" trheight = "11.5" end local formspec = "size[12,"..trheight.."]" .. "label[3.3,0.0;Travelnet:]" .. "label[0.3,0.4;Name of this station:]" .. "label[3.3,0.4;" .. (station_name or "?") .. "]" .. "label[0.3,0.8;Assigned to Network:]" .. "label[3.3,0.8;" .. (station_network or "?") .. "]" .. "label[0.3,1.2;Owned by:]" .. "label[3.3,1.2;" .. (owner_name or "?") .. "]" .. "label[6.3,0.0;Punch box to update]" .. "label[6.3,0.4;Click on target to travel there]" .. "button_exit[6.3,1.2;3,0.7;station_dig;Remove station]" .. zusatzstr -- "button_exit[5.3,0.3;8,0.8;do_update;Punch box to update destination list. Click on target to travel there.]".. local x = 0 local y = 0 local i = 0 -- collect all station names in a table local stations = {} for k,v in pairs (travelnet.targets [owner_name][station_network]) do table.insert (stations, k) end -- minetest.chat_send_player(puncher_name, "stations: "..minetest.serialize( stations )); local ground_level = 1 if is_elevator then table.sort (stations, function (a, b) return travelnet.targets [owner_name][station_network][a].pos.y > travelnet.targets [owner_name][station_network][b].pos.y end) -- find ground level local vgl_timestamp = 999999999999 for index,k in ipairs (stations) do if not (travelnet.targets [owner_name][station_network][k].timestamp) then travelnet.targets [owner_name][station_network][k].timestamp = os.time() end if travelnet.targets [owner_name][station_network][k].timestamp < vgl_timestamp then vgl_timestamp = travelnet.targets [owner_name][station_network][k].timestamp ground_level = index end end for index,k in ipairs (stations) do if index == ground_level then travelnet.targets [owner_name][station_network][k].nr = 'G' else travelnet.targets [owner_name][station_network][k].nr = tostring (ground_level - index) end end else if travelnet.enable_legacy_sort then table.sort (stations, function (a,b) return travelnet.targets [owner_name] [station_network] [a].timestamp < travelnet.targets [owner_name] [station_network] [b].timestamp end) else table.sort (stations, function (a,b) return a < b end) end end -- if there are only 8 stations (plus this one), -- center them in the formspec if #stations < 10 then x = 4 end for index,k in ipairs (stations) do -- check if there is an elevator door in front that needs to be opened local open_door_cmd = false if k == station_name then open_door_cmd = true end if k ~= station_name or open_door_cmd then i = i+1 -- new column if y == 8 then x = x+4 y = 0 end if( open_door_cmd ) then formspec = formspec .."button_exit["..(x)..","..(y+2.5)..";1,0.5;open_door;<>]".. "label["..(x+0.9)..","..(y+2.35)..";"..tostring( k ).."]"; elseif is_elevator then formspec = formspec .."button_exit["..(x)..","..(y+2.5)..";1,0.5;target;"..tostring( travelnet.targets[ owner_name ][ station_network ][ k ].nr ).."]".. "label["..(x+0.9)..","..(y+2.35)..";"..tostring( k ).."]"; else formspec = formspec .."button_exit["..(x)..","..(y+2.5)..";4,0.5;target;"..k.."]"; end -- if is_elevator then -- formspec = formspec ..' ('..tostring( travelnet.targets[ owner_name ][ station_network ][ k ].pos.y )..'m)'; -- end -- formspec = formspec .. ']'; y = y+1 -- x = x+4 end end meta:set_string( "formspec", formspec ); meta:set_string( "infotext", "Station '"..tostring( station_name ).."' on travelnet '"..tostring( station_network ).. "' (owned by "..tostring( owner_name )..") ready for usage. Right-click to travel, punch to update."); minetest.chat_send_player (puncher_name, "The target list of this box on the travelnet has been updated.") end local function get_far_node (pos) local node = minetest.get_node (pos) if node.name == "ignore" then minetest.get_voxel_manip():read_from_map(pos, pos) node = minetest.get_node (pos) end return node end -- add a new target; meta is optional travelnet.add_target = function (station_name, network_name, pos, player_name, meta, owner_name) -- if it is an elevator, determine the network name through x and z coordinates local this_node = minetest.get_node( pos ) local is_elevator = false if( this_node.name == 'travelnet:elevator' ) then -- owner_name = '*'; -- the owner name is not relevant here is_elevator = true network_name = tostring( pos.x )..','..tostring( pos.z ) if( not( station_name ) or station_name == '' ) then station_name = 'at '..tostring( pos.y )..'m'; end end if station_name == "" or not (station_name) then minetest.chat_send_player(player_name, "Please provide a name for this station."); return end if( network_name == "" or not( network_name )) then minetest.chat_send_player(player_name, "Please provide the name of the network this station ought to be connected to."); return end if (owner_name == nil or owner_name == '' or owner_name == player_name) then owner_name = player_name elseif( is_elevator ) then -- elevator networks owner_name = player_name elseif( not( minetest.check_player_privs(player_name, {interact=true}))) then minetest.chat_send_player(player_name, "There is no player with interact privilege named '"..tostring( player_name ).."'. Aborting."); return elseif( not( minetest.check_player_privs(player_name, {travelnet_attach=true})) and not( travelnet.allow_attach( player_name, owner_name, network_name ))) then minetest.chat_send_player(player_name, "You do not have the travelnet_attach priv which is required to attach your box to the network of someone else. Aborting."); return end if not (travelnet.targets) then travelnet.targets = {} end -- first one by this player? if not (travelnet.targets [owner_name]) then travelnet.targets [owner_name] = {} end -- first station on this network? if not (travelnet.targets [owner_name][network_name]) then travelnet.targets [owner_name][network_name] = {} end for k,v in pairs (travelnet.targets [owner_name][network_name]) do if k == station_name then local oldpos = v.pos if oldpos ~= nil then local oldname = nil local oldnode = get_far_node (oldpos) if oldnode ~= nil then oldname = oldnode.name end if oldname ~= "travelnet:travelnet" and oldname ~= "travelnet:pad" and oldname ~= "travelnet:palantiri" and oldname ~= "travelnet:elevator" and oldname ~= "locked_travelnet:travelnet" and oldname ~= "travelnet:travelnet_private" then local mmeta = minetest.get_meta (oldpos) travelnet.remove_box (oldpos, nil, mmeta:to_table(), nil) break end end end end local anz = 0 for k,v in pairs (travelnet.targets [owner_name][network_name]) do if k == station_name then minetest.chat_send_player (player_name, "Error: A station named '" .. station_name .. "' already exists on this network. Please choose a different name."); return end anz = anz + 1 end -- we don't want too many stations in the same network -- because that would get confusing when displaying the targets if( anz+1 > travelnet.MAX_STATIONS_PER_NETWORK ) then minetest.chat_send_player(player_name, "Error: Network '"..network_name.."' already contains the maximum number (=" ..(travelnet.MAX_STATIONS_PER_NETWORK)..") of allowed stations per network. Please choose a diffrent/new network name."); return end -- add this station travelnet.targets [owner_name][network_name][station_name] = { pos=pos, timestamp=os.time()} -- do we have a new node to set up? (and are not just reading -- from a safefile?) if (meta) then minetest.chat_send_player(player_name, "Station '"..station_name.."' has been added to the network '" ..network_name.."', which now consists of "..( anz+1 ).." station(s)."); meta:set_string( "station_name", station_name ); meta:set_string( "station_network", network_name ); meta:set_string( "owner", owner_name ); meta:set_int( "timestamp", travelnet.targets[ owner_name ][ network_name ][ station_name ].timestamp); meta:set_string("formspec", "size[12,10]".. "field[0.3,0.6;6,0.7;station_name;Station:;".. meta:get_string("station_name").."]".. "field[0.3,3.6;6,0.7;station_network;Network:;"..meta:get_string("station_network").."]" ); -- display a list of all stations that can be reached from here travelnet.update_formspec (pos, player_name) -- save the updated network data in a savefile over server restart travelnet.save_data() end end -- allow doors to open travelnet.open_close_door = function (pos, player, mode) local this_node = minetest.get_node (pos) local pos2 = {x=pos.x,y=pos.y,z=pos.z} if( this_node.param2 == 0 ) then pos2 = {x=pos.x,y=pos.y,z=(pos.z-1)}; elseif( this_node.param2 == 1 ) then pos2 = {x=(pos.x-1),y=pos.y,z=pos.z}; elseif( this_node.param2 == 2 ) then pos2 = {x=pos.x,y=pos.y,z=(pos.z+1)}; elseif( this_node.param2 == 3 ) then pos2 = {x=(pos.x+1),y=pos.y,z=pos.z}; end local door_node = minetest.get_node( pos2 ); if( door_node ~= nil and door_node.name ~= 'ignore' and door_node.name ~= 'air' and minetest.registered_nodes[ door_node.name ] ~= nil and minetest.registered_nodes[ door_node.name ].on_rightclick ~= nil) then -- at least for homedecor, same facedir would mean "door closed" -- do not close the elevator door if it is already closed if( mode==1 and ( door_node.name == 'travelnet:elevator_door_glass_closed' or door_node.name == 'travelnet:elevator_door_steel_closed' -- handle doors that change their facedir or ( door_node.param2 == this_node.param2 and door_node.name ~= 'travelnet:elevator_door_glass_open' and door_node.name ~= 'travelnet:elevator_door_steel_open'))) then return; end -- do not open the doors if they are already open (works only on elevator-doors; not on doors in general) if( mode==2 and ( door_node.name == 'travelnet:elevator_door_glass_open' or door_node.name == 'travelnet:elevator_door_steel_open' -- handle doors that change their facedir or ( door_node.param2 ~= this_node.param2 and door_node.name ~= 'travelnet:elevator_door_glass_closed' and door_node.name ~= 'travelnet:elevator_door_steel_closed'))) then return; end if( mode==2 ) then minetest.after( 1, minetest.registered_nodes[ door_node.name ].on_rightclick, pos2, door_node, player ); else minetest.registered_nodes[ door_node.name ].on_rightclick(pos2, door_node, player); end end end travelnet.on_receive_fields = function (pos, formname, fields, player) local meta = minetest.get_meta (pos) local pname = player:get_player_name() -- The player wants to remove the station if fields.station_dig then local owner = meta:get_string ("owner") local node = minetest.get_node (pos) local nname = "?" if node and node.name then nname = node.name end local description = "station" if nname == "travelnet:travelnet" then description = "travelnet box" elseif nname == "travelnet:elevator" then description = "elevator" elseif nname == "travelnet:palantiri" then description = "palantiri" elseif nname == "travelnet:pad" then description = "pad" else minetest.chat_send_player (pname, "Error: Unknown node") return end -- Players with travelnet_remove priv can dig the station if not minetest.check_player_privs (pname, { travelnet_remove=true }) and owner ~= pname and owner ~= nil and owner ~= "" then local msg = "This " .. description .. " belongs to " .. owner .. ". You can't remove it." minetest.chat_send_player (pname, msg) return end local pinv = player:get_inventory() if(not(pinv:room_for_item("main", node.name))) then minetest.chat_send_player (pname, "You do not have enough room in your inventory.") return end -- give the player the box pinv:add_item("main", node.name) -- remove the box from the data structure travelnet.remove_box( pos, nil, meta:to_table(), player ); -- remove the node as such minetest.remove_node(pos) return; end -- if the box has not been configured yet if( meta:get_string("station_network")=="" ) then travelnet.add_target( fields.station_name, fields.station_network, pos, pname, meta, fields.owner_name ); return; end if( fields.open_door ) then travelnet.open_close_door( pos, player, 0 ); return; end if( not( fields.target )) then minetest.chat_send_player (pname, "Please click on the target you want to travel to."); return; end -- if there is something wrong with the data local owner_name = meta:get_string( "owner" ) local station_name = meta:get_string( "station_name" ) local station_network = meta:get_string( "station_network" ) if( not( owner_name ) or not( station_name ) or not( station_network ) or not( travelnet.targets[ owner_name ] ) or not( travelnet.targets[ owner_name ][ station_network ] )) then if( owner_name and station_name and station_network ) then travelnet.add_target( station_name, station_network, pos, owner_name, meta, owner_name ); else minetest.chat_send_player(pname, "Error: There is something wrong with the configuration of this station. ".. " DEBUG DATA: owner: "..( owner_name or "?").. " station_name: "..(station_name or "?").. " station_network: "..(station_network or "?").."."); return end end if( not( owner_name ) or not( station_network ) or not( travelnet.targets ) or not( travelnet.targets[ owner_name ] ) or not( travelnet.targets[ owner_name ][ station_network ] )) then minetest.chat_send_player(pname, "Error: This travelnet is lacking data and/or improperly configured."); print( "ERROR: The travelnet at "..minetest.pos_to_string( pos ).." has a problem: ".. " DATA: owner: "..( owner_name or "?").. " station_name: "..(station_name or "?").. " station_network: "..(station_network or "?").."."); return; end local this_node = minetest.get_node( pos ); if( this_node ~= nil and this_node.name == 'travelnet:elevator' ) then for k,v in pairs( travelnet.targets[ owner_name ][ station_network ] ) do if( travelnet.targets[ owner_name ][ station_network ][ k ].nr --..' ('..tostring( travelnet.targets[ owner_name ][ station_network ][ k ].pos.y )..'m)' == fields.target) then fields.target = k; end end end -- if the target station is gone if( not( travelnet.targets[ owner_name ][ station_network ][ fields.target ] )) then minetest.chat_send_player (pname, "Station '"..( fields.target or "?").." does not exist (anymore?) on this network."); travelnet.update_formspec( pos, pname ); return; end if( not( travelnet.allow_travel( pname, owner_name, station_network, station_name, fields.target ))) then return; end minetest.chat_send_player (pname, "Initiating transfer to station '"..( fields.target or "?").."'.'"); local sndfile = nil if not travelnet.disable_sound then if this_node.name == "travelnet:elevator" then sndfile = "travelnet_bell" else sndfile = travelnet.sound end minetest.sound_play (sndfile, { pos = pos, gain = 0.75, max_hear_distance = 25 }) end -- close the doors at the sending station travelnet.open_close_door (pos, player, 1) -- transport the player to the target location local target_pos = travelnet.targets[ owner_name ][ station_network ][ fields.target ].pos; local player_model_bottom = tonumber(minetest.settings:get("player_model_bottom")) or -.5; -- may be 0.0 for some versions of MT 5 player model local player_model_vec = vector.new(0, player_model_bottom, 0) local target_pos = travelnet.targets[ owner_name ][ station_network ][ fields.target ].pos player:move_to( vector.add(target_pos, player_model_vec), false) -- check if the box has at the other end has been removed local node2 = minetest.get_node_or_nil (target_pos) local oldmeta = { owner = owner_name , name = fields.target , network = station_network , pname = pname , } travelnet.rotate_player (target_pos, player, 0, sndfile, oldmeta) end -- =================================================================== travelnet.rotate_player = function (target_pos, player, tries, sndfile, oldmeta) -- play sound at the target position as well -- if sndfile ~= nil then minetest.sound_play (sndfile, { pos = target_pos, gain = 0.75, max_hear_distance = 25 }) end -- Wait until box is loaded local node2 = minetest.get_node_or_nil (target_pos) if node2 == nil then if tries < 30 then minetest.after (0, travelnet.rotate_player, target_pos, player, tries+1, sndfile, oldmeta) end return end if node2.name ~= 'ignore' and node2.name ~= 'travelnet:travelnet' and node2.name ~= 'travelnet:pad' and node2.name ~= 'travelnet:palantiri' and node2.name ~= 'travelnet:elevator' and node2.name ~= "locked_travelnet:travelnet" and node2.name ~= "travelnet:travelnet_private" then local t = target_pos local t_str = t.x .. "," .. t.y .. "," .. t.z local upnode = minetest.get_node ({ x = t.x, y = t.y + 1, z = t.z}) if node2.name == "air" and upnode.name == "air" then minetest.set_node (t, { name = "travelnet:palantiri" }) local meta = minetest.get_meta (t) meta:set_string ("infotext" , "Travelnet" ) meta:set_string ("station_name" , oldmeta.name ) meta:set_string ("station_network" , oldmeta.network ) meta:set_string ("owner" , oldmeta.owner ) travelnet.update_formspec (t, oldmeta.pname) minetest.log ("action", "travelnet missing at " .. t_str .. "; replaced it") else minetest.log ("action", "travelnet missing at " .. t_str .. "; couldn't replace it: " .. node2.name .. " " .. upnode.name) end end local yaw = 0 local param2 = node2.param2 if param2 == 0 then yaw = 180 elseif param2 == 1 then yaw = 90 elseif param2 == 2 then yaw = 0 elseif param2 == 3 then yaw = 270 end -- Rotate the player so that he/she can walk straight out of the box. -- Do this only on servers where the function(s) needed to do this ex- -- ist. if player.set_look_horizontal then player:set_look_horizontal (math.rad (yaw)) player:set_look_vertical (math.rad (0)) elseif player.set_look_yaw then player:set_look_yaw (math.rad (yaw)) player:set_look_pitch (math.rad (0)) end travelnet.open_close_door (target_pos, player, 2) end travelnet.remove_box = function (pos, oldnode, oldmetadata, digger) if not (oldmetadata) or oldmetadata == "nil" or not (oldmetadata.fields) then if digger ~= nil then minetest.chat_send_player (digger:get_player_name(), "Error: Could not find information about the station that is to be removed."); end return end local owner_name = oldmetadata.fields[ "owner" ]; local station_name = oldmetadata.fields[ "station_name" ]; local station_network = oldmetadata.fields[ "station_network" ]; -- station is not known? then just remove it if( not( owner_name ) or not( station_name ) or not( station_network ) or not( travelnet.targets[ owner_name ] ) or not( travelnet.targets[ owner_name ][ station_network ] )) then if digger ~= nil then minetest.chat_send_player( digger:get_player_name(), "Error: Could not find the station that is to be removed."); end return end travelnet.targets[ owner_name ][ station_network ][ station_name ] = nil; -- inform the owner minetest.chat_send_player( owner_name, "Station '"..station_name.."' has been REMOVED from the network '"..station_network.."'."); if( digger ~= nil and owner_name ~= digger:get_player_name() ) then minetest.chat_send_player( digger:get_player_name(), "Station '"..station_name.."' has been REMOVED from the network '"..station_network.."'."); end -- save the updated network data in a savefile over server restart travelnet.save_data(); end travelnet.can_dig = function( pos, player, description ) -- forbid digging of the travelnet return false; end -- obsolete function travelnet.can_dig_old = function( pos, player, description ) if( not( player )) then return false; end local name = player:get_player_name(); -- players with that priv can dig regardless of owner if( minetest.check_player_privs(name, {travelnet_remove=true}) or travelnet.allow_dig( player_name, owner_name, network_name )) then return true; end local meta = minetest.get_meta( pos ); local owner = meta:get_string('owner'); if( not( meta ) or not( owner) or owner=='') then minetest.chat_send_player(name, "This "..description.." has not been configured yet. Please set it up first to claim it. Afterwards you can remove it because you are then the owner."); return false; elseif( owner ~= name ) then minetest.chat_send_player(name, "This "..description.." belongs to "..tostring( meta:get_string('owner'))..". You can't remove it."); return false; end return true end if not travelnet.disable then minetest.log ("action", "travelnet enabled") dofile(modpath.."/travelnet.lua") -- the travelnet node definition end if not travelnet.disable_travelpad then dofile(modpath.."/travelpad.lua") end if not travelnet.disable_elevator then dofile(modpath.."/elevator.lua") -- allows up/down transfers only end if not travelnet.disable_doors then dofile (modpath .. "/doors.lua") -- doors that open and close automatically -- when the travelnet or elevator is used end if not travelnet.disable_abm then -- restore travelnet data when players pass by broken networks dofile (modpath .. "/restorenet.lua") end -- upon server start, read the savefile travelnet.restore_data() minetest.log ("action", "travelnet loaded")