--[[ MIT License Copyright (c) 2020 Pierre-Yves Rollo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local todeg = 180 / math.pi local torad = math.pi / 180 local contexts = {} local mod_storage = minetest.get_mod_storage() local saved_positionings = minetest.deserialize( mod_storage:get_string("positionings"), true) or {} local function get_positionings(player_name) return saved_positionings[player_name] or {} end local function save_positionings(player_name, player_positionings) saved_positionings[player_name] = player_positionings mod_storage:set_string("positionings", minetest.serialize(saved_positionings)) end local function list_positionings(player_name) local result = {} for name, _ in pairs(get_positionings(player_name)) do result[#result + 1] = name end table.sort(result) return result end local function set_player_positioning(player_name, positioning) local player = minetest.get_player_by_name(player_name) if not player then return end player:set_pos({ x = positioning.pos_x, y = positioning.pos_y, z = positioning.pos_z, }) player:set_look_vertical(positioning.look_v * torad) player:set_look_horizontal(positioning.look_h * torad) player:set_fov(positioning.fov) end local function get_player_positioning(player_name) local player = minetest.get_player_by_name(player_name) if not player then return end local pos = player:get_pos() local fov = player:get_fov() if fov == 0 then fov = minetest.settings:get("fov") end return { pos_x = pos.x, pos_y = pos.y, pos_z = pos.z, look_v = player:get_look_vertical() * todeg, look_h = player:get_look_horizontal() * todeg, fov = fov, } end local positioning_fields = { { name = "pos_x", label = "X", step = 0.5, format = ".3f"}, { name = "pos_y", label = "Y", step = 0.5, format = ".3f"}, { name = "pos_z", label = "Z", step = 0.5, format = ".3f"}, { name = "look_v", label = "Vertical", step = 5.0, format = ".1f", min = -90, max = 90}, { name = "look_h", label = "Horiz.", step = 5.0, format = ".1f"}, { name = "fov", label = "FOV", step = 5.0, format = ".1f", min = 45, max = 180}, } local function fields_to_positioning(fields) local positioning = {} for _, field in ipairs(positioning_fields) do positioning[field.name] = tonumber(fields[field.name]) if not positioning[field.name] then return end end return positioning end local function show_formspec(player_name) local positioning = get_player_positioning(player_name) local context = contexts[player_name] or { current_positioning = positioning } context.positionings = list_positionings(player_name) local list = "" for _, name in pairs(context.positionings) do list = list .. "," .. name or name end local fs = "formspec_version[3]".. "size[15,12]" .. "no_prepend[]" .. "bgcolor[black;neither;black]" .. "container[0,0]".. "box[0,0;3,7;#00000040]".. "label[0.2,0.4;Saved positionings]".. "textlist[0.2,0.7;2.6,3;list;" .. list .. ";" .. (context.selected or 1) .. ";false]" .. "button[0.2,4;2.6,0.7;btn_delete;Delete selected]" .. "field[0.2,5.3;2.6,0.5;name;Save positioning as;]" .. "button[0.2,6;2.6,0.7;btn_save;Save]" .. "container_end[]".. "container[2.25,9.8]".. "box[0,0;10.5,2.2;#00000040]".. "container[0.2,0.4]".. "label[0,0;Position:]" .. "label[4.5,0;Look:]" local x = 0 for _, field in ipairs(positioning_fields) do fs = fs .. ("field[%.1f,0.5;1.4,0.5;%s;%s;%".. field.format .."]") :format(x, field.name, field.label, positioning[field.name]) .. ("button[%.1f,1.1;0.5,0.5;btn_%s_dec;-]"):format(x, field.name) .. ("button[%.1f,1.1;0.5,0.5;btn_%s_inc;+]"):format(x + 0.9, field.name) x = x + 1.5 end fs = fs .. ("button[%.1f,0.5;1,0.5;btn_apply;Apply]"):format(x) .. "container_end[]" .. "container_end[]" if context.message then fs = fs .. "container[3.5,8]".. "box[0,0;8,1;#80000040]".. "label[1,0.5;" .. context.message .. "]" .. "container_end[]" context.message = nil end contexts[player_name] = context minetest.show_formspec(player_name, "dev_positioning", fs) end minetest.register_on_player_receive_fields( function(player, formname, fields) if formname ~= "dev_positioning" then return end local player_name = player:get_player_name() if fields.quit and not fields.key_enter then contexts[player_name] = nil minetest.close_formspec(player_name, formname) return end if not minetest.check_player_privs(player_name, {server=true}) then minetest.log("error", "Suspicious fields received. Player:" .. player_name .. " Formspec:" .. formname .. " (missing privilege).") minetest.close_formspec(player_name, formname) return end local context = contexts[player_name] if not context then minetest.log("error", "Suspicious fields received. Player:" .. player_name .. " Formspec:" .. formname .. " (no context found).") minetest.close_formspec(player_name, formname) return end local positioning = fields_to_positioning(fields) if not positioning then context.message = "Invalid positioning" show_formspec(player_name) return end if fields.list then local event = minetest.explode_textlist_event(fields.list) if event.type == "CHG" then context.selected = event.index if context.selected == 1 then positioning = context.current_positioning else local positionings = get_positionings(player_name) positioning = positionings[context.positionings[context.selected - 1]] end if positioning then set_player_positioning(player_name, positioning) show_formspec(player_name) end end return end if fields.btn_save then if not fields.name or fields.name == "" then context.message = "Please enter a name" show_formspec(player_name) return end -- TODO : Add warning if name exists local positionings = get_positionings(player_name) positionings[fields.name] = positioning save_positionings(player_name, positionings) show_formspec(player_name) return end if fields.btn_delete and context.selected then local positionings = get_positionings(player_name) if context.positionings[context.selected - 1] then positionings[context.positionings[context.selected - 1]] = nil save_positionings(player_name, positionings) context.selected = 1 show_formspec(player_name) end return end for _, field in ipairs(positioning_fields) do local value = math.floor(positioning[field.name] / field.step + 0.5) * field.step if fields["btn_".. field.name .."_dec"] then positioning[field.name] = value - field.step if field.min and positioning[field.name] < field.min then positioning[field.name] = field.min end end if fields["btn_".. field.name .."_inc"] then positioning[field.name] = value + field.step if field.max and positioning[field.name] > field.max then positioning[field.name] = field.max end end end set_player_positioning(player_name, positioning) show_formspec(player_name) end) minetest.register_chatcommand("devpos", { params = "", description = "Open dev positioning formspec", privs = { server=true }, func = function(player_name, param) show_formspec(player_name) return true end })