local mod_name = minetest.get_current_modname() local mod_version = "2.15" local function log(level, message) minetest.log(level, ('[%s] %s'):format(mod_name, message)) end log('action', 'CSM cs_waypoints '..mod_version..' loading...') minetest.display_chat_message("CSM cs_waypoints "..mod_version.." loading...") local mod_storage = minetest.get_mod_storage() local search_delta_default = 10 local daydelay_default = 5 local bouncedelay_default = 10 -- -- -- local functions -- -- local function load_waypoints() if string.find(mod_storage:get_string('waypoints'), 'return') then return minetest.deserialize(mod_storage:get_string('waypoints')) else return {} end end local function load_waypoints_stack() if string.find(mod_storage:get_string('waypoints_stack'), 'return nil') then return {} end if string.find(mod_storage:get_string('waypoints_stack'), 'return') then return minetest.deserialize(mod_storage:get_string('waypoints_stack')) else return {} end end local waypoints = load_waypoints() local function safe(func) -- wrap a function w/ logic to avoid crashing the game local f = function(...) local status, out = pcall(func, ...) if status then return out else log('warning', 'Error (func): ' .. out) return nil end end return f end local function round(x) return math.floor(x+0.5) end local function pairsByKeys(t, f) local a = {} for n in pairs(t) do table.insert(a, n) end table.sort(a, f) local i = 0 return function() i = i + 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end end local function lc_cmp(a, b) return a:lower() < b:lower() end local function tostring_point(point) if not point then return " - - - " end return ('%i %i %i'):format(round(point.x), round(point.y), round(point.z)) end local function teleport_to(position_name) local wpname = position_name local waypoint = waypoints[wpname] if waypoint ~= nil then minetest.run_server_chatcommand('teleport', tostring_point(waypoint)) else minetest.display_chat_message(('waypoint "%s" not found.'):format(wpname)) end return true end local function show_pos(position_name) local wpname = position_name local waypoint = waypoints[wpname] local rg = "" if waypoint ~= nil then rg = wpname .. ": " .. tostring_point(waypoint) else rg = ('waypoint "%s" not found.'):format(wpname) end return true,rg end local function stack_push() local point = minetest.localplayer:get_pos() local wp_stack = load_waypoints_stack() local count = #wp_stack +1 wp_stack[count] = point mod_storage:set_string('waypoints_stack', minetest.serialize(wp_stack)) end local function stack_pop() local wp_stack = load_waypoints_stack() local count = 0 if nil ~= wp_stack then count = #wp_stack end if count<1 then minetest.display_chat_message('stack empty - no teleporting') return end minetest.run_server_chatcommand('teleport', tostring_point(wp_stack[count])) wp_stack[count] = nil mod_storage:set_string('waypoints_stack', minetest.serialize(wp_stack)) return true end local function stack_use() local wp_stack = load_waypoints_stack() count = 0 if nil ~= wp_stack then count = #wp_stack end if count<1 then minetest.display_chat_message('stack empty - no teleporting') return end minetest.run_server_chatcommand('teleport', tostring_point(wp_stack[count])) return true end local function stack_exch() local wp_stack = load_waypoints_stack() count = 0 if nil ~= wp_stack then count = #wp_stack end if count<2 then minetest.display_chat_message('less than 2 entries - no change') return end local exch = wp_stack[count] wp_stack[count] = wp_stack[count-1] wp_stack[count-1] = exch mod_storage:set_string('waypoints_stack', minetest.serialize(wp_stack)) return true end local function stack_show() local wp_stack = load_waypoints_stack() count = 0 if nil ~= wp_stack then count = #wp_stack end if count<1 then minetest.display_chat_message('stack empty') return true end output = "" for i = count,1,-1 do output = output .. tostring(i) .. " " .. tostring_point(wp_stack[i]).."\n" end return true ,output end local function stack_clear() mod_storage:set_string('waypoints_stack', minetest.serialize(nil)) end local function stack_search(d) local delta = d if delta then delta = tonumber(delta) end if nil == delta then delta = search_delta_default end if delta < 0 then delta = 0 end here = minetest.localplayer:get_pos() minetest.display_chat_message( ('%s : %s'):format("current position", tostring_point(here)) ) for name,pos in pairsByKeys(waypoints, lc_cmp) do if math.abs(here.y-pos.y) <= delta then if math.abs(here.x-pos.x) <= delta then if math.abs(here.z-pos.z) <= delta then minetest.display_chat_message( ('%s -> %s'):format(name, tostring_point(pos))) end end end end return true end local function position_shift(p) local param = p if not p then return end while p:sub(1,1) == " " and p:len()> 3 do p = p:sub(2,99) end if p:len()<3 then return end direction = p:sub(1,1) d = "" if direction == "x" or direction == "X" then d = "x" end if direction == "y" or direction == "Y" then d = "y" end if direction == "z" or direction == "Z" then d = "z" end if d == "" then return end here = minetest.localplayer:get_pos() distance = tonumber(p:sub(2,8)) if not distance then return end if distance == 0 then return end here[d] = here[d] + distance -- here.y = here.y - 1 -- correction minetest.run_server_chatcommand('teleport', tostring_point(here)) end -- new shift with more than one possible shift coordinate -- only the last value for one coordinate is used -- viewing direction (?) added by Maverick2797 on 2021-09-23 local function position_shift2(p) if not p then return end local player = minetest.localplayer local param = p:split(" ") local shift = {} shift.x = 0 shift.y = 0 shift.z = 0 local vp = 1 while (vp+1 <= #param ) do local direction = param[vp] local distance = tonumber(param[vp+1]) if not distance then distance = 0 end local d = "" if direction == "x" or direction == "X" then d = "x" end if direction == "y" or direction == "Y" then d = "y" end if direction == "z" or direction == "Z" then d = "z" end if direction == "?" then --tp according to facing angle local yaw = player:get_last_look_horizontal() local pitch = player:get_last_look_vertical() shift.x = distance*math.cos(yaw)*math.cos(pitch) shift.z = distance*math.sin(yaw)*math.cos(pitch) shift.y = distance*math.sin(pitch) elseif d ~= "" then shift[direction] = distance end vp = vp+2 end if shift.x == 0 and shift.y == 0 and shift.z == 0 then return end local here = player:get_pos() here.x = here.x+shift.x here.y = here.y+shift.y here.z = here.z+shift.z -- here.y = here.y - 1 -- correction minetest.run_server_chatcommand('teleport', tostring_point(here)) end local function calc_distance(wp) local wpname = wp local waypoint = waypoints[wpname] local rg = "" if waypoint == nil then rg = ('waypoint "%s" not found.'):format(wp) else here = minetest.localplayer:get_pos() dx = math.abs(here.x-waypoint.x) dy = math.abs(here.y-waypoint.y) dz = math.abs(here.z-waypoint.z) l1 = "x: " .. tostring(round(100*dx)/100) .. " y: " .. tostring(round(100*dy)/100) .. " z: " .. tostring(round(100*dz)/100) delta_hor = math.floor(math.sqrt(dx*dx+dz*dz)*100)/100 delta_3d = math.floor(math.sqrt(dx*dx+dy*dy+dz*dz)*100)/100 l2 = "distance: " .. tostring(delta_3d) .. " horizontal distance: " .. tostring(delta_hor) rg = l1 .. "\n" .. l2 end return true,rg end local function teleport_day_back(x,y,z) minetest.run_server_chatcommand('teleport', string.format("%f %f %f",x,y+1,z) ) return end local function teleport_day(params) local daypos = mod_storage:get_string('daypos') local daydelay = mod_storage:get_string('daydelay') daypos = minetest.deserialize(daypos) daydelay = (daydelay and tonumber(minetest.deserialize(daydelay))) or daydelay_default if not params or params == "" then -- no parameter - execute the function if not daypos then minetest.display_chat_message("no saved position") return end local pstr = tostring_point(daypos) minetest.display_chat_message("position "..pstr) minetest.run_server_chatcommand('teleport', pstr) local point = minetest.localplayer:get_pos() minetest.after(daydelay,teleport_day_back,point.x,point.y,point.z) return end if params == "show" then minetest.display_chat_message("position: "..tostring_point(daypos).. " delay:"..daydelay) return end if params == "setpos" then local point = minetest.localplayer:get_pos() local pstr = tostring_point(point) minetest.display_chat_message("position "..pstr) mod_storage:set_string('daypos', minetest.serialize(point)) minetest.display_chat_message("position saved") return end local sdp = string.find(params,"setdelay ",1,false) if sdp and sdp == 1 then local newtime = tonumber(params:sub(10,16)) if newtime and newtime>0 then mod_storage:set_string('daydelay', minetest.serialize(newtime)) minetest.display_chat_message("delay saved") end return end -- wrong parameters - what now? return end local function teleport_bounce(params) if not params or params == "" then -- no parameter - return minetest.display_chat_message("no waypoint given") return end local bouncedelay = mod_storage:get_string('bouncedelay') bouncedelay = (bouncedelay and tonumber(minetest.deserialize(bouncedelay))) or bouncedelay_default local sdp = string.find(params,"setdelay",1,false) if sdp and sdp == 1 then local newtime = tonumber(params:sub(10,16)) if newtime and newtime>0 then mod_storage:set_string('bouncedelay', minetest.serialize(newtime)) minetest.display_chat_message("delay saved") else minetest.display_chat_message("delay: "..bouncedelay) end return end local bouncepos = params local bouncetarget = waypoints[bouncepos] if not bouncetarget then minetest.display_chat_message("unknown waypoint") return end local pstr = tostring_point(bouncetarget) local point = minetest.localplayer:get_pos() -- from Maverick2897 with some small changes local player = minetest.localplayer local hud_id = player:hud_add({ hud_elem_type = "text", position = {x=0.0,y=0.8}, size = {x=-20,y=-20}, alignment = {x=1,y=0}, offset = {x=8,y=0}, number = 0xffffff, }) for i=bouncedelay,1,-1 do minetest.after(bouncedelay-i, function() local text = "" if i == 1 then text = "Returning Now" else text = "Returning in "..i.."s\nTarget Pos: "..bouncepos.." ("..pstr..")\nReturn Pos: "..tostring_point(point) end player:hud_change(hud_id,"text",text) end) end minetest.after(bouncedelay,function()player:hud_remove(hud_id)end) -- minetest.after(bouncedelay,teleport_day_back,point.x,point.y,point.z) minetest.display_chat_message("position "..pstr) minetest.run_server_chatcommand('teleport', pstr) return end local function wp_export(param) local wpl = load_waypoints() if not wpl then return end local list = "" for k,p in pairsByKeys(wpl,lc_cmp) do if param == "" or string.find(k,param,1,true) then local line = "("..p.x..","..p.y..","..p.y..") name = "..k.."\n" list = list .. line end end minetest.display_chat_message(list) minetest.show_formspec("cs_waypoints:export", "size[15,12]" .. "label[3.5,0;Waypoint List]".. "textarea[0.3,1;15,11;note;;".. list .."]".. "button_exit[4.5,11.2;1.4,1;e;Close]") return end -- -- -- chat commands -- -- minetest.register_chatcommand('wp_set', { params = '', description = 'set a waypoint', func = safe(function(param) waypoints = load_waypoints() local point = minetest.localplayer:get_pos() if waypoints[param] then minetest.display_chat_message( ('waypoint "%s" not saved: already set'):format(param) ) return end waypoints[param] = point mod_storage:set_string('waypoints', minetest.serialize(waypoints)) minetest.display_chat_message( ('set waypoint "%s" to "%s"'):format(param, tostring_point(point)) ) end), }) minetest.register_chatcommand('wp_unset', { params = '', description = 'remove a waypoint', func = safe(function(param) waypoints = load_waypoints() waypoints[param] = nil mod_storage:set_string('waypoints', minetest.serialize(waypoints)) minetest.display_chat_message( ('removed waypoint "%s"'):format(param) ) end), }) minetest.register_chatcommand('wp_list', { params = '', description = 'lists waypoints', func = safe(function(_) for name, point in pairsByKeys(waypoints, lc_cmp) do minetest.display_chat_message( ('%s -> %s'):format(name, tostring_point(point)) ) end end), }) minetest.register_chatcommand('tw', { params = '', description = 'teleport to a waypoint', func = safe(function(param) safe(teleport_to(param)) end), } ) minetest.register_chatcommand('tw_push', { params = '', description = 'teleport to a waypoint and save old position', func = safe(function(param) stack_push() safe(teleport_to(param)) end), } ) minetest.register_chatcommand('wp_push', { params = '', description = 'teleport to a position/player and save old position', func = safe(function(param) stack_push() minetest.run_server_chatcommand('teleport', param) end), } ) minetest.register_chatcommand('tw_pop', { params = '', description = 'return to the last saved position', func = stack_pop, } ) minetest.register_chatcommand('wp_pop', { params = '', description = 'return to the last saved position', func = stack_pop, } ) minetest.register_chatcommand('tw_use', { params = '', description = "use the last saved position but don't remove it", func = stack_use, } ) minetest.register_chatcommand('wp_use', { params = '', description = "use the last saved position but don't remove it", func = stack_use, } ) minetest.register_chatcommand('tw_exch', { params = '', description = 'exchange the top two stack entried', func = stack_exch, } ) minetest.register_chatcommand('wp_exch', { params = '', description = 'exchange the top two stack entried', func = stack_exch, } ) minetest.register_chatcommand('wp_stack', { params = '', description = 'shows the stack content', func = stack_show, } ) minetest.register_chatcommand('wp_stack_clear', { params = '', description = 'clears the position stack', func = stack_clear, } ) minetest.register_chatcommand('wp_search', { params = '()', description = 'search a waypoint near the current position', func = stack_search, } ) minetest.register_chatcommand('wp_shift', { params = ' ', description = '"shift" the player along the given axis and add the given number', func = position_shift2, } ) minetest.register_chatcommand('wp_dist', { params = '', description = 'calculate the distance to a given waypoint', func = calc_distance, } ) minetest.register_chatcommand('wp_show', { params = '', description = 'show the coordinates of a given waypoint', func = show_pos, } ) minetest.register_chatcommand('tp', { params = '', description = 'shortcut for /teleport', func = safe(function(param) safe(minetest.run_server_chatcommand('teleport',param )) end), } ) -- wp_grep written by erstazi (player at Linux-Forks.de ) minetest.register_chatcommand('wp_grep', { params = '', description = 'lists matching waypoints', func = safe(function(param) local wpname = param local count = 0 for name, point in pairsByKeys(waypoints, lc_cmp) do if string.find(name, wpname) then count = count + 1 minetest.display_chat_message( ('%s -> %s'):format(name, tostring_point(point)) ) end end if count == 0 then minetest.display_chat_message(('waypoint "%s" not found.'):format(wpname)) end end), }) -- wp_fgrep derived from wp_grep by erstazi minetest.register_chatcommand('wp_fgrep', { params = '', description = 'lists matching waypoints', func = safe(function(param) local wpname = param local count = 0 for name, point in pairsByKeys(waypoints, lc_cmp) do if string.find(name, wpname,1,true) then count = count + 1 minetest.display_chat_message( ('%s -> %s'):format(name, tostring_point(point)) ) end end if count == 0 then minetest.display_chat_message(('waypoint "%s" not found.'):format(wpname)) end end), }) minetest.register_chatcommand('day', { params = '[show|setpos|setdelay ]', description = 'teleports to the day button and return after a few seconds ', func = teleport_day, } ) -- bounce name idea by Maverick2797 on 2021-05-20 -- jump to a waypoint and 'bounce' back to the start point after a delay minetest.register_chatcommand('bounce', { params = '[|setdelay []]', description = 'teleports to the given waypoint and returns after a few seconds ', func = teleport_bounce, } ) minetest.register_chatcommand('wp_export', { params = '[]', description = 'Export all waypoints as list', func = wp_export, } ) log('action', 'CSM cs_waypoints '..mod_version..' loaded') minetest.display_chat_message("CSM cs_waypoints "..mod_version.." loaded")