diff --git a/mainmenu/init.lua b/mainmenu/init.lua index 7dbff38..bc821f0 100644 --- a/mainmenu/init.lua +++ b/mainmenu/init.lua @@ -22,59 +22,46 @@ state = {} local fe = minetest.formspec_escape local hte = minetest.hypertext_escape +--FIXME: Replace with /content because pause menu +dofile(minetest.get_builtin_path().."mainmenu/settings/settingtypes.lua") + ---[[ +minetest.set_formspec_prepend("\ + style[*;textcolor=#aaa]\ + style_type[field;border=false]\ + style_type[pwdfield;border=false]\ + style_type[image_button;border=false]\ + style_type[button;border=false;bgimg="..assets.."btn_bg_2.png;bgimg_middle=8,8]\ + style_type[button:hovered;border=false;bgimg="..assets.."btn_bg_2_hover.png;bgimg_middle=8,8]\ + style[nobg,nobg:hovered,nobg:focused,nobg:hovered+focused;border=false;bgimg="..default_textures.."blank.png;bgimg_middle=0]\ +") + -- Prepended to the meta menu's formspec. local meta_header = "formspec_version[8]\ size["..size.x..","..size.y.."]\ padding[0,0]\ - style[*;textcolor=#aaa]\ - style_type[image_button;border=false]\ - style_type[button;border=false;bgimg="..assets.."btn_bg.png;bgimg_middle=8,8]\ bgcolor[#000;true;#151618]\ style_type[box;bordercolors=#124722;borderwidths=-5;colors=#151618]\ - box[0,0;"..size.x..","..size.y..";]\ image[0,"..(size.y *0.08)..");"..size.x..","..(size.x *(72/672))..";"..default_textures.."menu_header.png]\ " -local meta_footer = "\ - style[nobg;border=false;bgimg="..default_textures.."blank.png;bgimg_middle=0]\ - style[show_servers_view;border=false;bgimg="..fe(assets).."menu_servers.png;bgimg_middle=0]\ - style[show_servers_view:hovered;border=false;bgimg="..fe(assets).."menu_servers_hovered.png;bgimg_middle=0]\ - button["..(size.x *0.05)..","..(size.y *0.3)..";4,4;show_servers_view;]\ - button["..(size.x *0.05)..","..(size.y *0.3 +4)..";4,0.5;nobg;Servers]\ - style[show_content_view;border=false;bgimg="..fe(assets).."menu_content.png;bgimg_middle=0]\ - style[show_content_view:hovered;border=false;bgimg="..fe(assets).."menu_content_hovered.png;bgimg_middle=0]\ - button["..(size.x -(size.x *0.05) -4)..","..(size.y *0.3)..";4,4;show_content_view;]\ - button["..(size.x -(size.x *0.05) -4)..","..(size.y *0.3 +4)..";4,0.5;nobg;Content]\ - " +-- box[0,0;"..size.x..","..size.y..";]\ -- Prepended to game menus' formspecs. local game_header = "formspec_version[8]\ size["..size.x..","..size.y.."]\ padding[0,0]\ - style[*;textcolor=#aaa]\ - style_type[image_button;border=false]\ - style_type[button;border=false;bgimg="..assets.."btn_bg.png;bgimg_middle=8,8]\ bgcolor[#0000;true;#0000]\ " local servers_header = "formspec_version[8]\ size["..size.x..","..size.y.."]\ padding[0,0]\ - style[*;textcolor=#aaa]\ - style_type[image_button;border=false]\ - style_type[field;border=false]\ - style_type[pwdfield;border=false]\ - style_type[button;border=false;bgimg="..assets.."btn_bg_2.png;bgimg_middle=8,8]\ - style_type[button:hovered;border=false;bgimg="..assets.."btn_bg_2_hover.png;bgimg_middle=8,8]\ bgcolor[#0000;true;#151618]\ " local content_header = "formspec_version[8]\ size["..size.x..","..size.y.."]\ padding[0,0]\ - style[*;textcolor=#aaa]\ - style_type[image_button;border=false]\ - style_type[button;border=false;bgimg="..assets.."btn_bg.png;bgimg_middle=8,8]\ bgcolor[#0000;true;#151618]\ " @@ -194,7 +181,7 @@ local function get_worlds_for_game(id) end -- Returns a list of content available for download. -function get_all_content() +function get_available_content() local version = minetest.get_version() local cdb = minetest.settings:get("contentdb_url") local url = cdb.."/api/packages/?type=mod&type=game&type=txp&protocol_version="..minetest.get_max_supp_proto().."&engine_version="..minetest.urlencode(version.string) @@ -232,6 +219,88 @@ function get_all_content() return out end +-- Returns a list of all the installed mods. +function get_all_mods(dir) + local all = false + if not dir then + if state.all_mods then return state.all_mods end + dir = minetest.get_modpath() + all = true + end + local out = {} + for _, x in ipairs(minetest.get_dir_list(dir, true)) do + local info = minetest.get_content_info(dir.."/"..x) + if info.type == "mod" then + out[#out +1] = info + end + end + if all then + state.all_mods = out + end + return out +end + +-- Returns a list of all the installed games. +function get_all_games(dir) + local all = false + if not dir then + if state.all_games then return state.all_games end + dir = minetest.get_gamepath() + all = true + end + local out = {} + for _, x in ipairs(minetest.get_dir_list(dir, true)) do + local info = minetest.get_content_info(dir.."/"..x) + if info.type == "game" then + out[#out +1] = info + end + end + if all then + state.all_games = out + end + return out +end + +-- Returns a list of all the installed texture packs. +function get_all_texture_packs(dir) + local all = false + if not dir then + if state.all_texture_packs then return state.all_texture_packs end + dir = minetest.get_texturepath() + all = true + end + local out = {} + for _, x in ipairs(minetest.get_dir_list(dir, true)) do + local info = minetest.get_content_info(dir.."/"..x) + if info.type == "txp" then + out[#out +1] = info + end + end + if all then + state.all_texture_packs = out + end + return out +end + +-- Return a list of all content, as a conglomeration of the lists of mods, games, and texture packs. +function get_all_content() + if not state.all_content then + state.all_content = {} + table.insert_all(state.all_content, get_all_mods()) + table.insert_all(state.all_content, get_all_games()) + table.insert_all(state.all_content, get_all_texture_packs()) + table.sort(state.all_content, function(a, b) + if not a then + return b ~= nil + elseif not b then + return a == nil + end + return a.name < b.name + end) + end + return state.all_content +end + -- Parses text as .conf format. function parse_conf_text(txt) local out = {} @@ -557,7 +626,7 @@ local function evaluate_template_expression(expr, vars, depth) vars = {item = vars} end - -- Expand all variables so we can deal with a constexpr + -- Expand all variables so we can deal with a constexpr. local offset = 1 while offset < 100000 do local a, b, name = expr:find("@([%a]+)", offset) @@ -709,8 +778,7 @@ function show_meta_menu(v) " if dist == 0 then fs = fs.."\ - style[_game_label;bgimg="..default_textures.."blank.png]\ - button["..(lc -(scale /2))..","..(size.y /2 +4)..";4,0.5;_game_label;"..fe(x.title).."]\ + button["..(lc -(scale /2))..","..(size.y /2 +4)..";4,0.5;nobg;"..fe(x.title).."]\ " end end @@ -723,7 +791,20 @@ function show_meta_menu(v) scrollbar[0,-800;"..size.x..",0;horizontal;carousel;"..(v or "").."]\ " - minetest.update_formspec(meta_header..fs..meta_footer) + minetest.update_formspec(meta_header..fs.."\ + style[show_servers_view;border=false;bgimg="..fe(assets).."menu_servers.png;bgimg_middle=0]\ + style[show_servers_view:hovered;border=false;bgimg="..fe(assets).."menu_servers_hovered.png;bgimg_middle=0]\ + button["..(size.x *0.05)..","..(size.y *0.3)..";4,4;show_servers_view;]\ + button["..(size.x *0.05)..","..(size.y *0.3 +4)..";4,0.5;nobg;Servers]\ + style[show_content_view;border=false;bgimg="..fe(assets).."menu_content.png;bgimg_middle=0]\ + style[show_content_view:hovered;border=false;bgimg="..fe(assets).."menu_content_hovered.png;bgimg_middle=0]\ + button["..(size.x -(size.x *0.05) -4)..","..(size.y *0.3)..";4,4;show_content_view;]\ + button["..(size.x -(size.x *0.05) -4)..","..(size.y *0.3 +4)..";4,0.5;nobg;Content]\ + style[show_settings_view,show_about_view;bgimg="..assets.."menu_tab_bg.png]\ + style[show_settings_view:hovered,show_about_view:hovered;bgimg="..assets.."menu_tab_bg.png;bgcolor=#fffd]\ + button[1,0;2,0.5;show_about_view;About]\ + button["..(size.x -3)..",0;2,0.5;show_settings_view;Settings]\ + ") end -- Shows games' custom menus. @@ -823,6 +904,13 @@ function show_game_menu(args) ") end +function add_favorite_server(address, port) + state.favorite_servers[#state.favorite_servers +1] = {address = address, port = port} + local f = io.open(minetest.get_user_path().."/client/serverlist/favoriteservers.json", "w") + f:write(minetest.write_json(state.favorite_servers)) + f:flush() + f:close() +end function refresh_server_list() minetest.handle_async( @@ -843,17 +931,9 @@ function refresh_server_list() end, nil, function(result) - state.serverlist = result - -- If we need to load the server list, we need to read the list of favorite servers as well. - local favorite_servers = minetest.parse_json(read_file(minetest.get_user_path().."/client/serverlist/favoriteservers.json") or "{}") - if #favorite_servers > 0 then - for _, x in ipairs(favorite_servers) do - x.favorite = true - end - table.insert_all(favorite_servers, state.serverlist) - state.serverlist = favorite_servers - end - state.favorite_servers = favorite_servers + local list = table.copy(state.favorite_servers) + table.insert_all(list, result) + state.serverlist = list if state.loc == "servers" then show_servers_menu() end @@ -861,6 +941,94 @@ function refresh_server_list() ) end +function search_server_list(input) + if input:trim() == "" then + return nil + end + + local search_str = "" + local words = {} + local mods = {} + local games = {} + local players = {} + + input = input:lower() + + for x in input:gmatch("%S+") do + if x:sub(1, 4) == "mod:" then + mods[#mods +1] = x:sub(5) + elseif x:sub(1, 7) == "player:" then + players[#players +1] = x:sub(8) + elseif x:sub(1, 5) == "game:" then + games[#games +1] = x:sub(6) + else + words[#words +1] = x + end + end + +-- print(dump({words = words, mods= mods, players =players, games = games}, " ")) + + local out = {} + for _, x in ipairs(state.serverlist) do + local passed = true + + for _, a in ipairs(words) do + if not (x.description and x.description:lower():find(a, 1, true) or x.name and x.name:lower():find(a, 1, true)) then + passed = false + end + end + + -- If only `continue` existed... + if passed then + if #games > 0 then + passed = false + end + for _, a in ipairs(games) do + if a == x.gameid then + passed = true + end + end + + if passed then + if x.mods and #mods > 0 then + local found = 0 + for _, a in ipairs(x.mods) do + for _, b in ipairs(mods) do + if a == b then + found = found +1 + end + end + end + passed = found == #mods + else + passed = not not x.mods + end + + if passed then + if x.clients_list and #players > 0 then + local found = 0 + for _, a in ipairs(x.clients_list) do + for _, b in ipairs(players) do + if a:lower() == b then + found = found +1 + end + end + end + + passed = found == #players + else + passed = not not x.clients_list + end + if passed then + out[#out +1] = x + end + end + end + end + end + return out +end + -- Shows the server list. function show_servers_menu() local fs = "" @@ -872,23 +1040,28 @@ function show_servers_menu() for _, x in ipairs(favorite_servers) do x.favorite = true end + state.favorite_servers = favorite_servers state.serverlist = favorite_servers --- fs = "\ --- hypertext[0,0;"..size.x..","..size.y..";ht;\ --- ]\ --- " end + + -- Overlay dialogs don't occlude area tooltips, so such tooltips are disabled in that case. + local showing_dialog = state.joining_server or state.connecting_to_server or state.showing_server_mods or state.showing_server_players + local current_server local joining_server fs = "\ style[servers_filter;border=false;textcolor=#aaa]\ - image["..(size.x *0.095)..","..(size.y *0.1 -0.85)..";"..(size.x *0.81 -2.55)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ - field["..(size.x *0.095 +0.1)..","..(size.y *0.1 -0.85)..";"..(size.x *0.81 -2.2)..",0.75;servers_filter;;"..(state.menu_vars.servers_filter or "").."]\ + image["..(size.x *0.095)..","..(size.y *0.1 -0.85)..";"..(size.x *0.81 -5.65)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ + field["..(size.x *0.095 +0.1)..","..(size.y *0.1 -0.85)..";"..(size.x *0.81 -5.45)..",0.75;servers_filter;;"..(state.menu_vars.servers_filter or "").."]\ style[servers_search,servers_refresh,servers_cancel;bgimg="..assets.."btn_bg_2.png;bgimg_middle=8,8]\ - image_button["..(size.x *0.91 -0.85)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."refresh.png;servers_refresh;]\ - image_button["..(size.x *0.91 -1.7)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."cancel.png;servers_cancel;]\ - image_button["..(size.x *0.91 -2.55)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."search.png;servers_search;]\ + image_button["..(size.x *0.91 -5.65)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."search.png;servers_search;]\ + tooltip[servers_search;Search server list;#444;#aaa]\ + image_button["..(size.x *0.91 -4.8)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."cancel.png;servers_cancel;]\ + tooltip[servers_cancel;Cancel search;#444;#aaa]\ + image_button["..(size.x *0.91 -3.95)..","..(size.y *0.1 -0.85)..";0.75,0.75;"..assets.."refresh.png;servers_refresh;]\ + tooltip[servers_refresh;Refresh server list;#444;#aaa]\ + button["..(size.x *0.91 -3.1)..","..(size.y *0.1 -0.85)..";3,0.75;direct_connection;Direct Connection...]\ image["..(size.x *0.095)..","..(size.y *0.1)..";"..(size.x *0.81)..","..(size.y *0.8)..";"..assets.."btn_bg.png;8,8]\ scroll_container[0,"..(size.y *0.1 +0.1)..";"..size.x..","..(size.y *0.8 -0.2)..";serverscroll;vertical;;0,0]\ " @@ -896,47 +1069,7 @@ function show_servers_menu() local infox = state.current_server and size.x *0.6 or size.x *0.9 local max = size.x *0.9 local i = 0 --- if #state.favorite_servers > 0 then --- fs = fs.."\ --- box["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;#"..(i %2 == 1 and "373530" or "403e39").."ff]\ --- box["..(listx -0.05)..","..(i *0.5 +0.2)..";"..(infox -listx +0.1)..",0.1;#292d2fff]\ --- box["..((infox -listx) /2 -1.5)..","..(i *0.5)..";3,0.5;#"..(i %2 == 1 and "373530" or "403e39").."ff]\ --- box["..((infox -listx) /2 -1.55)..","..(i *0.5 +0.125)..";0.1,0.25;#292d2fff]\ --- box["..((infox -listx) /2 +1.45)..","..(i *0.5 +0.125)..";0.1,0.25;#292d2fff]\ --- hypertext["..((infox -listx) /2 -1.5)..","..(i *0.5)..";3,0.5;;Favorite Servers]\ --- " --- i = i +1 --- end --- for idx, x in ipairs(state.favorite_servers) do --- if x.address and x.port then --- local address = minetest.encode_base64(x.address) --- if address == state.current_server then --- current_server = x --- end --- if address == state.joining_server then --- joining_server = x --- end --- fs = fs.."\ --- style[serverinfof"..idx..";border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#"..(i %2 == 1 and "373530" or "403e39").."ff]\ --- style[serverinfof"..idx..":hovered;border=false;bgimg="..assets.."white.png;bgimg_middle=0]\ --- button["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;serverinfof"..idx..";]\ --- label["..(listx +0.1)..","..(i *0.5 +0.25)..";"..fe(x.address..":"..x.port).."]\ --- " --- i = i +1 --- end --- end --- if i > 0 then --- fs = fs.."\ --- box["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;#"..(i %2 == 1 and "373530" or "403e39").."ff]\ --- box["..(listx -0.05)..","..(i *0.5 +0.2)..";"..(infox -listx +0.1)..",0.1;#292d2fff]\ --- box["..((infox -listx) /2 -1.5)..","..(i *0.5)..";3,0.5;#"..(i %2 == 1 and "373530" or "403e39").."ff]\ --- box["..((infox -listx) /2 -1.55)..","..(i *0.5 +0.125)..";0.1,0.25;#292d2fff]\ --- box["..((infox -listx) /2 +1.45)..","..(i *0.5 +0.125)..";0.1,0.25;#292d2fff]\ --- hypertext["..((infox -listx) /2 -1.5)..","..(i *0.5)..";3,0.5;;Public Servers]\ --- " --- i = i +1 --- end - for idx, x in ipairs(state.serverlist) do + for idx, x in ipairs(state.serverlist_filtered or state.serverlist) do -- Skip incompatible servers. You can't join them, so showing them is rather pointless. if (x.proto_max or version.proto_min) >= version.proto_min then local ping_lvl = 0 @@ -955,8 +1088,6 @@ function show_servers_menu() if name == "" then name = minetest.colorize("#888", x.address..":"..x.port) end - -- Otherwise, the colons in an IPv6 address would make the style element blow up. - --local address = minetest.encode_base64(x.address) local label_offset = 0.1 fs = fs.."\ style[serverinfo"..idx..";border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#"..(i %2 == 1 and "373530" or "403e39").."ff]\ @@ -964,17 +1095,50 @@ function show_servers_menu() button["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;serverinfo"..idx..";]\ " if x.favorite then - -- The overlay dialog doesn't occlude area tooltips, so they are disabled in that case. fs = fs.."\ image["..(listx)..","..(i *0.5)..";0.5,0.5;"..assets.."menu_servers_favorite.png]\ - "..(state.joining_server and "" or "tooltip["..(listx)..","..(i *0.5)..";0.5,0.5;Favorite server;#444;#aaa]\ + "..(showing_dialog and "" or "tooltip["..(listx)..","..(i *0.5)..";0.5,0.5;Favorite server;#444;#aaa]\ ") label_offset = 0.6 end + local clients = "" + local color = "#aaa" + local icons_offset = (infox -0.6) + if x.clients then + icons_offset = icons_offset -1 + clients = x.clients..(x.clients_max and "/"..x.clients_max or "") + if x.clients > 0 and x.clients_max then + local percent = x.clients /x.clients_max + if percent < 0.75 then + color = "#638b67" + elseif percent < 1 then + color = "#a69174" + else + color = "#9d5b5b" + end + end + fs = fs.."\ + hypertext["..icons_offset..","..(i *0.5)..";1,0.5;;"..fe(clients).."]\ + " + end + if x.pvp == false then + icons_offset = icons_offset -0.5 + fs = fs.."\ + image["..(icons_offset)..","..(i *0.5)..";0.5,0.5;"..assets.."menu_servers_peaceful.png]\ + "..(showing_dialog and "" or "tooltip["..(icons_offset)..","..(i *0.5)..";0.5,0.5;Peaceful;#444;#aaa]\ + ") + end + if x.creative then + icons_offset = icons_offset -0.5 + fs = fs.."\ + image["..(icons_offset)..","..(i *0.5)..";0.5,0.5;"..assets.."menu_servers_creative.png]\ + "..(showing_dialog and "" or "tooltip["..(icons_offset)..","..(i *0.5)..";0.5,0.5;Creative;#444;#aaa]\ + ") + end fs = fs.."\ label["..(listx +label_offset)..","..(i *0.5 +0.25)..";"..fe(name).."]\ "..((x.ping or x.lag) and "image["..(infox -0.6)..","..(i *0.5)..";0.5,0.5;"..assets.."menu_servers_icon_ping_"..ping_lvl..".png]\ - "..(state.joining_server and "" or "tooltip["..(infox -0.6)..","..(i *0.5)..";0.5,0.5;Ping: "..math.floor(lag)..";#444;#aaa]\ + "..(showing_dialog and "" or "tooltip["..(infox -0.6)..","..(i *0.5)..";0.5,0.5;Ping: "..math.floor(lag)..";#444;#aaa]\ ") or "") i = i +1 end @@ -984,6 +1148,11 @@ function show_servers_menu() hypertext["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;;Loading...]\ " end + if i < 1 then + fs = fs.."\ + hypertext["..listx..","..(i *0.5)..";"..(infox -listx)..",0.5;;No servers found.]\ + " + end fs = fs.."\ scroll_container_end[]\ scrollbar[-800,0;1,1;vertical;serverscroll;"..(state.menu_vars.serverscroll and tonumber(state.menu_vars.serverscroll:sub(5)) or 0).."]\ @@ -996,7 +1165,31 @@ function show_servers_menu() box["..((max -infox) *0.7 -0.05)..",0.75;0.1,0.5;#292d2fff]\ hypertext[0.2,0.8;"..((max -infox) *0.7 -0.2)..",0.5;server_address;"..fe(hte(state.current_server.address)).."]\ hypertext["..((max -infox) *0.7 +0.1)..",0.8;"..((max -infox) *0.3 -0.2)..",0.5;server_port;"..fe(hte(tostring(state.current_server.port))).."]\ - hypertext[0.1,1.5;"..(max -infox -0.2)..","..((size.y *0.8 -1.5 -1.05))..";server_desc;"..fe(hte(state.current_server.description or "")).."]\ + " + local buttons_offset = 0.1 + if state.current_server.mods then + fs = fs.."\ + image_button["..buttons_offset..",1.5;0.5,0.5;"..assets.."menu_servers_mods.png;show_server_mods;]\ + tooltip[show_server_mods;Show mod list;#444;#aaa]\ + " + buttons_offset = buttons_offset +0.5 + end + if state.current_server.clients then + fs = fs.."\ + image_button["..buttons_offset..",1.5;0.5,0.5;"..assets.."menu_servers_players.png;show_server_players;]\ + tooltip[show_server_players;Show player list;#444;#aaa]\ + " + buttons_offset = buttons_offset +0.5 + end + if state.current_server.favorite then + fs = fs.."\ + image_button["..buttons_offset..",1.5;0.5,0.5;"..assets.."menu_servers_unfavorite.png;unfavorite_server;]\ + tooltip[unfavorite_server;Remove from favorites;#444;#aaa]\ + " + buttons_offset = buttons_offset +0.5 + end + fs = fs.."\ + hypertext[0.1,2.25;"..(max -infox -0.2)..","..((size.y *0.8 -1.5 -1.05))..";server_desc;"..fe(hte(state.current_server.description or "")).."]\ button[0.1,"..(size.y *0.8 -1)..";"..(max -infox -0.2)..",0.75;join_server;Join Server]\ container_end[]\ " @@ -1006,28 +1199,98 @@ function show_servers_menu() button[0,"..(size.y -1)..";1,1;to_meta_menu;<]\ " - if state.joining_server or state.connecting_to_server then - local offset = 0 - if state.server_connection_error then - offset = offset +1 - fs= fs.."\ - hypertext[0,0;"..(size.x *0.5)..",1;;"..fe(hte(state.server_connection_error)).."]\ + if state.showing_server_mods then + fs = fs.."\ + box[0,0;"..size.x..","..size.y..";#0008]\ + style[_even,_even:hovered;border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#403e39ff]\ + style[_odd,_odd:hovered;border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#373530ff]\ + image["..(size.x /4 -0.1)..","..(size.y /4 -0.1)..";"..(size.x /2 +0.2)..","..(size.y /2 +0.2)..";"..assets.."btn_bg.png;8,8]\ + scroll_container["..(size.x /4)..","..(size.y /4)..";"..(size.x /2)..","..(size.y /2 -1)..";modsscroll;vertical;;0,0]\ + " + for i, x in ipairs(state.current_server.mods) do + fs = fs.."\ + button[0,"..(i *0.5 -0.5)..";"..(infox -listx)..",0.5;"..(i %2 == 1 and "_odd" or "_even")..";]\ + label[0.1,"..(i *0.5 -0.25)..";"..fe(x).."]\ " end fs = fs.."\ + scroll_container_end[]\ + scrollbar[-800,0;1,1;vertical;modsscroll;]\ + button["..(size.x /4)..","..(size.y *0.75 -0.875)..";"..(size.x /2)..",0.75;close_dialog;Back]\ + " + end + + if state.showing_server_players then + fs = fs.."\ + box[0,0;"..size.x..","..size.y..";#0008]\ + style[_even,_even:hovered;border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#403e39ff]\ + style[_odd,_odd:hovered;border=false;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#373530ff]\ + image["..(size.x /4 -0.1)..","..(size.y /4 -0.1)..";"..(size.x /2 +0.2)..","..(size.y /2 +0.2)..";"..assets.."btn_bg.png;8,8]\ + scroll_container["..(size.x /4)..","..(size.y /4)..";"..(size.x /2)..","..(size.y /2 -1)..";modsscroll;vertical;;0,0]\ + " + for i, x in ipairs(state.current_server.clients_list) do + fs = fs.."\ + button[0,"..(i *0.5 -0.5)..";"..(infox -listx)..",0.5;"..(i %2 == 1 and "_odd" or "_even")..";]\ + label[0.1,"..(i *0.5 -0.25)..";"..fe(x).."]\ + " + end + fs = fs.."\ + scroll_container_end[]\ + scrollbar[-800,0;1,1;vertical;modsscroll;]\ + button["..(size.x /4)..","..(size.y *0.75 -0.875)..";"..(size.x /2)..",0.75;close_dialog;Back]\ + " + end + + if state.joining_server or state.connecting_to_server then + local offset = 0 + fs = fs.."\ box[0,0;"..size.x..","..size.y..";#0008]\ image["..(size.x *0.25 -0.2)..","..(size.y *0.25 -0.1)..";"..(size.x *0.5 +0.4)..","..(size.y *0.5 +0.2)..";"..assets.."btn_bg.png;8,8]\ scroll_container["..(size.x *0.25)..","..(size.y *0.25)..";"..(size.x *0.5)..","..(size.y *0.5 +0.2)..";serverconfscroll;vertical;;0,0]\ - hypertext[0,"..offset..";"..(size.x *0.5)..",0.75;;Connecting to "..fe(hte(state.current_server.name or state.current_server.address..":"..state.current_server.port)).."...]\ + " + + if state.server_connection_error then + offset = offset +0.75 + fs = fs.."\ + hypertext[0,0;"..(size.x *0.5)..",0.75;;"..fe(hte(state.server_connection_error.msg)).."]\ + set_focus["..fe(state.server_connection_error.element)..";true]\ + " + else + fs = fs.."\ + set_focus[server_"..(state.joining_server and "username" or "address")..";true]\ + " + end + if state.joining_server then + fs = fs.."\ + hypertext[0,"..offset..";"..(size.x *0.5)..",0.75;;Connecting to "..fe(hte(state.current_server.name or state.current_server.address..":"..state.current_server.port)).."...]\ + " + offset = offset +1 + else + fs = fs.."\ + set_focus[server_address;true]\ + hypertext[0,"..offset..";"..(size.x *0.5)..",0.75;;Connecting to server...]\ + " + offset = offset +1 + fs = fs.."\ + image[0,"..(0.25 +offset)..";"..(size.x *0.5)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ + label[0.2,"..(0.1 +offset)..";Address]\ + field[0.2,"..(0.25 +offset)..";"..(size.x *0.4 -0.4)..",0.75;server_address;;"..(state.menu_vars.server_address or "").."]\ + box["..(size.x *0.4 -0.25)..","..(0.25 +offset)..";0.1,0.75;#292d2fff]\ + label["..(size.x *0.4 -0.2)..","..(0.1 +offset)..";Port]\ + field["..(size.x *0.4)..","..(0.25 +offset)..";"..(size.x *0.1 -0.2)..",0.75;server_port;;"..(state.menu_vars.server_port or "").."]\ + " + offset = offset +1 + end + fs = fs.."\ + image[0,"..(0.25 +offset)..";"..(size.x *0.5)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ + label[0.2,"..(0.1 +offset)..";Username]\ + field[0.2,"..(0.25 +offset)..";"..(size.x *0.5 -0.4)..",0.75;server_username;;"..minetest.settings:get("name").."]\ image[0,"..(1.25 +offset)..";"..(size.x *0.5)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ - label[0.2,"..(1.1 +offset)..";Username]\ - field[0.2,"..(1.25 +offset)..";"..(size.x *0.5 -0.4)..",0.75;server_username;;"..minetest.settings:get("name").."]\ - image[0,"..(2.25 +offset)..";"..(size.x *0.5)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ style[server_password;border=false;textcolor=#aaa;bgimg="..assets.."btn_bg.png]\ - label[0.2,"..(2.1 +offset)..";Password]\ - pwdfield[0.2,"..(2.25 +offset)..";"..(size.x *0.5 -0.4)..",0.75;server_password;]\ - button[0,"..(3.25 +offset)..";"..(size.x *0.5)..",0.75;cancel_join_server;Cancel]\ - button[0,"..(4.25 +offset)..";"..(size.x *0.5)..",0.75;confirm_join_server;Join]\ + label[0.2,"..(1.1 +offset)..";Password]\ + pwdfield[0.2,"..(1.25 +offset)..";"..(size.x *0.5 -0.4)..",0.75;server_password;]\ + button[0,"..(2.25 +offset)..";"..(size.x *0.5)..",0.75;cancel_join_server;Cancel]\ + button[0,"..(3.25 +offset)..";"..(size.x *0.5)..",0.75;confirm_join_server;Join]\ scroll_container_end[]\ scrollbar[-800,0;1,1;vertical;serverconfscroll;"..(state.menu_vars.serverconfscroll and tonumber(state.menu_vars.serverconfscroll:sub(5)) or 0).."]\ " @@ -1038,6 +1301,90 @@ end -- Shows the content manager. function show_content_menu() local fs = "" + + if not state.all_content then + get_all_content() + end + + local w = size.x + local h = size.y -1.1 + + fs = "\ + scroll_container[0,1.1;"..w..","..h..";contentscroll;horizontal;;]\ + " + + local cols = math.floor(w /2.5) + local spacing_x = w %2.5 /cols +0.5 + + local rows = math.floor(h /3) + local spacing_y = h %3 /rows +0.5 + + local pages = math.ceil(#state.all_content /(rows *cols)) + local page = state.content_page or 1 + + local offset = page /10 + + local x = 0 + local y = 0 + local i = (page -1) *(rows *cols) +1 + + while true do + local pkg = state.all_content[i] + if not pkg then break end + local icon = pkg.type == "game" and pkg.path.."/menu/icon.png" or pkg.path.."/icon.png" + if not file_exists(icon) then + icon = assets.."menu_content.png" + end + local title = pkg.title:trim() + if title == "" then + title = pkg.name + end + fs = fs.."\ + image_button["..(x *(2 +spacing_x) +(spacing_x /2) +offset)..","..(y *(2 +spacing_y) +(spacing_y /2))..";2,2;"..fe(icon)..";view_package_"..fe(pkg.path)..";]\ + tooltip[view_package_"..fe(pkg.path)..";"..fe(title)..";#444;#aaa]\ + hypertext["..(x *(2 +spacing_x) +(spacing_x /2) +offset)..","..(y *(2 +spacing_y) +(spacing_y /2) +2)..";2,0.5;nobg;"..fe(hte(title)).."]\ + " + i = i +1 + x = x +1 + if x > cols -1 then + x = 0 + y = y +1 + if y > rows -1 then break end + end + end + + fs = fs.."\ + box[0,0;"..(pages *w)..",1;#0000]\ + scroll_container_end[]\ + scrollbaroptions[min=1;max="..pages..";smallstep=1]\ + scrollbar[-800,0;1,1;horizontal;contentscroll;"..page.."]\ + " + + fs = fs.."\ + box[0,0;"..size.x..",1;#403e39ff]\ + box[0,1;"..size.x..",0.1;#292d2fff]\ + image_button[0,0;1,1;"..assets.."search.png;test;]\ + image_button[1,0;1,1;"..assets.."cancel.png;test;]\ + style[test;bgimg="..assets.."white.png;bgimg_middle=0;bgcolor=#403e39ff]\ + style[test:hovered;bgimg="..assets.."white.png;bgimg_middle=0]\ + image[2,0.125;"..(size.x -9)..",0.75;"..assets.."btn_bg_2_light.png;8,8]\ + field[2.1,0.125;"..(size.x -9.2)..",0.75;content_filter;;]\ + button["..(w -7)..",0;4,1;test;Browse Online Content...]\ + button["..(w -3)..",0;3,1;test;Add Content...]\ + " + + local start = w /2 -(pages *0.125) + + for i = 1, pages do + fs = fs.."\ + image_button["..(start +(i *0.25))..","..(size.y -0.5)..";0.25,0.25;"..assets..(i == page and "circle_light.png" or "circle.png")..";page"..i..";]\ + " + end + + if state.current_package then + + end + minetest.update_formspec(content_header..fs.."\ button[0,"..(size.y -1)..";1,1;to_meta_menu;<]\ ") @@ -1074,26 +1421,67 @@ function minetest.button_handler(data) if data.servers_refresh then refresh_server_list() elseif data.servers_search or data.key_enter_field == "servers_filter" then - state.servers_filter = data.servers_filter + state.serverlist_filtered = search_server_list(data.servers_filter) show_servers_menu() elseif data.servers_cancel then - state.servers_filter = nil + state.serverlist_filtered = nil + show_servers_menu() + elseif data.show_server_mods then + state.showing_server_mods = true + show_servers_menu() + elseif data.show_server_players then + state.showing_server_players = true + show_servers_menu() + elseif data.close_dialog then + state.showing_server_players = nil + state.showing_server_mods = nil + show_servers_menu() + elseif data.direct_connection then + state.connecting_to_server = true show_servers_menu() elseif data.join_server then state.joining_server = true show_servers_menu() elseif data.cancel_join_server then state.joining_server = nil + state.connecting_to_server = nil show_servers_menu() - elseif data.confirm_join_server or data.key_enter_field == "server_username" or data.key_enter_field == "server_password" then + elseif data.confirm_join_server or data.key_enter_field == "server_username" or data.key_enter_field == "server_password" or data.key_enter_field == "server_address" then minetest.settings:set("name", data.server_username) - minetest.settings:set("address", state.current_server.address) - minetest.settings:set("remote_port", state.current_server.port) + if not tonumber(data.server_port) then + state.server_connection_error = { + msg = "Invalid port.", + element = "server_port" + } + show_servers_menu() + state.server_connection_error = nil + return + end + local address + local port + if state.connecting_to_server then + address = data.server_address:lower() + port = data.server_port:lower() + else + address = state.current_server.address:lower() + port = state.current_server.port:lower() + end + local is_favorite = false + for _, x in ipairs(state.favorite_servers) do + if x.address == address and x.port == port then + is_favorite = true + end + end + if not is_favorite then + add_favorite_server(address, port) + end + minetest.settings:set("address", address) + minetest.settings:set("remote_port", port) gamedata = { playername = data.server_username, password = data.server_password, - address = state.current_server.address, - port = state.current_server.port, + address = address, + port = port, selected_world = 0, singleplayer = false } @@ -1106,15 +1494,23 @@ function minetest.button_handler(data) if idx:sub(1, 1) == "f" then state.current_server = state.favorite_servers[tonumber(idx:sub(2))] else - state.current_server = state.serverlist[tonumber(idx)] + state.current_server = (state.serverlist_filtered or state.serverlist)[tonumber(idx)] end show_servers_menu() end end end elseif state.loc == "content" then - for k, v in pairs(data) do - + if data.contentscroll and data.contentscroll:sub(1, 4) == "CHG:" then + state.content_page = tonumber(data.contentscroll:sub(5)) + show_content_menu() + else + for k, v in pairs(data) do + if k:sub(1, 4) == "page" then + state.content_page = tonumber(k:sub(5)) + show_content_menu() + end + end end elseif state.loc == "game" then for k, v in pairs(data) do @@ -1149,10 +1545,13 @@ function minetest.button_handler(data) end function minetest.event_handler(ev) - -- Quit on quit + -- When Esc is pressed, close the current dialog, or the game if we are on the meta menu. if ev == "MenuQuit" then - if state.joining_server then + if state.joining_server or state.connecting_to_server or state.showing_server_mods or state.showing_server_players then state.joining_server = nil + state.connecting_to_server = nil + state.showing_server_mods = nil + state.showing_server_players = nil show_servers_menu() elseif state.loc ~= "games" then show_meta_menu()