diff options
37 files changed, 8036 insertions, 3927 deletions
diff --git a/builtin/gamemgr.lua b/builtin/gamemgr.lua new file mode 100644 index 000000000..bbff51305 --- /dev/null +++ b/builtin/gamemgr.lua @@ -0,0 +1,309 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +gamemgr = {} + +-------------------------------------------------------------------------------- +function gamemgr.dialog_new_game() + local retval = + "label[2,2;Game Name]".. + "field[4.5,2.4;6,0.5;te_game_name;;]" .. + "button[5,4.2;2.6,0.5;new_game_confirm;Create]" .. + "button[7.5,4.2;2.8,0.5;new_game_cancel;Cancel]" + + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_games_buttons(fields) + if fields["gamelist"] ~= nil then + local event = explode_textlist_event(fields["gamelist"]) + gamemgr.selected_game = event.index + end + + if fields["btn_game_mgr_edit_game"] ~= nil then + return { + is_dialog = true, + show_buttons = false, + current_tab = "dialog_edit_game" + } + end + + if fields["btn_game_mgr_new_game"] ~= nil then + return { + is_dialog = true, + show_buttons = false, + current_tab = "dialog_new_game" + } + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_new_game_buttons(fields) + + if fields["new_game_confirm"] and + fields["te_game_name"] ~= nil and + fields["te_game_name"] ~= "" then + local gamepath = engine.get_gamepath() + + if gamepath ~= nil and + gamepath ~= "" then + local gamefolder = cleanup_path(fields["te_game_name"]) + + --TODO check for already existing first + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder) + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "mods") + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "menu") + + local gameconf = + io.open(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "game.conf","w") + + if gameconf then + gameconf:write("name = " .. fields["te_game_name"]) + gameconf:close() + end + end + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_edit_game_buttons(fields) + local current_game = gamemgr.get_game(gamemgr.selected_game) + + if fields["btn_close_edit_game"] ~= nil or + current_game == nil then + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_remove_mod_from_game"] ~= nil then + gamemgr.delete_mod(current_game,engine.get_textlist_index("mods_current")) + end + + if fields["btn_add_mod_to_game"] ~= nil then + local modindex = engine.get_textlist_index("mods_available") + + if modindex > 0 and + modindex <= #modmgr.global_mods then + + local sourcepath = + engine.get_modpath() .. DIR_DELIM .. modmgr.global_mods[modindex] + + gamemgr.add_mod(current_game,sourcepath) + end + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.add_mod(gamespec,sourcepath) + if gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + + local modname = get_last_folder(sourcepath) + + engine.copy_dir(sourcepath,gamespec.gamemods_path .. DIR_DELIM .. modname); + end +end + +-------------------------------------------------------------------------------- +function gamemgr.delete_mod(gamespec,modindex) + if gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + local game_mods = {} + get_mods(gamespec.gamemods_path,game_mods) + + if modindex > 0 and + #game_mods >= modindex then + + local modname = game_mods[modindex] + + if modname:find("<MODPACK>") ~= nil then + modname = modname:sub(0,modname:find("<") -2) + end + + local modpath = gamespec.gamemods_path .. DIR_DELIM .. modname + if modpath:sub(0,gamespec.gamemods_path:len()) == gamespec.gamemods_path then + engine.delete_dir(modpath) + end + end + end +end + +-------------------------------------------------------------------------------- +function gamemgr.get_game_mods(gamespec) + + local retval = "" + + if gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + local game_mods = {} + get_mods(gamespec.gamemods_path,game_mods) + + for i=1,#game_mods,1 do + if retval ~= "" then + retval = retval.."," + end + retval = retval .. game_mods[i] + end + end + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.gettab(name) + local retval = "" + + if name == "dialog_edit_game" then + retval = retval .. gamemgr.dialog_edit_game() + end + + if name == "dialog_new_game" then + retval = retval .. gamemgr.dialog_new_game() + end + + if name == "game_mgr" then + retval = retval .. gamemgr.tab() + end + + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.tab() + if gamemgr.selected_game == nil then + gamemgr.selected_game = 1 + end + + local retval = + "vertlabel[0,-0.25;GAMES]" .. + "label[1,-0.25;Games:]" .. + "textlist[1,0.25;4.5,4.4;gamelist;" .. + gamemgr.gamelist() .. + ";" .. gamemgr.selected_game .. "]" + + local current_game = gamemgr.get_game(gamemgr.selected_game) + + if current_game ~= nil then + if current_game.menuicon_path ~= nil and + current_game.menuicon_path ~= "" then + retval = retval .. + "image[5.8,-0.25;2,2;" .. current_game.menuicon_path .. "]" + end + + retval = retval .. + "field[8,-0.25;6,2;;" .. current_game.name .. ";]".. + "label[6,1.4;Mods:]" .. + "button[9.7,1.5;2,0.2;btn_game_mgr_edit_game;edit game]" .. + "textlist[6,2;5.5,3.3;game_mgr_modlist;" + .. gamemgr.get_game_mods(current_game) ..";0]" .. + "button[1,4.75;3.2,0.5;btn_game_mgr_new_game;new game]" + end + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.dialog_edit_game() + local current_game = gamemgr.get_game(gamemgr.selected_game) + if current_game ~= nil then + local retval = + "vertlabel[0,-0.25;EDIT GAME]" .. + "label[0,-0.25;" .. current_game.name .. "]" .. + "button[11.55,-0.2;0.75,0.5;btn_close_edit_game;x]" + + if current_game.menuicon_path ~= nil and + current_game.menuicon_path ~= "" then + retval = retval .. + "image[5.25,0;2,2;" .. current_game.menuicon_path .. "]" + end + + retval = retval .. + "textlist[0.5,0.5;4.5,4.3;mods_current;" + .. gamemgr.get_game_mods(current_game) ..";0]" + + + retval = retval .. + "textlist[7,0.5;4.5,4.3;mods_available;" + .. modmgr.get_mods_list() .. ";0]" + + retval = retval .. + "button[0.55,4.95;4.7,0.5;btn_remove_mod_from_game;Remove selected mod]" + + retval = retval .. + "button[7.05,4.95;4.7,0.5;btn_add_mod_to_game;<<-- Add mod]" + + return retval + end +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_buttons(tab,fields) + local retval = nil + + if tab == "dialog_edit_game" then + retval = gamemgr.handle_edit_game_buttons(fields) + end + + if tab == "dialog_new_game" then + retval = gamemgr.handle_new_game_buttons(fields) + end + + if tab == "game_mgr" then + retval = gamemgr.handle_games_buttons(fields) + end + + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.get_game(index) + if index > 0 and index <= #gamemgr.games then + return gamemgr.games[index] + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.update_gamelist() + gamemgr.games = engine.get_games() +end + +-------------------------------------------------------------------------------- +function gamemgr.gamelist() + local retval = "" + if #gamemgr.games > 0 then + retval = retval .. gamemgr.games[1].id + + for i=2,#gamemgr.games,1 do + retval = retval .. "," .. gamemgr.games[i].name + end + end + return retval +end diff --git a/builtin/mainmenu.lua b/builtin/mainmenu.lua new file mode 100644 index 000000000..ef0ba7226 --- /dev/null +++ b/builtin/mainmenu.lua @@ -0,0 +1,1190 @@ +local scriptpath = engine.get_scriptdir() + +dofile(scriptpath .. DIR_DELIM .. "modmgr.lua") +dofile(scriptpath .. DIR_DELIM .. "modstore.lua") +dofile(scriptpath .. DIR_DELIM .. "gamemgr.lua") + +local menu = {} +local tabbuilder = {} +local menubar = {} + +-------------------------------------------------------------------------------- +function render_favourite(spec) + local text = "" + + if spec.name ~= nil then + text = text .. spec.name:trim() + + if spec.description ~= nil then + --TODO make sure there's no invalid chat in spec.description + text = text .. " (" .. fs_escape_string(spec.description) .. ")" + end + else + if spec.address ~= nil then + text = text .. spec.address:trim() + end + end + + local details = "" + if spec.password == true then + details = " *" + else + details = " " + end + + if spec.creative then + details = details .. "C" + else + details = details .. " " + end + + if spec.damage then + details = details .. "D" + else + details = details .. " " + end + + if spec.pvp then + details = details .. "P" + else + details = details .. " " + end + + text = text .. ":" .. spec.port:trim() + + return text +end + +-------------------------------------------------------------------------------- +os.tempfolder = function() + local filetocheck = os.tmpname() + os.remove(filetocheck) + + local randname = "MTTempModFolder_" .. math.random(0,10000) + if DIR_DELIM == "\\" then + local tempfolder = os.getenv("TEMP") + return tempfolder .. filetocheck + else + local backstring = filetocheck:reverse() + return filetocheck:sub(0,filetocheck:len()-backstring:find(DIR_DELIM)+1) ..randname + end + +end + +-------------------------------------------------------------------------------- +function cleanup_path(temppath) + + local parts = temppath:split("-") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "_" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split(".") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "_" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split("'") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split(" ") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath + end + temppath = temppath .. parts[i] + end + + return temppath +end + +-------------------------------------------------------------------------------- +function menu.update_gametype() + if (menu.game_last_check == nil or + menu.game_last_check ~= menu.last_game) and + tabbuilder.current_tab == "singleplayer" then + + local gamedetails = menu.lastgame() + engine.set_topleft_text(gamedetails.name) + + --background + local path_background_texture = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. "background.png" + if engine.set_background("background",path_background_texture) then + engine.set_clouds(false) + else + engine.set_clouds(true) + end + + --overlay + local path_overlay_texture = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. "overlay.png" + engine.set_background("overlay",path_overlay_texture) + + --header + local path_overlay_texture = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. "header.png" + engine.set_background("header",path_overlay_texture) + + --footer + local path_overlay_texture = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. "footer.png" + engine.set_background("footer",path_overlay_texture) + + menu.game_last_check = menu.last_game + else + if menu.game_last_check ~= menu.last_game then + menu.game_last_check = menu.last_game + menu.reset_gametype() + end + end +end + +-------------------------------------------------------------------------------- +function menu.reset_gametype() + menu.game_last_check = nil + engine.set_clouds(true) + engine.set_background("background","") + engine.set_background("overlay",menu.basetexturedir .. "menu_overlay.png") + engine.set_background("header",menu.basetexturedir .. "menu_header.png") + engine.set_background("footer",menu.basetexturedir .. "menu_footer.png") + engine.set_topleft_text("") +end + +-------------------------------------------------------------------------------- +function get_last_folder(text,count) + local parts = text:split(DIR_DELIM) + + if count == nil then + return parts[#parts] + end + + local retval = "" + for i=1,count,1 do + retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM + end + + return retval +end + +-------------------------------------------------------------------------------- +function init_globals() + --init gamedata + gamedata.worldindex = 0 +end + +-------------------------------------------------------------------------------- +function identify_filetype(name) + + if name:sub(-3):lower() == "zip" then + return { + name = name, + type = "zip" + } + end + + if name:sub(-6):lower() == "tar.gz" or + name:sub(-3):lower() == "tgz"then + return { + name = name, + type = "tgz" + } + end + + if name:sub(-6):lower() == "tar.bz2" then + return { + name = name, + type = "tbz" + } + end + + if name:sub(-2):lower() == "7z" then + return { + name = name, + type = "7z" + } + end + + return { + name = name, + type = "ukn" + } +end + +-------------------------------------------------------------------------------- +function update_menu() + + local formspec = "size[12,5.2]" + + -- handle errors + if gamedata.errormessage ~= nil then + formspec = formspec .. + "field[1,2;10,2;;ERROR: " .. + gamedata.errormessage .. + ";]".. + "button[4.5,4.2;3,0.5;btn_error_confirm;Ok]" + else + formspec = formspec .. tabbuilder.gettab() + end + + engine.update_formspec(formspec) +end + +-------------------------------------------------------------------------------- +function menu.filtered_game_list() + local retval = "" + + local current_game = menu.lastgame() + + for i=1,#menu.worldlist,1 do + if menu.worldlist[i].gameid == current_game.id then + if retval ~= "" then + retval = retval .."," + end + + retval = retval .. menu.worldlist[i].name .. + " [[" .. menu.worldlist[i].gameid .. "]]" + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function menu.filtered_game_list_raw() + local retval = {} + + local current_game = menu.lastgame() + + for i=1,#menu.worldlist,1 do + if menu.worldlist[i].gameid == current_game.id then + table.insert(retval,menu.worldlist[i]) + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function menu.filtered_index_to_plain(filtered_index) + + local current_game = menu.lastgame() + + local temp_idx = 0 + + for i=1,#menu.worldlist,1 do + if menu.worldlist[i].gameid == current_game.id then + temp_idx = temp_idx +1 + end + + if temp_idx == filtered_index then + return i + end + end + return -1 +end + +-------------------------------------------------------------------------------- +function menu.init() + --init menu data + gamemgr.update_gamelist() + + menu.worldlist = engine.get_worlds() + + menu.last_world = tonumber(engine.setting_get("main_menu_last_world_idx")) + menu.last_game = tonumber(engine.setting_get("main_menu_last_game_idx")) + + if type(menu.last_world) ~= "number" then + menu.last_world = 1 + end + + if type(menu.last_game) ~= "number" then + menu.last_game = 1 + end + + if engine.setting_getbool("public_serverlist") then + menu.favorites = engine.get_favorites("online") + else + menu.favorites = engine.get_favorites("local") + end + + + menu.basetexturedir = engine.get_gamepath() .. DIR_DELIM .. ".." .. + DIR_DELIM .. "textures" .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM +end + +-------------------------------------------------------------------------------- +function menu.lastgame() + if menu.last_game > 0 and menu.last_game <= #gamemgr.games then + return gamemgr.games[menu.last_game] + end + + if #gamemgr.games >= 1 then + menu.last_game = 1 + return gamemgr.games[menu.last_game] + end + + --error case!! + return nil +end + +-------------------------------------------------------------------------------- +function menu.lastworld() + if menu.last_world ~= nil and + menu.last_world > 0 and + menu.last_world <= #menu.worldlist then + return menu.worldlist[menu.last_world] + end + + if #menu.worldlist >= 1 then + menu.last_world = 1 + return menu.worldlist[menu.last_world] + end + + --error case!! + return nil +end + +-------------------------------------------------------------------------------- +function menu.update_last_game(world_idx) + if gamedata.selected_world <= #menu.worldlist then + local world = menu.worldlist[gamedata.selected_world] + + for i=1,#gamemgr.games,1 do + if gamemgr.games[i].id == world.gameid then + menu.last_game = i + engine.setting_set("main_menu_last_game_idx",menu.last_game) + break + end + end + end +end + +-------------------------------------------------------------------------------- +function menubar.handle_buttons(fields) + for i=1,#menubar.buttons,1 do + if fields[menubar.buttons[i].btn_name] ~= nil then + menu.last_game = menubar.buttons[i].index + engine.setting_set("main_menu_last_game_idx",menu.last_game) + menu.update_gametype() + end + end +end + +-------------------------------------------------------------------------------- +function menubar.refresh() + menubar.formspec = "box[-2,7.625;15.75,1.75;BLK]" + menubar.buttons = {} + + local button_base = -1.8 + + local maxbuttons = #gamemgr.games + + if maxbuttons > 12 then + maxbuttons = 12 + end + + for i=1,maxbuttons,1 do + + local btn_name = "menubar_btn_" .. gamemgr.games[i].id + local buttonpos = button_base + (i-1) * 1.3 + if gamemgr.games[i].menuicon_path ~= nil and + gamemgr.games[i].menuicon_path ~= "" then + + menubar.formspec = menubar.formspec .. + "image_button[" .. buttonpos .. ",7.9;1.3,1.3;" .. + gamemgr.games[i].menuicon_path .. ";" .. btn_name .. ";;true;false]" + else + + local part1 = gamemgr.games[i].id:sub(1,5) + local part2 = gamemgr.games[i].id:sub(6,10) + local part3 = gamemgr.games[i].id:sub(11) + + local text = part1 .. "\n" .. part2 + if part3 ~= nil and + part3 ~= "" then + text = text .. "\n" .. part3 + end + menubar.formspec = menubar.formspec .. + "image_button[" .. buttonpos .. ",7.9;1.3,1.3;;" ..btn_name .. + ";" .. text .. ";true;true]" + end + + local toadd = { + btn_name = btn_name, + index = i, + } + + table.insert(menubar.buttons,toadd) + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.dialog_create_world() + local retval = + "label[2,0;World name]".. + "label[2,1;Mapgen]".. + "field[4.5,0.4;6,0.5;te_world_name;;]" .. + "label[2,2;Game]".. + "button[5,4.5;2.6,0.5;world_create_confirm;Create]" .. + "button[7.5,4.5;2.8,0.5;world_create_cancel;Cancel]" .. + "dropdown[4.2,1;6.3;dd_mapgen;v6,v7,indev,singlenode,math;1]" .. --TODO read from minetest + "textlist[4.2,1.9;5.8,2.3;games;" .. + gamemgr.gamelist() .. + ";" .. menu.last_game .. ";true]" + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.dialog_delete_world() + return "label[2,2;Delete World \"" .. menu.lastworld().name .. "\"?]".. + "button[3.5,4.2;2.6,0.5;world_delete_confirm;Yes]" .. + "button[6,4.2;2.8,0.5;world_delete_cancel;No]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.gettab() + local retval = "" + + if tabbuilder.show_buttons then + retval = retval .. tabbuilder.tab_header() + end + + if tabbuilder.current_tab == "singleplayer" then + retval = retval .. tabbuilder.tab_singleplayer() + end + + if tabbuilder.current_tab == "multiplayer" then + retval = retval .. tabbuilder.tab_multiplayer() + end + + if tabbuilder.current_tab == "server" then + retval = retval .. tabbuilder.tab_server() + end + + if tabbuilder.current_tab == "settings" then + retval = retval .. tabbuilder.tab_settings() + end + + if tabbuilder.current_tab == "credits" then + retval = retval .. tabbuilder.tab_credits() + end + + if tabbuilder.current_tab == "dialog_create_world" then + retval = retval .. tabbuilder.dialog_create_world() + end + + if tabbuilder.current_tab == "dialog_delete_world" then + retval = retval .. tabbuilder.dialog_delete_world() + end + + retval = retval .. modmgr.gettab(tabbuilder.current_tab) + retval = retval .. gamemgr.gettab(tabbuilder.current_tab) + retval = retval .. modstore.gettab(tabbuilder.current_tab) + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_create_world_buttons(fields) + + if fields["world_create_confirm"] then + + local worldname = fields["te_world_name"] + local gameindex = engine.get_textlist_index("games") + + if gameindex > 0 and + worldname ~= "" then + engine.setting_set("mg_name",fields["dd_mapgen"]) + local message = engine.create_world(worldname,gameindex) + + menu.last_game = gameindex + engine.setting_set("main_menu_last_game_idx",gameindex) + + if message ~= nil then + gamedata.errormessage = message + else + menu.worldlist = engine.get_worlds() + + local worldlist = menu.worldlist + + if tabbuilder.current_tab == "singleplayer" then + worldlist = menu.filtered_game_list_raw() + end + + local index = 0 + + for i=1,#worldlist,1 do + if worldlist[i].name == worldname then + index = i + print("found new world index: " .. index) + break + end + end + + if tabbuilder.current_tab == "singleplayer" then + engine.setting_set("main_menu_singleplayer_world_idx",index) + else + menu.last_world = index + end + end + else + gamedata.errormessage = "No worldname given or no game selected" + end + end + + if fields["games"] then + tabbuilder.skipformupdate = true + return + end + + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_delete_world_buttons(fields) + + if fields["world_delete_confirm"] then + if menu.last_world > 0 and + menu.last_world < #menu.worldlist then + engine.delete_world(menu.last_world) + menu.worldlist = engine.get_worlds() + menu.last_world = 1 + end + end + + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_multiplayer_buttons(fields) + if fields["favourites"] ~= nil then + local event = explode_textlist_event(fields["favourites"]) + if event.typ == "DCL" then + gamedata.address = menu.favorites[event.index].name + if gamedata.address == nil then + gamedata.address = menu.favorites[event.index].address + end + gamedata.port = menu.favorites[event.index].port + gamedata.playername = fields["te_name"] + gamedata.password = fields["te_pwd"] + gamedata.selected_world = 0 + + if gamedata.address ~= nil and + gamedata.port ~= nil then + + engine.start() + end + end + + if event.typ == "CHG" then + local address = menu.favorites[event.index].name + if address == nil then + address = menu.favorites[event.index].address + end + local port = menu.favorites[event.index].port + + if address ~= nil and + port ~= nil then + engine.setting_set("address",address) + engine.setting_set("port",port) + end + end + return + end + + if fields["cb_public_serverlist"] ~= nil then + engine.setting_setbool("public_serverlist", + tabbuilder.tobool(fields["cb_public_serverlist"])) + + if engine.setting_getbool("public_serverlist") then + menu.favorites = engine.get_favorites("online") + else + menu.favorites = engine.get_favorites("local") + end + end + + if fields["btn_delete_favorite"] ~= nil then + local current_favourite = engine.get_textlist_index("favourites") + engine.delete_favorite(current_favourite) + menu.favorites = engine.get_favorites() + + engine.setting_set("address","") + engine.setting_get("port","") + + return + end + + if fields["btn_mp_connect"] ~= nil then + gamedata.playername = fields["te_name"] + gamedata.password = fields["te_pwd"] + gamedata.address = fields["te_address"] + gamedata.port = fields["te_port"] + gamedata.selected_world = 0 + + engine.start() + return + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_server_buttons(fields) + + local world_doubleclick = false + + if fields["worlds"] ~= nil then + local event = explode_textlist_event(fields["worlds"]) + + if event.typ == "DBL" then + world_doubleclick = true + end + end + + if fields["cb_creative_mode"] then + engine.setting_setbool("creative_mode",tabbuilder.tobool(fields["cb_creative_mode"])) + end + + if fields["cb_enable_damage"] then + engine.setting_setbool("enable_damage",tabbuilder.tobool(fields["cb_enable_damage"])) + end + + if fields["start_server"] ~= nil or + world_doubleclick then + local selected = engine.get_textlist_index("srv_worlds") + if selected > 0 then + gamedata.playername = fields["te_playername"] + gamedata.password = fields["te_pwd"] + gamedata.address = "" + gamedata.port = fields["te_serverport"] + gamedata.selected_world = selected + + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + engine.setting_set("main_menu_last_world_idx",gamedata.selected_world) + + menu.update_last_game(gamedata.selected_world) + engine.start() + end + end + + if fields["world_create"] ~= nil then + tabbuilder.current_tab = "dialog_create_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + + if fields["world_delete"] ~= nil then + local selected = engine.get_textlist_index("srv_worlds") + if selected > 0 then + menu.last_world = engine.get_textlist_index("worlds") + if menu.lastworld() ~= nil and + menu.lastworld().name ~= nil and + menu.lastworld().name ~= "" then + tabbuilder.current_tab = "dialog_delete_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + else + menu.last_world = 0 + end + end + end + + if fields["world_configure"] ~= nil then + selected = engine.get_textlist_index("srv_worlds") + if selected > 0 then + modmgr.world_config_selected_world = selected + if modmgr.init_worldconfig() then + tabbuilder.current_tab = "dialog_configure_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.tobool(text) + if text == "true" then + return true + else + return false + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_settings_buttons(fields) + if fields["cb_fancy_trees"] then + engine.setting_setbool("new_style_leaves",tabbuilder.tobool(fields["cb_fancy_trees"])) + end + + if fields["cb_smooth_lighting"] then + engine.setting_setbool("smooth_lighting",tabbuilder.tobool(fields["cb_smooth_lighting"])) + end + if fields["cb_3d_clouds"] then + engine.setting_setbool("enable_3d_clouds",tabbuilder.tobool(fields["cb_3d_clouds"])) + end + if fields["cb_opaque_water"] then + engine.setting_setbool("opaque_water",tabbuilder.tobool(fields["cb_opaque_water"])) + end + + if fields["cb_mipmapping"] then + engine.setting_setbool("mip_map",tabbuilder.tobool(fields["cb_mipmapping"])) + end + if fields["cb_anisotrophic"] then + engine.setting_setbool("anisotropic_filter",tabbuilder.tobool(fields["cb_anisotrophic"])) + end + if fields["cb_bilinear"] then + engine.setting_setbool("bilinear_filter",tabbuilder.tobool(fields["cb_bilinear"])) + end + if fields["cb_trilinear"] then + engine.setting_setbool("trilinear_filter",tabbuilder.tobool(fields["cb_trilinear"])) + end + + if fields["cb_shaders"] then + engine.setting_setbool("enable_shaders",tabbuilder.tobool(fields["cb_shaders"])) + end + if fields["cb_pre_ivis"] then + engine.setting_setbool("preload_item_visuals",tabbuilder.tobool(fields["cb_pre_ivis"])) + end + if fields["cb_particles"] then + engine.setting_setbool("enable_particles",tabbuilder.tobool(fields["cb_particles"])) + end + if fields["cb_finite_liquid"] then + engine.setting_setbool("liquid_finite",tabbuilder.tobool(fields["cb_finite_liquid"])) + end + + if fields["btn_change_keys"] ~= nil then + engine.show_keys_menu() + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_singleplayer_buttons(fields) + + local world_doubleclick = false + + if fields["sp_worlds"] ~= nil then + local event = explode_textlist_event(fields["sp_worlds"]) + + if event.typ == "DCL" then + world_doubleclick = true + end + end + + if fields["cb_creative_mode"] then + engine.setting_setbool("creative_mode",tabbuilder.tobool(fields["cb_creative_mode"])) + end + + if fields["cb_enable_damage"] then + engine.setting_setbool("enable_damage",tabbuilder.tobool(fields["cb_enable_damage"])) + end + + if fields["play"] ~= nil or + world_doubleclick then + local selected = engine.get_textlist_index("sp_worlds") + if selected > 0 then + gamedata.selected_world = menu.filtered_index_to_plain(selected) + gamedata.singleplayer = true + + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + engine.setting_set("main_menu_singleplayer_world_idx",selected) + + menu.update_last_game(gamedata.selected_world) + + engine.start() + end + end + + if fields["world_create"] ~= nil then + tabbuilder.current_tab = "dialog_create_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + + if fields["world_delete"] ~= nil then + local selected = engine.get_textlist_index("sp_worlds") + if selected > 0 then + menu.last_world = menu.filtered_index_to_plain(selected) + if menu.lastworld() ~= nil and + menu.lastworld().name ~= nil and + menu.lastworld().name ~= "" then + tabbuilder.current_tab = "dialog_delete_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + else + menu.last_world = 0 + end + end + end + + if fields["world_configure"] ~= nil then + selected = engine.get_textlist_index("sp_worlds") + if selected > 0 then + modmgr.world_config_selected_world = menu.filtered_index_to_plain(selected) + if modmgr.init_worldconfig() then + tabbuilder.current_tab = "dialog_configure_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_header() + + if tabbuilder.last_tab_index == nil then + tabbuilder.last_tab_index = 1 + end + + local toadd = "" + + for i=1,#tabbuilder.current_buttons,1 do + + if toadd ~= "" then + toadd = toadd .. "," + end + + toadd = toadd .. tabbuilder.current_buttons[i].caption + end + return "tabheader[-0.3,-0.99;main_tab;" .. toadd ..";" .. tabbuilder.last_tab_index .. ";true;false]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_tab_buttons(fields) + + if fields["main_tab"] then + local index = tonumber(fields["main_tab"]) + tabbuilder.last_tab_index = index + tabbuilder.current_tab = tabbuilder.current_buttons[index].name + + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + end + + --handle tab changes + if tabbuilder.current_tab ~= tabbuilder.old_tab then + if tabbuilder.current_tab ~= "singleplayer" then + menu.reset_gametype() + end + end + + if tabbuilder.current_tab == "singleplayer" then + menu.update_gametype() + end + + tabbuilder.old_tab = tabbuilder.current_tab +end + +-------------------------------------------------------------------------------- +function tabbuilder.init() + tabbuilder.current_tab = engine.setting_get("main_menu_tab") + + if tabbuilder.current_tab == nil or + tabbuilder.current_tab == "" then + tabbuilder.current_tab = "singleplayer" + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + end + + + --initialize tab buttons + tabbuilder.last_tab = nil + tabbuilder.show_buttons = true + + tabbuilder.current_buttons = {} + table.insert(tabbuilder.current_buttons,{name="singleplayer", caption="Singleplayer"}) + table.insert(tabbuilder.current_buttons,{name="multiplayer", caption="Client"}) + table.insert(tabbuilder.current_buttons,{name="server", caption="Server"}) + table.insert(tabbuilder.current_buttons,{name="settings", caption="Settings"}) + + if engine.setting_getbool("main_menu_game_mgr") then + table.insert(tabbuilder.current_buttons,{name="game_mgr", caption="Games"}) + end + + if engine.setting_getbool("main_menu_mod_mgr") then + table.insert(tabbuilder.current_buttons,{name="mod_mgr", caption="Mods"}) + end + table.insert(tabbuilder.current_buttons,{name="credits", caption="Credits"}) + + + for i=1,#tabbuilder.current_buttons,1 do + if tabbuilder.current_buttons[i].name == tabbuilder.current_tab then + tabbuilder.last_tab_index = i + end + end + + menu.update_gametype() +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_multiplayer() + local retval = + "vertlabel[0,-0.25;CLIENT]" .. + "label[1,-0.25;Favorites:]".. + "label[1,4.25;Address/Port]".. + "label[9,0;Name/Password]" .. + "field[1.25,5.25;5.5,0.5;te_address;;" ..engine.setting_get("address") .."]" .. + "field[6.75,5.25;2.25,0.5;te_port;;" ..engine.setting_get("port") .."]" .. + "button[6.45,3.95;2.25,0.5;btn_delete_favorite;Delete]" .. + "button[9,4.95;2.5,0.5;btn_mp_connect;Connect]" .. + "field[9.25,1;2.5,0.5;te_name;;" ..engine.setting_get("name") .."]" .. + "pwdfield[9.25,1.75;2.5,0.5;te_pwd;]" .. + "checkbox[1,3.6;cb_public_serverlist;Public Serverlist;" .. + dump(engine.setting_getbool("public_serverlist")) .. "]" .. + "textlist[1,0.35;7.5,3.35;favourites;" + + if #menu.favorites > 0 then + retval = retval .. render_favourite(menu.favorites[1]) + + for i=2,#menu.favorites,1 do + retval = retval .. "," .. render_favourite(menu.favorites[i]) + end + end + + retval = retval .. ";1]" + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_server() + local retval = + "button[4,4.15;2.6,0.5;world_delete;Delete]" .. + "button[6.5,4.15;2.8,0.5;world_create;New]" .. + "button[9.2,4.15;2.55,0.5;world_configure;Configure]" .. + "button[8.5,4.9;3.25,0.5;start_server;Start Game]" .. + "label[4,-0.25;Select World:]".. + "vertlabel[0,-0.25;START SERVER]" .. + "checkbox[0.5,0.25;cb_creative_mode;Creative Mode;" .. + dump(engine.setting_getbool("creative_mode")) .. "]".. + "checkbox[0.5,0.7;cb_enable_damage;Enable Damage;" .. + dump(engine.setting_getbool("enable_damage")) .. "]".. + "field[0.8,2.2;3,0.5;te_playername;Name;" .. + engine.setting_get("name") .. "]" .. + "pwdfield[0.8,3.2;3,0.5;te_passwd;Password]" .. + "field[0.8,5.2;3,0.5;te_serverport;Server Port;30000]" .. + "textlist[4,0.25;7.5,3.7;srv_worlds;" + + if #menu.worldlist > 0 then + retval = retval .. menu.worldlist[1].name .. + " [[" .. menu.worldlist[1].gameid .. "]]" + + for i=2,#menu.worldlist,1 do + retval = retval .. "," .. menu.worldlist[i].name .. + " [[" .. menu.worldlist[i].gameid .. "]]" + end + end + + retval = retval .. ";" .. menu.last_world .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_settings() + return "vertlabel[0,0;SETTINGS]" .. + "checkbox[1,0.75;cb_fancy_trees;Fancy trees;" .. dump(engine.setting_getbool("new_style_leaves")) .. "]".. + "checkbox[1,1.25;cb_smooth_lighting;Smooth Lighting;".. dump(engine.setting_getbool("smooth_lighting")) .. "]".. + "checkbox[1,1.75;cb_3d_clouds;3D Clouds;" .. dump(engine.setting_getbool("enable_3d_clouds")) .. "]".. + "checkbox[1,2.25;cb_opaque_water;Opaque Water;" .. dump(engine.setting_getbool("opaque_water")) .. "]".. + + "checkbox[4,0.75;cb_mipmapping;Mip-Mapping;" .. dump(engine.setting_getbool("mip_map")) .. "]".. + "checkbox[4,1.25;cb_anisotrophic;Anisotropic Filtering;".. dump(engine.setting_getbool("anisotropic_filter")) .. "]".. + "checkbox[4,1.75;cb_bilinear;Bi-Linear Filtering;" .. dump(engine.setting_getbool("bilinear_filter")) .. "]".. + "checkbox[4,2.25;cb_trilinear;Tri-Linear Filtering;" .. dump(engine.setting_getbool("trilinear_filter")) .. "]".. + + "checkbox[7.5,0.75;cb_shaders;Shaders;" .. dump(engine.setting_getbool("enable_shaders")) .. "]".. + "checkbox[7.5,1.25;cb_pre_ivis;Preload item visuals;".. dump(engine.setting_getbool("preload_item_visuals")) .. "]".. + "checkbox[7.5,1.75;cb_particles;Enable Particles;" .. dump(engine.setting_getbool("enable_particles")) .. "]".. + "checkbox[7.5,2.25;cb_finite_liquid;Finite Liquid;" .. dump(engine.setting_getbool("liquid_finite")) .. "]".. + + "button[1,3.75;2.25,0.5;btn_change_keys;Change keys]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_singleplayer() + local index = engine.setting_get("main_menu_singleplayer_world_idx") + + if index == nil then + index = 0 + end + + return "button[4,4.15;2.6,0.5;world_delete;Delete]" .. + "button[6.5,4.15;2.8,0.5;world_create;New]" .. + "button[9.2,4.15;2.55,0.5;world_configure;Configure]" .. + "button[8.5,4.95;3.25,0.5;play;Play]" .. + "label[4,-0.25;Select World:]".. + "vertlabel[0,-0.25;SINGLE PLAYER]" .. + "checkbox[0.5,0.25;cb_creative_mode;Creative Mode;" .. + dump(engine.setting_getbool("creative_mode")) .. "]".. + "checkbox[0.5,0.7;cb_enable_damage;Enable Damage;" .. + dump(engine.setting_getbool("enable_damage")) .. "]".. + "textlist[4,0.25;7.5,3.7;sp_worlds;" .. + menu.filtered_game_list() .. + ";" .. index .. "]" .. + menubar.formspec +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_credits() + return "vertlabel[0,-0.5;CREDITS]" .. + "label[0.5,3;Minetest " .. engine.get_version() .. "]" .. + "label[0.5,3.3;http://minetest.net]" .. + "image[0.5,1;" .. menu.basetexturedir .. "logo.png]" .. + "textlist[3.5,-0.25;8.5,5.8;list_credits;" .. + "#YLWCore Developers," .. + "Perttu Ahola (celeron55) <celeron55@gmail.com>,".. + "Ryan Kwolek (kwolekr) <kwolekr@minetest.net>,".. + "PilzAdam <pilzadam@minetest.net>," .. + "IIya Zhuravlev (thexyz) <xyz@minetest.net>,".. + "Lisa Milne (darkrose) <lisa@ltmnet.com>,".. + "Maciej Kasatkin (RealBadAngel) <mk@realbadangel.pl>,".. + "proller <proler@gmail.com>,".. + "sfan5 <sfan5@live.de>,".. + "kahrl <kahrl@gmx.net>,".. + ",".. + "#YLWActive Contributors," .. + "sapier,".. + "Vanessa Ezekowitz (VanessaE) <vanessaezekowitz@gmail.com>,".. + "Jurgen Doser (doserj) <jurgen.doser@gmail.com>,".. + "Jeija <jeija@mesecons.net>,".. + "MirceaKitsune <mirceakitsune@gmail.com>,".. + "ShadowNinja".. + "dannydark <the_skeleton_of_a_child@yahoo.co.uk>".. + "0gb.us <0gb.us@0gb.us>,".. + "," .. + "#YLWPrevious Contributors," .. + "Guiseppe Bilotta (Oblomov) <guiseppe.bilotta@gmail.com>,".. + "Jonathan Neuschafer <j.neuschaefer@gmx.net>,".. + "Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net>,".. + "Constantin Wenger (SpeedProg) <constantin.wenger@googlemail.com>,".. + "matttpt <matttpt@gmail.com>,".. + "JacobF <queatz@gmail.com>,".. + ";0;true]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.checkretval(retval) + + if retval ~= nil then + if retval.current_tab ~= nil then + tabbuilder.current_tab = retval.current_tab + end + + if retval.is_dialog ~= nil then + tabbuilder.is_dialog = retval.is_dialog + end + + if retval.show_buttons ~= nil then + tabbuilder.show_buttons = retval.show_buttons + end + + if retval.skipformupdate ~= nil then + tabbuilder.skipformupdate = retval.skipformupdate + end + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- initialize callbacks +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +engine.button_handler = function(fields) + --print("Buttonhandler: tab: " .. tabbuilder.current_tab .. " fields: " .. dump(fields)) + + if fields["btn_error_confirm"] then + gamedata.errormessage = nil + end + + local retval = modmgr.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + retval = gamemgr.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + retval = modstore.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + if tabbuilder.current_tab == "dialog_create_world" then + tabbuilder.handle_create_world_buttons(fields) + end + + if tabbuilder.current_tab == "dialog_delete_world" then + tabbuilder.handle_delete_world_buttons(fields) + end + + if tabbuilder.current_tab == "singleplayer" then + tabbuilder.handle_singleplayer_buttons(fields) + end + + if tabbuilder.current_tab == "multiplayer" then + tabbuilder.handle_multiplayer_buttons(fields) + end + + if tabbuilder.current_tab == "settings" then + tabbuilder.handle_settings_buttons(fields) + end + + if tabbuilder.current_tab == "server" then + tabbuilder.handle_server_buttons(fields) + end + + --tab buttons + tabbuilder.handle_tab_buttons(fields) + + --menubar buttons + menubar.handle_buttons(fields) + + if not tabbuilder.skipformupdate then + --update menu + update_menu() + else + tabbuilder.skipformupdate = false + end +end + +-------------------------------------------------------------------------------- +engine.event_handler = function(event) + if event == "MenuQuit" then + if tabbuilder.is_dialog then + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") + update_menu() + else + engine.close() + end + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- menu startup +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +init_globals() +menu.init() +tabbuilder.init() +menubar.refresh() +modstore.init() +update_menu() diff --git a/builtin/mainmenu_helper.lua b/builtin/mainmenu_helper.lua new file mode 100644 index 000000000..f5a470b72 --- /dev/null +++ b/builtin/mainmenu_helper.lua @@ -0,0 +1,105 @@ +-------------------------------------------------------------------------------- +function dump(o, dumped) + dumped = dumped or {} + if type(o) == "number" then + return tostring(o) + elseif type(o) == "string" then + return string.format("%q", o) + elseif type(o) == "table" then + if dumped[o] then + return "<circular reference>" + end + dumped[o] = true + local t = {} + for k,v in pairs(o) do + t[#t+1] = "" .. k .. " = " .. dump(v, dumped) + end + return "{" .. table.concat(t, ", ") .. "}" + elseif type(o) == "boolean" then + return tostring(o) + elseif type(o) == "function" then + return "<function>" + elseif type(o) == "userdata" then + return "<userdata>" + elseif type(o) == "nil" then + return "nil" + else + error("cannot dump a " .. type(o)) + return nil + end +end + +-------------------------------------------------------------------------------- +function string:split(sep) + local sep, fields = sep or ",", {} + local pattern = string.format("([^%s]+)", sep) + self:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +-------------------------------------------------------------------------------- +function string:trim() + return (self:gsub("^%s*(.-)%s*$", "%1")) +end + +-------------------------------------------------------------------------------- +engine.get_game = function(index) + local games = game.get_games() + + if index > 0 and index <= #games then + return games[index] + end + + return nil +end + +-------------------------------------------------------------------------------- +function fs_escape_string(text) + if text ~= nil then + while (text:find("\r\n") ~= nil) do + local newtext = text:sub(1,text:find("\r\n")-1) + newtext = newtext .. " " .. text:sub(text:find("\r\n")+3) + + text = newtext + end + + while (text:find("\n") ~= nil) do + local newtext = text:sub(1,text:find("\n")-1) + newtext = newtext .. " " .. text:sub(text:find("\n")+1) + + text = newtext + end + + while (text:find("\r") ~= nil) do + local newtext = text:sub(1,text:find("\r")-1) + newtext = newtext .. " " .. text:sub(text:find("\r")+1) + + text = newtext + end + + text = text:gsub("%[","%[%[") + text = text:gsub("]","]]") + text = text:gsub(";"," ") + end + return text +end + +-------------------------------------------------------------------------------- +function explode_textlist_event(text) + + local retval = {} + retval.typ = "INV" + + local parts = text:split(":") + + if #parts == 2 then + retval.typ = parts[1]:trim() + retval.index= tonumber(parts[2]:trim()) + + if type(retval.index) ~= "number" then + retval.typ = "INV" + end + end + + return retval +end diff --git a/builtin/modmgr.lua b/builtin/modmgr.lua new file mode 100644 index 000000000..1cb4b3922 --- /dev/null +++ b/builtin/modmgr.lua @@ -0,0 +1,881 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +function get_mods(path,retval,basefolder) + + local mods = engine.get_dirlist(path,true) + + for i=1,#mods,1 do + local filename = path .. DIR_DELIM .. mods[i] .. DIR_DELIM .. "modpack.txt" + local modpackfile,error = io.open(filename,"r") + + local name = mods[i] + if basefolder ~= nil and + basefolder ~= "" then + name = basefolder .. DIR_DELIM .. mods[i] + end + + if modpackfile ~= nil then + modpackfile:close() + table.insert(retval,name .. " <MODPACK>") + get_mods(path .. DIR_DELIM .. name,retval,name) + else + + table.insert(retval,name) + end + end +end + +--modmanager implementation +modmgr = {} + +-------------------------------------------------------------------------------- +function modmgr.extract(modfile) + if modfile.type == "zip" then + local tempfolder = os.tempfolder() + + if tempfolder ~= nil and + tempfodler ~= "" then + engine.create_dir(tempfolder) + engine.extract_zip(modfile.name,tempfolder) + return tempfolder + end + end +end + +------------------------------------------------------------------------------- +function modmgr.getbasefolder(temppath) + + if temppath == nil then + return { + type = "invalid", + path = "" + } + end + + local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r") + if testfile ~= nil then + testfile:close() + return { + type="mod", + path=temppath + } + end + + testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r") + if testfile ~= nil then + testfile:close() + return { + type="modpack", + path=temppath + } + end + + local subdirs = engine.get_dirlist(temppath,true) + + --only single mod or modpack allowed + if #subdirs ~= 1 then + return { + type = "invalid", + path = "" + } + end + + testfile = + io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r") + if testfile ~= nil then + testfile:close() + return { + type="mod", + path= temppath .. DIR_DELIM .. subdirs[1] + } + end + + testfile = + io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r") + if testfile ~= nil then + testfile:close() + return { + type="modpack", + path=temppath .. DIR_DELIM .. subdirs[1] + } + end + + return { + type = "invalid", + path = "" + } +end + +-------------------------------------------------------------------------------- +function modmgr.isValidModname(modpath) + if modpath:find("-") ~= nil then + return false + end + + return true +end + +-------------------------------------------------------------------------------- +function modmgr.parse_register_line(line) + local pos1 = line:find("\"") + local pos2 = nil + if pos1 ~= nil then + pos2 = line:find("\"",pos1+1) + end + + if pos1 ~= nil and pos2 ~= nil then + local item = line:sub(pos1+1,pos2-1) + + if item ~= nil and + item ~= "" then + local pos3 = item:find(":") + + if pos3 ~= nil then + return item:sub(1,pos3-1) + end + end + end + return nil +end + +-------------------------------------------------------------------------------- +function modmgr.parse_dofile_line(modpath,line) + local pos1 = line:find("\"") + local pos2 = nil + if pos1 ~= nil then + pos2 = line:find("\"",pos1+1) + end + + if pos1 ~= nil and pos2 ~= nil then + local filename = line:sub(pos1+1,pos2-1) + + if filename ~= nil and + filename ~= "" and + filename:find(".lua") then + return modmgr.identify_modname(modpath,filename) + end + end + return nil +end + +-------------------------------------------------------------------------------- +function modmgr.update_global_mods() + local modpath = engine.get_modpath() + modmgr.global_mods = {} + if modpath ~= nil and + modpath ~= "" then + get_mods(modpath,modmgr.global_mods) + end +end + +-------------------------------------------------------------------------------- +function modmgr.get_mods_list() + local toadd = "" + + modmgr.update_global_mods() + + if modmgr.global_mods ~= nil then + for i=1,#modmgr.global_mods,1 do + if toadd ~= "" then + toadd = toadd.."," + end + toadd = toadd .. modmgr.global_mods[i] + end + end + + return toadd +end + +-------------------------------------------------------------------------------- +function modmgr.mod_exists(basename) + modmgr.update_global_mods() + + if modmgr.global_mods ~= nil then + for i=1,#modmgr.global_mods,1 do + if modmgr.global_mods[i] == basename then + return true + end + end + end + + return false +end + +-------------------------------------------------------------------------------- +function modmgr.identify_modname(modpath,filename) + local testfile = io.open(modpath .. DIR_DELIM .. filename,"r") + if testfile ~= nil then + local line = testfile:read() + + while line~= nil do + local modname = nil + + if line:find("minetest.register_tool") then + modname = modmgr.parse_register_line(line) + end + + if line:find("minetest.register_craftitem") then + modname = modmgr.parse_register_line(line) + end + + + if line:find("minetest.register_node") then + modname = modmgr.parse_register_line(line) + end + + if line:find("dofile") then + modname = modmgr.parse_dofile_line(modpath,line) + end + + if modname ~= nil then + testfile:close() + return modname + end + + line = testfile:read() + end + testfile:close() + end + + return nil +end + +-------------------------------------------------------------------------------- +function modmgr.tab() + if modmgr.selected_mod == nil then + modmgr.selected_mod = 1 + end + + local retval = + "vertlabel[0,-0.25;MODS]" .. + "label[0.8,-0.25;Installed Mods:]" .. + "textlist[0.75,0.25;4.5,4.3;modlist;" .. + modmgr.get_mods_list() .. + ";" .. modmgr.selected_mod .. "]" + + retval = retval .. + "button[1,4.85;2,0.5;btn_mod_mgr_install_local;Install]" .. + "button[3,4.85;2,0.5;btn_mod_mgr_download;Download]" + + if #modmgr.global_mods >= modmgr.selected_mod and + modmgr.global_mods[modmgr.selected_mod]:find("<MODPACK>") then + retval = retval .. "button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;Rename]" + end + + if #modmgr.global_mods >= modmgr.selected_mod then + local modpath = engine.get_modpath() + --show dependencys + if modmgr.global_mods[modmgr.selected_mod]:find("<MODPACK>") == nil then + retval = retval .. + "label[6,1.9;Depends:]" .. + "textlist[6,2.4;5.7,2;deplist;" + + toadd = modmgr.get_dependencys(modpath .. DIR_DELIM .. + modmgr.global_mods[modmgr.selected_mod]) + + retval = retval .. toadd .. ";0;true,false]" + + --TODO read modinfo + end + --show delete button + retval = retval .. "button[8,4.85;2,0.5;btn_mod_mgr_delete_mod;Delete]" + end + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.dialog_rename_modpack() + + local modname = modmgr.global_mods[modmgr.selected_mod] + modname = modname:sub(0,modname:find("<") -2) + + local retval = + "label[1.75,1;Rename Modpack:]".. + "field[4.5,1.4;6,0.5;te_modpack_name;;" .. + modname .. + "]" .. + "button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;Accept]" .. + "button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;Cancel]" + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.precheck() + if modmgr.global_mods == nil then + modmgr.update_global_mods() + end + + if modmgr.world_config_selected_world == nil then + modmgr.world_config_selected_world = 1 + end + + if modmgr.world_config_selected_mod == nil then + modmgr.world_config_selected_mod = 1 + end + + if modmgr.hide_gamemods == nil then + modmgr.hide_gamemods = true + end +end + +-------------------------------------------------------------------------------- +function modmgr.get_worldmod_idx() + if not modmgr.hide_gamemods then + return modmgr.world_config_selected_mod - #modmgr.worldconfig.game_mods + else + return modmgr.world_config_selected_mod + end +end + +-------------------------------------------------------------------------------- +function modmgr.is_gamemod() + if not modmgr.hide_gamemods then + if modmgr.world_config_selected_mod <= #modmgr.worldconfig.game_mods then + return true + else + return false + end + else + return false + end +end + +-------------------------------------------------------------------------------- +function modmgr.render_worldmodlist() + local retval = "" + + for i=1,#modmgr.global_mods,1 do + local parts = modmgr.global_mods[i]:split(DIR_DELIM) + local shortname = parts[#parts] + if modmgr.worldconfig.global_mods[shortname] then + retval = retval .. "#GRN" .. modmgr.global_mods[i] .. "," + else + retval = retval .. modmgr.global_mods[i] .. "," + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.render_gamemodlist() + local retval = "" + for i=1,#modmgr.worldconfig.game_mods,1 do + retval = retval .. + "#BLU" .. modmgr.worldconfig.game_mods[i] .. "," + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.dialog_configure_world() + modmgr.precheck() + + local modpack_selected = false + local gamemod_selected = modmgr.is_gamemod() + local modname = "" + local modfolder = "" + local shortname = "" + + if not gamemod_selected then + local worldmodidx = modmgr.get_worldmod_idx() + modname = modmgr.global_mods[worldmodidx] + + if modname:find("<MODPACK>") ~= nil then + modname = modname:sub(0,modname:find("<") -2) + modpack_selected = true + end + + local parts = modmgr.global_mods[worldmodidx]:split(DIR_DELIM) + shortname = parts[#parts] + + modfolder = engine.get_modpath() .. DIR_DELIM .. modname + end + + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + + local retval = + "size[11,6.5]" .. + "label[1.5,-0.25;World: " .. worldspec.name .. "]" + + if modmgr.hide_gamemods then + retval = retval .. "checkbox[5.5,6.15;cb_hide_gamemods;Hide Game;true]" + else + retval = retval .. "checkbox[5.5,6.15;cb_hide_gamemods;Hide Game;false]" + end + retval = retval .. + "button[9.25,6.35;2,0.5;btn_config_world_save;Save]" .. + "button[7.4,6.35;2,0.5;btn_config_world_cancel;Cancel]" .. + "textlist[5.5,-0.25;5.5,6.5;world_config_modlist;" + + + if not modmgr.hide_gamemods then + retval = retval .. modmgr.render_gamemodlist() + end + + retval = retval .. modmgr.render_worldmodlist() + + retval = retval .. ";" .. modmgr.world_config_selected_mod .."]" + + if not gamemod_selected then + retval = retval .. + "label[0,0.45;Mod:]" .. + "label[0.75,0.45;" .. modname .. "]" .. + "label[0,1.5;depends on:]" .. + "textlist[0,2;5,2;world_config_depends;" .. + modmgr.get_dependencys(modfolder) .. ";0]" .. + "label[0,4;depends on:]" .. + "textlist[0,4.5;5,2;world_config_is_required;;0]" + + if modpack_selected then + retval = retval .. + "button[-0.05,1.05;2,0.5;btn_cfgw_enable_all;Enable All]" .. + "button[3.25,1.05;2,0.5;btn_cfgw_disable_all;Disable All]" + else + retval = retval .. + "checkbox[0,0.8;cb_mod_enabled;enabled;" + + if modmgr.worldconfig.global_mods[shortname] then + print("checkbox " .. shortname .. " enabled") + retval = retval .. "true" + else + print("checkbox " .. shortname .. " disabled") + retval = retval .. "false" + end + + retval = retval .. "]" + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.handle_buttons(tab,fields) + + local retval = nil + + if tab == "mod_mgr" then + retval = modmgr.handle_modmgr_buttons(fields) + end + + if tab == "dialog_rename_modpack" then + retval = modmgr.handle_rename_modpack_buttons(fields) + end + + if tab == "dialog_delete_mod" then + retval = modmgr.handle_delete_mod_buttons(fields) + end + + if tab == "dialog_configure_world" then + retval = modmgr.handle_configure_world_buttons(fields) + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.get_dependencys(modfolder) + local filename = modfolder .. + DIR_DELIM .. "depends.txt" + + local dependencyfile = io.open(filename,"r") + + local toadd = "" + if dependencyfile then + local dependency = dependencyfile:read("*l") + while dependency do + if toadd ~= "" then + toadd = toadd .. "," + end + toadd = toadd .. dependency + dependency = dependencyfile:read() + end + dependencyfile:close() + else + print(filename .. " not found") + end + + return toadd +end + + +-------------------------------------------------------------------------------- +function modmgr.get_worldconfig(worldpath) + local filename = worldpath .. + DIR_DELIM .. "world.mt" + + local worldfile = io.open(filename,"r") + + local worldconfig = {} + worldconfig.global_mods = {} + worldconfig.game_mods = {} + + if worldfile then + local dependency = worldfile:read("*l") + while dependency do + local parts = dependency:split("=") + + local key = parts[1]:trim() + + if key == "gameid" then + worldconfig.id = parts[2]:trim() + else + local key = parts[1]:trim():sub(10) + if parts[2]:trim() == "true" then + print("found enabled mod: >" .. key .. "<") + worldconfig.global_mods[key] = true + else + print("found disabled mod: >" .. key .. "<") + worldconfig.global_mods[key] = false + end + end + dependency = worldfile:read("*l") + end + worldfile:close() + else + print(filename .. " not found") + end + + --read gamemods + local gamemodpath = engine.get_gamepath() .. DIR_DELIM .. worldconfig.id .. DIR_DELIM .. "mods" + + print("reading game mods from: " .. dump(gamemodpath)) + get_mods(gamemodpath,worldconfig.game_mods) + + return worldconfig +end +-------------------------------------------------------------------------------- +function modmgr.handle_modmgr_buttons(fields) + local retval = { + tab = nil, + is_dialog = nil, + show_buttons = nil, + } + + if fields["modlist"] ~= nil then + local event = explode_textlist_event(fields["modlist"]) + modmgr.selected_mod = event.index + end + + if fields["btn_mod_mgr_install_local"] ~= nil then + engine.show_file_open_dialog("mod_mgt_open_dlg","Select Mod File:") + end + + if fields["btn_mod_mgr_download"] ~= nil then + retval.current_tab = "dialog_modstore_unsorted" + retval.is_dialog = true + retval.show_buttons = false + return retval + end + + if fields["btn_mod_mgr_rename_modpack"] ~= nil then + retval.current_tab = "dialog_rename_modpack" + retval.is_dialog = true + retval.show_buttons = false + return retval + end + + if fields["btn_mod_mgr_delete_mod"] ~= nil then + retval.current_tab = "dialog_delete_mod" + retval.is_dialog = true + retval.show_buttons = false + return retval + end + + if fields["mod_mgt_open_dlg_accepted"] ~= nil and + fields["mod_mgt_open_dlg_accepted"] ~= "" then + modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil) + end + + return nil; +end + +-------------------------------------------------------------------------------- +function modmgr.installmod(modfilename,basename) + local modfile = identify_filetype(modfilename) + + local modpath = modmgr.extract(modfile) + + if modpath == nil then + gamedata.errormessage = "Install Mod: file: " .. modfile.name .. + "\nInstall Mod: unsupported filetype \"" .. modfile.type .. "\"" + return + end + + + local basefolder = modmgr.getbasefolder(modpath) + + if basefolder.type == "modpack" then + local clean_path = nil + + if basename ~= nil then + clean_path = "mp_" .. basename + end + + if clean_path == nil then + clean_path = get_last_folder(cleanup_path(basefolder.path)) + end + + if clean_path ~= nil then + local targetpath = engine.get_modpath() .. DIR_DELIM .. clean_path + engine.copy_dir(basefolder.path,targetpath) + else + gamedata.errormessage = "Install Mod: unable to find suitable foldername for modpack " + .. modfilename + end + end + + if basefolder.type == "mod" then + local targetfolder = basename + + if targetfolder == nil then + targetfolder = modmgr.identify_modname(basefolder.path,"init.lua") + end + + --if heuristic failed try to use current foldername + if targetfolder == nil then + targetfolder = get_last_folder(basefolder.path) + end + + if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then + local targetpath = engine.get_modpath() .. DIR_DELIM .. targetfolder + engine.copy_dir(basefolder.path,targetpath) + else + gamedata.errormessage = "Install Mod: unable to find real modname for: " + .. modfilename + end + end + + engine.delete_dir(modpath) +end + +-------------------------------------------------------------------------------- +function modmgr.handle_rename_modpack_buttons(fields) + local oldname = modmgr.global_mods[modmgr.selected_mod] + oldname = oldname:sub(0,oldname:find("<") -2) + + if fields["dlg_rename_modpack_confirm"] ~= nil then + local oldpath = engine.get_modpath() .. DIR_DELIM .. oldname + local targetpath = engine.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"] + engine.copy_dir(oldpath,targetpath,false) + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end +-------------------------------------------------------------------------------- +function modmgr.handle_configure_world_buttons(fields) + if fields["world_config_modlist"] ~= nil then + local event = explode_textlist_event(fields["world_config_modlist"]) + modmgr.world_config_selected_mod = event.index + end + + if fields["cb_mod_enabled"] ~= nil then + local index = modmgr.get_worldmod_idx() + local modname = modmgr.global_mods[index] + + local parts = modmgr.global_mods[index]:split(DIR_DELIM) + local shortname = parts[#parts] + + if fields["cb_mod_enabled"] == "true" then + modmgr.worldconfig.global_mods[shortname] = true + else + modmgr.worldconfig.global_mods[shortname] = false + end + end + + if fields["cb_hide_gamemods"] ~= nil then + if fields["cb_hide_gamemods"] == "true" then + modmgr.hide_gamemods = true + else + modmgr.hide_gamemods = false + end + end + + if fields["btn_config_world_save"] then + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + + local filename = worldspec.path .. + DIR_DELIM .. "world.mt" + + local worldfile = io.open(filename,"w") + + if worldfile then + worldfile:write("gameid = " .. modmgr.worldconfig.id .. "\n") + for key,value in pairs(modmgr.worldconfig.global_mods) do + if value then + worldfile:write("load_mod_" .. key .. " = true" .. "\n") + else + worldfile:write("load_mod_" .. key .. " = false" .. "\n") + end + end + + worldfile:close() + end + + modmgr.worldconfig = nil + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_config_world_cancel"] then + + modmgr.worldconfig = nil + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_cfgw_enable_all"] then + local worldmodidx = modmgr.get_worldmod_idx() + modname = modmgr.global_mods[worldmodidx] + + modname = modname:sub(0,modname:find("<") -2) + + for i=1,#modmgr.global_mods,1 do + + if modmgr.global_mods[i]:find("<MODPACK>") == nil then + local modpackpart = modmgr.global_mods[i]:sub(0,modname:len()) + + if modpackpart == modname then + local parts = modmgr.global_mods[i]:split(DIR_DELIM) + local shortname = parts[#parts] + modmgr.worldconfig.global_mods[shortname] = true + end + end + end + end + + if fields["btn_cfgw_disable_all"] then + local worldmodidx = modmgr.get_worldmod_idx() + modname = modmgr.global_mods[worldmodidx] + + modname = modname:sub(0,modname:find("<") -2) + + for i=1,#modmgr.global_mods,1 do + local modpackpart = modmgr.global_mods[i]:sub(0,modname:len()) + + if modpackpart == modname then + local parts = modmgr.global_mods[i]:split(DIR_DELIM) + local shortname = parts[#parts] + modmgr.worldconfig.global_mods[shortname] = nil + end + end + end + + return nil +end +-------------------------------------------------------------------------------- +function modmgr.handle_delete_mod_buttons(fields) + local modname = modmgr.global_mods[modmgr.selected_mod] + + if modname:find("<MODPACK>") ~= nil then + modname = modname:sub(0,modname:find("<") -2) + end + + if fields["dlg_delete_mod_confirm"] ~= nil then + local oldpath = engine.get_modpath() .. DIR_DELIM .. modname + + if oldpath ~= nil and + oldpath ~= "" and + oldpath ~= engine.get_modpath() then + engine.delete_dir(oldpath) + end + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end + +-------------------------------------------------------------------------------- +function modmgr.dialog_delete_mod() + + local modname = modmgr.global_mods[modmgr.selected_mod] + + if modname:find("<MODPACK>") ~= nil then + modname = modname:sub(0,modname:find("<") -2) + end + + local retval = + "field[1.75,1;10,3;;Are you sure you want to delete ".. modname .. "?;]".. + "button[4,4.2;1,0.5;dlg_delete_mod_confirm;Yes]" .. + "button[6.5,4.2;3,0.5;dlg_delete_mod_cancel;No of course not!]" + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.init_worldconfig() + + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + + if worldspec ~= nil then + --read worldconfig + modmgr.worldconfig = modmgr.get_worldconfig(worldspec.path) + + if modmgr.worldconfig.id == nil or + modmgr.worldconfig.id == "" then + modmgr.worldconfig = nil + return false + end + + return true + end + + return false +end + +-------------------------------------------------------------------------------- +function modmgr.gettab(name) + local retval = "" + + if name == "mod_mgr" then + retval = retval .. modmgr.tab() + end + + if name == "dialog_rename_modpack" then + retval = retval .. modmgr.dialog_rename_modpack() + end + + if name == "dialog_delete_mod" then + retval = retval .. modmgr.dialog_delete_mod() + end + + if name == "dialog_configure_world" then + retval = retval .. modmgr.dialog_configure_world() + end + + return retval +end diff --git a/builtin/modstore.lua b/builtin/modstore.lua new file mode 100644 index 000000000..73afc3899 --- /dev/null +++ b/builtin/modstore.lua @@ -0,0 +1,275 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- + +--modstore implementation +modstore = {} + +-------------------------------------------------------------------------------- +function modstore.init() + modstore.tabnames = {} + + table.insert(modstore.tabnames,"dialog_modstore_unsorted") + table.insert(modstore.tabnames,"dialog_modstore_search") + + modstore.modsperpage = 5 + + modstore.basetexturedir = engine.get_gamepath() .. DIR_DELIM .. ".." .. + DIR_DELIM .. "textures" .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + modstore.update_modlist() + + modstore.current_list = nil + + modstore.details_cache = {} +end +-------------------------------------------------------------------------------- +function modstore.nametoindex(name) + + for i=1,#modstore.tabnames,1 do + if modstore.tabnames[i] == name then + return i + end + end + + return 1 +end + +-------------------------------------------------------------------------------- +function modstore.gettab(tabname) + local retval = "" + + local is_modstore_tab = false + + if tabname == "dialog_modstore_unsorted" then + retval = modstore.getmodlist(modstore.modlist_unsorted) + is_modstore_tab = true + end + + if tabname == "dialog_modstore_search" then + + + is_modstore_tab = true + end + + if is_modstore_tab then + return modstore.tabheader(tabname) .. retval + end + + if tabname == "modstore_mod_installed" then + return "size[6,2]label[0.25,0.25;Mod: " .. modstore.lastmodtitle .. + " installed successfully]" .. + "button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]" + end + + return "" +end + +-------------------------------------------------------------------------------- +function modstore.tabheader(tabname) + local retval = "size[12,9.25]" + retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" .. + "Unsorted,Search;" .. + modstore.nametoindex(tabname) .. ";true;false]" + + return retval +end + +-------------------------------------------------------------------------------- +function modstore.handle_buttons(current_tab,fields) + + modstore.lastmodtitle = "" + + if fields["modstore_tab"] then + local index = tonumber(fields["modstore_tab"]) + + if index > 0 and + index <= #modstore.tabnames then + return { + current_tab = modstore.tabnames[index], + is_dialog = true, + show_buttons = false + } + end + + modstore.modlist_page = 0 + end + + if fields["btn_modstore_page_up"] then + if modstore.current_list ~= nil and modstore.current_list.page > 0 then + modstore.current_list.page = modstore.current_list.page - 1 + end + end + + if fields["btn_modstore_page_down"] then + if modstore.current_list ~= nil and + modstore.current_list.page <modstore.current_list.pagecount then + modstore.current_list.page = modstore.current_list.page +1 + end + end + + if fields["btn_confirm_mod_successfull"] then + return { + current_tab = modstore.tabnames[1], + is_dialog = true, + show_buttons = false + } + end + + for i=1, modstore.modsperpage, 1 do + local installbtn = "btn_install_mod_" .. i + + if fields[installbtn] then + local modlistentry = + modstore.current_list.page * modstore.modsperpage + i + + local moddetails = modstore.get_details(modstore.current_list.data[modlistentry].id) + + local fullurl = engine.setting_get("modstore_download_url") .. + moddetails.download_url + local modfilename = os.tempfolder() .. ".zip" + print("Downloading mod from: " .. fullurl .. " to ".. modfilename) + + if engine.download_file(fullurl,modfilename) then + + modmgr.installmod(modfilename,moddetails.basename) + + os.remove(modfilename) + modstore.lastmodtitle = modstore.current_list.data[modlistentry].title + + return { + current_tab = "modstore_mod_installed", + is_dialog = true, + show_buttons = false + } + else + gamedata.errormessage = "Unable to download " .. + moddetails.download_url .. " (internet connection?)" + end + end + end +end + +-------------------------------------------------------------------------------- +function modstore.update_modlist() + modstore.modlist_unsorted = {} + modstore.modlist_unsorted.data = engine.get_modstore_list() + + if modstore.modlist_unsorted.data ~= nil then + modstore.modlist_unsorted.pagecount = + math.floor((#modstore.modlist_unsorted.data / modstore.modsperpage)) + else + modstore.modlist_unsorted.data = {} + modstore.modlist_unsorted.pagecount = 0 + end + modstore.modlist_unsorted.page = 0 +end + +-------------------------------------------------------------------------------- +function modstore.getmodlist(list) + local retval = "" + retval = retval .. "label[10,-0.4;Page " .. (list.page +1) .. + " of " .. (list.pagecount +1) .. "]" + + retval = retval .. "button[11.6,-0.1;0.5,0.5;btn_modstore_page_up;^]" + retval = retval .. "box[11.6,0.35;0.28,8.6;BLK]" + local scrollbarpos = 0.35 + (8.1/list.pagecount) * list.page + retval = retval .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;LIM]" + retval = retval .. "button[11.6,9.0;0.5,0.5;btn_modstore_page_down;v]" + + + if #list.data < (list.page * modstore.modsperpage) then + return retval + end + + local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage + + if (endmod > #list.data) then + endmod = #list.data + end + + for i=(list.page * modstore.modsperpage) +1, endmod, 1 do + --getmoddetails + local details = modstore.get_details(list.data[i].id) + + if details ~= nil then + local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2 + + retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;WHT]" + + --screenshot + if details.screenshot_url ~= nil and + details.screenshot_url ~= "" then + if list.data[i].texturename == nil then + print("downloading screenshot: " .. details.screenshot_url) + local filename = os.tempfolder() + + if engine.download_file(details.screenshot_url,filename) then + list.data[i].texturename = filename + end + end + end + + if list.data[i].texturename == nil then + list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png" + end + + retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" .. + list.data[i].texturename .. "]" + + --title + author + retval = retval .."label[2.75," .. screenshot_ypos .. ";" .. + fs_escape_string(details.title) .. " (" .. details.author .. ")]" + + --description + local descriptiony = screenshot_ypos + 0.5 + retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.6;;" .. + fs_escape_string(details.description) .. ";]" + --rating + local ratingy = screenshot_ypos + 0.6 + retval = retval .."label[10.1," .. ratingy .. ";Rating: " .. details.rating .."]" + + --install button + local buttony = screenshot_ypos + 1.2 + local buttonnumber = (i - (list.page * modstore.modsperpage)) + retval = retval .."button[9.6," .. buttony .. ";2,0.5;btn_install_mod_" .. buttonnumber .. ";" + + if modmgr.mod_exists(details.basename) then + retval = retval .. "re-Install]" + else + retval = retval .. "Install]" + end + end + end + + modstore.current_list = list + + return retval +end + +-------------------------------------------------------------------------------- +function modstore.get_details(modid) + + if modstore.details_cache[modid] ~= nil then + return modstore.details_cache[modid] + end + + local retval = engine.get_modstore_details(tostring(modid)) + modstore.details_cache[modid] = retval + return retval +end
\ No newline at end of file diff --git a/doc/lua_api.txt b/doc/lua_api.txt index cd0824eb3..ebba40fbf 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -883,6 +883,15 @@ background[<X>,<Y>;<W>,<H>;<texture name>] ^ Position and size units are inventory slots ^ Example for formspec 8x4 in 16x resolution: image shall be sized 8*16px x 4*16px +pwdfield[<X>,<Y>;<W>,<H>;<name>;<label>] +^ Textual password style field; will be sent to server when a button is clicked +^ x and y position the field relative to the top left of the menu +^ w and h are the size of the field +^ fields are a set height, but will be vertically centred on h +^ Position and size units are inventory slots +^ name is the name of the field as returned in fields to on_receive_fields +^ label, if not blank, will be text printed on the top left above the field + field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>] ^ Textual field; will be sent to server when a button is clicked ^ x and y position the field relative to the top left of the menu @@ -910,6 +919,12 @@ label[<X>,<Y>;<label>] ^ label is the text on the label ^ Position and size units are inventory slots +vertlabel[<X>,<Y>;<label>] +^ Textual label drawn verticaly +^ x and y work as per field +^ label is the text on the label +^ Position and size units are inventory slots + button[<X>,<Y>;<W>,<H>;<name>;<label>] ^ Clickable button. When clicked, fields will be sent. ^ x, y and name work as per field @@ -922,6 +937,13 @@ image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>] ^ image is the filename of an image ^ Position and size units are inventory slots +image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>] +^ x, y, w, h, and name work as per button +^ image is the filename of an image +^ Position and size units are inventory slots +^ noclip true meand imagebutton doesn't need to be within specified formsize +^ drawborder draw button bodrer or not + item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>] ^ x, y, w, h, name and label work as per button ^ item name is the registered name of an item/node, @@ -934,6 +956,42 @@ button_exit[<X>,<Y>;<W>,<H>;<name>;<label>] image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>] ^ When clicked, fields will be sent and the form will quit. +textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>] +^Scrollabel itemlist showing arbitrary text elements +^ x and y position the itemlist relative to the top left of the menu +^ w and h are the size of the itemlist +^ listelements can be prepended by #colorkey (see colorkeys), +^ if you want a listelement to start with # write ## +^ name fieldname sent to server on doubleclick value is current selected element + +tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>;<current_tab>;<transparent>;<draw_border>] +^ show a tabHEADER at specific position (ignores formsize) +^ x and y position the itemlist relative to the top left of the menu +^ name fieldname data is transfered to lua +^ caption 1... name shown on top of tab +^ current_tab index of selected tab 1... +^ transparent (optional) show transparent +^ draw_border (optional) draw border + +box[<X>,<Y>;<W>,<H>;<colorkey>] +^ simple colored semitransparent box +^ x and y position the box relative to the top left of the menu +^ w and h are the size of box +^ colorkey (see colorkeys) + +Available colorkeys: +- YLW yellow +- GRN green +- LIM lime +- ORN orange +- RED red +- BLU blue +- CYN cyan +- BLK black +- BRN brown +- WHT white +- GRY grey + Inventory location: - "context": Selected node metadata (deprecated: "current_name") diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt new file mode 100644 index 000000000..337091a93 --- /dev/null +++ b/doc/menu_lua_api.txt @@ -0,0 +1,163 @@ +Minetest Lua Mainmenu API Reference 0.4.6 +======================================== + +Introduction +------------- +The main menu is defined as a formspec by Lua in builtin/mainmenu.lua +Description of formspec language to show your menu is in lua_api.txt + +Callbacks +--------- +engine.buttonhandler(fields): called when a button is pressed. +^ fields = {name1 = value1, name2 = value2, ...} +engine.event_handler(event) +^ event: "MenuQuit", "KeyEnter", "ExitButton" or "EditBoxEnter" + +Gamedata +-------- +The "gamedata" table is read when calling engine.start(). It should contain: +{ + playername = <name>, + password = <password>, + address = <IP/adress>, + port = <port>, + selected_world = <index>, -- 0 for client mode + singleplayer = <true/false>, +} + +Functions +--------- +engine.start() +engine.close() + +Filesystem: +engine.get_scriptdir() +^ returns directory of script +engine.get_modpath() +^ returns path to global modpath +engine.get_modstore_details(modid) +^ modid numeric id of mod in modstore +^ returns { + id = <numeric id of mod in modstore>, + title = <human readable title>, + basename = <basename for mod>, + description = <description of mod>, + author = <author of mod>, + download_url= <best match download url>, + license = <short description of license>, + rating = <float value of current rating> +} +engine.get_modstore_list() +^ returns { + [1] = { + id = <numeric id of mod in modstore>, + title = <human readable title>, + basename = <basename for mod> + } +} +engine.get_gamepath() +^ returns path to global gamepath +engine.get_dirlist(path,onlydirs) +^ path to get subdirs from +^ onlydirs should result contain only dirs? +^ returns list of folders within path +engine.create_dir(absolute_path) +^ absolute_path to directory to create (needs to be absolute) +^ returns true/false +engine.delete_dir(absolute_path) +^ absolute_path to directory to delete (needs to be absolute) +^ returns true/false +engine.copy_dir(source,destination,keep_soure) +^ source folder +^ destination folder +^ keep_source DEFAULT true --> if set to false source is deleted after copying +^ returns true/false +engine.extract_zip(zipfile,destination) [unzip within path required] +^ zipfile to extract +^ destination folder to extract to +^ returns true/false +engine.download_file(url,target) +^ url to download +^ target to store to +^ returns true/false +engine.get_version() +^ returns current minetest version + +GUI: +engine.update_formspec(formspec) +- engine.set_background(type, texturepath) +^ type: "background", "overlay", "header" or "footer" +engine.set_clouds(<true/false>) +engine.set_topleft_text(text) + +Games: +engine.get_game(index) +^ returns { + id = <id>, + path = <full path to game>, + gamemods_path = <path>, + name = <name of game>, + menuicon_path = <full path to menuicon>, + DEPRECATED: + addon_mods_paths = {[1] = <path>,}, +} +engine.get_games() -> table of all games in upper format + +Favorites: +engine.get_favorites(location) -> list of favorites +^ location: "local" or "online" +^ returns { + [1] = { + clients = <number of clients/nil>, + clients_max = <maximum number of clients/nil>, + version = <server version/nil>, + password = <true/nil>, + creative = <true/nil>, + damage = <true/nil>, + pvp = <true/nil>, + description = <server description/nil>, + name = <server name/nil>, + address = <address of server/nil>, + port = <port> + }, +} +engine.delete_favorite(id, location) -> success + +Settings: +engine.setting_set(name, value) +engine.setting_get(name) -> string or nil +engine.setting_setbool(name, value) +engine.setting_getbool(name) -> bool or nil + +Worlds: +engine.get_worlds() -> list of worlds +^ returns { + [1] = { + path = <full path to world>, + name = <name of world>, + gameid = <gameid of world>, + }, +} +engine.create_world(worldname, gameid) +engine.delete_world(index) + + +UI: +engine.get_textlist_index(textlistname) -> index +engine.show_keys_menu() +engine.file_open_dialog(formname,caption) +^ shows a file open dialog +^ formname is base name of dialog response returned in fields +^ -if dialog was accepted "_accepted" +^^ will be added to fieldname containing the path +^ -if dialog was canceled "_cancelled" +^ will be added to fieldname value is set to formname itself +^ returns nil or selected file/folder + +Helpers: +dump(obj, dumped={}) +^ Return object serialized as a string +string:split(separator) +^ eg. string:split("a,b", ",") == {"a","b"} +string:trim() +^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"
\ No newline at end of file diff --git a/minetest.conf.example b/minetest.conf.example index 75e546c2f..d7fa621a0 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -373,3 +373,9 @@ # Enable/disable running an IPv6 server. An IPv6 server may be restricted # to IPv6 clients, depending on system configuration. #ipv6_server = false + +#main_menu_game_mgr = 0 +#main_menu_mod_mgr = 0 +#modstore_download_url = http://forum.minetest.net/media/ +#modstore_listmods_url = http://forum.minetest.net/mmdb/mods/ +#modstore_details_url = http://forum.minetest.net/mmdb/mod/*/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bcd265858..2b60ff0d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -271,6 +271,7 @@ set(common_SRCS staticobject.cpp serverlist.cpp pathfinder.cpp + convert_json.cpp ${SCRIPT_SRCS} ${UTIL_SRCS} ) @@ -313,7 +314,6 @@ set(minetest_SRCS clientobject.cpp chat.cpp hud.cpp - guiMainMenu.cpp guiKeyChangeMenu.cpp guiMessageMenu.cpp guiTextInputMenu.cpp @@ -323,15 +323,16 @@ set(minetest_SRCS guiVolumeChange.cpp guiDeathScreen.cpp guiChatConsole.cpp - guiCreateWorld.cpp - guiConfigureWorld.cpp - guiConfirmMenu.cpp client.cpp filecache.cpp tile.cpp shader.cpp game.cpp main.cpp + guiEngine.cpp + guiLuaApi.cpp + guiFileSelectMenu.cpp + convert_json.cpp ) if(USE_FREETYPE) @@ -488,7 +489,7 @@ else() endif() set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops") - set(CMAKE_CXX_FLAGS_DEBUG "-g -O1 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") if(USE_GPROF) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") diff --git a/src/convert_json.cpp b/src/convert_json.cpp new file mode 100644 index 000000000..7a69071ef --- /dev/null +++ b/src/convert_json.cpp @@ -0,0 +1,367 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <vector> +#include <iostream> +#include <sstream> + +#include "convert_json.h" +#include "mods.h" +#include "config.h" +#include "log.h" + +#if USE_CURL +#include <curl/curl.h> + +static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +#endif + +Json::Value fetchJsonValue(const std::string url, + struct curl_slist *chunk) { +#if USE_CURL + std::string liststring; + CURL *curl; + + curl = curl_easy_init(); + if (curl) + { + CURLcode res; + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring); + + if (chunk != 0) + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + errorstream<<"Jsonreader: "<< url <<" not found (internet connection?)"<<std::endl; + curl_easy_cleanup(curl); + } + + Json::Value root; + Json::Reader reader; + std::istringstream stream(liststring); + if (!liststring.size()) { + return Json::Value(); + } + + if (!reader.parse( stream, root ) ) + { + errorstream << "URL: " << url << std::endl; + errorstream << "Failed to parse json data " << reader.getFormattedErrorMessages(); + errorstream << "data: \"" << liststring << "\"" << std::endl; + return Json::Value(); + } + + if (root.isArray()) { + return root; + } + if ((root["list"].isArray())) { + return root["list"]; + } + else { + return root; + } +#endif + return Json::Value(); +} + +std::vector<ModStoreMod> readModStoreList(Json::Value& modlist) { + std::vector<ModStoreMod> retval; + + if (modlist.isArray()) { + for (unsigned int i = 0; i < modlist.size(); i++) + { + ModStoreMod toadd; + toadd.valid = true; + + //id + if (modlist[i]["id"].asString().size()) { + const char* id_raw = modlist[i]["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + toadd.id = numbervalue; + } + } + else { + toadd.valid = false; + } + + //title + if (modlist[i]["title"].asString().size()) { + toadd.title = modlist[i]["title"].asString(); + } + else { + toadd.valid = false; + } + + //basename + if (modlist[i]["basename"].asString().size()) { + toadd.basename = modlist[i]["basename"].asString(); + } + else { + toadd.valid = false; + } + + //author + + //rating + + //version + + if (toadd.valid) { + retval.push_back(toadd); + } + } + } + return retval; +} + +ModStoreModDetails readModStoreModDetails(Json::Value& details) { + + ModStoreModDetails retval; + + retval.valid = true; + + //version set + if (details["version_set"].isArray()) { + for (unsigned int i = 0; i < details["version_set"].size(); i++) + { + ModStoreVersionEntry toadd; + + if (details["version_set"][i]["id"].asString().size()) { + const char* id_raw = details["version_set"][i]["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + toadd.id = numbervalue; + } + } + else { + retval.valid = false; + } + + //date + if (details["version_set"][i]["date"].asString().size()) { + toadd.date = details["version_set"][i]["date"].asString(); + } + + //file + if (details["version_set"][i]["file"].asString().size()) { + toadd.file = details["version_set"][i]["file"].asString(); + } + else { + retval.valid = false; + } + + //approved + + //mtversion + + if( retval.valid ) { + retval.versions.push_back(toadd); + } + else { + break; + } + } + } + + if (retval.versions.size() < 1) { + retval.valid = false; + } + + //categories + if (details["categories"].isObject()) { + for (unsigned int i = 0; i < details["categories"].size(); i++) { + ModStoreCategoryInfo toadd; + + if (details["categories"][i]["id"].asString().size()) { + + const char* id_raw = details["categories"][i]["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + toadd.id = numbervalue; + } + } + else { + retval.valid = false; + } + if (details["categories"][i]["title"].asString().size()) { + toadd.name = details["categories"][i]["title"].asString(); + } + else { + retval.valid = false; + } + + if( retval.valid ) { + retval.categories.push_back(toadd); + } + else { + break; + } + } + } + + //author + if (details["author"].isObject()) { + if (details["author"]["id"].asString().size()) { + + const char* id_raw = details["author"]["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + retval.author.id = numbervalue; + } + else { + retval.valid = false; + } + } + else { + retval.valid = false; + } + + if (details["author"]["username"].asString().size()) { + retval.author.username = details["author"]["username"].asString(); + } + else { + retval.valid = false; + } + } + else { + retval.valid = false; + } + + //license + if (details["license"].isObject()) { + if (details["license"]["id"].asString().size()) { + + const char* id_raw = details["license"]["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + retval.license.id = numbervalue; + } + } + else { + retval.valid = false; + } + + if (details["license"]["short"].asString().size()) { + retval.license.shortinfo = details["license"]["short"].asString(); + } + else { + retval.valid = false; + } + + if (details["license"]["link"].asString().size()) { + retval.license.url = details["license"]["link"].asString(); + } + + } + + //id + if (details["id"].asString().size()) { + + const char* id_raw = details["id"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(id_raw,&endptr,10); + + if ((*id_raw != 0) && (*endptr == 0)) { + retval.id = numbervalue; + } + } + else { + retval.valid = false; + } + + //title + if (details["title"].asString().size()) { + retval.title = details["title"].asString(); + } + else { + retval.valid = false; + } + + //basename + if (details["basename"].asString().size()) { + retval.basename = details["basename"].asString(); + } + else { + retval.valid = false; + } + + //description + if (details["desc"].asString().size()) { + retval.description = details["desc"].asString(); + } + + //repository + if (details["replink"].asString().size()) { + retval.repository = details["replink"].asString(); + } + + //value + if (details["rating"].asString().size()) { + + const char* id_raw = details["rating"].asString().c_str(); + char* endptr = 0; + float numbervalue = strtof(id_raw,&endptr); + + if ((*id_raw != 0) && (*endptr == 0)) { + retval.rating = numbervalue; + } + } + else { + retval.rating = 0.0; + } + + //depends + if (details["depends"].isArray()) { + //TODO + } + + //softdepends + if (details["softdep"].isArray()) { + //TODO + } + + //screenshot url + if (details["screenshot_url"].asString().size()) { + retval.screenshot_url = details["screenshot_url"].asString(); + } + + return retval; +} diff --git a/src/guiConfirmMenu.h b/src/convert_json.h index 7d217d6a6..3fa9903b1 100644 --- a/src/guiConfirmMenu.h +++ b/src/convert_json.h @@ -17,40 +17,18 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef GUICONFIRMMENU_HEADER -#define GUICONFIRMMENU_HEADER - -#include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" -#include <string> - -struct ConfirmDest -{ - virtual void answer(bool answer) = 0; - virtual ~ConfirmDest() {}; -}; - -class GUIConfirmMenu : public GUIModalMenu -{ -public: - GUIConfirmMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - ConfirmDest *dest, - std::wstring message_text); - ~GUIConfirmMenu(); - - void removeChildren(); - // Remove and re-add (or reposition) stuff - void regenerateGui(v2u32 screensize); - void drawMenu(); - void acceptInput(bool answer); - bool OnEvent(const SEvent& event); - -private: - ConfirmDest *m_dest; - std::wstring m_message_text; -}; +#ifndef __CONVERT_JSON_H__ +#define __CONVERT_JSON_H__ -#endif +#include "json/json.h" + +struct ModStoreMod; +struct ModStoreModDetails; + +std::vector<ModStoreMod> readModStoreList(Json::Value& modlist); +ModStoreModDetails readModStoreModDetails(Json::Value& details); +Json::Value fetchJsonValue(const std::string url, + struct curl_slist *chunk); + +#endif diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 44f5d1e86..37be3b651 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -256,6 +256,11 @@ void set_default_settings(Settings *settings) // IPv6 settings->setDefault("enable_ipv6", "true"); settings->setDefault("ipv6_server", "false"); + + settings->setDefault("modstore_download_url", "http://forum.minetest.net/media/"); + settings->setDefault("modstore_listmods_url", "http://forum.minetest.net/mmdb/mods/"); + settings->setDefault("modstore_details_url", "http://forum.minetest.net/mmdb/mod/*/"); + } void override_default_settings(Settings *settings, Settings *from) diff --git a/src/filesys.cpp b/src/filesys.cpp index 256c8f16a..21ff199a8 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -20,7 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "strfnd.h" #include <iostream> +#include <stdio.h> #include <string.h> +#include <errno.h> #include "log.h" namespace fs @@ -30,11 +32,9 @@ namespace fs #define _WIN32_WINNT 0x0501 #include <windows.h> -#include <stdio.h> #include <malloc.h> #include <tchar.h> #include <wchar.h> -#include <stdio.h> #define BUFSIZE MAX_PATH @@ -145,6 +145,11 @@ bool IsDir(std::string path) (attr & FILE_ATTRIBUTE_DIRECTORY)); } +bool IsDirDelimiter(char c) +{ + return c == '/' || c == '\\'; +} + bool RecursiveDelete(std::string path) { infostream<<"Recursively deleting \""<<path<<"\""<<std::endl; @@ -207,11 +212,26 @@ bool DeleteSingleFileOrEmptyDirectory(std::string path) } } +std::string TempPath() +{ + DWORD bufsize = GetTempPath(0, ""); + if(bufsize == 0){ + errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; + return ""; + } + std::vector<char> buf(bufsize); + DWORD len = GetTempPath(bufsize, &buf[0]); + if(len == 0 || len > bufsize){ + errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; + return ""; + } + return std::string(buf.begin(), buf.begin() + len); +} + #else // POSIX #include <sys/types.h> #include <dirent.h> -#include <errno.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> @@ -301,6 +321,11 @@ bool IsDir(std::string path) return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } +bool IsDirDelimiter(char c) +{ + return c == '/'; +} + bool RecursiveDelete(std::string path) { /* @@ -364,6 +389,20 @@ bool DeleteSingleFileOrEmptyDirectory(std::string path) } } +std::string TempPath() +{ + /* + Should the environment variables TMPDIR, TMP and TEMP + and the macro P_tmpdir (if defined by stdio.h) be checked + before falling back on /tmp? + + Probably not, because this function is intended to be + compatible with lua's os.tmpname which under the default + configuration hardcodes mkstemp("/tmp/lua_XXXXXX"). + */ + return std::string(DIR_DELIM) + "tmp"; +} + #endif void GetRecursiveSubPaths(std::string path, std::vector<std::string> &dst) @@ -414,16 +453,14 @@ bool RecursiveDeleteContent(std::string path) bool CreateAllDirs(std::string path) { - size_t pos; std::vector<std::string> tocreate; std::string basepath = path; while(!PathExists(basepath)) { tocreate.push_back(basepath); - pos = basepath.rfind(DIR_DELIM_C); - if(pos == std::string::npos) + basepath = RemoveLastPathComponent(basepath); + if(basepath.empty()) break; - basepath = basepath.substr(0,pos); } for(int i=tocreate.size()-1;i>=0;i--) if(!CreateDir(tocreate[i])) @@ -431,5 +468,221 @@ bool CreateAllDirs(std::string path) return true; } +bool CopyFileContents(std::string source, std::string target) +{ + FILE *sourcefile = fopen(source.c_str(), "rb"); + if(sourcefile == NULL){ + errorstream<<source<<": can't open for reading: " + <<strerror(errno)<<std::endl; + return false; + } + + FILE *targetfile = fopen(target.c_str(), "wb"); + if(targetfile == NULL){ + errorstream<<target<<": can't open for writing: " + <<strerror(errno)<<std::endl; + fclose(sourcefile); + return false; + } + + size_t total = 0; + bool retval = true; + bool done = false; + char readbuffer[BUFSIZ]; + while(!done){ + size_t readbytes = fread(readbuffer, 1, + sizeof(readbuffer), sourcefile); + total += readbytes; + if(ferror(sourcefile)){ + errorstream<<source<<": IO error: " + <<strerror(errno)<<std::endl; + retval = false; + done = true; + } + if(readbytes > 0){ + fwrite(readbuffer, 1, readbytes, targetfile); + } + if(feof(sourcefile) || ferror(sourcefile)){ + // flush destination file to catch write errors + // (e.g. disk full) + fflush(targetfile); + done = true; + } + if(ferror(targetfile)){ + errorstream<<target<<": IO error: " + <<strerror(errno)<<std::endl; + retval = false; + done = true; + } + } + infostream<<"copied "<<total<<" bytes from " + <<source<<" to "<<target<<std::endl; + fclose(sourcefile); + fclose(targetfile); + return retval; +} + +bool CopyDir(std::string source, std::string target) +{ + if(PathExists(source)){ + if(!PathExists(target)){ + fs::CreateAllDirs(target); + } + bool retval = true; + std::vector<DirListNode> content = fs::GetDirListing(source); + + for(unsigned int i=0; i < content.size(); i++){ + std::string sourcechild = source + DIR_DELIM + content[i].name; + std::string targetchild = target + DIR_DELIM + content[i].name; + if(content[i].dir){ + if(!fs::CopyDir(sourcechild, targetchild)){ + retval = false; + } + } + else { + if(!fs::CopyFileContents(sourcechild, targetchild)){ + retval = false; + } + } + } + return retval; + } + else { + return false; + } +} + +bool PathStartsWith(std::string path, std::string prefix) +{ + size_t pathsize = path.size(); + size_t pathpos = 0; + size_t prefixsize = prefix.size(); + size_t prefixpos = 0; + for(;;){ + bool delim1 = pathpos == pathsize + || IsDirDelimiter(path[pathpos]); + bool delim2 = prefixpos == prefixsize + || IsDirDelimiter(prefix[prefixpos]); + + if(delim1 != delim2) + return false; + + if(delim1){ + while(pathpos < pathsize && + IsDirDelimiter(path[pathpos])) + ++pathpos; + while(prefixpos < prefixsize && + IsDirDelimiter(prefix[prefixpos])) + ++prefixpos; + if(prefixpos == prefixsize) + return true; + if(pathpos == pathsize) + return false; + } + else{ + size_t len = 0; + do{ + char pathchar = path[pathpos+len]; + char prefixchar = prefix[prefixpos+len]; + if(FILESYS_CASE_INSENSITIVE){ + pathchar = tolower(pathchar); + prefixchar = tolower(prefixchar); + } + if(pathchar != prefixchar) + return false; + ++len; + } while(pathpos+len < pathsize + && !IsDirDelimiter(path[pathpos+len]) + && prefixpos+len < prefixsize + && !IsDirDelimiter( + prefix[prefixsize+len])); + pathpos += len; + prefixpos += len; + } + } +} + +std::string RemoveLastPathComponent(std::string path, + std::string *removed, int count) +{ + if(removed) + *removed = ""; + + size_t remaining = path.size(); + + for(int i = 0; i < count; ++i){ + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + // strip a path component + size_t component_end = remaining; + while(remaining != 0 && !IsDirDelimiter(path[remaining-1])) + remaining--; + size_t component_start = remaining; + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + if(removed){ + std::string component = path.substr(component_start, + component_end - component_start); + if(i) + *removed = component + DIR_DELIM + *removed; + else + *removed = component; + } + } + return path.substr(0, remaining); +} + +std::string RemoveRelativePathComponents(std::string path) +{ + size_t pos = path.size(); + size_t dotdot_count = 0; + while(pos != 0){ + size_t component_with_delim_end = pos; + // skip a dir delimiter + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + // strip a path component + size_t component_end = pos; + while(pos != 0 && !IsDirDelimiter(path[pos-1])) + pos--; + size_t component_start = pos; + + std::string component = path.substr(component_start, + component_end - component_start); + bool remove_this_component = false; + if(component == "."){ + remove_this_component = true; + } + else if(component == ".."){ + remove_this_component = true; + dotdot_count += 1; + } + else if(dotdot_count != 0){ + remove_this_component = true; + dotdot_count -= 1; + } + + if(remove_this_component){ + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + path = path.substr(0, pos) + DIR_DELIM + + path.substr(component_with_delim_end, + std::string::npos); + pos++; + } + } + + if(dotdot_count > 0) + return ""; + + // remove trailing dir delimiters + pos = path.size(); + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + return path.substr(0, pos); +} + } // namespace fs diff --git a/src/filesys.h b/src/filesys.h index 263eb796f..d0bf400c7 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -26,10 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef _WIN32 // WINDOWS #define DIR_DELIM "\\" -#define DIR_DELIM_C '\\' +#define FILESYS_CASE_INSENSITIVE 1 #else // POSIX #define DIR_DELIM "/" -#define DIR_DELIM_C '/' +#define FILESYS_CASE_INSENSITIVE 0 #endif namespace fs @@ -49,12 +49,17 @@ bool PathExists(std::string path); bool IsDir(std::string path); +bool IsDirDelimiter(char c); + // Only pass full paths to this one. True on success. // NOTE: The WIN32 version returns always true. bool RecursiveDelete(std::string path); bool DeleteSingleFileOrEmptyDirectory(std::string path); +// Returns path to temp directory, can return "" on error +std::string TempPath(); + /* Multiplatform */ // The path itself not included @@ -69,6 +74,30 @@ bool RecursiveDeleteContent(std::string path); // Create all directories on the given path that don't already exist. bool CreateAllDirs(std::string path); +// Copy a regular file +bool CopyFileContents(std::string source, std::string target); + +// Copy directory and all subdirectories +// Omits files and subdirectories that start with a period +bool CopyDir(std::string source, std::string target); + +// Check if one path is prefix of another +// For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2" +// Ignores case differences and '/' vs. '\\' on Windows +bool PathStartsWith(std::string path, std::string prefix); + +// Remove last path component and the dir delimiter before and/or after it, +// returns "" if there is only one path component. +// removed: If non-NULL, receives the removed component(s). +// count: Number of components to remove +std::string RemoveLastPathComponent(std::string path, + std::string *removed = NULL, int count = 1); + +// Remove "." and ".." path components and for every ".." removed, remove +// the last normal path component before it. Unlike AbsolutePath, +// this does not resolve symlinks and check for existence of directories. +std::string RemoveRelativePathComponents(std::string path); + }//fs #endif diff --git a/src/game.cpp b/src/game.cpp index bcd155a79..44ec9ee37 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -208,33 +208,6 @@ public: Client *m_client; }; -class FormspecFormSource: public IFormSource -{ -public: - FormspecFormSource(std::string formspec,FormspecFormSource** game_formspec) - { - m_formspec = formspec; - m_game_formspec = game_formspec; - } - - ~FormspecFormSource() - { - *m_game_formspec = 0; - } - - void setForm(std::string formspec) { - m_formspec = formspec; - } - - std::string getForm() - { - return m_formspec; - } - - std::string m_formspec; - FormspecFormSource** m_game_formspec; -}; - /* Check if a node is pointable */ diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp deleted file mode 100644 index 8f5ef937d..000000000 --- a/src/guiConfigureWorld.cpp +++ /dev/null @@ -1,693 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - - -#include <iostream> -#include <string> -#include <map> - -#include "guiConfigureWorld.h" -#include "guiMessageMenu.h" -#include <IGUIButton.h> -#include <IGUICheckBox.h> -#include <IGUIListBox.h> -#include <IGUIStaticText.h> -#include <IGUITreeView.h> -#include "gettext.h" -#include "util/string.h" -#include "settings.h" -#include "filesys.h" - -enum -{ - GUI_ID_MOD_TREEVIEW = 101, - GUI_ID_ENABLED_CHECKBOX, - GUI_ID_ENABLEALL, - GUI_ID_DISABLEALL, - GUI_ID_DEPENDS_LISTBOX, - GUI_ID_RDEPENDS_LISTBOX, - GUI_ID_CANCEL, - GUI_ID_SAVE -}; - -#define QUESTIONMARK_STR L"?" -#define CHECKMARK_STR L"\411" -#define CROSS_STR L"\403" - -GUIConfigureWorld::GUIConfigureWorld(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, WorldSpec wspec): - GUIModalMenu(env, parent, id, menumgr), - m_wspec(wspec), - m_gspec(findWorldSubgame(m_wspec.path)), - m_menumgr(menumgr) -{ - //will be initialized in regenerateGUI() - m_treeview=NULL; - - // game mods - m_gamemods = flattenModTree(getModsInPath(m_gspec.gamemods_path)); - - // world mods - std::string worldmods_path = wspec.path + DIR_DELIM + "worldmods"; - m_worldmods = flattenModTree(getModsInPath(worldmods_path)); - - // fill m_addontree with add-on mods - std::set<std::string> paths = m_gspec.addon_mods_paths; - for(std::set<std::string>::iterator it=paths.begin(); - it != paths.end(); ++it) - { - std::map<std::string,ModSpec> mods = getModsInPath(*it); - m_addontree.insert(mods.begin(), mods.end()); - } - - // expand modpacks - m_addonmods = flattenModTree(m_addontree); - - // collect reverse dependencies - for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin(); - it != m_addonmods.end(); ++it) - { - std::string modname = (*it).first; - ModSpec mod = (*it).second; - for(std::set<std::string>::iterator dep_it = mod.depends.begin(); - dep_it != mod.depends.end(); ++dep_it) - { - m_reverse_depends.insert(std::make_pair((*dep_it),modname)); - } - } - - m_settings.readConfigFile((m_wspec.path + DIR_DELIM + "world.mt").c_str()); - std::vector<std::string> names = m_settings.getNames(); - - // mod_names contains the names of mods mentioned in the world.mt file - std::set<std::string> mod_names; - for(std::vector<std::string>::iterator it = names.begin(); - it != names.end(); ++it) - { - std::string name = *it; - if (name.compare(0,9,"load_mod_")==0) - mod_names.insert(name.substr(9)); - } - - // find new mods (installed but not mentioned in world.mt) - for(std::map<std::string, ModSpec>::iterator it = m_addonmods.begin(); - it != m_addonmods.end(); ++it) - { - std::string modname = (*it).first; - ModSpec mod = (*it).second; - // a mod is new if it is not a modpack, and does not occur in - // mod_names - if(!mod.is_modpack && - mod_names.count(modname) == 0) - m_settings.setBool("load_mod_"+modname, false); - } - // find missing mods (mentioned in world.mt, but not installed) - for(std::set<std::string>::iterator it = mod_names.begin(); - it != mod_names.end(); ++it) - { - std::string modname = *it; - if(m_addonmods.count(modname) == 0) - m_settings.remove("load_mod_"+modname); - } - std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt"; - m_settings.updateConfigFile(worldmtfile.c_str()); -} - -void GUIConfigureWorld::drawMenu() -{ - gui::IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140,0,0,0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); - - gui::IGUIElement::draw(); -} - - -void GUIConfigureWorld::regenerateGui(v2u32 screensize) -{ - - /* - Remove stuff - */ - removeChildren(); - - /* - Calculate new sizes and positions - */ - core::rect<s32> rect( - screensize.X/2 - 580/2, - screensize.Y/2 - 300/2, - screensize.X/2 + 580/2, - screensize.Y/2 + 300/2 - ); - - DesiredRect = rect; - recalculateAbsolutePosition(false); - - v2s32 topleft = v2s32(10, 10); - - /* - Add stuff - */ - changeCtype(""); - { - core::rect<s32> rect(0, 0, 200, 20); - rect += topleft; - //proper text is set below, when a mod is selected - m_modname_text = Environment->addStaticText(L"Mod: N/A", rect, false, - false, this, -1); - } - { - core::rect<s32> rect(0, 0, 200, 20); - rect += v2s32(0, 25) + topleft; - wchar_t* text = wgettext("enabled"); - m_enabled_checkbox = - Environment->addCheckBox(false, rect, this, GUI_ID_ENABLED_CHECKBOX, - text); - delete[] text; - m_enabled_checkbox->setVisible(false); - } - { - core::rect<s32> rect(0, 0, 85, 30); - rect = rect + v2s32(0, 25) + topleft; - wchar_t* text = wgettext("Enable All"); - m_enableall = Environment->addButton(rect, this, GUI_ID_ENABLEALL, - text); - delete[] text; - m_enableall->setVisible(false); - } - { - core::rect<s32> rect(0, 0, 85, 30); - rect = rect + v2s32(115, 25) + topleft; - wchar_t* text = wgettext("Disable All"); - m_disableall = Environment->addButton(rect, this, GUI_ID_DISABLEALL, text ); - delete[] text; - m_disableall->setVisible(false); - } - { - core::rect<s32> rect(0, 0, 200, 20); - rect += v2s32(0, 60) + topleft; - wchar_t* text = wgettext("depends on:"); - Environment->addStaticText(text, rect, false, false, this, -1); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 200, 85); - rect += v2s32(0, 80) + topleft; - m_dependencies_listbox = - Environment->addListBox(rect, this, GUI_ID_DEPENDS_LISTBOX, true); - } - { - core::rect<s32> rect(0, 0, 200, 20); - rect += v2s32(0, 175) + topleft; - wchar_t* text = wgettext("is required by:"); - Environment->addStaticText( text, rect, false, false, this, -1); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 200, 85); - rect += v2s32(0, 195) + topleft; - m_rdependencies_listbox = - Environment->addListBox(rect,this, GUI_ID_RDEPENDS_LISTBOX,true); - } - { - core::rect<s32> rect(0, 0, 340, 250); - rect += v2s32(220, 0) + topleft; - m_treeview = Environment->addTreeView(rect, this, - GUI_ID_MOD_TREEVIEW,true); - gui::IGUITreeViewNode* node - = m_treeview->getRoot()->addChildBack(L"Add-Ons"); - buildTreeView(m_addontree, node); - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect = rect + v2s32(330, 270) - topleft; - wchar_t* text = wgettext("Cancel"); - Environment->addButton(rect, this, GUI_ID_CANCEL, text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect = rect + v2s32(460, 270) - topleft; - wchar_t* text = wgettext("Save"); - Environment->addButton(rect, this, GUI_ID_SAVE, text); - delete[] text; - } - changeCtype("C"); - - // at start, none of the treeview nodes is selected, so we select - // the first element in the treeview of mods manually here. - if(m_treeview->getRoot()->hasChilds()) - { - m_treeview->getRoot()->getFirstChild()->setExpanded(true); - m_treeview->getRoot()->getFirstChild()->setSelected(true); - // Because a manual ->setSelected() doesn't cause an event, we - // have to do this here: - adjustSidebar(); - } -} - -bool GUIConfigureWorld::OnEvent(const SEvent& event) -{ - - gui::IGUITreeViewNode* selected_node = NULL; - if(m_treeview != NULL) - selected_node = m_treeview->getSelected(); - - if(event.EventType==EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) - { - switch (event.KeyInput.Key) { - case KEY_ESCAPE: { - quitMenu(); - return true; - } - // irrlicht's built-in TreeView gui has no keyboard control, - // so we do it here: up/down to select prev/next node, - // left/right to collapse/expand nodes, space to toggle - // enabled/disabled. - case KEY_DOWN: { - if(selected_node != NULL) - { - gui::IGUITreeViewNode* node = selected_node->getNextVisible(); - if(node != NULL) - { - node->setSelected(true); - adjustSidebar(); - } - } - return true; - } - case KEY_UP: { - if(selected_node != NULL) - { - gui::IGUITreeViewNode* node = selected_node->getPrevSibling(); - if(node!=NULL) - { - node->setSelected(true); - adjustSidebar(); - } - else - { - gui::IGUITreeViewNode* parent = selected_node->getParent(); - if(selected_node == parent->getFirstChild() && - parent != m_treeview->getRoot()) - { - parent->setSelected(true); - adjustSidebar(); - } - } - } - return true; - } - case KEY_RIGHT: { - if(selected_node != NULL && selected_node->hasChilds()) - selected_node->setExpanded(true); - return true; - } - case KEY_LEFT: { - if(selected_node != NULL && selected_node->hasChilds()) - selected_node->setExpanded(false); - return true; - } - case KEY_SPACE: { - if(selected_node != NULL && !selected_node->hasChilds() && - selected_node->getText() != NULL) - { - std::string modname = wide_to_narrow(selected_node->getText()); - bool checked = m_enabled_checkbox->isChecked(); - m_enabled_checkbox->setChecked(!checked); - setEnabled(modname,!checked); - } - return true; - } - default: {} - } - } - if(event.EventType==EET_GUI_EVENT) - { - if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) - { - if(!canTakeFocus(event.GUIEvent.Element)) - { - dstream<<"GUIConfigureWorld: Not allowing focus change." - <<std::endl; - // Returning true disables focus change - return true; - } - } - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED){ - switch(event.GUIEvent.Caller->getID()){ - case GUI_ID_CANCEL: { - quitMenu(); - return true; - } - case GUI_ID_SAVE: { - std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt"; - m_settings.updateConfigFile(worldmtfile.c_str()); - - // The trailing spaces are because there seems to be a - // bug in the text-size calculation. if the trailing - // spaces are removed from the message text, the - // message gets wrapped and parts of it are cut off: - wchar_t* text = wgettext("Configuration saved. "); - GUIMessageMenu *menu = - new GUIMessageMenu(Environment, Parent, -1, m_menumgr, - text ); - delete[] text; - menu->drop(); - - try - { - ModConfiguration modconf(m_wspec.path); - if(!modconf.isConsistent()) - { - wchar_t* text = wgettext("Warning: Configuration not consistent. "); - GUIMessageMenu *menu = - new GUIMessageMenu(Environment, Parent, -1, m_menumgr, - text ); - delete[] text; - menu->drop(); - } - } - catch(ModError &err) - { - errorstream<<err.what()<<std::endl; - std::wstring text = narrow_to_wide(err.what()) + wgettext("\nCheck debug.txt for details."); - GUIMessageMenu *menu = - new GUIMessageMenu(Environment, Parent, -1, m_menumgr, - text ); - menu->drop(); - } - - quitMenu(); - return true; - } - case GUI_ID_ENABLEALL: { - if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot()) - { - enableAllMods(m_addonmods,true); - } - else if(selected_node != NULL && selected_node->getText() != NULL) - { - std::string modname = wide_to_narrow(selected_node->getText()); - std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname); - if(mod_it != m_addonmods.end()) - enableAllMods(mod_it->second.modpack_content,true); - } - return true; - } - case GUI_ID_DISABLEALL: { - if(selected_node != NULL && selected_node->getParent() == m_treeview->getRoot()) - { - enableAllMods(m_addonmods,false); - } - if(selected_node != NULL && selected_node->getText() != NULL) - { - std::string modname = wide_to_narrow(selected_node->getText()); - std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname); - if(mod_it != m_addonmods.end()) - enableAllMods(mod_it->second.modpack_content,false); - } - return true; - } - } - } - if(event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED && - event.GUIEvent.Caller->getID() == GUI_ID_ENABLED_CHECKBOX) - { - if(selected_node != NULL && !selected_node->hasChilds() && - selected_node->getText() != NULL) - { - std::string modname = wide_to_narrow(selected_node->getText()); - setEnabled(modname, m_enabled_checkbox->isChecked()); - } - return true; - } - if(event.GUIEvent.EventType==gui::EGET_TREEVIEW_NODE_SELECT && - event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW) - { - selecting_dep = -1; - selecting_rdep = -1; - adjustSidebar(); - return true; - } - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && - event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX) - { - selecting_dep = m_dependencies_listbox->getSelected(); - selecting_rdep = -1; - return true; - } - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && - event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX) - { - selecting_dep = -1; - selecting_rdep = m_rdependencies_listbox->getSelected(); - return true; - } - - //double click in a dependency listbox: find corresponding - //treeviewnode and select it: - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) - { - gui::IGUIListBox* box = NULL; - if(event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX) - { - box = m_dependencies_listbox; - if(box->getSelected() != selecting_dep) - return true; - } - if(event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX) - { - box = m_rdependencies_listbox; - if(box->getSelected() != selecting_rdep) - return true; - } - if(box != NULL && box->getSelected() != -1 && - box->getListItem(box->getSelected()) != NULL) - { - std::string modname = - wide_to_narrow(box->getListItem(box->getSelected())); - std::map<std::string, gui::IGUITreeViewNode*>::iterator it = - m_nodes.find(modname); - if(it != m_nodes.end()) - { - // select node and make sure node is visible by - // expanding all parents - gui::IGUITreeViewNode* node = (*it).second; - node->setSelected(true); - while(!node->isVisible() && - node->getParent() != m_treeview->getRoot()) - { - node = node->getParent(); - node->setExpanded(true); - } - adjustSidebar(); - } - } - return true; - } - } - - return Parent ? Parent->OnEvent(event) : false; -} - -void GUIConfigureWorld::buildTreeView(std::map<std::string, ModSpec> mods, - gui::IGUITreeViewNode* node) -{ - for(std::map<std::string,ModSpec>::iterator it = mods.begin(); - it != mods.end(); ++it) - { - std::string modname = (*it).first; - ModSpec mod = (*it).second; - gui::IGUITreeViewNode* new_node = - node->addChildBack(narrow_to_wide(modname).c_str()); - m_nodes.insert(std::make_pair(modname, new_node)); - if(mod.is_modpack) - buildTreeView(mod.modpack_content, new_node); - else - { - // set icon for node: x for disabled mods, checkmark for enabled mods - bool mod_enabled = false; - if(m_settings.exists("load_mod_"+modname)) - mod_enabled = m_settings.getBool("load_mod_"+modname); - if(mod_enabled) - new_node->setIcon(CHECKMARK_STR); - else - new_node->setIcon(CROSS_STR); - } - } -} - - -void GUIConfigureWorld::adjustSidebar() -{ - gui::IGUITreeViewNode* node = m_treeview->getSelected(); - std::wstring modname_w; - if(node->getText() != NULL) - modname_w = node->getText(); - else - modname_w = L"N/A"; - std::string modname = wide_to_narrow(modname_w); - - ModSpec mspec; - std::map<std::string, ModSpec>::iterator it = m_addonmods.find(modname); - if(it != m_addonmods.end()) - mspec = it->second; - - m_dependencies_listbox->clear(); - m_rdependencies_listbox->clear(); - - // if no mods installed, there is nothing to enable/disable, so we - // don't show buttons or checkbox on the sidebar - if(node->getParent() == m_treeview->getRoot() && !node->hasChilds()) - { - m_disableall->setVisible(false); - m_enableall->setVisible(false); - m_enabled_checkbox->setVisible(false); - return; - } - - // a modpack is not enabled/disabled by itself, only its cotnents - // are. so we show show enable/disable all buttons, but hide the - // checkbox - if(node->getParent() == m_treeview->getRoot() || - mspec.is_modpack) - { - m_enabled_checkbox->setVisible(false); - m_disableall->setVisible(true); - m_enableall->setVisible(true); - m_modname_text->setText((L"Modpack: "+modname_w).c_str()); - return; - } - - // for a normal mod, we hide the enable/disable all buttons, but show the checkbox. - m_disableall->setVisible(false); - m_enableall->setVisible(false); - m_enabled_checkbox->setVisible(true); - m_modname_text->setText((L"Mod: "+modname_w).c_str()); - - // the mod is enabled unless it is disabled in the world.mt settings. - bool mod_enabled = true; - if(m_settings.exists("load_mod_"+modname)) - mod_enabled = m_settings.getBool("load_mod_"+modname); - m_enabled_checkbox->setChecked(mod_enabled); - - for(std::set<std::string>::iterator it=mspec.depends.begin(); - it != mspec.depends.end(); ++it) - { - // check if it is an add-on mod or a game/world mod. We only - // want to show add-ons - std::string dependency = (*it); - if(m_gamemods.count(dependency) > 0) - dependency += " (" + m_gspec.id + ")"; - else if(m_worldmods.count(dependency) > 0) - dependency += " (" + m_wspec.name + ")"; - else if(m_addonmods.count(dependency) == 0) - dependency += " (missing)"; - m_dependencies_listbox->addItem(narrow_to_wide(dependency).c_str()); - } - - // reverse dependencies of this mod: - std::pair< std::multimap<std::string, std::string>::iterator, - std::multimap<std::string, std::string>::iterator > rdep = - m_reverse_depends.equal_range(modname); - for(std::multimap<std::string,std::string>::iterator it = rdep.first; - it != rdep.second; ++it) - { - // check if it is an add-on mod or a game/world mod. We only - // want to show add-ons - std::string rdependency = (*it).second; - if(m_addonmods.count(rdependency) > 0) - m_rdependencies_listbox->addItem(narrow_to_wide(rdependency).c_str()); - } -} - -void GUIConfigureWorld::enableAllMods(std::map<std::string, ModSpec> mods,bool enable) -{ - for(std::map<std::string, ModSpec>::iterator it = mods.begin(); - it != mods.end(); ++it) - { - ModSpec mod = (*it).second; - if(mod.is_modpack) - // a modpack, recursively enable all mods in it - enableAllMods(mod.modpack_content,enable); - else // not a modpack - setEnabled(mod.name, enable); - - } -} - -void GUIConfigureWorld::enableMod(std::string modname) -{ - std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname); - if(mod_it == m_addonmods.end()){ - errorstream << "enableMod() called with invalid mod name \"" << modname << "\"" << std::endl; - return; - } - ModSpec mspec = mod_it->second; - m_settings.setBool("load_mod_"+modname,true); - std::map<std::string,gui::IGUITreeViewNode*>::iterator it = - m_nodes.find(modname); - if(it != m_nodes.end()) - (*it).second->setIcon(CHECKMARK_STR); - //also enable all dependencies - for(std::set<std::string>::iterator it=mspec.depends.begin(); - it != mspec.depends.end(); ++it) - { - std::string dependency = *it; - // only enable it if it is an add-on mod - if(m_addonmods.count(dependency) > 0) - enableMod(dependency); - } -} - -void GUIConfigureWorld::disableMod(std::string modname) -{ - std::map<std::string, ModSpec>::iterator mod_it = m_addonmods.find(modname); - if(mod_it == m_addonmods.end()){ - errorstream << "disableMod() called with invalid mod name \"" << modname << "\"" << std::endl; - return; - } - - m_settings.setBool("load_mod_"+modname,false); - std::map<std::string,gui::IGUITreeViewNode*>::iterator it = - m_nodes.find(modname); - if(it != m_nodes.end()) - (*it).second->setIcon(CROSS_STR); - //also disable all mods that depend on this one - std::pair<std::multimap<std::string, std::string>::iterator, - std::multimap<std::string, std::string>::iterator > rdep = - m_reverse_depends.equal_range(modname); - for(std::multimap<std::string,std::string>::iterator it = rdep.first; - it != rdep.second; ++it) - { - std::string rdependency = (*it).second; - // only disable it if it is an add-on mod - if(m_addonmods.count(rdependency) > 0) - disableMod(rdependency); - } -} - diff --git a/src/guiConfigureWorld.h b/src/guiConfigureWorld.h deleted file mode 100644 index 23ebac66d..000000000 --- a/src/guiConfigureWorld.h +++ /dev/null @@ -1,107 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef GUICONFIGUREWORLD_HEADER -#define GUICONFIGUREWORLD_HEADER - -#include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" -#include "mods.h" -#include "subgame.h" -#include "settings.h" - - -namespace irr{ - namespace gui{ - class IGUITreeViewNode; - } -} - -class GUIConfigureWorld : public GUIModalMenu -{ -public: - GUIConfigureWorld(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, WorldSpec wspec); - - void regenerateGui(v2u32 screensize); - - void drawMenu(); - - bool OnEvent(const SEvent& event); - -private: - WorldSpec m_wspec; - SubgameSpec m_gspec; - - // tree of installed add-on mods. key is the mod name, modpacks - // are not expanded. - std::map<std::string, ModSpec> m_addontree; - - // like m_addontree, but modpacks are expanded. - std::map<std::string, ModSpec> m_addonmods; - - // list of game mods (flattened) - std::map<std::string, ModSpec> m_gamemods; - - // list of world mods (flattened) - std::map<std::string, ModSpec> m_worldmods; - - // for each mod, the set of mods depending on it - std::multimap<std::string, std::string> m_reverse_depends; - - // the settings in the world.mt file - Settings m_settings; - - // maps modnames to nodes in m_treeview - std::map<std::string,gui::IGUITreeViewNode*> m_nodes; - - gui::IGUIStaticText* m_modname_text; - gui::IGUITreeView* m_treeview; - gui::IGUIButton* m_enableall; - gui::IGUIButton* m_disableall; - gui::IGUICheckBox* m_enabled_checkbox; - gui::IGUIListBox* m_dependencies_listbox; - gui::IGUIListBox* m_rdependencies_listbox; - void buildTreeView(std::map<std::string,ModSpec> mods, - gui::IGUITreeViewNode* node); - void adjustSidebar(); - void enableAllMods(std::map<std::string,ModSpec> mods, bool enable); - void setEnabled(std::string modname, bool enable) - { - if(enable) - enableMod(modname); - else - disableMod(modname); - }; - - void enableMod(std::string modname); - void disableMod(std::string modname); - - // hack to work around wonky handling of double-click in - // irrlicht. store selected index of listbox items here so event - // handling can check whether it was a real double click on the - // same item. (irrlicht also reports a double click if you rapidly - // select two different items.) - int selecting_dep; - int selecting_rdep; - - IMenuManager* m_menumgr; -}; -#endif diff --git a/src/guiConfirmMenu.cpp b/src/guiConfirmMenu.cpp deleted file mode 100644 index 86b230506..000000000 --- a/src/guiConfirmMenu.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiConfirmMenu.h" -#include "debug.h" -#include "serialization.h" -#include <string> -#include <IGUICheckBox.h> -#include <IGUIEditBox.h> -#include <IGUIButton.h> -#include <IGUIStaticText.h> -#include <IGUIFont.h> - -#include "gettext.h" - -enum -{ - GUI_ID_YES = 101, - GUI_ID_NO, -}; - -GUIConfirmMenu::GUIConfirmMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - ConfirmDest *dest, - std::wstring message_text -): - GUIModalMenu(env, parent, id, menumgr), - m_dest(dest), - m_message_text(message_text) -{ -} - -GUIConfirmMenu::~GUIConfirmMenu() -{ - removeChildren(); - if(m_dest) - delete m_dest; -} - -void GUIConfirmMenu::removeChildren() -{ - const core::list<gui::IGUIElement*> &children = getChildren(); - core::list<gui::IGUIElement*> children_copy; - for(core::list<gui::IGUIElement*>::ConstIterator - i = children.begin(); i != children.end(); i++) - { - children_copy.push_back(*i); - } - for(core::list<gui::IGUIElement*>::Iterator - i = children_copy.begin(); - i != children_copy.end(); i++) - { - (*i)->remove(); - } -} - -void GUIConfirmMenu::regenerateGui(v2u32 screensize) -{ - /* - Remove stuff - */ - removeChildren(); - - /* - Calculate new sizes and positions - */ - core::rect<s32> rect( - screensize.X/2 - 580/2, - screensize.Y/2 - 300/2, - screensize.X/2 + 580/2, - screensize.Y/2 + 300/2 - ); - - DesiredRect = rect; - recalculateAbsolutePosition(false); - - v2s32 size = rect.getSize(); - - gui::IGUISkin *skin = Environment->getSkin(); - gui::IGUIFont *font = skin->getFont(); - s32 msg_h = font->getDimension(m_message_text.c_str()).Height; - s32 msg_w = font->getDimension(m_message_text.c_str()).Width; - if(msg_h > 200) - msg_h = 200; - if(msg_w > 540) - msg_w = 540; - - /* - Add stuff - */ - { - core::rect<s32> rect(0, 0, msg_w, msg_h); - rect += v2s32(size.X/2-msg_w/2, size.Y/2-30/2 - msg_h/2); - Environment->addStaticText(m_message_text.c_str(), - rect, false, true, this, -1); - } - changeCtype(""); - int bw = 100; - { - core::rect<s32> rect(0, 0, bw, 30); - rect = rect + v2s32(size.X/2-bw/2-(bw/2+5), size.Y/2-30/2+5 + msg_h/2); - wchar_t* text = wgettext("Yes"); - Environment->addButton(rect, this, GUI_ID_YES, - text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, bw, 30); - rect = rect + v2s32(size.X/2-bw/2+(bw/2+5), size.Y/2-30/2+5 + msg_h/2); - wchar_t* text = wgettext("No"); - Environment->addButton(rect, this, GUI_ID_NO, - text); - delete[] text; - } - changeCtype("C"); -} - -void GUIConfirmMenu::drawMenu() -{ - gui::IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140,0,0,0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); - - gui::IGUIElement::draw(); -} - -void GUIConfirmMenu::acceptInput(bool answer) -{ - if(m_dest) - m_dest->answer(answer); -} - -bool GUIConfirmMenu::OnEvent(const SEvent& event) -{ - if(event.EventType==EET_KEY_INPUT_EVENT) - { - if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) - { - acceptInput(false); - quitMenu(); - return true; - } - if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) - { - acceptInput(true); - quitMenu(); - return true; - } - } - if(event.EventType==EET_GUI_EVENT) - { - if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) - { - if(!canTakeFocus(event.GUIEvent.Element)) - { - dstream<<"GUIConfirmMenu: Not allowing focus change." - <<std::endl; - // Returning true disables focus change - return true; - } - } - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) - { - switch(event.GUIEvent.Caller->getID()) - { - case GUI_ID_YES: - acceptInput(true); - quitMenu(); - // quitMenu deallocates menu - return true; - case GUI_ID_NO: - acceptInput(false); - quitMenu(); - // quitMenu deallocates menu - return true; - } - } - } - - return Parent ? Parent->OnEvent(event) : false; -} - diff --git a/src/guiCreateWorld.cpp b/src/guiCreateWorld.cpp deleted file mode 100644 index f9afe9592..000000000 --- a/src/guiCreateWorld.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiCreateWorld.h" -#include "debug.h" -#include "serialization.h" -#include <string> -#include <IGUICheckBox.h> -#include <IGUIEditBox.h> -#include <IGUIButton.h> -#include <IGUIStaticText.h> -#include <IGUIFont.h> -#include <IGUIListBox.h> -#include "gettext.h" -#include "util/string.h" - -enum -{ - GUI_ID_NAME_INPUT = 101, - GUI_ID_GAME_LISTBOX, - GUI_ID_CREATE, - GUI_ID_CANCEL -}; - -GUICreateWorld::GUICreateWorld(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - CreateWorldDest *dest, - const std::vector<SubgameSpec> &games, - const std::string &initial_game -): - GUIModalMenu(env, parent, id, menumgr), - m_dest(dest), - m_games(games), - m_initial_game_i(0) -{ - assert(games.size() > 0); - - for(size_t i=0; i<games.size(); i++){ - if(games[i].id == initial_game){ - m_initial_game_i = i; - break; - } - } -} - -GUICreateWorld::~GUICreateWorld() -{ - removeChildren(); - if(m_dest) - delete m_dest; -} - -void GUICreateWorld::removeChildren() -{ - const core::list<gui::IGUIElement*> &children = getChildren(); - core::list<gui::IGUIElement*> children_copy; - for(core::list<gui::IGUIElement*>::ConstIterator - i = children.begin(); i != children.end(); i++) - { - children_copy.push_back(*i); - } - for(core::list<gui::IGUIElement*>::Iterator - i = children_copy.begin(); - i != children_copy.end(); i++) - { - (*i)->remove(); - } -} - -void GUICreateWorld::regenerateGui(v2u32 screensize) -{ - std::wstring name = L""; - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_NAME_INPUT); - if(e != NULL) - name = e->getText(); - } - - /* - Remove stuff - */ - removeChildren(); - - /* - Calculate new sizes and positions - */ - core::rect<s32> rect( - screensize.X/2 - 580/2, - screensize.Y/2 - 300/2, - screensize.X/2 + 580/2, - screensize.Y/2 + 300/2 - ); - - DesiredRect = rect; - recalculateAbsolutePosition(false); - - v2s32 topleft = v2s32(10+80, 10+70); - - /* - Add stuff - */ - { - core::rect<s32> rect(0, 0, 100, 20); - rect += v2s32(0, 5) + topleft; - wchar_t* text = wgettext("World name"); - Environment->addStaticText(text, rect, false, true, this, -1); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 300, 30); - rect = rect + v2s32(100, 0) + topleft; - gui::IGUIElement *e = - Environment->addEditBox(name.c_str(), rect, true, this, GUI_ID_NAME_INPUT); - Environment->setFocus(e); - - irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.PressedDown = true; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = 0; - evt.KeyInput.Shift = 0; - e->OnEvent(evt); - } - { - core::rect<s32> rect(0, 0, 100, 20); - rect += v2s32(0, 40+5) + topleft; - wchar_t* text = wgettext("Game"); - Environment->addStaticText(text, rect, false, true, this, -1); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 300, 80); - rect += v2s32(100, 40) + topleft; - gui::IGUIListBox *e = Environment->addListBox(rect, this, - GUI_ID_GAME_LISTBOX); - e->setDrawBackground(true); - for(u32 i=0; i<m_games.size(); i++){ - std::wostringstream os(std::ios::binary); - os<<narrow_to_wide(m_games[i].name).c_str(); - os<<L" ["; - os<<narrow_to_wide(m_games[i].id).c_str(); - os<<L"]"; - e->addItem(os.str().c_str()); - } - e->setSelected(m_initial_game_i); - } - changeCtype(""); - { - core::rect<s32> rect(0, 0, 120, 30); - rect = rect + v2s32(170, 140) + topleft; - wchar_t* text = wgettext("Create"); - Environment->addButton(rect, this, GUI_ID_CREATE, - text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect = rect + v2s32(300, 140) + topleft; - wchar_t* text = wgettext("Cancel"); - Environment->addButton(rect, this, GUI_ID_CANCEL, - text); - delete [] text; - } - changeCtype("C"); -} - -void GUICreateWorld::drawMenu() -{ - gui::IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140,0,0,0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); - - gui::IGUIElement::draw(); -} - -void GUICreateWorld::acceptInput() -{ - if(m_dest) - { - int selected = -1; - { - gui::IGUIElement *e = getElementFromId(GUI_ID_GAME_LISTBOX); - if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX) - selected = ((gui::IGUIListBox*)e)->getSelected(); - } - std::wstring name; - { - gui::IGUIElement *e = getElementFromId(GUI_ID_NAME_INPUT); - if(e != NULL) - name = e->getText(); - } - if(selected != -1 && name != L"") - m_dest->accepted(name, m_games[selected].id); - delete m_dest; - m_dest = NULL; - } -} - -bool GUICreateWorld::OnEvent(const SEvent& event) -{ - if(event.EventType==EET_KEY_INPUT_EVENT) - { - if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) - { - quitMenu(); - return true; - } - if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) - { - acceptInput(); - quitMenu(); - return true; - } - } - if(event.EventType==EET_GUI_EVENT) - { - if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) - { - if(!canTakeFocus(event.GUIEvent.Element)) - { - dstream<<"GUICreateWorld: Not allowing focus change." - <<std::endl; - // Returning true disables focus change - return true; - } - } - bool accept_input = false; - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED){ - switch(event.GUIEvent.Caller->getID()){ - case GUI_ID_CANCEL: - quitMenu(); - return true; - break; - case GUI_ID_CREATE: - accept_input = true; - break; - } - } - if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER){ - switch(event.GUIEvent.Caller->getID()){ - case GUI_ID_NAME_INPUT: - accept_input = true; - break; - } - } - if(accept_input){ - acceptInput(); - quitMenu(); - // quitMenu deallocates menu - return true; - } - } - - return Parent ? Parent->OnEvent(event) : false; -} - diff --git a/src/guiCreateWorld.h b/src/guiCreateWorld.h deleted file mode 100644 index 2765dc2bd..000000000 --- a/src/guiCreateWorld.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef GUICREATEWORLD_HEADER -#define GUICREATEWORLD_HEADER - -#include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" -#include <string> -#include "subgame.h" - -struct CreateWorldDest -{ - virtual void accepted(std::wstring name, std::string gameid) = 0; - virtual ~CreateWorldDest() {}; -}; - -class GUICreateWorld : public GUIModalMenu -{ -public: - GUICreateWorld(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - CreateWorldDest *dest, - const std::vector<SubgameSpec> &games, - const std::string &initial_game); - ~GUICreateWorld(); - - void removeChildren(); - /* - Remove and re-add (or reposition) stuff - */ - void regenerateGui(v2u32 screensize); - - void drawMenu(); - - void acceptInput(); - - bool OnEvent(const SEvent& event); - -private: - CreateWorldDest *m_dest; - std::vector<SubgameSpec> m_games; - int m_initial_game_i; -}; - -#endif - diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp new file mode 100644 index 000000000..f04f15820 --- /dev/null +++ b/src/guiEngine.cpp @@ -0,0 +1,570 @@ +/* +Minetest +Copyright (C) 2013 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern "C" { +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +} + +#include "irrlicht.h" + +#include "porting.h" +#include "filesys.h" +#include "main.h" +#include "settings.h" +#include "guiMainMenu.h" + +#include "guiEngine.h" + +#if USE_CURL +#include <curl/curl.h> +#endif + +/******************************************************************************/ +int menuscript_ErrorHandler(lua_State *L) { + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); + lua_pushinteger(L, 2); + lua_call(L, 2, 1); + return 1; +} + +/******************************************************************************/ +TextDestGuiEngine::TextDestGuiEngine(GUIEngine* engine) +{ + m_engine = engine; +} + +/******************************************************************************/ +void TextDestGuiEngine::gotText(std::map<std::string, std::string> fields) +{ + m_engine->handleButtons(fields); +} + +/******************************************************************************/ +void TextDestGuiEngine::gotText(std::wstring text) +{ + m_engine->handleEvent(wide_to_narrow(text)); +} + +/******************************************************************************/ +GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, + IMenuManager *menumgr, + scene::ISceneManager* smgr, + MainMenuData* data) : + m_device(dev), + m_parent(parent), + m_menumanager(menumgr), + m_smgr(smgr), + m_data(data), + m_formspecgui(0), + m_buttonhandler(0), + m_menu(0), + m_startgame(false), + m_engineluastack(0), + m_luaerrorhandler(-1), + m_scriptdir(""), + m_irr_toplefttext(0), + m_clouds_enabled(true), + m_cloud() +{ + //initialize texture pointers + for (unsigned int i = 0; i < TEX_LAYER_MAX; i++) { + m_textures[i] = 0; + } + // is deleted by guiformspec! + m_buttonhandler = new TextDestGuiEngine(this); + + //create luastack + m_engineluastack = luaL_newstate(); + + //load basic lua modules + luaL_openlibs(m_engineluastack); + + //init + guiLuaApi::initialize(m_engineluastack,this); + + //push errorstring + if (m_data->errormessage != "") + { + lua_getglobal(m_engineluastack, "gamedata"); + int gamedata_idx = lua_gettop(m_engineluastack); + lua_pushstring(m_engineluastack, "errormessage"); + lua_pushstring(m_engineluastack,m_data->errormessage.c_str()); + lua_settable(m_engineluastack, gamedata_idx); + m_data->errormessage = ""; + } + + //create topleft header + core::rect<s32> rect(0, 0, 500, 40); + rect += v2s32(4, 0); + std::string t = "Minetest " VERSION_STRING; + + m_irr_toplefttext = + m_device->getGUIEnvironment()->addStaticText(narrow_to_wide(t).c_str(), + rect,false,true,0,-1); + + //create formspecsource + m_formspecgui = new FormspecFormSource("",&m_formspecgui); + + /* Create menu */ + m_menu = + new GUIFormSpecMenu( m_device, + m_parent, + -1, + m_menumanager, + 0 /* &client */, + 0 /* gamedef */); + + m_menu->allowClose(false); + m_menu->lockSize(true,v2u32(800,600)); + m_menu->setFormSource(m_formspecgui); + m_menu->setTextDest(m_buttonhandler); + m_menu->useGettext(true); + + std::string builtin_helpers + = porting::path_share + DIR_DELIM + "builtin" + + DIR_DELIM + "mainmenu_helper.lua"; + + if (!runScript(builtin_helpers)) { + errorstream + << "GUIEngine::GUIEngine unable to load builtin helper script" + << std::endl; + return; + } + + std::string menuscript = ""; + if (g_settings->exists("main_menu_script")) + menuscript = g_settings->get("main_menu_script"); + std::string builtin_menuscript = + porting::path_share + DIR_DELIM + "builtin" + + DIR_DELIM + "mainmenu.lua"; + + lua_pushcfunction(m_engineluastack, menuscript_ErrorHandler); + m_luaerrorhandler = lua_gettop(m_engineluastack); + + m_scriptdir = menuscript.substr(0,menuscript.find_last_of(DIR_DELIM)-1); + if((menuscript == "") || (!runScript(menuscript))) { + infostream + << "GUIEngine::GUIEngine execution of custom menu failed!" + << std::endl + << "\tfalling back to builtin menu" + << std::endl; + m_scriptdir = fs::RemoveRelativePathComponents(porting::path_share + DIR_DELIM + "builtin"+ DIR_DELIM); + if(!runScript(builtin_menuscript)) { + errorstream + << "GUIEngine::GUIEngine unable to load builtin menu" + << std::endl; + return; + } + } + + run(); + + m_menumanager->deletingMenu(m_menu); + m_menu->drop(); + m_menu = 0; +} + +/******************************************************************************/ +bool GUIEngine::runScript(std::string script) { + + int ret = luaL_loadfile(m_engineluastack, script.c_str()) || + lua_pcall(m_engineluastack, 0, 0, m_luaerrorhandler); + if(ret){ + errorstream<<"========== ERROR FROM LUA WHILE CREATING MAIN MENU ==========="<<std::endl; + errorstream<<"Failed to load and run script from "<<std::endl; + errorstream<<script<<":"<<std::endl; + errorstream<<std::endl; + errorstream<<lua_tostring(m_engineluastack, -1)<<std::endl; + errorstream<<std::endl; + errorstream<<"=================== END OF ERROR FROM LUA ===================="<<std::endl; + lua_pop(m_engineluastack, 1); // Pop error message from stack + lua_pop(m_engineluastack, 1); // Pop the error handler from stack + return false; + } + return true; +} + +/******************************************************************************/ +void GUIEngine::run() +{ + + // Always create clouds because they may or may not be + // needed based on the game selected + video::IVideoDriver* driver = m_device->getVideoDriver(); + + cloudInit(); + + while(m_device->run() && (!m_startgame)) { + driver->beginScene(true, true, video::SColor(255,140,186,250)); + + if (m_clouds_enabled) + { + cloudPreProcess(); + drawOverlay(driver); + } + else + drawBackground(driver); + + drawHeader(driver); + drawFooter(driver); + + m_device->getGUIEnvironment()->drawAll(); + + driver->endScene(); + + if (m_clouds_enabled) + cloudPostProcess(); + else + sleep_ms(25); + } + + m_menu->quitMenu(); +} + +/******************************************************************************/ +void GUIEngine::handleEvent(std::string text) +{ + lua_getglobal(m_engineluastack, "engine"); + + lua_getfield(m_engineluastack, -1, "event_handler"); + + if(lua_isnil(m_engineluastack, -1)) + return; + + luaL_checktype(m_engineluastack, -1, LUA_TFUNCTION); + + lua_pushstring(m_engineluastack, text.c_str()); + + if(lua_pcall(m_engineluastack, 1, 0, m_luaerrorhandler)) + scriptError("error: %s", lua_tostring(m_engineluastack, -1)); +} + +/******************************************************************************/ +void GUIEngine::handleButtons(std::map<std::string, std::string> fields) +{ + lua_getglobal(m_engineluastack, "engine"); + + lua_getfield(m_engineluastack, -1, "button_handler"); + + if(lua_isnil(m_engineluastack, -1)) + return; + + luaL_checktype(m_engineluastack, -1, LUA_TFUNCTION); + + lua_newtable(m_engineluastack); + for(std::map<std::string, std::string>::const_iterator + i = fields.begin(); i != fields.end(); i++){ + const std::string &name = i->first; + const std::string &value = i->second; + lua_pushstring(m_engineluastack, name.c_str()); + lua_pushlstring(m_engineluastack, value.c_str(), value.size()); + lua_settable(m_engineluastack, -3); + } + + if(lua_pcall(m_engineluastack, 1, 0, m_luaerrorhandler)) + scriptError("error: %s", lua_tostring(m_engineluastack, -1)); +} + +/******************************************************************************/ +GUIEngine::~GUIEngine() +{ + video::IVideoDriver* driver = m_device->getVideoDriver(); + assert(driver != 0); + + lua_close(m_engineluastack); + + m_irr_toplefttext->setText(L""); + + //initialize texture pointers + for (unsigned int i = 0; i < TEX_LAYER_MAX; i++) { + if (m_textures[i] != 0) + driver->removeTexture(m_textures[i]); + } + + m_cloud.clouds->drop(); +} + +/******************************************************************************/ +void GUIEngine::cloudInit() +{ + m_cloud.clouds = new Clouds(m_smgr->getRootSceneNode(), + m_smgr, -1, rand(), 100); + m_cloud.clouds->update(v2f(0, 0), video::SColor(255,200,200,255)); + + m_cloud.camera = m_smgr->addCameraSceneNode(0, + v3f(0,0,0), v3f(0, 60, 100)); + m_cloud.camera->setFarValue(10000); + + m_cloud.lasttime = m_device->getTimer()->getTime(); +} + +/******************************************************************************/ +void GUIEngine::cloudPreProcess() +{ + u32 time = m_device->getTimer()->getTime(); + + if(time > m_cloud.lasttime) + m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0; + else + m_cloud.dtime = 0; + + m_cloud.lasttime = time; + + m_cloud.clouds->step(m_cloud.dtime*3); + m_cloud.clouds->render(); + m_smgr->drawAll(); +} + +/******************************************************************************/ +void GUIEngine::cloudPostProcess() +{ + float fps_max = g_settings->getFloat("fps_max"); + // Time of frame without fps limit + float busytime; + u32 busytime_u32; + // not using getRealTime is necessary for wine + u32 time = m_device->getTimer()->getTime(); + if(time > m_cloud.lasttime) + busytime_u32 = time - m_cloud.lasttime; + else + busytime_u32 = 0; + busytime = busytime_u32 / 1000.0; + + // FPS limiter + u32 frametime_min = 1000./fps_max; + + if(busytime_u32 < frametime_min) { + u32 sleeptime = frametime_min - busytime_u32; + m_device->sleep(sleeptime); + } +} + +/******************************************************************************/ +void GUIEngine::drawBackground(video::IVideoDriver* driver) +{ + v2u32 screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND]; + + /* If no texture, draw background of solid color */ + if(!texture){ + video::SColor color(255,80,58,37); + core::rect<s32> rect(0, 0, screensize.X, screensize.Y); + driver->draw2DRectangle(color, rect, NULL); + return; + } + + /* Draw background texture */ + v2u32 sourcesize = texture->getSize(); + driver->draw2DImage(texture, + core::rect<s32>(0, 0, screensize.X, screensize.Y), + core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), + NULL, NULL, true); +} + +/******************************************************************************/ +void GUIEngine::drawOverlay(video::IVideoDriver* driver) +{ + v2u32 screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY]; + + /* If no texture, draw background of solid color */ + if(!texture) + return; + + /* Draw background texture */ + v2u32 sourcesize = texture->getSize(); + driver->draw2DImage(texture, + core::rect<s32>(0, 0, screensize.X, screensize.Y), + core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), + NULL, NULL, true); +} + +/******************************************************************************/ +void GUIEngine::drawHeader(video::IVideoDriver* driver) +{ + core::dimension2d<u32> screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_HEADER]; + + /* If no texture, draw nothing */ + if(!texture) + return; + + f32 mult = (((f32)screensize.Width / 2)) / + ((f32)texture->getOriginalSize().Width); + + v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, + ((f32)texture->getOriginalSize().Height) * mult); + + // Don't draw the header is there isn't enough room + s32 free_space = (((s32)screensize.Height)-320)/2; + + if (free_space > splashsize.Y) { + core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y); + splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), + ((free_space/2)-splashsize.Y/2)+10); + + video::SColor bgcolor(255,50,50,50); + + driver->draw2DImage(texture, splashrect, + core::rect<s32>(core::position2d<s32>(0,0), + core::dimension2di(texture->getSize())), + NULL, NULL, true); + } +} + +/******************************************************************************/ +void GUIEngine::drawFooter(video::IVideoDriver* driver) +{ + core::dimension2d<u32> screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_FOOTER]; + + /* If no texture, draw nothing */ + if(!texture) + return; + + f32 mult = (((f32)screensize.Width)) / + ((f32)texture->getOriginalSize().Width); + + v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult, + ((f32)texture->getOriginalSize().Height) * mult); + + // Don't draw the footer if there isn't enough room + s32 free_space = (((s32)screensize.Height)-320)/2; + + if (free_space > footersize.Y) { + core::rect<s32> rect(0,0,footersize.X,footersize.Y); + rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); + rect -= v2s32(footersize.X/2, 0); + + driver->draw2DImage(texture, rect, + core::rect<s32>(core::position2d<s32>(0,0), + core::dimension2di(texture->getSize())), + NULL, NULL, true); + } +} + +/******************************************************************************/ +bool GUIEngine::setTexture(texture_layer layer,std::string texturepath) { + + video::IVideoDriver* driver = m_device->getVideoDriver(); + assert(driver != 0); + + if (m_textures[layer] != 0) + { + driver->removeTexture(m_textures[layer]); + m_textures[layer] = 0; + } + + if ((texturepath == "") || !fs::PathExists(texturepath)) + return false; + + m_textures[layer] = driver->getTexture(texturepath.c_str()); + + if (m_textures[layer] == 0) return false; + + return true; +} + +/******************************************************************************/ +#if USE_CURL +static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + FILE* targetfile = (FILE*) userp; + fwrite(contents,size,nmemb,targetfile); + return size * nmemb; +} +#endif +bool GUIEngine::downloadFile(std::string url,std::string target) { +#if USE_CURL + //download file via curl + CURL *curl; + + curl = curl_easy_init(); + + if (curl) + { + CURLcode res; + bool retval = true; + + FILE* targetfile = fopen(target.c_str(),"wb"); + + if (targetfile) { + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, targetfile); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + errorstream << "File at url \"" << url + <<"\" not found (internet connection?)" <<std::endl; + retval = false; + } + fclose(targetfile); + } + else { + retval = false; + } + + curl_easy_cleanup(curl); + return retval; + } +#endif + return false; +} + +/******************************************************************************/ +void GUIEngine::scriptError(const char *fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + char buf[10000]; + vsnprintf(buf, 10000, fmt, argp); + va_end(argp); + errorstream<<"MAINMENU ERROR: "<<buf; +} + +/******************************************************************************/ +void GUIEngine::setTopleftText(std::string append) { + std::string toset = "Minetest " VERSION_STRING; + + if (append != "") { + toset += " / "; + toset += append; + } + + m_irr_toplefttext->setText(narrow_to_wide(toset).c_str()); +} diff --git a/src/guiEngine.h b/src/guiEngine.h new file mode 100644 index 000000000..2445ed847 --- /dev/null +++ b/src/guiEngine.h @@ -0,0 +1,260 @@ +/* +Minetest +Copyright (C) 2013 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef GUI_ENGINE_H_ +#define GUI_ENGINE_H_ + +/******************************************************************************/ +/* Includes */ +/******************************************************************************/ +#include "irrlichttypes.h" +#include "modalMenu.h" +#include "clouds.h" +#include "guiLuaApi.h" +#include "guiFormSpecMenu.h" + +/******************************************************************************/ +/* Typedefs and macros */ +/******************************************************************************/ +#define MAX_MENUBAR_BTN_COUNT 10 +#define MAX_MENUBAR_BTN_ID 256 +#define MIN_MENUBAR_BTN_ID (MAX_MENUBAR_BTN_ID - MAX_MENUBAR_BTN_COUNT) + +/** texture layer ids */ +typedef enum { + TEX_LAYER_BACKGROUND = 0, + TEX_LAYER_OVERLAY, + TEX_LAYER_HEADER, + TEX_LAYER_FOOTER, + TEX_LAYER_MAX +} texture_layer; + +/******************************************************************************/ +/* forward declarations */ +/******************************************************************************/ +class GUIEngine; +struct MainMenuData; + +/******************************************************************************/ +/* declarations */ +/******************************************************************************/ + +/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */ +class TextDestGuiEngine : public TextDest +{ +public: + /** + * default constructor + * @param engine the engine data is transmitted for further processing + */ + TextDestGuiEngine(GUIEngine* engine); + /** + * receive fields transmitted by guiFormSpecMenu + * @param fields map containing formspec field elements currently active + */ + void gotText(std::map<std::string, std::string> fields); + + /** + * receive text/events transmitted by guiFormSpecMenu + * @param text textual representation of event + */ + void gotText(std::wstring text); +private: + /** target to transmit data to */ + GUIEngine* m_engine; +}; + + +/** implementation of main menu based uppon formspecs */ +class GUIEngine { +public: + /** TextDestGuiEngine needs to transfer data to engine */ + friend class TextDestGuiEngine; + /** guiLuaApi is used to initialize contained stack */ + friend class guiLuaApi; + + /** + * default constructor + * @param dev device to draw at + * @param parent parent gui element + * @param menumgr manager to add menus to + * @param smgr scene manager to add scene elements to + * @param data struct to transfer data to main game handling + */ + GUIEngine( irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, + IMenuManager *menumgr, + scene::ISceneManager* smgr, + MainMenuData* data); + + /** default destructor */ + virtual ~GUIEngine(); + +protected: + /** + * process field data recieved from formspec + * @param fields data in field format + */ + void handleButtons(std::map<std::string, std::string> fields); + /** + * process events received from formspec + * @param text events in textual form + */ + void handleEvent(std::string text); + + /** + * return dir of current menuscript + */ + std::string getScriptDir() { + return m_scriptdir; + } + +private: + + /* run main menu loop */ + void run(); + + /** handler to limit frame rate within main menu */ + void limitFrameRate(); + + /** device to draw at */ + irr::IrrlichtDevice* m_device; + /** parent gui element */ + gui::IGUIElement* m_parent; + /** manager to add menus to */ + IMenuManager* m_menumanager; + /** scene manager to add scene elements to */ + scene::ISceneManager* m_smgr; + /** pointer to data beeing transfered back to main game handling */ + MainMenuData* m_data; + + /** representation of form source to be used in mainmenu formspec */ + FormspecFormSource* m_formspecgui; + /** formspec input receiver */ + TextDestGuiEngine* m_buttonhandler; + /** the formspec menu */ + GUIFormSpecMenu* m_menu; + + /** variable used to abort menu and return back to main game handling */ + bool m_startgame; + + /** + * initialize lua stack + * @param L stack to initialize + */ + void initalize_api(lua_State * L); + + /** + * run a lua script + * @param script full path to script to run + */ + bool runScript(std::string script); + + /** + * script error handler to process errors within lua + */ + void scriptError(const char *fmt, ...); + + /** lua stack */ + lua_State* m_engineluastack; + /** lua internal stack number of error handler*/ + int m_luaerrorhandler; + + /** script basefolder */ + std::string m_scriptdir; + + /** + * draw background layer + * @param driver to use for drawing + */ + void drawBackground(video::IVideoDriver* driver); + /** + * draw overlay layer + * @param driver to use for drawing + */ + void drawOverlay(video::IVideoDriver* driver); + /** + * draw header layer + * @param driver to use for drawing + */ + void drawHeader(video::IVideoDriver* driver); + /** + * draw footer layer + * @param driver to use for drawing + */ + void drawFooter(video::IVideoDriver* driver); + + /** + * load a texture for a specified layer + * @param layer draw layer to specify texture + * @param texturepath full path of texture to load + */ + bool setTexture(texture_layer layer,std::string texturepath); + + /** + * download a file using curl + * @param url url to download + * @param target file to store to + */ + bool downloadFile(std::string url,std::string target); + + /** array containing pointers to current specified texture layers */ + video::ITexture* m_textures[TEX_LAYER_MAX]; + + /** draw version string in topleft corner */ + void drawVersion(); + + /** + * specify text to be appended to version string + * @param text to set + */ + void setTopleftText(std::string append); + + /** pointer to gui element shown at topleft corner */ + irr::gui::IGUIStaticText* m_irr_toplefttext; + + /** initialize cloud subsystem */ + void cloudInit(); + /** do preprocessing for cloud subsystem */ + void cloudPreProcess(); + /** do postprocessing for cloud subsystem */ + void cloudPostProcess(); + + /** internam data required for drawing clouds */ + struct clouddata { + /** delta time since last cloud processing */ + f32 dtime; + /** absolute time of last cloud processing */ + u32 lasttime; + /** pointer to cloud class */ + Clouds* clouds; + /** camera required for drawing clouds */ + scene::ICameraSceneNode* camera; + }; + + /** is drawing of clouds enabled atm */ + bool m_clouds_enabled; + /** data used to draw clouds */ + clouddata m_cloud; + +}; + + + +#endif /* GUI_ENGINE_H_ */ diff --git a/src/guiFileSelectMenu.cpp b/src/guiFileSelectMenu.cpp new file mode 100644 index 000000000..93d43f786 --- /dev/null +++ b/src/guiFileSelectMenu.cpp @@ -0,0 +1,133 @@ +/* + Minetest + Copyright (C) 2013 sapier + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "guiFileSelectMenu.h" +#include "util/string.h" +#include <locale.h> + +GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, + std::string title, std::string formname) : +GUIModalMenu(env, parent, id, menumgr) +{ + m_title = narrow_to_wide(title); + m_parent = parent; + m_formname = formname; + m_text_dst = 0; + m_accepted = false; + m_previous_locale = setlocale(LC_ALL,0); +} + +GUIFileSelectMenu::~GUIFileSelectMenu() +{ + removeChildren(); + setlocale(LC_ALL,m_previous_locale.c_str()); +} + +void GUIFileSelectMenu::removeChildren() +{ + const core::list<gui::IGUIElement*> &children = getChildren(); + core::list<gui::IGUIElement*> children_copy; + for (core::list<gui::IGUIElement*>::ConstIterator i = children.begin(); i + != children.end(); i++) + { + children_copy.push_back(*i); + } + for (core::list<gui::IGUIElement*>::Iterator i = children_copy.begin(); i + != children_copy.end(); i++) + { + (*i)->remove(); + } +} + +void GUIFileSelectMenu::regenerateGui(v2u32 screensize) +{ + removeChildren(); + m_fileOpenDialog = 0; + + core::dimension2du size(600,400); + core::rect < s32 > rect(0,0,screensize.X,screensize.Y); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + m_fileOpenDialog = + Environment->addFileOpenDialog(m_title.c_str(),false,this,-1); + + core::position2di pos = core::position2di(screensize.X/2 - size.Width/2,screensize.Y/2 -size.Height/2); + m_fileOpenDialog->setRelativePosition(pos); + m_fileOpenDialog->setMinSize(size); +} + +void GUIFileSelectMenu::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + gui::IGUIElement::draw(); +} + +void GUIFileSelectMenu::acceptInput() { + if ((m_text_dst != 0) && (this->m_formname != "")){ + std::map<std::string, std::string> fields; + + if (m_accepted) + fields[m_formname + "_accepted"] = wide_to_narrow(m_fileOpenDialog->getFileName()); + else + fields[m_formname + "_canceled"] = m_formname; + + this->m_text_dst->gotText(fields); + } +} + +bool GUIFileSelectMenu::OnEvent(const SEvent& event) +{ + + if (event.EventType == irr::EET_GUI_EVENT) { + + int callerId = event.GUIEvent.Caller->getID(); + if (callerId >= 0) { + std::cout << "CallerId:" << callerId << std::endl; + } + + switch (event.GUIEvent.EventType) { + case gui::EGET_ELEMENT_CLOSED: + case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED: + m_accepted=false; + acceptInput(); + quitMenu(); + return true; + break; + + case gui::EGET_DIRECTORY_SELECTED: + case gui::EGET_FILE_SELECTED: + m_accepted=true; + acceptInput(); + quitMenu(); + return true; + break; + + default: + //ignore this event + break; + } + } + return Parent ? Parent->OnEvent(event) : false; +} diff --git a/src/guiFileSelectMenu.h b/src/guiFileSelectMenu.h new file mode 100644 index 000000000..987a9f2ee --- /dev/null +++ b/src/guiFileSelectMenu.h @@ -0,0 +1,80 @@ +/* + Minetest + Copyright (C) 2013 sapier + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUIFILESELECTMENU_H_ +#define GUIFILESELECTMENU_H_ + +#include <string> + +#include "modalMenu.h" +#include "IGUIFileOpenDialog.h" +#include "guiFormSpecMenu.h" //required because of TextDest only !!! + + +class GUIFileSelectMenu: public GUIModalMenu +{ +public: + GUIFileSelectMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, + s32 id, IMenuManager *menumgr, + std::string title, + std::string formid); + ~GUIFileSelectMenu(); + + void removeChildren(); + + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool OnEvent(const SEvent& event); + + bool isRunning() { + return m_running; + } + + void setTextDest(TextDest * dest) { + m_text_dst = dest; + } + +private: + void acceptInput(); + + std::wstring m_title; + bool m_accepted; + gui::IGUIElement* m_parent; + + std::string m_selectedPath; + + gui::IGUIFileOpenDialog* m_fileOpenDialog; + + std::string m_previous_locale; + + bool m_running; + + TextDest *m_text_dst; + + std::string m_formname; +}; + + + +#endif /* GUIFILESELECTMENU_H_ */ diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index ee39df8b7..0aa2c2dcd 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -18,6 +18,11 @@ with this program; if not, write to the Free Software Foundation, Inc., */ +#include <cstdlib> +#include <algorithm> +#include <iterator> +#include <sstream> +#include <limits> #include "guiFormSpecMenu.h" #include "constants.h" #include "gamedef.h" @@ -28,13 +33,34 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IGUIButton.h> #include <IGUIStaticText.h> #include <IGUIFont.h> +#include <IGUIListBox.h> +#include <IGUITabControl.h> +#include <IGUIScrollBar.h> +#include <IGUIComboBox.h> #include "log.h" #include "tile.h" // ITextureSource #include "util/string.h" #include "util/numeric.h" +#include "filesys.h" #include "gettext.h" + +#define MY_CHECKPOS(a,b) \ + if (v_pos.size() != 2) { \ + errorstream<< "Invalid pos for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ + } + +#define MY_CHECKGEOM(a,b) \ + if (v_geom.size() != 2) { \ + errorstream<< "Invalid pos for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ + } + + void drawItemStack(video::IVideoDriver *driver, gui::IGUIFont *font, const ItemStack &item, @@ -140,7 +166,10 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, m_selected_item(NULL), m_selected_amount(0), m_selected_dragging(false), - m_tooltip_element(NULL) + m_tooltip_element(NULL), + m_allowclose(true), + m_use_gettext(false), + m_lock(false) { } @@ -180,436 +209,1337 @@ void GUIFormSpecMenu::removeChildren() } } -void GUIFormSpecMenu::regenerateGui(v2u32 screensize) -{ - // Remove children - removeChildren(); - - v2s32 size(100,100); - s32 helptext_h = 15; - core::rect<s32> rect; +int GUIFormSpecMenu::getListboxIndex(std::string listboxname) { - // Base position of contents of form - v2s32 basepos = getBasePos(); - // State of basepos, 0 = not set, 1= set by formspec, 2 = set by size[] element - // Used to adjust form size automatically if needed - // A proceed button is added if there is no size[] element - int bp_set = 0; - - /* Convert m_init_draw_spec to m_inventorylists */ - - m_inventorylists.clear(); - m_images.clear(); - m_backgrounds.clear(); - m_itemimages.clear(); - m_fields.clear(); + std::wstring wlistboxname = narrow_to_wide(listboxname.c_str()); - Strfnd f(m_formspec_string); - while(f.atend() == false) - { - std::string type = trim(f.next_esc("[")); - if(type == "invsize" || type == "size") - { - v2f invsize; - invsize.X = stof(f.next_esc(",")); - if(type == "size") - { - invsize.Y = stof(f.next_esc("]")); - } - else{ - invsize.Y = stof(f.next_esc(";")); - f.next_esc("]"); - } - infostream<<"Form size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl; - - padding = v2s32(screensize.Y/40, screensize.Y/40); - spacing = v2s32(screensize.Y/12, screensize.Y/13); - imgsize = v2s32(screensize.Y/15, screensize.Y/15); - size = v2s32( - padding.X*2+spacing.X*(invsize.X-1.0)+imgsize.X, - padding.Y*2+spacing.Y*(invsize.Y-1.0)+imgsize.Y + (helptext_h-5) - ); - rect = core::rect<s32>( - screensize.X/2 - size.X/2, - screensize.Y/2 - size.Y/2, - screensize.X/2 + size.X/2, - screensize.Y/2 + size.Y/2 - ); - DesiredRect = rect; - recalculateAbsolutePosition(false); - basepos = getBasePos(); - bp_set = 2; - } - else if(type == "list") - { - std::string name = f.next_esc(";"); - InventoryLocation loc; - if(name == "context" || name == "current_name") - loc = m_current_inventory_location; - else - loc.deSerialize(name); - std::string listname = f.next_esc(";"); - v2s32 pos = basepos; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = stoi(f.next_esc(",")); - geom.Y = stoi(f.next_esc(";")); - infostream<<"list inv="<<name<<", listname="<<listname - <<", pos=("<<pos.X<<","<<pos.Y<<")" - <<", geom=("<<geom.X<<","<<geom.Y<<")" - <<std::endl; - std::string start_i_s = f.next_esc("]"); - s32 start_i = 0; - if(start_i_s != "") - start_i = stoi(start_i_s); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of list without a size[] element"<<std::endl; - m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom, start_i)); - } - else if(type == "image") - { - v2s32 pos = basepos; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = stof(f.next_esc(",")) * (float)imgsize.X; - geom.Y = stof(f.next_esc(";")) * (float)imgsize.Y; - std::string name = f.next_esc("]"); - infostream<<"image name="<<name - <<", pos=("<<pos.X<<","<<pos.Y<<")" - <<", geom=("<<geom.X<<","<<geom.Y<<")" - <<std::endl; - if(bp_set != 2) - errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl; - m_images.push_back(ImageDrawSpec(name, pos, geom)); - } - else if(type == "item_image") - { - v2s32 pos = basepos; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = stof(f.next_esc(",")) * (float)imgsize.X; - geom.Y = stof(f.next_esc(";")) * (float)imgsize.Y; - std::string name = f.next_esc("]"); - infostream<<"item name="<<name - <<", pos=("<<pos.X<<","<<pos.Y<<")" - <<", geom=("<<geom.X<<","<<geom.Y<<")" - <<std::endl; - if(bp_set != 2) - errorstream<<"WARNING: invalid use of item_image without a size[] element"<<std::endl; - m_itemimages.push_back(ImageDrawSpec(name, pos, geom)); - } - else if(type == "background") - { - v2s32 pos = basepos; - pos.X += stof(f.next_esc(",")) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2; - v2s32 geom; - geom.X = stof(f.next_esc(",")) * (float)spacing.X; - geom.Y = stof(f.next_esc(";")) * (float)spacing.Y; - std::string name = f.next_esc("]"); - infostream<<"image name="<<name - <<", pos=("<<pos.X<<","<<pos.Y<<")" - <<", geom=("<<geom.X<<","<<geom.Y<<")" - <<std::endl; - if(bp_set != 2) - errorstream<<"WARNING: invalid use of background without a size[] element"<<std::endl; - m_backgrounds.push_back(ImageDrawSpec(name, pos, geom)); + for(unsigned int i=0; i < m_listboxes.size(); i++) { + + std::wstring name(m_listboxes[i].first.fname.c_str()); + if ( name == wlistboxname) { + return m_listboxes[i].second->getSelected(); } - else if(type == "field" || type == "textarea") - { - std::string fname = f.next_esc(";"); - std::string flabel = f.next_esc(";"); + } + return -1; +} - if(fname.find(",") == std::string::npos && flabel.find(",") == std::string::npos) - { - if (type == "textarea") - errorstream<<"WARNING: Textarea connot be unpositioned"<<std::endl; +std::vector<std::string> split(const std::string &s, char delim, bool escape=false) { + std::vector<std::string> tokens; - if(!bp_set) - { - rect = core::rect<s32>( - screensize.X/2 - 580/2, - screensize.Y/2 - 300/2, - screensize.X/2 + 580/2, - screensize.Y/2 + 300/2 - ); - DesiredRect = rect; - recalculateAbsolutePosition(false); - basepos = getBasePos(); - bp_set = 1; + if (!escape) { + int startpos = 0; + size_t nextpos = s.find(delim); + + while(nextpos != std::string::npos) { + std::string toadd = s.substr(startpos,nextpos-startpos); + tokens.push_back(toadd); + startpos = nextpos+1; + nextpos = s.find(delim,nextpos+1); + } + + //push last element + tokens.push_back(s.substr(startpos)); + } + else { + std::string current = ""; + current += s.c_str()[0]; + bool last_was_delim = false; + for(unsigned int i=1; i < s.size(); i++) { + if (last_was_delim) { + if (s.c_str()[i] == delim) { + current += s.c_str()[i]; + last_was_delim = false; } - else if(bp_set == 2) - errorstream<<"WARNING: invalid use of unpositioned "<<type<<" in inventory"<<std::endl; + else { + tokens.push_back(current); - v2s32 pos = basepos; - pos.Y = ((m_fields.size()+2)*60); - v2s32 size = DesiredRect.getSize(); - rect = core::rect<s32>(size.X/2-150, pos.Y, (size.X/2-150)+300, pos.Y+30); + current = ""; + current += s.c_str()[i]; + last_was_delim = false; + } } - else - { - v2s32 pos; - pos.X = stof(fname.substr(0,fname.find(","))) * (float)spacing.X; - pos.Y = stof(fname.substr(fname.find(",")+1)) * (float)spacing.Y; - v2s32 geom; - geom.X = (stof(flabel.substr(0,flabel.find(","))) * (float)spacing.X)-(spacing.X-imgsize.X); - if (type == "textarea") - { - geom.Y = (stof(flabel.substr(flabel.find(",")+1)) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); - pos.Y += 15; + else { + if (s.c_str()[i] == delim) { + last_was_delim = true; } - else - { - pos.Y += (stof(flabel.substr(flabel.find(",")+1)) * (float)imgsize.Y)/2; - pos.Y -= 15; - geom.Y = 30; + else { + last_was_delim = false; + current += s.c_str()[i]; } + } + } + //push last element + tokens.push_back(current); + } + + return tokens; +} - rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); +void GUIFormSpecMenu::parseSize(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,','); + if (parts.size() == 2) { + v2f invsize; - - fname = f.next_esc(";"); - flabel = f.next_esc(";"); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of positioned "<<type<<" without a size[] element"<<std::endl; - - } + if (parts[1].find(';') != std::string::npos) + parts[1] = parts[1].substr(0,parts[1].find(';')); + + invsize.X = stof(parts[0]); + invsize.Y = stof(parts[1]); + + infostream<<"Form size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl; + + if (m_lock) { + v2u32 current_screensize = m_device->getVideoDriver()->getScreenSize(); + v2u32 delta = current_screensize - m_lockscreensize; - std::string odefault = f.next_esc("]"); - std::string fdefault; + if (current_screensize.Y > m_lockscreensize.Y) + delta.Y /= 2; + else + delta.Y = 0; - // fdefault may contain a variable reference, which - // needs to be resolved from the node metadata - if(m_form_src) - fdefault = m_form_src->resolveText(odefault); + if (current_screensize.X > m_lockscreensize.X) + delta.X /= 2; else - fdefault = odefault; + delta.X = 0; + + offset = v2s32(delta.X,delta.Y); + + data->screensize = m_lockscreensize; + } + else { + offset = v2s32(0,0); + } - fdefault = unescape_string(fdefault); - flabel = unescape_string(flabel); + padding = v2s32(data->screensize.Y/40, data->screensize.Y/40); + spacing = v2s32(data->screensize.Y/12, data->screensize.Y/13); + imgsize = v2s32(data->screensize.Y/15, data->screensize.Y/15); + data->size = v2s32( + padding.X*2+spacing.X*(invsize.X-1.0)+imgsize.X, + padding.Y*2+spacing.Y*(invsize.Y-1.0)+imgsize.Y + (data->helptext_h-5) + ); + data->rect = core::rect<s32>( + data->screensize.X/2 - data->size.X/2 + offset.X, + data->screensize.Y/2 - data->size.Y/2 + offset.Y, + data->screensize.X/2 + data->size.X/2 + offset.X, + data->screensize.Y/2 + data->size.Y/2 + offset.Y + ); + + DesiredRect = data->rect; + recalculateAbsolutePosition(false); + data->basepos = getBasePos(); + data->bp_set = 2; + return; + } + errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseList(parserData* data,std::string element) { - FieldSpec spec = FieldSpec( - narrow_to_wide(fname.c_str()), - narrow_to_wide(flabel.c_str()), - narrow_to_wide(fdefault.c_str()), + if (m_gamedef == 0) { + errorstream<<"WARNING: invalid use of 'list' with m_gamedef==0"<<std::endl; + return; + } + + std::vector<std::string> parts = split(element,';'); + + if ((parts.size() == 4) || (parts.size() == 5)) { + std::string location = parts[0]; + std::string listname = parts[1]; + std::vector<std::string> v_pos = split(parts[2],','); + std::vector<std::string> v_geom = split(parts[3],','); + std::string startindex = ""; + if (parts.size() == 5) + startindex = parts[4]; + + MY_CHECKPOS("list",2); + MY_CHECKGEOM("list",3); + + InventoryLocation loc; + + if(location == "context" || location == "current_name") + loc = m_current_inventory_location; + else + loc.deSerialize(location); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = stoi(v_geom[0]); + geom.Y = stoi(v_geom[1]); + infostream<<"list inv="<<location<<", listname="<<listname + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<", geom=("<<geom.X<<","<<geom.Y<<")" + <<std::endl; + + s32 start_i = 0; + if(startindex != "") + start_i = stoi(startindex); + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of list without a size[] element"<<std::endl; + m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom, start_i)); + return; + } + errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if ((parts.size() == 3) || (parts.size() == 4)) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = parts[1]; + std::string label = parts[2]; + std::string selected = ""; + + if (parts.size() == 4) + selected = parts[3]; + + MY_CHECKPOS("checkbox",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15)); + + bool fselected = false; + + if (selected == "true") + fselected = true; + + wchar_t* wlabel = 0; + + if (m_use_gettext) + wlabel = wgettext(label.c_str()); + else + wlabel = (wchar_t*) narrow_to_wide(label.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + narrow_to_wide(""), + wlabel, 258+m_fields.size() ); - // three cases: field name and no label, label and field, label name and no field - gui::IGUIEditBox *e; - if (fname == "") - { - // spec field id to 0, this stops submit searching for a value that isn't there - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); - } - else - { - spec.send = true; - e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); - Environment->setFocus(e); + spec.ftype = f_CheckBox; - if (type == "textarea") - { - e->setMultiLine(true); - e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT); - } else { - irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.PressedDown = true; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = 0; - evt.KeyInput.Shift = 0; - e->OnEvent(evt); + gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this, + spec.fid, wlabel); + + m_checkboxes.push_back(std::pair<FieldSpec,gui::IGUICheckBox*>(spec,e)); + m_fields.push_back(spec); + if (m_use_gettext) + delete[] wlabel; + return; + } + errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseImage(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 3) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + + MY_CHECKPOS("image",0); + MY_CHECKGEOM("image",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stoi(v_geom[0]) * (float)imgsize.X; + geom.Y = stoi(v_geom[1]) * (float)imgsize.Y; + + infostream<<"image name="<<name + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<", geom=("<<geom.X<<","<<geom.Y<<")" + <<std::endl; + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl; + m_images.push_back(ImageDrawSpec(name, pos, geom)); + return; + } + + if (parts.size() == 2) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = parts[1]; + + MY_CHECKPOS("image",0); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + std::cout<<"image name="<<name + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<std::endl; + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of image without a size[] element"<<std::endl; + m_images.push_back(ImageDrawSpec(name, pos)); + return; + } + errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseItemImage(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 3) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + + MY_CHECKPOS("itemimage",0); + MY_CHECKGEOM("itemimage",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stoi(v_geom[0]) * (float)imgsize.X; + geom.Y = stoi(v_geom[1]) * (float)imgsize.Y; + + infostream<<"item name="<<name + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<", geom=("<<geom.X<<","<<geom.Y<<")" + <<std::endl; + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of item_image without a size[] element"<<std::endl; + m_itemimages.push_back(ImageDrawSpec(name, pos, geom)); + return; + } + errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseButton(parserData* data,std::string element,std::string type) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 4) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + + MY_CHECKPOS("button",0); + MY_CHECKGEOM("button",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl; + + label = unescape_string(label); + + wchar_t* wlabel = 0; + + if (m_use_gettext) + wlabel = wgettext(label.c_str()); + else + wlabel = (wchar_t*) narrow_to_wide(label.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + wlabel, + narrow_to_wide(""), + 258+m_fields.size() + ); + spec.ftype = f_Button; + if(type == "button_exit") + spec.is_exit = true; + + Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + m_fields.push_back(spec); + if (m_use_gettext) + delete[] wlabel; + return; + } + errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseBackground(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 3) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + + MY_CHECKPOS("background",0); + MY_CHECKGEOM("background",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2; + pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + infostream<<"image name="<<name + <<", pos=("<<pos.X<<","<<pos.Y<<")" + <<", geom=("<<geom.X<<","<<geom.Y<<")" + <<std::endl; + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of background without a size[] element"<<std::endl; + m_backgrounds.push_back(ImageDrawSpec(name, pos, geom)); + return; + } + errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if ((parts.size() == 5) || (parts.size() == 6)) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector<std::string> items = split(parts[3],','); + std::string str_initial_selection = ""; + std::string str_transparent = "false"; + + if (parts.size() >= 5) + str_initial_selection = parts[4]; + + if (parts.size() >= 6) + str_transparent = parts[5]; + + MY_CHECKPOS("textlist",0); + MY_CHECKGEOM("textlist",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + std::wstring fname_w = narrow_to_wide(name.c_str()); + + FieldSpec spec = FieldSpec( + fname_w, + narrow_to_wide(""), + narrow_to_wide(""), + 258+m_fields.size() + ); + + spec.ftype = f_ListBox; + + //now really show list + gui::IGUIListBox *e = Environment->addListBox(rect, this,spec.fid); + + //don't reset if we already have a user specified selection + if (data->listbox_selections.find(fname_w) == data->listbox_selections.end()) { + e->setAutoScrollEnabled(false); + } + + if (str_transparent == "false") + e->setDrawBackground(true); + + for (unsigned int i=0; i < items.size(); i++) { + if (items[i].c_str()[0] == '#') { + if (items[i].c_str()[1] == '#') { + e->addItem(narrow_to_wide(items[i]).c_str() +1); } + else { + std::wstring toadd = narrow_to_wide(items[i].c_str() + 4); + std::string color = items[i].substr(1,3); - if (flabel != "") - { - rect.UpperLeftCorner.Y -= 15; - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + e->addItem(toadd.c_str()); + + bool valid_color = true; + + irr::video::SColor toset = getColor(color,valid_color); + + if (valid_color) + e->setItemOverrideColor(i,toset); } } + else { + e->addItem(narrow_to_wide(items[i]).c_str()); + } + } + + if (str_initial_selection != "") + e->setSelected(stoi(str_initial_selection.c_str())-1); - m_fields.push_back(spec); + if (data->listbox_selections.find(fname_w) != data->listbox_selections.end()) { + e->setSelected(data->listbox_selections[fname_w]); } - else if(type == "label") - { - v2s32 pos = padding; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15)); - - std::string flabel = f.next_esc("]"); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl; + m_listboxes.push_back(std::pair<FieldSpec,gui::IGUIListBox*>(spec,e)); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; +} - flabel = unescape_string(flabel); - FieldSpec spec = FieldSpec( - narrow_to_wide(""), - narrow_to_wide(flabel.c_str()), - narrow_to_wide(""), - 258+m_fields.size() +void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 5) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = parts[2]; + std::vector<std::string> items = split(parts[3],','); + std::string str_initial_selection = ""; + str_initial_selection = parts[4]; + + MY_CHECKPOS("dropdown",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + s32 width = stof(parts[1]) * (float)spacing.Y; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+width, pos.Y+30); + + std::wstring fname_w = narrow_to_wide(name.c_str()); + + FieldSpec spec = FieldSpec( + fname_w, + narrow_to_wide(""), + narrow_to_wide(""), + 258+m_fields.size() + ); + + spec.ftype = f_DropDown; + spec.send = true; + + //now really show list + gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid); + + //don't reset if we already have a user specified selection + //if (data->combobox_selections.find(fname_w) == data->listbox_selections.end()) { + // e->setAutoScrollEnabled(false); + //} + + for (unsigned int i=0; i < items.size(); i++) { + e->addItem(narrow_to_wide(items[i]).c_str()); + } + + if (str_initial_selection != "") + e->setSelected(stoi(str_initial_selection.c_str())-1); + + //if (data->listbox_selections.find(fname_w) != data->listbox_selections.end()) { + // e->setSelected(data->listbox_selections[fname_w]); + //} + + //m_listboxes.push_back(std::pair<FieldSpec,gui::IGUIListBox*>(spec,e)); + m_fields.push_back(spec); + return; + } + errorstream << "Invalid dropdown element(" << parts.size() << "): '" + << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 4) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + + MY_CHECKPOS("pwdfield",0); + MY_CHECKGEOM("pwdfield",1); + + v2s32 pos; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= 15; + geom.Y = 30; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + label = unescape_string(label); + + wchar_t* wlabel = 0; + + if (m_use_gettext) { + if (label.length() > 1) + wlabel = wgettext(label.c_str()); + else + wlabel = (wchar_t*) narrow_to_wide("").c_str(); + } + else + wlabel = (wchar_t*) narrow_to_wide(label.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + wlabel, + narrow_to_wide(""), + 258+m_fields.size() ); - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); - m_fields.push_back(spec); + + spec.send = true; + gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid); + Environment->setFocus(e); + + if (label.length() > 1) + { + rect.UpperLeftCorner.Y -= 15; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); } - else if(type == "button" || type == "button_exit") + + e->setPasswordBox(true,L'*'); + + irr::SEvent evt; + evt.KeyInput.Key = KEY_END; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + m_fields.push_back(spec); + if ((m_use_gettext) && (label.length() >1)) + delete[] wlabel; + return; + } + errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseSimpleField(parserData* data,std::vector<std::string> &parts) { + std::string name = parts[0]; + std::string label = parts[1]; + std::string default_val = parts[2]; + + core::rect<s32> rect; + + if(!data->bp_set) + { + rect = core::rect<s32>( + data->screensize.X/2 - 580/2, + data->screensize.Y/2 - 300/2, + data->screensize.X/2 + 580/2, + data->screensize.Y/2 + 300/2 + ); + DesiredRect = rect; + recalculateAbsolutePosition(false); + data->basepos = getBasePos(); + data->bp_set = 1; + } + else if(data->bp_set == 2) + errorstream<<"WARNING: invalid use of unpositioned \"field\" in inventory"<<std::endl; + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.Y = ((m_fields.size()+2)*60); + v2s32 size = DesiredRect.getSize(); + + rect = core::rect<s32>(size.X/2-150, pos.Y, (size.X/2-150)+300, pos.Y+30); + + + if(m_form_src) + default_val = m_form_src->resolveText(default_val); + + default_val = unescape_string(default_val); + label = unescape_string(label); + + wchar_t* wlabel = 0; + + if (m_use_gettext) { + if (label.length() > 1) + wlabel = wgettext(label.c_str()); + else + wlabel = (wchar_t*) narrow_to_wide("").c_str(); + } + else + wlabel = (wchar_t*) narrow_to_wide(label.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + wlabel, + narrow_to_wide(default_val.c_str()), + 258+m_fields.size() + ); + + if (name == "") + { + // spec field id to 0, this stops submit searching for a value that isn't there + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + } + else + { + spec.send = true; + gui::IGUIEditBox *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + Environment->setFocus(e); + + irr::SEvent evt; + evt.KeyInput.Key = KEY_END; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + + if (label.length() > 1) { - v2s32 pos = padding; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X); - pos.Y += (stof(f.next_esc(";")) * (float)imgsize.Y)/2; - - rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15); - - std::string fname = f.next_esc(";"); - std::string flabel = f.next_esc("]"); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl; - - flabel = unescape_string(flabel); - - FieldSpec spec = FieldSpec( - narrow_to_wide(fname.c_str()), - narrow_to_wide(flabel.c_str()), - narrow_to_wide(""), - 258+m_fields.size() - ); - spec.is_button = true; - if(type == "button_exit") - spec.is_exit = true; - Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); - m_fields.push_back(spec); + rect.UpperLeftCorner.Y -= 15; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); } - else if(type == "image_button" || type == "image_button_exit") + } + if (m_use_gettext && (label.length() > 1)) + delete[] wlabel; + + m_fields.push_back(spec); +} + +void GUIFormSpecMenu::parseTextArea(parserData* data,std::vector<std::string>& parts,std::string type) { + + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + std::string default_val = parts[4]; + + MY_CHECKPOS(type,0); + MY_CHECKGEOM(type,1); + + v2s32 pos; + pos.X = stof(v_pos[0]) * (float) spacing.X; + pos.Y = stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + + if (type == "textarea") + { + geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); + pos.Y += 15; + } + else + { + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= 15; + geom.Y = 30; + } + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of positioned "<<type<<" without a size[] element"<<std::endl; + + if(m_form_src) + default_val = m_form_src->resolveText(default_val); + + + default_val = unescape_string(default_val); + label = unescape_string(label); + + wchar_t* wlabel = 0; + + if (m_use_gettext) { + if (label.length() > 1) + wlabel = wgettext(label.c_str()); + else + wlabel = (wchar_t*) narrow_to_wide("").c_str(); + } + else + wlabel = (wchar_t*) narrow_to_wide(label.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + wlabel, + narrow_to_wide(default_val.c_str()), + 258+m_fields.size() + ); + + if (name == "") + { + // spec field id to 0, this stops submit searching for a value that isn't there + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + } + else + { + spec.send = true; + gui::IGUIEditBox *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + Environment->setFocus(e); + + if (type == "textarea") { - v2s32 pos = padding; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X); - geom.Y = (stof(f.next_esc(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y); - - rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - - std::string fimage = f.next_esc(";"); - std::string fname = f.next_esc(";"); - std::string flabel = f.next_esc("]"); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of image_button without a size[] element"<<std::endl; - - flabel = unescape_string(flabel); - - FieldSpec spec = FieldSpec( - narrow_to_wide(fname.c_str()), - narrow_to_wide(flabel.c_str()), - narrow_to_wide(fimage.c_str()), - 258+m_fields.size() - ); - spec.is_button = true; - if(type == "image_button_exit") - spec.is_exit = true; - - video::ITexture *texture = m_gamedef->tsrc()->getTexture(fimage); - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); - e->setUseAlphaChannel(true); - e->setImage(texture); - e->setPressedImage(texture); - e->setScaleImage(true); - - m_fields.push_back(spec); + e->setMultiLine(true); + e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT); + } else { + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = 0; + evt.KeyInput.Shift = 0; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); } - else if(type == "item_image_button") + + if (label.length() > 1) { - v2s32 pos = padding; - pos.X += stof(f.next_esc(",")) * (float)spacing.X; - pos.Y += stof(f.next_esc(";")) * (float)spacing.Y; - v2s32 geom; - geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X); - geom.Y = (stof(f.next_esc(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y); - rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - std::string fimage = f.next_esc(";"); - std::string fname = f.next_esc(";"); - std::string flabel = f.next_esc("]"); - if(bp_set != 2) - errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl; - IItemDefManager *idef = m_gamedef->idef(); - ItemStack item; - item.deSerialize(fimage, idef); - video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); - std::string tooltip = item.getDefinition(idef).description; - flabel = unescape_string(flabel); - FieldSpec spec = FieldSpec( - narrow_to_wide(fname.c_str()), - narrow_to_wide(flabel.c_str()), - narrow_to_wide(fimage.c_str()), - 258+m_fields.size() - ); - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); - e->setUseAlphaChannel(true); - e->setImage(texture); - e->setPressedImage(texture); - e->setScaleImage(true); - spec.is_button = true; - rect+=basepos-padding; - spec.rect=rect; - if (tooltip!="") - spec.tooltip=tooltip; - m_fields.push_back(spec); + rect.UpperLeftCorner.Y -= 15; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); } + } + if (m_use_gettext && (label.length() > 1)) + delete[] wlabel; + m_fields.push_back(spec); +} + +void GUIFormSpecMenu::parseField(parserData* data,std::string element,std::string type) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 3) { + parseSimpleField(data,parts); + return; + } + + if (parts.size() == 5) { + parseTextArea(data,parts,type); + return; + } + errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 2) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string text = parts[1]; + + MY_CHECKPOS("label",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15)); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl; + + text = unescape_string(text); + + wchar_t* wlabel = 0; + + if (m_use_gettext) + wlabel = wgettext(text.c_str()); else - { - // Ignore others - std::string ts = f.next_esc("]"); - infostream<<"Unknown DrawSpec: type="<<type<<", data=\""<<ts<<"\"" - <<std::endl; + wlabel = (wchar_t*) narrow_to_wide(text.c_str()).c_str(); + + FieldSpec spec = FieldSpec( + narrow_to_wide(""), + wlabel, + narrow_to_wide(""), + 258+m_fields.size() + ); + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + m_fields.push_back(spec); + if (m_use_gettext) + delete[] wlabel; + return; + } + errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 2) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string text = parts[1]; + + MY_CHECKPOS("vertlabel",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+15, pos.Y+300); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl; + + text = unescape_string(text); + std::string label = ""; + + if (m_use_gettext) { + const char* toset = gettext(text.c_str()); + + text = std::string(toset); + } + + for (unsigned int i=0; i < text.length(); i++) { + label += text.c_str()[i]; + label += "\n"; + } + + FieldSpec spec = FieldSpec( + narrow_to_wide(""), + narrow_to_wide(label.c_str()), + narrow_to_wide(""), + 258+m_fields.size() + ); + gui::IGUIStaticText *t = + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,std::string type) { + std::vector<std::string> parts = split(element,';'); + + if ((parts.size() == 5) || (parts.size() == 7)) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string image_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; + + MY_CHECKPOS("imagebutton",0); + MY_CHECKGEOM("imagebutton",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y); + + bool noclip = false; + bool drawborder = true; + + if ((parts.size() == 7)) { + if (parts[5] == "true") + noclip = true; + + if (parts[6] == "false") + drawborder = false; + } + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl; + + label = unescape_string(label); + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + narrow_to_wide(label.c_str()), + narrow_to_wide(image_name.c_str()), + 258+m_fields.size() + ); + spec.ftype = f_Button; + if(type == "image_button_exit") + spec.is_exit = true; + + video::ITexture *texture = 0; + //if there's no gamedef specified try to get direct + //TODO check for possible texture leak + if (m_gamedef != 0) + texture = m_gamedef->tsrc()->getTexture(image_name); + else { + if (fs::PathExists(image_name)) { + texture = Environment->getVideoDriver()->getTexture(image_name.c_str()); + m_Textures.push_back(texture); + } + } + + gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + e->setUseAlphaChannel(true); + e->setImage(texture); + e->setPressedImage(texture); + e->setScaleImage(true); + e->setNotClipped(noclip); + e->setDrawBorder(drawborder); + + m_fields.push_back(spec); + return; + } + + errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if ((parts.size() == 4) || (parts.size() == 6)) { + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = parts[1]; + std::vector<std::string> buttons = split(parts[2],','); + std::string str_index = parts[3]; + bool show_background = true; + bool show_border = true; + int tab_index = stoi(str_index) -1; + + MY_CHECKPOS("tabheader",0); + + if (parts.size() == 6) { + if (parts[4] == "true") + show_background = false; + if (parts[5] == "false") + show_border = false; + } + + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + narrow_to_wide(""), + narrow_to_wide(""), + 258+m_fields.size() + ); + + spec.ftype = f_TabHeader; + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + v2s32 geom; + geom.X = data->screensize.Y; + geom.Y = 30; + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + gui::IGUITabControl *e = Environment->addTabControl(rect,this,show_background,show_border,spec.fid); + + e->setNotClipped(true); + + for (unsigned int i=0; i< buttons.size(); i++) { + wchar_t* wbutton = 0; + + if (m_use_gettext) + wbutton = wgettext(buttons[i].c_str()); + else + wbutton = (wchar_t*) narrow_to_wide(buttons[i].c_str()).c_str(); + + e->addTab(wbutton,-1); + + if (m_use_gettext) + delete[] wbutton; } + + if ((tab_index >= 0) && + (buttons.size() < INT_MAX) && + (tab_index < (int) buttons.size())) + e->setActiveTab(tab_index); + + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid TabHeader element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) { + + if (m_gamedef == 0) { + errorstream<<"WARNING: invalid use of item_image_button with m_gamedef==0"<<std::endl; + return; + } + + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 5) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string item_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; + + MY_CHECKPOS("itemimagebutton",0); + MY_CHECKGEOM("itemimagebutton",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y); + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(data->bp_set != 2) + errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl; + + IItemDefManager *idef = m_gamedef->idef(); + ItemStack item; + item.deSerialize(item_name, idef); + video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); + std::string tooltip = item.getDefinition(idef).description; + + label = unescape_string(label); + FieldSpec spec = FieldSpec( + narrow_to_wide(name.c_str()), + narrow_to_wide(label.c_str()), + narrow_to_wide(item_name.c_str()), + 258+m_fields.size() + ); + + gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + e->setUseAlphaChannel(true); + e->setImage(texture); + e->setPressedImage(texture); + e->setScaleImage(true); + spec.ftype = f_Button; + rect+=data->basepos-padding; + spec.rect=rect; + if (tooltip!="") + spec.tooltip=tooltip; + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseBox(parserData* data,std::string element) { + std::vector<std::string> parts = split(element,';'); + + if (parts.size() == 3) { + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string color_str = parts[2]; + + MY_CHECKPOS("box",0); + MY_CHECKGEOM("box",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + bool valid_color = false; + + irr::video::SColor color = getColor(color_str,valid_color); + + if (valid_color) { + BoxDrawSpec spec(pos,geom,color); + + m_boxes.push_back(spec); + } + else { + errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; + } + return; + } + errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseElement(parserData* data,std::string element) { + //some prechecks + if (element == "") + return; + + std::vector<std::string> parts = split(element,'[', true); + + if (parts.size() != 2) + return; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if ((type == "size") || (type == "invsize")){ + parseSize(data,description); + return; + } + + if (type == "list") { + parseList(data,description); + return; + } + + if (type == "checkbox") { + parseCheckbox(data,description); + return; + } + + if (type == "image") { + parseImage(data,description); + return; + } + + if (type == "item_image") { + parseItemImage(data,description); + return; + } + + if ((type == "button") || (type == "button_exit")) { + parseButton(data,description,type); + return; + } + + if (type == "background") { + parseBackground(data,description); + return; + } + + if (type == "textlist"){ + parseTextList(data,description); + return; + } + + if (type == "dropdown"){ + parseDropDown(data,description); + return; + } + + if (type == "pwdfield") { + parsePwdField(data,description); + return; + } + + if ((type == "field") || (type == "textarea")){ + parseField(data,description,type); + return; + } + + if (type == "label") { + parseLabel(data,description); + return; + } + + if (type == "vertlabel") { + parseVertLabel(data,description); + return; + } + + if (type == "item_image_button") { + parseItemImageButton(data,description); + return; + } + + if ((type == "image_button") || (type == "image_button_exit")) { + parseImageButton(data,description,type); + return; + } + + if (type == "tabheader") { + parseTabHeader(data,description); + return; + } + + if (type == "box") { + parseBox(data,description); + return; + } + + // Ignore others + infostream + << "Unknown DrawSpec: type="<<type<<", data=\""<<description<<"\"" + <<std::endl; +} + + + +void GUIFormSpecMenu::regenerateGui(v2u32 screensize) +{ + parserData mydata; + + //preserve listboxes + for (unsigned int i = 0; i < m_listboxes.size(); i++) { + int selection = m_listboxes[i].second->getSelected(); + if (selection != -1) { + std::wstring listboxname = m_listboxes[i].first.fname; + mydata.listbox_selections[listboxname] = selection; + } + } + + // Remove children + removeChildren(); + + mydata.size= v2s32(100,100); + mydata.helptext_h = 15; + mydata.screensize = screensize; + + // Base position of contents of form + mydata.basepos = getBasePos(); + + // State of basepos, 0 = not set, 1= set by formspec, 2 = set by size[] element + // Used to adjust form size automatically if needed + // A proceed button is added if there is no size[] element + mydata.bp_set = 0; + + + /* Convert m_init_draw_spec to m_inventorylists */ + + m_inventorylists.clear(); + m_images.clear(); + m_backgrounds.clear(); + m_itemimages.clear(); + m_listboxes.clear(); + m_checkboxes.clear(); + m_fields.clear(); + m_boxes.clear(); + + + std::vector<std::string> elements = split(m_formspec_string,']',true); + + for (unsigned int i=0;i< elements.size();i++) { + parseElement(&mydata,elements[i]); } // If there's inventory, put the usage string at the bottom if (m_inventorylists.size()) { changeCtype(""); - core::rect<s32> rect(0, 0, size.X-padding.X*2, helptext_h); - rect = rect + v2s32(size.X/2 - rect.getWidth()/2, - size.Y-rect.getHeight()-5); + core::rect<s32> rect(0, 0, mydata.size.X-padding.X*2, mydata.helptext_h); + rect = rect + v2s32((mydata.size.X/2 - mydata.rect.getWidth()/2) +5, + mydata.size.Y-5-mydata.helptext_h); const wchar_t *text = wgettext("Left click: Move all items, Right click: Move single item"); Environment->addStaticText(text, rect, false, true, this, 256); delete[] text; changeCtype("C"); } // If there's fields, add a Proceed button - if (m_fields.size() && bp_set != 2) + if (m_fields.size() && mydata.bp_set != 2) { // if the size wasn't set by an invsize[] or size[] adjust it now to fit all the fields - rect = core::rect<s32>( - screensize.X/2 - 580/2, - screensize.Y/2 - 300/2, - screensize.X/2 + 580/2, - screensize.Y/2 + 240/2+(m_fields.size()*60) + mydata.rect = core::rect<s32>( + mydata.screensize.X/2 - 580/2, + mydata.screensize.Y/2 - 300/2, + mydata.screensize.X/2 + 580/2, + mydata.screensize.Y/2 + 240/2+(m_fields.size()*60) ); - DesiredRect = rect; + DesiredRect = mydata.rect; recalculateAbsolutePosition(false); - basepos = getBasePos(); + mydata.basepos = getBasePos(); changeCtype(""); { - v2s32 pos = basepos; + v2s32 pos = mydata.basepos; pos.Y = ((m_fields.size()+2)*60); v2s32 size = DesiredRect.getSize(); - rect = core::rect<s32>(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y+30); + mydata.rect = core::rect<s32>(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y+30); wchar_t* text = wgettext("Proceed"); - Environment->addButton(rect, this, 257, text); + Environment->addButton(mydata.rect, this, 257, text); delete[] text; } changeCtype("C"); @@ -684,8 +1614,8 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase) for(s32 i=0; i<s.geom.X*s.geom.Y; i++) { - u32 item_i = i + s.start_item_i; - if(item_i >= ilist->getSize()) + s32 item_i = i + s.start_item_i; + if(item_i >= (s32) ilist->getSize()) break; s32 x = (i%s.geom.X) * spacing.X; s32 y = (i/s.geom.X) * spacing.Y; @@ -804,38 +1734,88 @@ void GUIFormSpecMenu::drawMenu() for(u32 i=0; i<m_backgrounds.size(); i++) { const ImageDrawSpec &spec = m_backgrounds[i]; - video::ITexture *texture = - m_gamedef->tsrc()->getTexture(spec.name); - // Image size on screen - core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); - // Image rectangle on screen - core::rect<s32> rect = imgrect + spec.pos; - const video::SColor color(255,255,255,255); - const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, - core::rect<s32>(core::position2d<s32>(0,0), - core::dimension2di(texture->getOriginalSize())), - NULL/*&AbsoluteClippingRect*/, colors, true); + video::ITexture *texture = 0; + + if (m_gamedef != 0) + texture = m_gamedef->tsrc()->getTexture(spec.name); + else + { + texture = driver->getTexture(spec.name.c_str()); + m_Textures.push_back(texture); + } + + if (texture != 0) { + // Image size on screen + core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); + // Image rectangle on screen + core::rect<s32> rect = imgrect + spec.pos; + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect<s32>(core::position2d<s32>(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + else { + errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl; + errorstream << "\t" << spec.name << std::endl; + } } /* + Draw Boxes + */ + for(u32 i=0; i<m_boxes.size(); i++) + { + const BoxDrawSpec &spec = m_boxes[i]; + + irr::video::SColor todraw = spec.color; + + todraw.setAlpha(140); + + core::rect<s32> rect(spec.pos.X,spec.pos.Y, + spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y); + + driver->draw2DRectangle(todraw, rect, 0); + } + /* Draw images */ for(u32 i=0; i<m_images.size(); i++) { const ImageDrawSpec &spec = m_images[i]; - video::ITexture *texture = - m_gamedef->tsrc()->getTexture(spec.name); - // Image size on screen - core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); - // Image rectangle on screen - core::rect<s32> rect = imgrect + spec.pos; - const video::SColor color(255,255,255,255); - const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, - core::rect<s32>(core::position2d<s32>(0,0), - core::dimension2di(texture->getOriginalSize())), - NULL/*&AbsoluteClippingRect*/, colors, true); + video::ITexture *texture = 0; + + if (m_gamedef != 0) + texture = m_gamedef->tsrc()->getTexture(spec.name); + else + { + texture = driver->getTexture(spec.name.c_str()); + m_Textures.push_back(texture); + } + if (texture != 0) { + const core::dimension2d<u32>& img_origsize = texture->getOriginalSize(); + // Image size on screen + core::rect<s32> imgrect; + + if (spec.scale) + imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y); + else { + + imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height); + } + // Image rectangle on screen + core::rect<s32> rect = imgrect + spec.pos; + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect<s32>(core::position2d<s32>(0,0),img_origsize), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + else { + errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl; + errorstream << "\t" << spec.name << std::endl; + } } /* @@ -843,6 +1823,9 @@ void GUIFormSpecMenu::drawMenu() */ for(u32 i=0; i<m_itemimages.size(); i++) { + if (m_gamedef == 0) + break; + const ImageDrawSpec &spec = m_itemimages[i]; IItemDefManager *idef = m_gamedef->idef(); ItemStack item; @@ -1025,24 +2008,77 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() return ItemStack(); } -void GUIFormSpecMenu::acceptInput() +void GUIFormSpecMenu::acceptInput(int eventtype) { if(m_text_dst) { std::map<std::string, std::string> fields; - gui::IGUIElement *e; + for(u32 i=0; i<m_fields.size(); i++) { const FieldSpec &s = m_fields[i]; if(s.send) { - if(s.is_button) + if(s.ftype == f_Button) { fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str()); } + else if(s.ftype == f_ListBox) { + std::stringstream ss; + if (eventtype == gui::EGET_LISTBOX_CHANGED) { + ss << "CHG:"; + } + else { + ss << "DCL:"; + } + ss << (getListboxIndex(wide_to_narrow(s.fname.c_str()))+1); + fields[wide_to_narrow(s.fname.c_str())] = ss.str(); + } + else if(s.ftype == f_DropDown) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUIComboBox *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) { + e = static_cast<gui::IGUIComboBox*>(element); + } + fields[wide_to_narrow(s.fname.c_str())] = + wide_to_narrow(e->getItem(e->getSelected())); + } + else if (s.ftype == f_TabHeader) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUITabControl *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) { + e = static_cast<gui::IGUITabControl*>(element); + } + + if (e != 0) { + std::stringstream ss; + ss << (e->getActiveTab() +1); + fields[wide_to_narrow(s.fname.c_str())] = ss.str(); + } + } + else if (s.ftype == f_CheckBox) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUICheckBox *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) { + e = static_cast<gui::IGUICheckBox*>(element); + } + + if (e != 0) { + if (e->isChecked()) + fields[wide_to_narrow(s.fname.c_str())] = "true"; + else + fields[wide_to_narrow(s.fname.c_str())] = "false"; + } + } else { - e = getElementFromId(s.fid); + IGUIElement* e = getElementFromId(s.fid); if(e != NULL) { fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(e->getText()); @@ -1050,6 +2086,7 @@ void GUIFormSpecMenu::acceptInput() } } } + m_text_dst->gotText(fields); } } @@ -1062,13 +2099,20 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (event.KeyInput.PressedDown && (kp == EscapeKey || kp == getKeySetting("keymap_inventory"))) { - quitMenu(); + if (m_allowclose) + quitMenu(); + else + m_text_dst->gotText(narrow_to_wide("MenuQuit")); return true; } if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) { acceptInput(); - quitMenu(); + + if (m_allowclose) + quitMenu(); + else + m_text_dst->gotText(narrow_to_wide("KeyEnter")); return true; } } @@ -1360,6 +2404,27 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if(event.EventType==EET_GUI_EVENT) { + + if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED + && isVisible()) + { + // find the element that was clicked + for(u32 i=0; i<m_fields.size(); i++) + { + FieldSpec &s = m_fields[i]; + // if its a button, set the send field so + // lua knows which button was pressed + if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) + { + s.send = true; + acceptInput(); + s.send = false; + // Restore focus to the full form + Environment->setFocus(this); + return true; + } + } + } if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { @@ -1371,28 +2436,37 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) return true; } } - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) + if((event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) || + (event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED)) { - switch(event.GUIEvent.Caller->getID()) - { - case 257: + unsigned int btn_id = event.GUIEvent.Caller->getID(); + + if (btn_id == 257) { acceptInput(); - quitMenu(); + if (m_allowclose) + quitMenu(); + else + m_text_dst->gotText(narrow_to_wide("ExitButton")); // quitMenu deallocates menu return true; } + // find the element that was clicked for(u32 i=0; i<m_fields.size(); i++) { FieldSpec &s = m_fields[i]; // if its a button, set the send field so // lua knows which button was pressed - if (s.is_button && s.fid == event.GUIEvent.Caller->getID()) + if (((s.ftype == f_Button) || (s.ftype == f_CheckBox)) && + (s.fid == event.GUIEvent.Caller->getID())) { s.send = true; acceptInput(); if(s.is_exit){ - quitMenu(); + if (m_allowclose) + quitMenu(); + else + m_text_dst->gotText(narrow_to_wide("ExitButton")); return true; }else{ s.send = false; @@ -1408,13 +2482,103 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if(event.GUIEvent.Caller->getID() > 257) { acceptInput(); - quitMenu(); + if (m_allowclose) + quitMenu(); + else + m_text_dst->gotText(narrow_to_wide("EditBoxEnter")); // quitMenu deallocates menu return true; } } + + if((event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) || + (event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED)) + { + int current_id = event.GUIEvent.Caller->getID(); + if(current_id > 257) + { + // find the element that was clicked + for(u32 i=0; i<m_fields.size(); i++) + { + FieldSpec &s = m_fields[i]; + // if its a button, set the send field so + // lua knows which button was pressed + if ((s.ftype == f_ListBox) && (s.fid == current_id)) + { + s.send = true; + acceptInput(event.GUIEvent.EventType); + s.send=false; + // Restore focus to the full form + Environment->setFocus(this); + } + } + return true; + } + } } return Parent ? Parent->OnEvent(event) : false; } +irr::video::SColor GUIFormSpecMenu::getColor(std::string color,bool& valid_color) { + + if (color == "YLW") { + valid_color = true; + return irr::video::SColor(255,255,255,0); + } + + if (color == "GRN") { + valid_color = true; + return irr::video::SColor(255,34,249,34); + } + + if (color == "LIM") { + valid_color = true; + return irr::video::SColor(255,50,205,50); + } + + if (color == "RED") { + valid_color = true; + return irr::video::SColor(255,255,0,0); + } + + if (color == "ORN") { + valid_color = true; + return irr::video::SColor(255,255,140,0); + } + + if (color == "BLU") { + valid_color = true; + return irr::video::SColor(255,0,0,255); + } + + if (color == "CYN") { + valid_color = true; + return irr::video::SColor(255,0,255,255); + } + + if (color == "BLK") { + valid_color = true; + return irr::video::SColor(255,0,0,0); + } + + if (color == "BRN") { + valid_color = true; + return irr::video::SColor(255,139,69,19); + } + + if (color == "WHT") { + valid_color = true; + return irr::video::SColor(255,255,255,255); + } + + if (color == "GRY") { + valid_color = true; + return irr::video::SColor(255,205,201,201); + } + + valid_color = false; + + return irr::video::SColor(0,0,0,0); +} + diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index ae985adde..f5a273668 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef GUIINVENTORYMENU_HEADER #define GUIINVENTORYMENU_HEADER +#include <utility> + #include "irrlichttypes_extrabloated.h" #include "inventory.h" #include "inventorymanager.h" @@ -29,6 +31,15 @@ with this program; if not, write to the Free Software Foundation, Inc., class IGameDef; class InventoryManager; +typedef enum { + f_Button, + f_ListBox, + f_TabHeader, + f_CheckBox, + f_DropDown, + f_Unknown +} FormspecFieldType; + struct TextDest { virtual ~TextDest() {}; @@ -113,10 +124,19 @@ class GUIFormSpecMenu : public GUIModalMenu pos(a_pos), geom(a_geom) { + scale = true; + } + ImageDrawSpec(const std::string &a_name, + v2s32 a_pos): + name(a_name), + pos(a_pos) + { + scale = false; } std::string name; v2s32 pos; v2s32 geom; + bool scale; }; struct FieldSpec @@ -131,7 +151,7 @@ class GUIFormSpecMenu : public GUIModalMenu fid(id) { send = false; - is_button = false; + ftype = f_Unknown; is_exit = false; tooltip=""; } @@ -140,12 +160,24 @@ class GUIFormSpecMenu : public GUIModalMenu std::wstring fdefault; int fid; bool send; - bool is_button; + FormspecFieldType ftype; bool is_exit; core::rect<s32> rect; std::string tooltip; }; + struct BoxDrawSpec { + BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color): + pos(a_pos), + geom(a_geom), + color(a_color) + { + } + v2s32 pos; + v2s32 geom; + irr::video::SColor color; + }; + public: GUIFormSpecMenu(irr::IrrlichtDevice* dev, gui::IGUIElement* parent, s32 id, @@ -153,6 +185,7 @@ public: InventoryManager *invmgr, IGameDef *gamedef ); + ~GUIFormSpecMenu(); void setFormSpec(const std::string &formspec_string, @@ -175,6 +208,20 @@ public: m_text_dst = text_dst; } + void allowClose(bool value) + { + m_allowclose = value; + } + + void useGettext(bool value) { + m_use_gettext = true; + } + + void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0)) { + m_lock = lock; + m_lockscreensize = basescreensize; + } + void removeChildren(); /* Remove and re-add (or reposition) stuff @@ -188,18 +235,21 @@ public: void updateSelectedItem(); ItemStack verifySelectedItem(); - void acceptInput(); + void acceptInput(int evttype=-1); bool OnEvent(const SEvent& event); + int getListboxIndex(std::string listboxname); + protected: v2s32 getBasePos() const { - return padding + AbsoluteRect.UpperLeftCorner; + return padding + offset + AbsoluteRect.UpperLeftCorner; } v2s32 padding; v2s32 spacing; v2s32 imgsize; + v2s32 offset; irr::IrrlichtDevice* m_device; InventoryManager *m_invmgr; @@ -214,7 +264,10 @@ protected: std::vector<ImageDrawSpec> m_backgrounds; std::vector<ImageDrawSpec> m_images; std::vector<ImageDrawSpec> m_itemimages; + std::vector<BoxDrawSpec> m_boxes; std::vector<FieldSpec> m_fields; + std::vector<std::pair<FieldSpec,gui::IGUIListBox*> > m_listboxes; + std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes; ItemSpec *m_selected_item; u32 m_selected_amount; @@ -228,6 +281,74 @@ protected: v2s32 m_pointer; gui::IGUIStaticText *m_tooltip_element; + + bool m_allowclose; + bool m_use_gettext; + bool m_lock; + v2u32 m_lockscreensize; +private: + typedef struct { + v2s32 size; + s32 helptext_h; + core::rect<s32> rect; + v2s32 basepos; + int bp_set; + v2u32 screensize; + std::map<std::wstring,int> listbox_selections; + } parserData; + + std::vector<video::ITexture *> m_Textures; + + void parseElement(parserData* data,std::string element); + + void parseSize(parserData* data,std::string element); + void parseList(parserData* data,std::string element); + void parseCheckbox(parserData* data,std::string element); + void parseImage(parserData* data,std::string element); + void parseItemImage(parserData* data,std::string element); + void parseButton(parserData* data,std::string element,std::string typ); + void parseBackground(parserData* data,std::string element); + void parseTextList(parserData* data,std::string element); + void parseDropDown(parserData* data,std::string element); + void parsePwdField(parserData* data,std::string element); + void parseField(parserData* data,std::string element,std::string type); + void parseSimpleField(parserData* data,std::vector<std::string> &parts); + void parseTextArea(parserData* data,std::vector<std::string>& parts,std::string type); + void parseLabel(parserData* data,std::string element); + void parseVertLabel(parserData* data,std::string element); + void parseImageButton(parserData* data,std::string element,std::string type); + void parseItemImageButton(parserData* data,std::string element); + void parseTabHeader(parserData* data,std::string element); + void parseBox(parserData* data,std::string element); + + irr::video::SColor getColor(std::string color,bool& valid_color); +}; + +class FormspecFormSource: public IFormSource +{ +public: + FormspecFormSource(std::string formspec,FormspecFormSource** game_formspec) + { + m_formspec = formspec; + m_game_formspec = game_formspec; + } + + ~FormspecFormSource() + { + *m_game_formspec = 0; + } + + void setForm(std::string formspec) { + m_formspec = formspec; + } + + std::string getForm() + { + return m_formspec; + } + + std::string m_formspec; + FormspecFormSource** m_game_formspec; }; #endif diff --git a/src/guiLuaApi.cpp b/src/guiLuaApi.cpp new file mode 100644 index 000000000..bc02c062d --- /dev/null +++ b/src/guiLuaApi.cpp @@ -0,0 +1,1067 @@ +/* +Minetest +Copyright (C) 2013 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern "C" { +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +} +#include "porting.h" +#include "guiMainMenu.h" +#include "subgame.h" +#include "guiKeyChangeMenu.h" +#include "guiFileSelectMenu.h" +#include "main.h" +#include "settings.h" +#include "filesys.h" +#include "convert_json.h" + + +#include "IFileArchive.h" +#include "IFileSystem.h" + +#include "guiLuaApi.h" +#include "guiEngine.h" + +#define API_FCT(name) registerFunction(L,#name,l_##name,top) + +void guiLuaApi::initialize(lua_State* L,GUIEngine* engine) +{ + lua_pushlightuserdata(L, engine); + lua_setfield(L, LUA_REGISTRYINDEX, "engine"); + + lua_pushstring(L, DIR_DELIM); + lua_setglobal(L, "DIR_DELIM"); + + lua_newtable(L); + lua_setglobal(L, "gamedata"); + + lua_newtable(L); + lua_setglobal(L, "engine"); + + lua_getglobal(L, "engine"); + int top = lua_gettop(L); + + bool retval = true; + + //add api functions + retval &= API_FCT(update_formspec); + retval &= API_FCT(set_clouds); + retval &= API_FCT(get_textlist_index); + retval &= API_FCT(get_worlds); + retval &= API_FCT(get_games); + retval &= API_FCT(start); + retval &= API_FCT(close); + retval &= API_FCT(get_favorites); + retval &= API_FCT(show_keys_menu); + retval &= API_FCT(setting_set); + retval &= API_FCT(setting_get); + retval &= API_FCT(setting_getbool); + retval &= API_FCT(setting_setbool); + retval &= API_FCT(create_world); + retval &= API_FCT(delete_world); + retval &= API_FCT(delete_favorite); + retval &= API_FCT(set_background); + retval &= API_FCT(set_topleft_text); + retval &= API_FCT(get_modpath); + retval &= API_FCT(get_gamepath); + retval &= API_FCT(get_dirlist); + retval &= API_FCT(create_dir); + retval &= API_FCT(delete_dir); + retval &= API_FCT(copy_dir); + retval &= API_FCT(extract_zip); + retval &= API_FCT(get_scriptdir); + retval &= API_FCT(show_file_open_dialog); + retval &= API_FCT(get_version); + retval &= API_FCT(download_file); + retval &= API_FCT(get_modstore_details); + retval &= API_FCT(get_modstore_list); + + if (!retval) { + //TODO show error + } +} + +/******************************************************************************/ +bool guiLuaApi::registerFunction( lua_State* L, + const char* name, + lua_CFunction fct, + int top + ) +{ + lua_pushstring(L,name); + lua_pushcfunction(L,fct); + lua_settable(L, top); + + return true; +} + +/******************************************************************************/ +GUIEngine* guiLuaApi::get_engine(lua_State *L) +{ + // Get server from registry + lua_getfield(L, LUA_REGISTRYINDEX, "engine"); + GUIEngine* sapi_ptr = (GUIEngine*) lua_touserdata(L, -1); + lua_pop(L, 1); + return sapi_ptr; +} + +/******************************************************************************/ +std::string guiLuaApi::getTextData(lua_State *L, std::string name) +{ + lua_getglobal(L, "gamedata"); + + lua_getfield(L, -1, name.c_str()); + + if(lua_isnil(L, -1)) + return ""; + + return luaL_checkstring(L, -1); +} + +/******************************************************************************/ +int guiLuaApi::getIntegerData(lua_State *L, std::string name,bool& valid) +{ + lua_getglobal(L, "gamedata"); + + lua_getfield(L, -1, name.c_str()); + + if(lua_isnil(L, -1)) { + valid = false; + return -1; + } + + valid = true; + return luaL_checkinteger(L, -1); +} + +/******************************************************************************/ +int guiLuaApi::getBoolData(lua_State *L, std::string name,bool& valid) +{ + lua_getglobal(L, "gamedata"); + + lua_getfield(L, -1, name.c_str()); + + if(lua_isnil(L, -1)) { + valid = false; + return false; + } + + valid = true; + return lua_toboolean(L, -1); +} + +/******************************************************************************/ +int guiLuaApi::l_update_formspec(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + if (engine->m_startgame) + return 0; + + //read formspec + std::string formspec(luaL_checkstring(L, 1)); + + if (engine->m_formspecgui != 0) { + engine->m_formspecgui->setForm(formspec); + } + + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_start(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + //update c++ gamedata from lua table + + bool valid = false; + + + engine->m_data->selected_world = getIntegerData(L, "selected_world",valid) -1; + engine->m_data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); + engine->m_data->name = getTextData(L,"playername"); + engine->m_data->password = getTextData(L,"password"); + engine->m_data->address = getTextData(L,"address"); + engine->m_data->port = getTextData(L,"port"); + + //close menu next time + engine->m_startgame = true; + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_close(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + engine->m_data->kill = true; + + //close menu next time + engine->m_startgame = true; + engine->m_menu->quitMenu(); + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_set_background(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::string backgroundlevel(luaL_checkstring(L, 1)); + std::string texturename(luaL_checkstring(L, 2)); + + bool retval = false; + + if (backgroundlevel == "background") { + retval |= engine->setTexture(TEX_LAYER_BACKGROUND,texturename); + } + + if (backgroundlevel == "overlay") { + retval |= engine->setTexture(TEX_LAYER_OVERLAY,texturename); + } + + if (backgroundlevel == "header") { + retval |= engine->setTexture(TEX_LAYER_HEADER,texturename); + } + + if (backgroundlevel == "footer") { + retval |= engine->setTexture(TEX_LAYER_FOOTER,texturename); + } + + lua_pushboolean(L,retval); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_set_clouds(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + bool value = lua_toboolean(L,1); + + engine->m_clouds_enabled = value; + + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_get_textlist_index(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::string listboxname(luaL_checkstring(L, 1)); + + int selection = engine->m_menu->getListboxIndex(listboxname); + + if (selection >= 0) + selection++; + + lua_pushinteger(L, selection); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_worlds(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::vector<WorldSpec> worlds = getAvailableWorlds(); + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + + for (unsigned int i = 0; i < worlds.size(); i++) + { + lua_pushnumber(L,index); + + lua_newtable(L); + int top_lvl2 = lua_gettop(L); + + lua_pushstring(L,"path"); + lua_pushstring(L,worlds[i].path.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"name"); + lua_pushstring(L,worlds[i].name.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"gameid"); + lua_pushstring(L,worlds[i].gameid.c_str()); + lua_settable(L, top_lvl2); + + lua_settable(L, top); + index++; + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_games(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::vector<SubgameSpec> games = getAvailableGames(); + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + + for (unsigned int i = 0; i < games.size(); i++) + { + lua_pushnumber(L,index); + lua_newtable(L); + int top_lvl2 = lua_gettop(L); + + lua_pushstring(L,"id"); + lua_pushstring(L,games[i].id.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"path"); + lua_pushstring(L,games[i].path.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"gamemods_path"); + lua_pushstring(L,games[i].gamemods_path.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"name"); + lua_pushstring(L,games[i].name.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"menuicon_path"); + lua_pushstring(L,games[i].menuicon_path.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"addon_mods_paths"); + lua_newtable(L); + int table2 = lua_gettop(L); + int internal_index=1; + for (std::set<std::string>::iterator iter = games[i].addon_mods_paths.begin(); + iter != games[i].addon_mods_paths.end(); iter++) { + lua_pushnumber(L,internal_index); + lua_pushstring(L,(*iter).c_str()); + lua_settable(L, table2); + internal_index++; + } + lua_settable(L, top_lvl2); + lua_settable(L, top); + index++; + } + return 1; +} +/******************************************************************************/ +int guiLuaApi::l_get_modstore_details(lua_State *L) +{ + const char *modid = luaL_checkstring(L, 1); + + if (modid != 0) { + Json::Value details; + std::string url = ""; + try{ + url = g_settings->get("modstore_details_url"); + } + catch(SettingNotFoundException &e) { + lua_pushnil(L); + return 1; + } + + size_t idpos = url.find("*"); + url.erase(idpos,1); + url.insert(idpos,modid); + + details = getModstoreUrl(url); + + ModStoreModDetails current_mod = readModStoreModDetails(details); + + if ( current_mod.valid) { + lua_newtable(L); + int top = lua_gettop(L); + + lua_pushstring(L,"id"); + lua_pushnumber(L,current_mod.id); + lua_settable(L, top); + + lua_pushstring(L,"title"); + lua_pushstring(L,current_mod.title.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"basename"); + lua_pushstring(L,current_mod.basename.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"description"); + lua_pushstring(L,current_mod.description.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"author"); + lua_pushstring(L,current_mod.author.username.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"download_url"); + lua_pushstring(L,current_mod.versions[0].file.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"license"); + lua_pushstring(L,current_mod.license.shortinfo.c_str()); + lua_settable(L, top); + + lua_pushstring(L,"rating"); + lua_pushnumber(L,current_mod.rating); + lua_settable(L, top); + + //TODO depends + + //TODO softdepends + return 1; + } + } + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_get_modstore_list(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::string listtype = "local"; + + if (!lua_isnone(L,1)) { + listtype = luaL_checkstring(L,1); + } + Json::Value mods; + std::string url = ""; + try{ + url = g_settings->get("modstore_listmods_url"); + } + catch(SettingNotFoundException &e) { + lua_pushnil(L); + return 1; + } + + mods = getModstoreUrl(url); + + std::vector<ModStoreMod> moddata = readModStoreList(mods); + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + + for (unsigned int i = 0; i < moddata.size(); i++) + { + if (moddata[i].valid) { + lua_pushnumber(L,index); + lua_newtable(L); + + int top_lvl2 = lua_gettop(L); + + lua_pushstring(L,"id"); + lua_pushnumber(L,moddata[i].id); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"title"); + lua_pushstring(L,moddata[i].title.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L,"basename"); + lua_pushstring(L,moddata[i].basename.c_str()); + lua_settable(L, top_lvl2); + + lua_settable(L, top); + index++; + } + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_favorites(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::string listtype = "local"; + + if (!lua_isnone(L,1)) { + listtype = luaL_checkstring(L,1); + } + + std::vector<ServerListSpec> servers; +#if USE_CURL + if(listtype == "online") { + servers = ServerList::getOnline(); + } else { + servers = ServerList::getLocal(); + } +#else + servers = ServerList::getLocal(); +#endif + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + + for (unsigned int i = 0; i < servers.size(); i++) + { + lua_pushnumber(L,index); + + lua_newtable(L); + int top_lvl2 = lua_gettop(L); + + if (servers[i]["clients"].asString().size()) { + + const char* clients_raw = servers[i]["clients"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(clients_raw,&endptr,10); + + if ((*clients_raw != 0) && (*endptr == 0)) { + lua_pushstring(L,"clients"); + lua_pushnumber(L,numbervalue); + lua_settable(L, top_lvl2); + } + } + + if (servers[i]["clients_max"].asString().size()) { + + const char* clients_max_raw = servers[i]["clients"].asString().c_str(); + char* endptr = 0; + int numbervalue = strtol(clients_max_raw,&endptr,10); + + if ((*clients_max_raw != 0) && (*endptr == 0)) { + lua_pushstring(L,"clients_max"); + lua_pushnumber(L,numbervalue); + lua_settable(L, top_lvl2); + } + } + + if (servers[i]["version"].asString().size()) { + lua_pushstring(L,"version"); + lua_pushstring(L,servers[i]["version"].asString().c_str()); + lua_settable(L, top_lvl2); + } + + if (servers[i]["password"].asString().size()) { + lua_pushstring(L,"password"); + lua_pushboolean(L,true); + lua_settable(L, top_lvl2); + } + + if (servers[i]["creative"].asString().size()) { + lua_pushstring(L,"creative"); + lua_pushboolean(L,true); + lua_settable(L, top_lvl2); + } + + if (servers[i]["damage"].asString().size()) { + lua_pushstring(L,"damage"); + lua_pushboolean(L,true); + lua_settable(L, top_lvl2); + } + + if (servers[i]["pvp"].asString().size()) { + lua_pushstring(L,"pvp"); + lua_pushboolean(L,true); + lua_settable(L, top_lvl2); + } + + if (servers[i]["description"].asString().size()) { + lua_pushstring(L,"description"); + lua_pushstring(L,servers[i]["description"].asString().c_str()); + lua_settable(L, top_lvl2); + } + + if (servers[i]["name"].asString().size()) { + lua_pushstring(L,"name"); + lua_pushstring(L,servers[i]["name"].asString().c_str()); + lua_settable(L, top_lvl2); + } + + if (servers[i]["address"].asString().size()) { + lua_pushstring(L,"address"); + lua_pushstring(L,servers[i]["address"].asString().c_str()); + lua_settable(L, top_lvl2); + } + + if (servers[i]["port"].asString().size()) { + lua_pushstring(L,"port"); + lua_pushstring(L,servers[i]["port"].asString().c_str()); + lua_settable(L, top_lvl2); + } + + lua_settable(L, top); + index++; + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_delete_favorite(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::vector<ServerListSpec> servers; + + std::string listtype = "local"; + + if (!lua_isnone(L,2)) { + listtype = luaL_checkstring(L,2); + } + + if ((listtype != "local") && + (listtype != "online")) + return 0; + +#if USE_CURL + if(listtype == "online") { + servers = ServerList::getOnline(); + } else { + servers = ServerList::getLocal(); + } +#else + servers = ServerList::getLocal(); +#endif + + int fav_idx = luaL_checkinteger(L,1) -1; + + if ((fav_idx >= 0) && + (fav_idx < (int) servers.size())) { + + ServerList::deleteEntry(servers[fav_idx]); + } + + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_show_keys_menu(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + GUIKeyChangeMenu *kmenu + = new GUIKeyChangeMenu( engine->m_device->getGUIEnvironment(), + engine->m_parent, + -1, + engine->m_menumanager); + kmenu->drop(); + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_setting_set(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + const char *value = luaL_checkstring(L, 2); + g_settings->set(name, value); + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_setting_get(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + try{ + std::string value = g_settings->get(name); + lua_pushstring(L, value.c_str()); + } catch(SettingNotFoundException &e){ + lua_pushnil(L); + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_setting_getbool(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + try{ + bool value = g_settings->getBool(name); + lua_pushboolean(L, value); + } catch(SettingNotFoundException &e){ + lua_pushnil(L); + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_setting_setbool(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + bool value = lua_toboolean(L,2); + + g_settings->setBool(name,value); + + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_create_world(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + const char *name = luaL_checkstring(L, 1); + int gameidx = luaL_checkinteger(L,2) -1; + + std::string path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + + name; + + std::vector<SubgameSpec> games = getAvailableGames(); + + if ((gameidx >= 0) && + (gameidx < (int) games.size())) { + + // Create world if it doesn't exist + if(!initializeWorld(path, games[gameidx].id)){ + lua_pushstring(L, "Failed to initialize world"); + + } + else { + lua_pushnil(L); + } + } + else { + lua_pushstring(L, "Invalid game index"); + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_delete_world(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + int worldidx = luaL_checkinteger(L,1) -1; + + std::vector<WorldSpec> worlds = getAvailableWorlds(); + + if ((worldidx >= 0) && + (worldidx < (int) worlds.size())) { + + WorldSpec spec = worlds[worldidx]; + + std::vector<std::string> paths; + paths.push_back(spec.path); + fs::GetRecursiveSubPaths(spec.path, paths); + + // Delete files + if (!fs::DeletePaths(paths)) { + lua_pushstring(L, "Failed to delete world"); + } + else { + lua_pushnil(L); + } + } + else { + lua_pushstring(L, "Invalid world index"); + } + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_set_topleft_text(lua_State *L) +{ + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + std::string text = ""; + + if (!lua_isnone(L,1) && !lua_isnil(L,1)) + text = luaL_checkstring(L, 1); + + engine->setTopleftText(text); + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_get_modpath(lua_State *L) +{ + //TODO this path may be controversial! + std::string modpath + = fs::RemoveRelativePathComponents(porting::path_share + DIR_DELIM + "mods" + DIR_DELIM); + lua_pushstring(L, modpath.c_str()); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_gamepath(lua_State *L) +{ + std::string gamepath + = fs::RemoveRelativePathComponents(porting::path_share + DIR_DELIM + "games" + DIR_DELIM); + lua_pushstring(L, gamepath.c_str()); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_dirlist(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + bool dironly = lua_toboolean(L, 2); + + std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path); + + unsigned int index = 1; + lua_newtable(L); + int table = lua_gettop(L); + + for (unsigned int i=0;i< dirlist.size(); i++) { + if ((dirlist[i].dir) || (dironly == false)) { + lua_pushnumber(L,index); + lua_pushstring(L,dirlist[i].name.c_str()); + lua_settable(L, table); + index++; + } + } + + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_create_dir(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + + if (guiLuaApi::isMinetestPath(path)) { + lua_pushboolean(L,fs::CreateAllDirs(path)); + return 1; + } + lua_pushboolean(L,false); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_delete_dir(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + + std::string absolute_path = fs::RemoveRelativePathComponents(path); + + if (guiLuaApi::isMinetestPath(absolute_path)) { + lua_pushboolean(L,fs::RecursiveDelete(absolute_path)); + return 1; + } + lua_pushboolean(L,false); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_copy_dir(lua_State *L) { + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + + bool keep_source = true; + + if ((!lua_isnone(L,3)) && + (!lua_isnil(L,3))) { + keep_source = lua_toboolean(L,3); + } + + std::string absolute_destination = fs::RemoveRelativePathComponents(destination); + std::string absolute_source = fs::RemoveRelativePathComponents(source); + + if ((guiLuaApi::isMinetestPath(absolute_source)) && + (guiLuaApi::isMinetestPath(absolute_destination))) { + bool retval = fs::CopyDir(absolute_source,absolute_destination); + + if (retval && (!keep_source)) { + + retval &= fs::RecursiveDelete(absolute_source); + } + lua_pushboolean(L,retval); + return 1; + } + lua_pushboolean(L,false); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_extract_zip(lua_State *L) { + + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + const char *zipfile = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + + std::string absolute_destination = fs::RemoveRelativePathComponents(destination); + + if (guiLuaApi::isMinetestPath(absolute_destination)) { + fs::CreateAllDirs(absolute_destination); + + io::IFileSystem* fs = engine->m_device->getFileSystem(); + + fs->addFileArchive(zipfile,true,false,io::EFAT_ZIP); + + assert(fs->getFileArchiveCount() > 0); + + /**********************************************************************/ + /* WARNING this is not threadsafe!! */ + /**********************************************************************/ + io::IFileArchive* opened_zip = + fs->getFileArchive(fs->getFileArchiveCount()-1); + + const io::IFileList* files_in_zip = opened_zip->getFileList(); + + unsigned int number_of_files = files_in_zip->getFileCount(); + + for (unsigned int i=0; i < number_of_files; i++) { + std::string fullpath = destination; + fullpath += DIR_DELIM; + fullpath += files_in_zip->getFullFileName(i).c_str(); + + if (files_in_zip->isDirectory(i)) { + if (! fs::CreateAllDirs(fullpath) ) { + fs->removeFileArchive(fs->getFileArchiveCount()-1); + lua_pushboolean(L,false); + return 1; + } + } + else { + io::IReadFile* toread = opened_zip->createAndOpenFile(i); + + FILE *targetfile = fopen(fullpath.c_str(),"wb"); + + if (targetfile == NULL) { + fs->removeFileArchive(fs->getFileArchiveCount()-1); + lua_pushboolean(L,false); + return 1; + } + + char read_buffer[1024]; + unsigned int total_read = 0; + + while (total_read < toread->getSize()) { + + unsigned int bytes_read = + toread->read(read_buffer,sizeof(read_buffer)); + unsigned int bytes_written; + if ((bytes_read < 0 ) || + (bytes_written = fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read)) + { + fclose(targetfile); + fs->removeFileArchive(fs->getFileArchiveCount()-1); + lua_pushboolean(L,false); + return 1; + } + total_read += bytes_read; + } + + fclose(targetfile); + } + + } + + fs->removeFileArchive(fs->getFileArchiveCount()-1); + lua_pushboolean(L,true); + return 1; + } + + lua_pushboolean(L,false); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_get_scriptdir(lua_State *L) { + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + lua_pushstring(L,engine->getScriptDir().c_str()); + return 1; +} + +/******************************************************************************/ +bool guiLuaApi::isMinetestPath(std::string path) { + + + if (fs::PathStartsWith(path,fs::TempPath())) + return true; + + /* games */ + if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_share + DIR_DELIM + "games"))) + return true; + + /* mods */ + if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_share + DIR_DELIM + "mods"))) + return true; + + /* worlds */ + if (fs::PathStartsWith(path,fs::RemoveRelativePathComponents(porting::path_user + DIR_DELIM + "worlds"))) + return true; + + + return false; +} + +/******************************************************************************/ +int guiLuaApi::l_show_file_open_dialog(lua_State *L) { + + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + const char *formname= luaL_checkstring(L, 1); + const char *title = luaL_checkstring(L, 2); + + GUIFileSelectMenu* fileOpenMenu = + new GUIFileSelectMenu(engine->m_device->getGUIEnvironment(), + engine->m_parent, + -1, + engine->m_menumanager, + title, + formname); + fileOpenMenu->setTextDest(engine->m_buttonhandler); + fileOpenMenu->drop(); + return 0; +} + +/******************************************************************************/ +int guiLuaApi::l_get_version(lua_State *L) { + lua_pushstring(L,VERSION_STRING); + return 1; +} + +/******************************************************************************/ +int guiLuaApi::l_download_file(lua_State *L) { + GUIEngine* engine = get_engine(L); + assert(engine != 0); + + const char *url = luaL_checkstring(L, 1); + const char *target = luaL_checkstring(L, 2); + + //check path + std::string absolute_destination = fs::RemoveRelativePathComponents(target); + + if (guiLuaApi::isMinetestPath(absolute_destination)) { + if (engine->downloadFile(url,absolute_destination)) { + lua_pushboolean(L,true); + return 1; + } + } + lua_pushboolean(L,false); + return 1; +} diff --git a/src/guiLuaApi.h b/src/guiLuaApi.h new file mode 100644 index 000000000..e0157f4a0 --- /dev/null +++ b/src/guiLuaApi.h @@ -0,0 +1,183 @@ +/* +Minetest +Copyright (C) 2013 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef GUILUAAPI_H_ +#define GUILUAAPI_H_ + +/******************************************************************************/ +/* Includes */ +/******************************************************************************/ +#include "serverlist.h" + +/******************************************************************************/ +/* Typedefs and macros */ +/******************************************************************************/ +typedef int (*lua_CFunction) (lua_State *L); + +/******************************************************************************/ +/* forward declarations */ +/******************************************************************************/ +class GUIEngine; + + +/******************************************************************************/ +/* declarations */ +/******************************************************************************/ + +/** Implementation of lua api support for mainmenu */ +class guiLuaApi { + +public: + /** + * initialize given Lua stack + * @param L lua stack to initialize + * @param engine pointer to GUIEngine element to use as reference + */ + static void initialize(lua_State* L,GUIEngine* engine); + + /** default destructor */ + virtual ~guiLuaApi() {} + +private: + /** + * read a text variable from gamedata table within lua stack + * @param L stack to read variable from + * @param name name of variable to read + * @return string value of requested variable + */ + static std::string getTextData(lua_State *L, std::string name); + + /** + * read a integer variable from gamedata table within lua stack + * @param L stack to read variable from + * @param name name of variable to read + * @return integer value of requested variable + */ + static int getIntegerData(lua_State *L, std::string name,bool& valid); + + /** + * read a bool variable from gamedata table within lua stack + * @param L stack to read variable from + * @param name name of variable to read + * @return bool value of requested variable + */ + static int getBoolData(lua_State *L, std::string name,bool& valid); + + /** + * get the corresponding engine pointer from a lua stack + * @param L stack to read pointer from + * @return pointer to GUIEngine + */ + static GUIEngine* get_engine(lua_State *L); + + + /** + * register a static member function as lua api call at current position of stack + * @param L stack to registe fct to + * @param name of function within lua + * @param fct C-Function to call on lua call of function + * @param top current top of stack + */ + static bool registerFunction( lua_State* L, + const char* name, + lua_CFunction fct, + int top + ); + + /** + * check if a path is within some of minetests folders + * @param path path to check + * @return true/false + */ + static bool isMinetestPath(std::string path); + + //api calls + + static int l_start(lua_State *L); + + static int l_close(lua_State *L); + + static int l_create_world(lua_State *L); + + static int l_delete_world(lua_State *L); + + static int l_get_worlds(lua_State *L); + + static int l_get_games(lua_State *L); + + static int l_get_favorites(lua_State *L); + + static int l_delete_favorite(lua_State *L); + + static int l_get_version(lua_State *L); + + //gui + + static int l_show_keys_menu(lua_State *L); + + static int l_show_file_open_dialog(lua_State *L); + + static int l_set_topleft_text(lua_State *L); + + static int l_set_clouds(lua_State *L); + + static int l_get_textlist_index(lua_State *L); + + static int l_set_background(lua_State *L); + + static int l_update_formspec(lua_State *L); + + //settings + + static int l_setting_set(lua_State *L); + + static int l_setting_get(lua_State *L); + + static int l_setting_getbool(lua_State *L); + + static int l_setting_setbool(lua_State *L); + + //filesystem + + static int l_get_scriptdir(lua_State *L); + + static int l_get_modpath(lua_State *L); + + static int l_get_gamepath(lua_State *L); + + static int l_get_dirlist(lua_State *L); + + static int l_create_dir(lua_State *L); + + static int l_delete_dir(lua_State *L); + + static int l_copy_dir(lua_State *L); + + static int l_extract_zip(lua_State *L); + + static int l_get_modstore_details(lua_State *L); + + static int l_get_modstore_list(lua_State *L); + + static int l_download_file(lua_State *L); + + +}; + +#endif diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp deleted file mode 100644 index 223bba93c..000000000 --- a/src/guiMainMenu.cpp +++ /dev/null @@ -1,1521 +0,0 @@ -/* -Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiMainMenu.h" -#include "guiKeyChangeMenu.h" -#include "guiCreateWorld.h" -#include "guiConfigureWorld.h" -#include "guiMessageMenu.h" -#include "guiConfirmMenu.h" -#include "debug.h" -#include "serialization.h" -#include <string> -#include <IGUICheckBox.h> -#include <IGUIEditBox.h> -#include <IGUIButton.h> -#include <IGUIStaticText.h> -#include <IGUIFont.h> -#include <IGUIListBox.h> -#include <IGUITabControl.h> -#include <IGUIImage.h> -// For IGameCallback -#include "guiPauseMenu.h" -#include "gettext.h" -#include "tile.h" // getTexturePath -#include "filesys.h" -#include "util/string.h" -#include "subgame.h" - -#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0])) -#define LSTRING(x) LSTRING_(x) -#define LSTRING_(x) L##x - -const wchar_t *contrib_core_strs[] = { - L"Perttu Ahola (celeron55) <celeron55@gmail.com>", - L"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>", - L"PilzAdam <pilzadam@minetest.net>", - L"Ilya Zhuravlev (thexyz) <xyz@minetest.net>", - L"Lisa Milne (darkrose) <lisa@ltmnet.com>", - L"Maciej Kasatkin (RealBadAngel) <mk@realbadangel.pl>", - L"proller <proler@gmail.com>", - L"sfan5 <sfan5@live.de>", - L"kahrl <kahrl@gmx.net>" -}; - -const wchar_t *contrib_active_strs[] = { - L"sapier <sapier@gmx.net>", - L"Vanessa Ezekowitz (VanessaE) <vanessaezekowitz@gmail.com>", - L"Jurgen Doser (doserj) <jurgen.doser@gmail.com>", - L"Jeija <jeija@mesecons.net>", - L"MirceaKitsune <mirceakitsune@gmail.com>", - L"ShadowNinja", - L"dannydark <the_skeleton_of_a_child@yahoo.co.uk>", - L"0gb.us <0gb.us@0gb.us>" -}; - -const wchar_t *contrib_previous_strs[] = { - L"Giuseppe Bilotta (Oblomov) <giuseppe.bilotta@gmail.com>", - L"Jonathan Neuschafer <j.neuschaefer@gmx.net>", - L"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net>", - L"Constantin Wenger (SpeedProg) <constantin.wenger@googlemail.com>", - L"matttpt <matttpt@gmail.com>", - L"JacobF <queatz@gmail.com>" -}; - - -struct CreateWorldDestMainMenu : public CreateWorldDest -{ - CreateWorldDestMainMenu(GUIMainMenu *menu): - m_menu(menu) - {} - void accepted(std::wstring name, std::string gameid) - { - std::string name_narrow = wide_to_narrow(name); - if(!string_allowed_blacklist(name_narrow, WORLDNAME_BLACKLISTED_CHARS)) - { - wchar_t* text = wgettext("Cannot create world: Name contains invalid characters"); - m_menu->displayMessageMenu(text); - delete[] text; - return; - } - std::vector<WorldSpec> worlds = getAvailableWorlds(); - for(std::vector<WorldSpec>::iterator i = worlds.begin(); - i != worlds.end(); i++) - { - if((*i).name == name_narrow) - { - wchar_t* text = wgettext("Cannot create world: A world by this name already exists"); - m_menu->displayMessageMenu(text); - delete[] text; - return; - } - } - m_menu->createNewWorld(name, gameid); - } - GUIMainMenu *m_menu; -}; - -struct ConfirmDestDeleteWorld : public ConfirmDest -{ - ConfirmDestDeleteWorld(WorldSpec spec, GUIMainMenu *menu, - const std::vector<std::string> &paths): - m_spec(spec), - m_menu(menu), - m_paths(paths) - {} - void answer(bool answer) - { - if(answer == false) - return; - m_menu->deleteWorld(m_paths); - } - WorldSpec m_spec; - GUIMainMenu *m_menu; - std::vector<std::string> m_paths; -}; - -enum -{ - GUI_ID_QUIT_BUTTON = 101, - GUI_ID_NAME_INPUT, - GUI_ID_ADDRESS_INPUT, - GUI_ID_PORT_INPUT, - GUI_ID_FANCYTREE_CB, - GUI_ID_SMOOTH_LIGHTING_CB, - GUI_ID_3D_CLOUDS_CB, - GUI_ID_OPAQUE_WATER_CB, - GUI_ID_MIPMAP_CB, - GUI_ID_ANISOTROPIC_CB, - GUI_ID_BILINEAR_CB, - GUI_ID_TRILINEAR_CB, - GUI_ID_SHADERS_CB, - GUI_ID_PRELOAD_ITEM_VISUALS_CB, - GUI_ID_ENABLE_PARTICLES_CB, - GUI_ID_LIQUID_FINITE_CB, - GUI_ID_DAMAGE_CB, - GUI_ID_CREATIVE_CB, - GUI_ID_PUBLIC_CB, - GUI_ID_JOIN_GAME_BUTTON, - GUI_ID_CHANGE_KEYS_BUTTON, - GUI_ID_DELETE_WORLD_BUTTON, - GUI_ID_CREATE_WORLD_BUTTON, - GUI_ID_CONFIGURE_WORLD_BUTTON, - GUI_ID_WORLD_LISTBOX, - GUI_ID_TAB_CONTROL, - GUI_ID_SERVERLIST, - GUI_ID_SERVERLIST_TOGGLE, - GUI_ID_SERVERLIST_DELETE, - GUI_ID_SERVERLIST_TITLE, - GUI_ID_GAME_BUTTON_FIRST = 130, - GUI_ID_GAME_BUTTON_MAX = 150, -}; - -GUIMainMenu::GUIMainMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - MainMenuData *data, - IGameCallback *gamecallback -): - GUIModalMenu(env, parent, id, menumgr), - m_data(data), - m_accepted(false), - m_gamecallback(gamecallback), - m_is_regenerating(false) -{ - assert(m_data); - this->env = env; - this->parent = parent; - this->id = id; - this->menumgr = menumgr; -} - -GUIMainMenu::~GUIMainMenu() -{ - removeChildren(); -} - -void GUIMainMenu::removeChildren() -{ - const core::list<gui::IGUIElement*> &children = getChildren(); - core::list<gui::IGUIElement*> children_copy; - for(core::list<gui::IGUIElement*>::ConstIterator - i = children.begin(); i != children.end(); i++) - { - children_copy.push_back(*i); - } - for(core::list<gui::IGUIElement*>::Iterator - i = children_copy.begin(); - i != children_copy.end(); i++) - { - (*i)->remove(); - } -} - -void GUIMainMenu::regenerateGui(v2u32 screensize) -{ - m_is_regenerating = true; - /* - Read stuff from elements into m_data - */ - readInput(m_data); - - /* - Remove stuff - */ - removeChildren(); - - /* - Calculate new sizes and positions - */ - - v2s32 size(screensize.X, screensize.Y); - - core::rect<s32> rect( - screensize.X/2 - size.X/2, - screensize.Y/2 - size.Y/2, - screensize.X/2 + size.X/2, - screensize.Y/2 + size.Y/2 - ); - - DesiredRect = rect; - recalculateAbsolutePosition(false); - - //v2s32 size = rect.getSize(); - - /* - Add stuff - */ - - changeCtype(""); - - // Version - { - core::rect<s32> rect(0, 0, size.X, 40); - rect += v2s32(4, 0); - std::string t = "Minetest " VERSION_STRING; - if(m_data->selected_game_name != "" && - m_data->selected_tab == TAB_SINGLEPLAYER){ - t += "/"; - t += m_data->selected_game_name; - } - Environment->addStaticText(narrow_to_wide(t).c_str(), - rect, false, true, this, -1); - } - - //v2s32 center(size.X/2, size.Y/2); - v2s32 c800(size.X/2-400, size.Y/2-270); - - m_topleft_client = c800 + v2s32(90, 70+50+30); - m_size_client = v2s32(620, 270); - - m_size_server = v2s32(620, 140); - - if(m_data->selected_tab == TAB_ADVANCED) - { - m_topleft_client = c800 + v2s32(90, 70+50+30); - m_size_client = v2s32(620, 200); - - m_size_server = v2s32(620, 140); - } - - m_topleft_server = m_topleft_client + v2s32(0, m_size_client.Y+20); - - // Tabs - { - core::rect<s32> rect(0, 0, m_size_client.X, 30); - rect += m_topleft_client + v2s32(0, -30); - gui::IGUITabControl *e = Environment->addTabControl( - rect, this, true, true, GUI_ID_TAB_CONTROL); - wchar_t* text = wgettext("Singleplayer"); - e->addTab(text); - delete[] text; - text = wgettext("Multiplayer"); - e->addTab(text); - delete[] text; - text = wgettext("Advanced"); - e->addTab(text); - delete[] text; - text = wgettext("Settings"); - e->addTab(text); - delete[] text; - text = wgettext("Credits"); - e->addTab(text); - delete[] text; - - e->setActiveTab(m_data->selected_tab); - - } - - if(m_data->selected_tab == TAB_SINGLEPLAYER) - { - // HYBRID - { - core::rect<s32> rect(0, 0, 10, m_size_client.Y); - rect += m_topleft_client + v2s32(15, 0); - //const wchar_t *text = L"H\nY\nB\nR\nI\nD"; - const wchar_t *text = L"S\nI\nN\nG\nL\nE\n \nP\nL\nA\nY\nE\nR\n"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - u32 bs = 5; - // World selection listbox - u32 world_sel_h = 160; - u32 world_sel_w = 365; - //s32 world_sel_x = 50; - s32 world_sel_x = m_size_client.X-world_sel_w-30; - s32 world_sel_y = 30; - u32 world_button_count = 3; - u32 world_button_w = (world_sel_w)/world_button_count - bs - + bs/(world_button_count-1); - { - core::rect<s32> rect(0, 0, world_sel_w-4, 20); - rect += m_topleft_client + v2s32(world_sel_x+4, world_sel_y-20); - wchar_t* text = wgettext("Select World:"); - /*gui::IGUIStaticText *e =*/ Environment->addStaticText( - text, - rect, false, true, this, -1); - delete[] text; - /*e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);*/ - } - { - core::rect<s32> rect(0, 0, world_sel_w, world_sel_h); - rect += m_topleft_client + v2s32(world_sel_x, world_sel_y); - gui::IGUIListBox *e = Environment->addListBox(rect, this, - GUI_ID_WORLD_LISTBOX); - e->setDrawBackground(true); - m_world_indices.clear(); - for(size_t wi = 0; wi < m_data->worlds.size(); wi++){ - const WorldSpec &spec = m_data->worlds[wi]; - if(spec.gameid == m_data->selected_game){ - //e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str()); - e->addItem(narrow_to_wide(spec.name).c_str()); - m_world_indices.push_back(wi); - if(m_data->selected_world == (int)wi) - e->setSelected(m_world_indices.size()-1); - } - } - Environment->setFocus(e); - } - // Delete world button - { - core::rect<s32> rect(0, 0, world_button_w, 30); - rect += m_topleft_client + v2s32(world_sel_x, world_sel_y+world_sel_h+0); - wchar_t* text = wgettext("Delete"); - Environment->addButton(rect, this, GUI_ID_DELETE_WORLD_BUTTON, - text); - delete[] text; - } - // Create world button - { - core::rect<s32> rect(0, 0, world_button_w, 30); - rect += m_topleft_client + v2s32(world_sel_x+world_button_w+bs, world_sel_y+world_sel_h+0); - wchar_t* text = wgettext("New"); - Environment->addButton(rect, this, GUI_ID_CREATE_WORLD_BUTTON, - text); - delete[] text; - } - // Configure world button - { - core::rect<s32> rect(0, 0, world_button_w, 30); - rect += m_topleft_client + v2s32(world_sel_x+(world_button_w+bs)*2, - world_sel_y+world_sel_h+0); - wchar_t* text = wgettext("Configure"); - Environment->addButton(rect, this, GUI_ID_CONFIGURE_WORLD_BUTTON, - text); - delete[] text; - } - // Start game button - { - /*core::rect<s32> rect(0, 0, world_button_w, 30); - rect += m_topleft_client + v2s32(world_sel_x+(world_button_w+bs)*3, - world_sel_y+world_sel_h+0);*/ - u32 bw = 160; - /*core::rect<s32> rect(0, 0, bw, 30); - rect += m_topleft_client + v2s32(m_size_client.X-bw-30, - m_size_client.Y-30-15);*/ - core::rect<s32> rect(0, 0, bw, 30); - rect += m_topleft_client + v2s32(world_sel_x+world_sel_w-bw, - world_sel_y+world_sel_h+30+bs); - wchar_t* text = wgettext("Play"); - Environment->addButton(rect, this, - GUI_ID_JOIN_GAME_BUTTON, text); - delete[] text; - } - // Options - s32 option_x = 50; - //s32 option_x = 50+world_sel_w+20; - s32 option_y = 30; - u32 option_w = 150; - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y+20*0); - wchar_t* text = wgettext("Creative Mode"); - Environment->addCheckBox(m_data->creative_mode, rect, this, - GUI_ID_CREATIVE_CB, text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y+20*1); - wchar_t* text = wgettext("Enable Damage"); - Environment->addCheckBox(m_data->enable_damage, rect, this, - GUI_ID_DAMAGE_CB, text); - delete[] text; - } - changeCtype("C"); - - /* Add game selection buttons */ - video::IVideoDriver* driver = Environment->getVideoDriver(); - for(size_t i=0; i<m_data->games.size(); i++){ - const SubgameSpec *spec = &m_data->games[i]; - v2s32 p(8 + i*(48+8), screensize.Y - (48+8)); - core::rect<s32> rect(0, 0, 48, 48); - rect += p; - video::ITexture *bgtexture = NULL; - if(spec->menuicon_path != "") - bgtexture = driver->getTexture(spec->menuicon_path.c_str()); - gui::IGUIButton *b = Environment->addButton(rect, this, - GUI_ID_GAME_BUTTON_FIRST+i, narrow_to_wide(wrap_rows(spec->id, 4)).c_str()); - if(bgtexture){ - b->setImage(bgtexture); - b->setText(L""); - b->setDrawBorder(false); - b->setUseAlphaChannel(true); - } - } - } - else if(m_data->selected_tab == TAB_MULTIPLAYER) - { - changeCtype(""); - // CLIENT - { - core::rect<s32> rect(0, 0, 10, m_size_client.Y); - rect += m_topleft_client + v2s32(15, 0); - const wchar_t *text = L"C\nL\nI\nE\nN\nT"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - // Nickname + password - { - core::rect<s32> rect(0, 0, 110, 20); - wchar_t* text = wgettext("Name/Password"); - rect += m_topleft_client + v2s32(m_size_client.X-60-100, 10+6); - Environment->addStaticText(text, - rect, false, true, this, -1); - delete [] text; - } - changeCtype("C"); - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(m_size_client.X-60-100, 50); - gui::IGUIElement *e = - Environment->addEditBox(m_data->name.c_str(), rect, true, this, GUI_ID_NAME_INPUT); - if(m_data->name == L"") - Environment->setFocus(e); - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(m_size_client.X-60-100, 90); - gui::IGUIEditBox *e = - Environment->addEditBox(L"", rect, true, this, 264); - e->setPasswordBox(true); - if(m_data->name != L"" && m_data->address != L"") - Environment->setFocus(e); - - } - changeCtype(""); - // Server List - { - core::rect<s32> rect(0, 0, 390, 140); - rect += m_topleft_client + v2s32(50, 30); - gui::IGUIListBox *e = Environment->addListBox(rect, this, - GUI_ID_SERVERLIST); - e->setDrawBackground(true); -#if USE_CURL - if(m_data->selected_serverlist == SERVERLIST_FAVORITES) { - m_data->servers = ServerList::getLocal(); - { - core::rect<s32> rect(0, 0, 390, 20); - rect += m_topleft_client + v2s32(50, 10); - wchar_t* text = wgettext("Favorites:"); - Environment->addStaticText(text, - rect, false, true, this, GUI_ID_SERVERLIST_TITLE); - delete[] text; - } - } else { - m_data->servers = ServerList::getOnline(); - { - core::rect<s32> rect(0, 0, 390, 20); - rect += m_topleft_client + v2s32(50, 10); - wchar_t* text = wgettext("Public Server List:"); - Environment->addStaticText(text, - rect, false, true, this, GUI_ID_SERVERLIST_TITLE); - delete[] text; - } - } -#else - m_data->servers = ServerList::getLocal(); - { - core::rect<s32> rect(0, 0, 390, 20); - rect += m_topleft_client + v2s32(50, 10); - wchar_t* text = wgettext("Favorites:"); - Environment->addStaticText(text, - rect, false, true, this, GUI_ID_SERVERLIST_TITLE); - delete[] text; - } -#endif - updateGuiServerList(); - e->setSelected(0); - } - // Address + port - { - core::rect<s32> rect(0, 0, 110, 20); - rect += m_topleft_client + v2s32(50, m_size_client.Y-50-15+6); - wchar_t* text = wgettext("Address/Port"); - Environment->addStaticText(text, - rect, false, true, this, -1); - delete [] text; - } - changeCtype("C"); - { - core::rect<s32> rect(0, 0, 260, 30); - rect += m_topleft_client + v2s32(50, m_size_client.Y-25-15); - gui::IGUIElement *e = - Environment->addEditBox(m_data->address.c_str(), rect, true, - this, GUI_ID_ADDRESS_INPUT); - if(m_data->name != L"" && m_data->address == L"") - Environment->setFocus(e); - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(50+260+10, m_size_client.Y-25-15); - Environment->addEditBox(m_data->port.c_str(), rect, true, - this, GUI_ID_PORT_INPUT); - } - changeCtype(""); - #if USE_CURL - // Toggle Serverlist (Favorites/Online) - { - core::rect<s32> rect(0, 0, 260, 30); - rect += m_topleft_client + v2s32(50, - 180); - wchar_t* text = wgettext("Show Public"); - gui::IGUIButton *e = Environment->addButton(rect, this, GUI_ID_SERVERLIST_TOGGLE, - text); - delete[] text; - e->setIsPushButton(true); - if (m_data->selected_serverlist == SERVERLIST_PUBLIC) - { - wchar_t* text = wgettext("Show Favorites"); - e->setText(text); - e->setPressed(); - delete[] text; - } - } - #endif - // Delete Local Favorite - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(50+260+10, 180); - wchar_t* text = wgettext("Delete"); - gui::IGUIButton *e = Environment->addButton(rect, this, GUI_ID_SERVERLIST_DELETE, - text); - if (m_data->selected_serverlist == SERVERLIST_PUBLIC) // Hidden when on public list - e->setVisible(false); - - delete [] text; - } - // Start game button - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(m_size_client.X-130-30, - m_size_client.Y-25-15); - wchar_t* text = wgettext("Connect"); - Environment->addButton(rect, this, GUI_ID_JOIN_GAME_BUTTON, - text); - delete[] text; - } - changeCtype("C"); - } - else if(m_data->selected_tab == TAB_ADVANCED) - { - changeCtype(""); - // CLIENT - { - core::rect<s32> rect(0, 0, 10, m_size_client.Y); - rect += m_topleft_client + v2s32(15, 0); - const wchar_t *text = L"C\nL\nI\nE\nN\nT"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - // Nickname + password - { - core::rect<s32> rect(0, 0, 110, 20); - rect += m_topleft_client + v2s32(35+30, 35+6); - wchar_t* text = wgettext("Name/Password"); - Environment->addStaticText(text, - rect, false, true, this, -1); - delete [] text; - } - changeCtype("C"); - { - core::rect<s32> rect(0, 0, 230, 30); - rect += m_topleft_client + v2s32(160+30, 35); - gui::IGUIElement *e = - Environment->addEditBox(m_data->name.c_str(), rect, true, this, GUI_ID_NAME_INPUT); - if(m_data->name == L"") - Environment->setFocus(e); - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(m_size_client.X-60-100, 35); - gui::IGUIEditBox *e = - Environment->addEditBox(L"", rect, true, this, 264); - e->setPasswordBox(true); - if(m_data->name != L"" && m_data->address != L"") - Environment->setFocus(e); - - } - changeCtype(""); - // Address + port - { - core::rect<s32> rect(0, 0, 110, 20); - rect += m_topleft_client + v2s32(35+30, 75+6); - wchar_t* text = wgettext("Address/Port"); - Environment->addStaticText(text, - rect, false, true, this, -1); - delete[] text; - } - changeCtype("C"); - { - core::rect<s32> rect(0, 0, 230, 30); - rect += m_topleft_client + v2s32(160+30, 75); - gui::IGUIElement *e = - Environment->addEditBox(m_data->address.c_str(), rect, true, - this, GUI_ID_ADDRESS_INPUT); - if(m_data->name != L"" && m_data->address == L"") - Environment->setFocus(e); - } - { - core::rect<s32> rect(0, 0, 120, 30); - rect += m_topleft_client + v2s32(m_size_client.X-60-100, 75); - Environment->addEditBox(m_data->port.c_str(), rect, true, - this, GUI_ID_PORT_INPUT); - } - changeCtype(""); - { - core::rect<s32> rect(0, 0, 400, 20); - rect += m_topleft_client + v2s32(160+30, 75+35); - wchar_t* text = wgettext("Leave address blank to start a local server."); - Environment->addStaticText(text, - rect, false, true, this, -1); - delete[] text; - } - // Start game button - { - core::rect<s32> rect(0, 0, 180, 30); - rect += m_topleft_client + v2s32(m_size_client.X-180-30, - m_size_client.Y-30-20); - wchar_t* text = wgettext("Start Game / Connect"); - Environment->addButton(rect, this, GUI_ID_JOIN_GAME_BUTTON, - text); - delete[] text; - } - /* - Server section - */ - // SERVER - { - core::rect<s32> rect(0, 0, 10, m_size_server.Y); - rect += m_topleft_server + v2s32(15, 0); - const wchar_t *text = L"S\nE\nR\nV\nE\nR"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - // Server parameters - { - core::rect<s32> rect(0, 0, 250, 30); - rect += m_topleft_server + v2s32(30+20+250+20, 20); - wchar_t* text = wgettext("Creative Mode"); - Environment->addCheckBox(m_data->creative_mode, rect, this, GUI_ID_CREATIVE_CB, - text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, 250, 30); - rect += m_topleft_server + v2s32(30+20+250+20, 40); - wchar_t* text = wgettext("Enable Damage"); - Environment->addCheckBox(m_data->enable_damage, rect, this, GUI_ID_DAMAGE_CB, - text); - delete[] text; - } - #if USE_CURL - { - core::rect<s32> rect(0, 0, 250, 30); - rect += m_topleft_server + v2s32(30+20+250+20, 60); - wchar_t* text = wgettext("Public"); - Environment->addCheckBox(m_data->enable_public, rect, this, GUI_ID_PUBLIC_CB, - text); - delete[] text; - } - #endif - // Delete world button - { - core::rect<s32> rect(0, 0, 130, 30); - rect += m_topleft_server + v2s32(30+20+250+20, 90); - wchar_t* text = wgettext("Delete world"); - Environment->addButton(rect, this, GUI_ID_DELETE_WORLD_BUTTON, - text ); - delete[] text; - } - // Create world button - { - core::rect<s32> rect(0, 0, 130, 30); - rect += m_topleft_server + v2s32(30+20+250+20+140, 90); - wchar_t* text = wgettext("Create world"); - Environment->addButton(rect, this, GUI_ID_CREATE_WORLD_BUTTON, - text ); - delete[] text; - } - // World selection listbox - { - core::rect<s32> rect(0, 0, 250, 120); - rect += m_topleft_server + v2s32(30+20, 10); - gui::IGUIListBox *e = Environment->addListBox(rect, this, - GUI_ID_WORLD_LISTBOX); - e->setDrawBackground(true); - m_world_indices.clear(); - for(size_t wi = 0; wi < m_data->worlds.size(); wi++){ - const WorldSpec &spec = m_data->worlds[wi]; - e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str()); - m_world_indices.push_back(wi); - } - e->setSelected(m_data->selected_world); - } - changeCtype("C"); - } - else if(m_data->selected_tab == TAB_SETTINGS) - { - { - core::rect<s32> rect(0, 0, 10, m_size_client.Y); - rect += m_topleft_client + v2s32(15, 0); - const wchar_t *text = L"S\nE\nT\nT\nI\nN\nG\nS"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - s32 option_x = 70; - s32 option_y = 50; - u32 option_w = 150; - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y); - wchar_t* text = wgettext("Fancy trees"); - Environment->addCheckBox(m_data->fancy_trees, rect, this, - GUI_ID_FANCYTREE_CB, text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y+20); - wchar_t* text = wgettext("Smooth Lighting"); - Environment->addCheckBox(m_data->smooth_lighting, rect, this, - GUI_ID_SMOOTH_LIGHTING_CB, text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y+20*2); - wchar_t* text = wgettext("3D Clouds"); - Environment->addCheckBox(m_data->clouds_3d, rect, this, - GUI_ID_3D_CLOUDS_CB, text); - delete[] text; - } - { - core::rect<s32> rect(0, 0, option_w, 30); - rect += m_topleft_client + v2s32(option_x, option_y+20*3); - wchar_t* text = wgettext("Opaque water"); - Environment->addCheckBox(m_data->opaque_water, rect, this, - GUI_ID_OPAQUE_WATER_CB, text); - delete[] text; - } - - - // Anisotropic/mipmap/bi-/trilinear settings - - { - core::rect<s32> rect(0, 0, option_w+20, 30); - rect += m_topleft_client + v2s32(option_x+175, option_y); - wchar_t* text = wgettext("Mip-Mapping"); - Environment->addCheckBox(m_data->mip_map, rect, this, - GUI_ID_MIPMAP_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20, 30); - rect += m_topleft_client + v2s32(option_x+175, option_y+20); - wchar_t* text = wgettext("Anisotropic Filtering"); - Environment->addCheckBox(m_data->anisotropic_filter, rect, this, - GUI_ID_ANISOTROPIC_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20, 30); - rect += m_topleft_client + v2s32(option_x+175, option_y+20*2); - wchar_t* text = wgettext("Bi-Linear Filtering"); - Environment->addCheckBox(m_data->bilinear_filter, rect, this, - GUI_ID_BILINEAR_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20, 30); - rect += m_topleft_client + v2s32(option_x+175, option_y+20*3); - wchar_t* text = wgettext("Tri-Linear Filtering"); - Environment->addCheckBox(m_data->trilinear_filter, rect, this, - GUI_ID_TRILINEAR_CB, text); - delete[] text; - } - - // shader/on demand image loading/particles settings - { - core::rect<s32> rect(0, 0, option_w+20, 30); - rect += m_topleft_client + v2s32(option_x+175*2, option_y); - wchar_t* text = wgettext("Shaders"); - Environment->addCheckBox(m_data->enable_shaders, rect, this, - GUI_ID_SHADERS_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20+20, 30); - rect += m_topleft_client + v2s32(option_x+175*2, option_y+20); - wchar_t* text = wgettext("Preload item visuals"); - Environment->addCheckBox(m_data->preload_item_visuals, rect, this, - GUI_ID_PRELOAD_ITEM_VISUALS_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20+20, 30); - rect += m_topleft_client + v2s32(option_x+175*2, option_y+20*2); - wchar_t* text = wgettext("Enable Particles"); - Environment->addCheckBox(m_data->enable_particles, rect, this, - GUI_ID_ENABLE_PARTICLES_CB, text); - delete[] text; - } - - { - core::rect<s32> rect(0, 0, option_w+20+20, 30); - rect += m_topleft_client + v2s32(option_x+175*2, option_y+20*3); - wchar_t* text = wgettext("Finite liquid"); - Environment->addCheckBox(m_data->liquid_finite, rect, this, - GUI_ID_LIQUID_FINITE_CB, text); - delete[] text; - } - - // Key change button - { - core::rect<s32> rect(0, 0, 120, 30); - /*rect += m_topleft_client + v2s32(m_size_client.X-120-30, - m_size_client.Y-30-20);*/ - rect += m_topleft_client + v2s32(option_x, option_y+120); - wchar_t* text = wgettext("Change keys"); - Environment->addButton(rect, this, - GUI_ID_CHANGE_KEYS_BUTTON, text); - delete[] text; - } - changeCtype("C"); - } - else if(m_data->selected_tab == TAB_CREDITS) - { - // CREDITS - { - core::rect<s32> rect(0, 0, 9, m_size_client.Y); - rect += m_topleft_client + v2s32(15, 0); - const wchar_t *text = L"C\nR\nE\nD\nI\nT\nS"; - gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, true, this, -1); - t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - } - { - core::rect<s32> rect(0, 0, 130, 70); - rect += m_topleft_client + v2s32(35, 160); - Environment->addStaticText( - L"Minetest " LSTRING(VERSION_STRING) L"\nhttp://minetest.net/", - rect, false, true, this, -1); - } - { - video::SColor yellow(255, 255, 255, 0); - core::rect<s32> rect(0, 0, 450, 260); - rect += m_topleft_client + v2s32(168, 5); - - irr::gui::IGUIListBox *list = Environment->addListBox(rect, this); - - list->addItem(L"Core Developers"); - list->setItemOverrideColor(list->getItemCount() - 1, yellow); - for (int i = 0; i != ARRAYLEN(contrib_core_strs); i++) - list->addItem(contrib_core_strs[i]); - list->addItem(L""); - list->addItem(L"Active Contributors"); - list->setItemOverrideColor(list->getItemCount() - 1, yellow); - for (int i = 0; i != ARRAYLEN(contrib_active_strs); i++) - list->addItem(contrib_active_strs[i]); - list->addItem(L""); - list->addItem(L"Previous Contributors"); - list->setItemOverrideColor(list->getItemCount() - 1, yellow); - for (int i = 0; i != ARRAYLEN(contrib_previous_strs); i++) - list->addItem(contrib_previous_strs[i]); - list->addItem(L""); - } - } - - m_is_regenerating = false; -} - -void GUIMainMenu::drawMenu() -{ - gui::IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - /* Draw menu background */ - - /*video::SColor bgcolor(140,0,0,0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);*/ - - video::SColor bgcolor(140,0,0,0); - - if(getTab() == TAB_SINGLEPLAYER) - { - { - core::rect<s32> rect(0, 0, m_size_client.X, m_size_client.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - } - else if(getTab() == TAB_MULTIPLAYER) - { - { - core::rect<s32> rect(0, 0, m_size_client.X, m_size_client.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - } - else if(getTab() == TAB_ADVANCED) - { - { - core::rect<s32> rect(0, 0, m_size_client.X, m_size_client.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - { - core::rect<s32> rect(0, 0, m_size_server.X, m_size_server.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_server; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - } - else if(getTab() == TAB_SETTINGS) - { - { - core::rect<s32> rect(0, 0, m_size_client.X, m_size_client.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - } - else if(getTab() == TAB_CREDITS) - { - { - core::rect<s32> rect(0, 0, m_size_client.X, m_size_client.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); - } - video::ITexture *logotexture = - driver->getTexture(getTexturePath("logo.png").c_str()); - if(logotexture) - { - v2s32 logosize(logotexture->getOriginalSize().Width, - logotexture->getOriginalSize().Height); - - core::rect<s32> rect(0,0,logosize.X,logosize.Y); - rect += AbsoluteRect.UpperLeftCorner + m_topleft_client; - rect += v2s32(50, 60); - driver->draw2DImage(logotexture, rect, - core::rect<s32>(core::position2d<s32>(0,0), - core::dimension2di(logotexture->getSize())), - NULL, NULL, true); - } - } - - /* Draw UI elements */ - - gui::IGUIElement::draw(); -} - -void GUIMainMenu::readInput(MainMenuData *dst) -{ - { - gui::IGUIElement *e = getElementFromId(GUI_ID_TAB_CONTROL); - if(e != NULL && e->getType() == gui::EGUIET_TAB_CONTROL) - dst->selected_tab = ((gui::IGUITabControl*)e)->getActiveTab(); - } - if(dst->selected_tab == TAB_SINGLEPLAYER) - { - dst->simple_singleplayer_mode = true; - } - else - { - dst->simple_singleplayer_mode = false; - { - gui::IGUIElement *e = getElementFromId(GUI_ID_NAME_INPUT); - if(e != NULL) - dst->name = e->getText(); - } - { - gui::IGUIElement *e = getElementFromId(264); - if(e != NULL) - dst->password = e->getText(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_ADDRESS_INPUT); - if(e != NULL) - dst->address = e->getText(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_PORT_INPUT); - if(e != NULL) - dst->port = e->getText(); - } - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_CREATIVE_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->creative_mode = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_DAMAGE_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->enable_damage = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_PUBLIC_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->enable_public = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_FANCYTREE_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->fancy_trees = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_SMOOTH_LIGHTING_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->smooth_lighting = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_3D_CLOUDS_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->clouds_3d = ((gui::IGUICheckBox*)e)->isChecked(); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_OPAQUE_WATER_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->opaque_water = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_MIPMAP_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->mip_map = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_ANISOTROPIC_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->anisotropic_filter = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_BILINEAR_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->bilinear_filter = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_TRILINEAR_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->trilinear_filter = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_SHADERS_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->enable_shaders = ((gui::IGUICheckBox*)e)->isChecked() ? 2 : 0; - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_PRELOAD_ITEM_VISUALS_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->preload_item_visuals = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_ENABLE_PARTICLES_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->enable_particles = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_LIQUID_FINITE_CB); - if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) - dst->liquid_finite = ((gui::IGUICheckBox*)e)->isChecked(); - } - - { - gui::IGUIElement *e = getElementFromId(GUI_ID_WORLD_LISTBOX); - if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX){ - int list_i = ((gui::IGUIListBox*)e)->getSelected(); - if(list_i == -1) - dst->selected_world = -1; - else - dst->selected_world = m_world_indices[list_i]; - } - } - { - ServerListSpec server = - getServerListSpec(wide_to_narrow(dst->address), wide_to_narrow(dst->port)); - dst->servername = server["name"].asString(); - dst->serverdescription = server["description"].asString(); - } -} - -void GUIMainMenu::acceptInput() -{ - readInput(m_data); - m_accepted = true; -} - -bool GUIMainMenu::OnEvent(const SEvent& event) -{ - if(event.EventType==EET_KEY_INPUT_EVENT) - { - if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) - { - m_gamecallback->exitToOS(); - quitMenu(); - return true; - } - if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) - { - acceptInput(); - quitMenu(); - return true; - } - } - if(event.EventType==EET_GUI_EVENT) - { - if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) - { - if(!canTakeFocus(event.GUIEvent.Element)) - { - dstream<<"GUIMainMenu: Not allowing focus change." - <<std::endl; - // Returning true disables focus change - return true; - } - } - if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED) - { - if(!m_is_regenerating) - regenerateGui(m_screensize_old); - return true; - } - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && event.GUIEvent.Caller->getID() == GUI_ID_SERVERLIST) - { - serverListOnSelected(); - return true; - } - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) - { - switch(event.GUIEvent.Caller->getID()) - { - case GUI_ID_JOIN_GAME_BUTTON: { - MainMenuData cur; - readInput(&cur); - if (getTab() == TAB_MULTIPLAYER && cur.address == L"") - { - wchar_t* text = wgettext("Address required."); - (new GUIMessageMenu(env, parent, -1, menumgr, - text) - )->drop(); - delete[] text; - return true; - } - acceptInput(); - quitMenu(); - return true; - } - case GUI_ID_CHANGE_KEYS_BUTTON: { - GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu(env, parent, -1,menumgr); - kmenu->drop(); - return true; - } - case GUI_ID_DELETE_WORLD_BUTTON: { - MainMenuData cur; - readInput(&cur); - if(cur.selected_world == -1){ - wchar_t* text = wgettext("Cannot delete world: Nothing selected"); - (new GUIMessageMenu(env, parent, -1, menumgr, - text) - )->drop(); - delete[] text; - } else { - WorldSpec spec = m_data->worlds[cur.selected_world]; - // Get files and directories involved - std::vector<std::string> paths; - paths.push_back(spec.path); - fs::GetRecursiveSubPaths(spec.path, paths); - // Launch confirmation dialog - ConfirmDestDeleteWorld *dest = new - ConfirmDestDeleteWorld(spec, this, paths); - wchar_t* text1 = wgettext("Delete world"); - wchar_t* text2 = wgettext("Files to be deleted"); - std::wstring text = text1; - text += L" \""; - text += narrow_to_wide(spec.name); - text += L"\"?\n\n"; - text += text2; - text += L":\n"; - delete[] text1; - delete[] text2; - for(u32 i=0; i<paths.size(); i++){ - if(i == 3){ text += L"..."; break; } - text += narrow_to_wide(paths[i]) + L"\n"; - } - (new GUIConfirmMenu(env, parent, -1, menumgr, dest, - text.c_str()))->drop(); - } - return true; - } - case GUI_ID_CREATE_WORLD_BUTTON: { - const std::vector<SubgameSpec> &games = m_data->games; - if(games.size() == 0){ - wchar_t* text = wgettext("Cannot create world: No games found"); - GUIMessageMenu *menu = new GUIMessageMenu(env, parent, - -1, menumgr, - text); - menu->drop(); - delete[] text; - } else { - CreateWorldDest *dest = new CreateWorldDestMainMenu(this); - GUICreateWorld *menu = new GUICreateWorld(env, parent, -1, - menumgr, dest, games, m_data->selected_game); - menu->drop(); - } - return true; - } - case GUI_ID_CONFIGURE_WORLD_BUTTON: { - MainMenuData cur; - readInput(&cur); - if(cur.selected_world == -1) - { - wchar_t* text = wgettext("Cannot configure world: Nothing selected"); - (new GUIMessageMenu(env, parent, -1, menumgr, - text) - )->drop(); - delete[] text; - } - else - { - WorldSpec wspec = m_data->worlds[cur.selected_world]; - GUIConfigureWorld *menu = new GUIConfigureWorld(env, parent, - -1, menumgr, wspec); - menu->drop(); - } - return true; - } - case GUI_ID_SERVERLIST_DELETE: { - gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); - s32 selected = ((gui::IGUIListBox*)serverlist)->getSelected(); - if (selected == -1) return true; - ServerList::deleteEntry(m_data->servers[selected]); - m_data->servers = ServerList::getLocal(); - updateGuiServerList(); - if (selected > 0) - selected -= 1; - serverlist->setSelected(selected); - serverListOnSelected(); - return true; - } - #if USE_CURL - case GUI_ID_SERVERLIST_TOGGLE: { - gui::IGUIElement *togglebutton = getElementFromId(GUI_ID_SERVERLIST_TOGGLE); - gui::IGUIElement *deletebutton = getElementFromId(GUI_ID_SERVERLIST_DELETE); - gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); - gui::IGUIElement *title = getElementFromId(GUI_ID_SERVERLIST_TITLE); - if (m_data->selected_serverlist == SERVERLIST_PUBLIC) // switch to favorite list - { - m_data->servers = ServerList::getLocal(); - wchar_t* text1 = wgettext("Show Public"); - wchar_t* text2 = wgettext("Favorites:"); - togglebutton->setText(text1); - title->setText(text2); - delete[] text1; - delete[] text2; - deletebutton->setVisible(true); - updateGuiServerList(); - serverlist->setSelected(0); - m_data->selected_serverlist = SERVERLIST_FAVORITES; - } - else // switch to online list - { - m_data->servers = ServerList::getOnline(); - wchar_t* text1 = wgettext("Show Favorites"); - wchar_t* text2 = wgettext("Public Server List:"); - togglebutton->setText(text1); - title->setText(text2); - delete[] text1; - delete[] text2; - deletebutton->setVisible(false); - updateGuiServerList(); - serverlist->setSelected(0); - m_data->selected_serverlist = SERVERLIST_PUBLIC; - } - serverListOnSelected(); - } - #endif - } - /* Game buttons */ - int eid = event.GUIEvent.Caller->getID(); - if(eid >= GUI_ID_GAME_BUTTON_FIRST && - eid <= GUI_ID_GAME_BUTTON_MAX){ - m_data->selected_game = - m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].id; - m_data->selected_game_name = - m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].name; - regenerateGui(m_screensize_old); - } - } - if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER) - { - switch(event.GUIEvent.Caller->getID()) - { - case GUI_ID_ADDRESS_INPUT: case GUI_ID_PORT_INPUT: case GUI_ID_NAME_INPUT: case 264: - MainMenuData cur; - readInput(&cur); - if (getTab() == TAB_MULTIPLAYER && cur.address == L"") - { - wchar_t* text = wgettext("Address required."); - (new GUIMessageMenu(env, parent, -1, menumgr, - text) - )->drop(); - delete[] text; - return true; - } - acceptInput(); - quitMenu(); - return true; - } - } - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED) - { - readInput(m_data); - } - if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) - { - switch(event.GUIEvent.Caller->getID()) - { - case GUI_ID_WORLD_LISTBOX: - acceptInput(); - if(getTab() != TAB_SINGLEPLAYER) - m_data->address = L""; // Force local game - quitMenu(); - return true; - case GUI_ID_SERVERLIST: - gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); - if (serverlist->getSelected() > -1) - { - acceptInput(); - quitMenu(); - return true; - } - } - } - } - - return Parent ? Parent->OnEvent(event) : false; -} - -void GUIMainMenu::createNewWorld(std::wstring name, std::string gameid) -{ - if(name == L"") - return; - acceptInput(); - m_data->create_world_name = name; - m_data->create_world_gameid = gameid; - quitMenu(); -} - -void GUIMainMenu::deleteWorld(const std::vector<std::string> &paths) -{ - // Delete files - bool did = fs::DeletePaths(paths); - if(!did){ - wchar_t* text = wgettext("Failed to delete all world files"); - GUIMessageMenu *menu = new GUIMessageMenu(env, parent, - -1, menumgr, text); - delete[] text; - menu->drop(); - } - // Quit menu to refresh it - acceptInput(); - m_data->only_refresh = true; - quitMenu(); -} - -int GUIMainMenu::getTab() -{ - gui::IGUIElement *e = getElementFromId(GUI_ID_TAB_CONTROL); - if(e != NULL && e->getType() == gui::EGUIET_TAB_CONTROL) - return ((gui::IGUITabControl*)e)->getActiveTab(); - return TAB_SINGLEPLAYER; // Default -} - -void GUIMainMenu::displayMessageMenu(std::wstring msg) -{ - (new GUIMessageMenu(env, parent, -1, menumgr, msg))->drop(); -} - -void GUIMainMenu::updateGuiServerList() -{ - gui::IGUIListBox *serverlist = (gui::IGUIListBox *)getElementFromId(GUI_ID_SERVERLIST); - serverlist->clear(); - - for(std::vector<ServerListSpec>::iterator i = m_data->servers.begin(); - i != m_data->servers.end(); i++) - { - std::string text; - - if ((*i)["clients"].asString().size()) - text += (*i)["clients"].asString(); - if ((*i)["clients_max"].asString().size()) - text += "/" + (*i)["clients_max"].asString(); - text += " "; - if ((*i)["version"].asString().size()) - text += (*i)["version"].asString() + " "; - if ((*i)["password"].asString().size()) - text += "*"; - if ((*i)["creative"].asString().size()) - text += "C"; - if ((*i)["damage"].asString().size()) - text += "D"; - if ((*i)["pvp"].asString().size()) - text += "P"; - text += " "; - - if ((*i)["name"] != "" && (*i)["description"] != "") - text += (*i)["name"].asString() + " (" + (*i)["description"].asString() + ")"; - else if ((*i)["name"] !="") - text += (*i)["name"].asString(); - else - text += (*i)["address"].asString() + ":" + (*i)["port"].asString(); - - serverlist->addItem(narrow_to_wide(text).c_str()); - } -} - -void GUIMainMenu::serverListOnSelected() -{ - if (!m_data->servers.empty()) - { - gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); - u16 id = serverlist->getSelected(); - //if (id < 0) return; // u16>0! - ((gui::IGUIEditBox*)getElementFromId(GUI_ID_ADDRESS_INPUT)) - ->setText(narrow_to_wide(m_data->servers[id]["address"].asString()).c_str()); - ((gui::IGUIEditBox*)getElementFromId(GUI_ID_PORT_INPUT)) - ->setText(narrow_to_wide(m_data->servers[id]["port"].asString()).c_str()); - } -} - -ServerListSpec GUIMainMenu::getServerListSpec(std::string address, std::string port) -{ - ServerListSpec server; - server["address"] = address; - server["port"] = port; - for(std::vector<ServerListSpec>::iterator i = m_data->servers.begin(); - i != m_data->servers.end(); i++) - { - if ((*i)["address"] == address && (*i)["port"] == port) - { - server["description"] = (*i)["description"]; - server["name"] = (*i)["name"]; - break; - } - } - return server; -} diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 8697344c8..5eaca23fa 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -24,15 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modalMenu.h" #include <string> #include <list> -#include "subgame.h" -#include "serverlist.h" - -class IGameCallback; - -enum { - SERVERLIST_FAVORITES, - SERVERLIST_PUBLIC, -}; enum { @@ -45,113 +36,31 @@ enum struct MainMenuData { - // These are in the native format of the gui elements - // Generic - int selected_tab; - std::string selected_game; - std::string selected_game_name; // Client options std::string servername; std::string serverdescription; - std::wstring address; - std::wstring port; - std::wstring name; - std::wstring password; - bool fancy_trees; - bool smooth_lighting; - bool clouds_3d; - bool opaque_water; - bool mip_map; - bool anisotropic_filter; - bool bilinear_filter; - bool trilinear_filter; - int enable_shaders; - bool preload_item_visuals; - bool enable_particles; - bool liquid_finite; + std::string address; + std::string port; + std::string name; + std::string password; + // Server options - bool creative_mode; - bool enable_damage; bool enable_public; int selected_world; bool simple_singleplayer_mode; - // Actions - std::wstring create_world_name; - std::string create_world_gameid; - bool only_refresh; - - int selected_serverlist; - std::vector<WorldSpec> worlds; - std::vector<SubgameSpec> games; - std::vector<ServerListSpec> servers; + // Actions + bool kill; + //error handling + std::string errormessage; MainMenuData(): - // Generic - selected_tab(0), - selected_game("minetest"), - selected_game_name("Minetest"), - // Client opts - fancy_trees(false), - smooth_lighting(false), - // Server opts - creative_mode(false), - enable_damage(false), enable_public(false), selected_world(0), simple_singleplayer_mode(false), - // Actions - only_refresh(false), - - selected_serverlist(SERVERLIST_FAVORITES) + errormessage("") {} }; -class GUIMainMenu : public GUIModalMenu -{ -public: - GUIMainMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - MainMenuData *data, - IGameCallback *gamecallback); - ~GUIMainMenu(); - - void removeChildren(); - // Remove and re-add (or reposition) stuff - void regenerateGui(v2u32 screensize); - void drawMenu(); - void readInput(MainMenuData *dst); - void acceptInput(); - bool getStatus() - { return m_accepted; } - bool OnEvent(const SEvent& event); - void createNewWorld(std::wstring name, std::string gameid); - void deleteWorld(const std::vector<std::string> &paths); - int getTab(); - void displayMessageMenu(std::wstring msg); - -private: - MainMenuData *m_data; - bool m_accepted; - IGameCallback *m_gamecallback; - - gui::IGUIEnvironment* env; - gui::IGUIElement* parent; - s32 id; - IMenuManager *menumgr; - - std::vector<int> m_world_indices; - - bool m_is_regenerating; - v2s32 m_topleft_client; - v2s32 m_size_client; - v2s32 m_topleft_server; - v2s32 m_size_server; - void updateGuiServerList(); - void serverListOnSelected(); - ServerListSpec getServerListSpec(std::string address, std::string port); -}; - #endif diff --git a/src/main.cpp b/src/main.cpp index eda992793..02bffa84d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverlist.h" #include "sound.h" #include "sound_openal.h" +#include "guiEngine.h" /* Settings. @@ -660,180 +661,6 @@ private: bool rightreleased; }; -struct MenuTextures -{ - std::string current_gameid; - bool global_textures; - video::ITexture *background; - video::ITexture *overlay; - video::ITexture *header; - video::ITexture *footer; - - MenuTextures(): - global_textures(false), - background(NULL), - overlay(NULL), - header(NULL), - footer(NULL) - {} - - static video::ITexture* getMenuTexture(const std::string &tname, - video::IVideoDriver* driver, const SubgameSpec *spec) - { - if(spec){ - std::string path; - // eg. minetest_menu_background.png (for texture packs) - std::string pack_tname = spec->id + "_menu_" + tname + ".png"; - path = getTexturePath(pack_tname); - if(path != "") - return driver->getTexture(path.c_str()); - // eg. games/minetest_game/menu/background.png - path = getImagePath(spec->path + DIR_DELIM + "menu" + DIR_DELIM + tname + ".png"); - if(path != "") - return driver->getTexture(path.c_str()); - } else { - std::string path; - // eg. menu_background.png - std::string pack_tname = "menu_" + tname + ".png"; - path = getTexturePath(pack_tname); - if(path != "") - return driver->getTexture(path.c_str()); - } - return NULL; - } - - void update(video::IVideoDriver* driver, const SubgameSpec *spec, int tab) - { - if(tab == TAB_SINGLEPLAYER){ - if(spec->id == current_gameid) - return; - current_gameid = spec->id; - global_textures = false; - background = getMenuTexture("background", driver, spec); - overlay = getMenuTexture("overlay", driver, spec); - header = getMenuTexture("header", driver, spec); - footer = getMenuTexture("footer", driver, spec); - } else { - if(global_textures) - return; - current_gameid = ""; - global_textures = true; - background = getMenuTexture("background", driver, NULL); - overlay = getMenuTexture("overlay", driver, NULL); - header = getMenuTexture("header", driver, NULL); - footer = getMenuTexture("footer", driver, NULL); - } - } -}; - -void drawMenuBackground(video::IVideoDriver* driver, const MenuTextures &menutextures) -{ - v2u32 screensize = driver->getScreenSize(); - video::ITexture *texture = menutextures.background; - - /* If no texture, draw background of solid color */ - if(!texture){ - video::SColor color(255,80,58,37); - core::rect<s32> rect(0, 0, screensize.X, screensize.Y); - driver->draw2DRectangle(color, rect, NULL); - return; - } - - /* Draw background texture */ - v2u32 sourcesize = texture->getSize(); - driver->draw2DImage(texture, - core::rect<s32>(0, 0, screensize.X, screensize.Y), - core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), - NULL, NULL, true); -} - -void drawMenuOverlay(video::IVideoDriver* driver, const MenuTextures &menutextures) -{ - v2u32 screensize = driver->getScreenSize(); - video::ITexture *texture = menutextures.overlay; - - /* If no texture, draw nothing */ - if(!texture) - return; - - /* Draw overlay texture */ - v2u32 sourcesize = texture->getSize(); - driver->draw2DImage(texture, - core::rect<s32>(0, 0, screensize.X, screensize.Y), - core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), - NULL, NULL, true); -} - -void drawMenuHeader(video::IVideoDriver* driver, const MenuTextures &menutextures) -{ - core::dimension2d<u32> screensize = driver->getScreenSize(); - video::ITexture *texture = menutextures.header; - - /* If no texture, draw nothing */ - if(!texture) - return; - - f32 mult = (((f32)screensize.Width / 2)) / - ((f32)texture->getOriginalSize().Width); - - v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, - ((f32)texture->getOriginalSize().Height) * mult); - - // Don't draw the header is there isn't enough room - s32 free_space = (((s32)screensize.Height)-320)/2; - if (free_space > splashsize.Y) { - core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y); - splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), - ((free_space/2)-splashsize.Y/2)+10); - - video::SColor bgcolor(255,50,50,50); - - driver->draw2DImage(texture, splashrect, - core::rect<s32>(core::position2d<s32>(0,0), - core::dimension2di(texture->getSize())), - NULL, NULL, true); - } -} - -void drawMenuFooter(video::IVideoDriver* driver, const MenuTextures &menutextures) -{ - core::dimension2d<u32> screensize = driver->getScreenSize(); - video::ITexture *texture = menutextures.footer; - - /* If no texture, draw nothing */ - if(!texture) - return; - - f32 mult = (((f32)screensize.Width)) / - ((f32)texture->getOriginalSize().Width); - - v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult, - ((f32)texture->getOriginalSize().Height) * mult); - - // Don't draw the footer if there isn't enough room - s32 free_space = (((s32)screensize.Height)-320)/2; - if (free_space > footersize.Y) { - core::rect<s32> rect(0,0,footersize.X,footersize.Y); - rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); - rect -= v2s32(footersize.X/2, 0); - - driver->draw2DImage(texture, rect, - core::rect<s32>(core::position2d<s32>(0,0), - core::dimension2di(texture->getSize())), - NULL, NULL, true); - } -} - -static const SubgameSpec* getMenuGame(const MainMenuData &menudata) -{ - for(size_t i=0; i<menudata.games.size(); i++){ - if(menudata.games[i].id == menudata.selected_game){ - return &menudata.games[i]; - } - } - return NULL; -} - #endif // !SERVER // These are defined global so that they're not optimized too much. @@ -1730,83 +1557,37 @@ int main(int argc, char *argv[]) // Initialize menu data MainMenuData menudata; - if(g_settings->exists("selected_mainmenu_tab")) - menudata.selected_tab = g_settings->getS32("selected_mainmenu_tab"); - if(g_settings->exists("selected_serverlist")) - menudata.selected_serverlist = g_settings->getS32("selected_serverlist"); - if(g_settings->exists("selected_mainmenu_game")){ - menudata.selected_game = g_settings->get("selected_mainmenu_game"); - menudata.selected_game_name = findSubgame(menudata.selected_game).name; - } - menudata.address = narrow_to_wide(address); - menudata.name = narrow_to_wide(playername); - menudata.port = narrow_to_wide(itos(port)); + menudata.kill = kill; + menudata.address = address; + menudata.name = playername; + menudata.port = itos(port); + menudata.errormessage = wide_to_narrow(error_message); + error_message = L""; if(cmd_args.exists("password")) - menudata.password = narrow_to_wide(cmd_args.get("password")); - menudata.fancy_trees = g_settings->getBool("new_style_leaves"); - menudata.smooth_lighting = g_settings->getBool("smooth_lighting"); - menudata.clouds_3d = g_settings->getBool("enable_3d_clouds"); - menudata.opaque_water = g_settings->getBool("opaque_water"); - menudata.mip_map = g_settings->getBool("mip_map"); - menudata.anisotropic_filter = g_settings->getBool("anisotropic_filter"); - menudata.bilinear_filter = g_settings->getBool("bilinear_filter"); - menudata.trilinear_filter = g_settings->getBool("trilinear_filter"); - menudata.enable_shaders = g_settings->getS32("enable_shaders"); - menudata.preload_item_visuals = g_settings->getBool("preload_item_visuals"); - menudata.enable_particles = g_settings->getBool("enable_particles"); - menudata.liquid_finite = g_settings->getBool("liquid_finite"); - driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, menudata.mip_map); - menudata.creative_mode = g_settings->getBool("creative_mode"); - menudata.enable_damage = g_settings->getBool("enable_damage"); + menudata.password = cmd_args.get("password"); + + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); + menudata.enable_public = g_settings->getBool("server_announce"); - // Default to selecting nothing - menudata.selected_world = -1; - // Get world listing for the menu + std::vector<WorldSpec> worldspecs = getAvailableWorlds(); - // If there is only one world, select it - if(worldspecs.size() == 1){ - menudata.selected_world = 0; - } - // Otherwise try to select according to selected_world_path - else if(g_settings->exists("selected_world_path")){ - std::string trypath = g_settings->get("selected_world_path"); - for(u32 i=0; i<worldspecs.size(); i++){ - if(worldspecs[i].path == trypath){ - menudata.selected_world = i; - break; - } - } - } + // If a world was commanded, append and select it if(commanded_world != ""){ + std::string gameid = getWorldGameId(commanded_world, true); std::string name = _("[--world parameter]"); if(gameid == ""){ gameid = g_settings->get("default_game"); name += " [new]"; } - WorldSpec spec(commanded_world, name, gameid); - worldspecs.push_back(spec); - menudata.selected_world = worldspecs.size()-1; - } - // Copy worldspecs to menu - menudata.worlds = worldspecs; - // Get game listing - menudata.games = getAvailableGames(); - // If selected game doesn't exist, take first from list - if(findSubgame(menudata.selected_game).id == "" && - !menudata.games.empty()){ - menudata.selected_game = menudata.games[0].id; + //TODO find within worldspecs and set config } - const SubgameSpec *menugame = getMenuGame(menudata); - - MenuTextures menutextures; - menutextures.update(driver, menugame, menudata.selected_tab); if(skip_main_menu == false) { video::IVideoDriver* driver = device->getVideoDriver(); - float fps_max = g_settings->getFloat("fps_max"); + infostream<<"Waiting for other menus"<<std::endl; while(device->run() && kill == false) { @@ -1814,7 +1595,6 @@ int main(int argc, char *argv[]) break; driver->beginScene(true, true, video::SColor(255,128,128,128)); - drawMenuBackground(driver, menutextures); guienv->drawAll(); driver->endScene(); // On some computers framerate doesn't seem to be @@ -1823,170 +1603,40 @@ int main(int argc, char *argv[]) } infostream<<"Waited for other menus"<<std::endl; - GUIMainMenu *menu = - new GUIMainMenu(guienv, guiroot, -1, - &g_menumgr, &menudata, g_gamecallback); - menu->allowFocusRemoval(true); - - if(error_message != L"") - { - verbosestream<<"error_message = " - <<wide_to_narrow(error_message)<<std::endl; - - GUIMessageMenu *menu2 = - new GUIMessageMenu(guienv, guiroot, -1, - &g_menumgr, error_message.c_str()); - menu2->drop(); - error_message = L""; - } - - // Time is in milliseconds, for clouds - u32 lasttime = device->getTimer()->getTime(); + GUIEngine* temp = new GUIEngine(device, guiroot, &g_menumgr,smgr,&menudata); + + delete temp; + //once finished you'll never end up here + smgr->clear(); + kill = menudata.kill; - MenuMusicFetcher soundfetcher; - ISoundManager *sound = NULL; -#if USE_SOUND - sound = createOpenALSoundManager(&soundfetcher); -#endif - if(!sound) - sound = &dummySoundManager; - SimpleSoundSpec spec; - spec.name = "main_menu"; - spec.gain = 1; - s32 handle = sound->playSound(spec, true); - - infostream<<"Created main menu"<<std::endl; - - while(device->run() && kill == false) - { - if(menu->getStatus() == true) - break; - - // Game can be selected in the menu - menugame = getMenuGame(menudata); - menutextures.update(driver, menugame, menu->getTab()); - // Clouds for the main menu - bool cloud_menu_background = g_settings->getBool("menu_clouds"); - if(menugame){ - // If game has regular background and no overlay, don't use clouds - if(cloud_menu_background && menutextures.background && - !menutextures.overlay){ - cloud_menu_background = false; - } - // If game game has overlay and no regular background, always draw clouds - else if(menutextures.overlay && !menutextures.background){ - cloud_menu_background = true; - } - } - - // Time calc for the clouds - f32 dtime=0; // in seconds - if (cloud_menu_background) { - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - } - - //driver->beginScene(true, true, video::SColor(255,0,0,0)); - driver->beginScene(true, true, video::SColor(255,140,186,250)); - - if (cloud_menu_background) { - // *3 otherwise the clouds would move very slowly - g_menuclouds->step(dtime*3); - g_menuclouds->render(); - g_menucloudsmgr->drawAll(); - drawMenuOverlay(driver, menutextures); - drawMenuHeader(driver, menutextures); - drawMenuFooter(driver, menutextures); - } else { - drawMenuBackground(driver, menutextures); - drawMenuHeader(driver, menutextures); - drawMenuFooter(driver, menutextures); - } - - guienv->drawAll(); - - driver->endScene(); - - // On some computers framerate doesn't seem to be - // automatically limited - if (cloud_menu_background) { - // Time of frame without fps limit - float busytime; - u32 busytime_u32; - // not using getRealTime is necessary for wine - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - busytime_u32 = time - lasttime; - else - busytime_u32 = 0; - busytime = busytime_u32 / 1000.0; - - // FPS limiter - u32 frametime_min = 1000./fps_max; - - if(busytime_u32 < frametime_min) { - u32 sleeptime = frametime_min - busytime_u32; - device->sleep(sleeptime); - } - } else { - sleep_ms(25); - } - } - sound->stopSound(handle); - if(sound != &dummySoundManager){ - delete sound; - sound = NULL; - } - - // Save controls status - menu->readInput(&menudata); + } - infostream<<"Dropping main menu"<<std::endl; + //update worldspecs (necessary as new world may have been created) + worldspecs = getAvailableWorlds(); - menu->drop(); - } + if (menudata.name == "") + menudata.name = std::string("Guest") + itos(myrand_range(1000,9999)); + else + playername = menudata.name; - playername = wide_to_narrow(menudata.name); - if (playername == "") - playername = std::string("Guest") + itos(myrand_range(1000,9999)); - password = translatePassword(playername, menudata.password); + password = translatePassword(playername, narrow_to_wide(menudata.password)); //infostream<<"Main: password hash: '"<<password<<"'"<<std::endl; - address = wide_to_narrow(menudata.address); - int newport = stoi(wide_to_narrow(menudata.port)); + address = menudata.address; + int newport = stoi(menudata.port); if(newport != 0) port = newport; + simple_singleplayer_mode = menudata.simple_singleplayer_mode; + // Save settings - g_settings->setS32("selected_mainmenu_tab", menudata.selected_tab); - g_settings->setS32("selected_serverlist", menudata.selected_serverlist); - g_settings->set("selected_mainmenu_game", menudata.selected_game); - g_settings->set("new_style_leaves", itos(menudata.fancy_trees)); - g_settings->set("smooth_lighting", itos(menudata.smooth_lighting)); - g_settings->set("enable_3d_clouds", itos(menudata.clouds_3d)); - g_settings->set("opaque_water", itos(menudata.opaque_water)); - - g_settings->set("mip_map", itos(menudata.mip_map)); - g_settings->set("anisotropic_filter", itos(menudata.anisotropic_filter)); - g_settings->set("bilinear_filter", itos(menudata.bilinear_filter)); - g_settings->set("trilinear_filter", itos(menudata.trilinear_filter)); - - g_settings->setS32("enable_shaders", menudata.enable_shaders); - g_settings->set("preload_item_visuals", itos(menudata.preload_item_visuals)); - g_settings->set("enable_particles", itos(menudata.enable_particles)); - g_settings->set("liquid_finite", itos(menudata.liquid_finite)); - - g_settings->set("creative_mode", itos(menudata.creative_mode)); - g_settings->set("enable_damage", itos(menudata.enable_damage)); - g_settings->set("server_announce", itos(menudata.enable_public)); g_settings->set("name", playername); g_settings->set("address", address); g_settings->set("port", itos(port)); - if(menudata.selected_world != -1) + + if((menudata.selected_world >= 0) && + (menudata.selected_world < worldspecs.size())) g_settings->set("selected_world_path", worldspecs[menudata.selected_world].path); @@ -2010,8 +1660,8 @@ int main(int argc, char *argv[]) { ServerListSpec server; server["name"] = menudata.servername; - server["address"] = wide_to_narrow(menudata.address); - server["port"] = wide_to_narrow(menudata.port); + server["address"] = menudata.address; + server["port"] = menudata.port; server["description"] = menudata.serverdescription; ServerList::insert(server); } @@ -2022,30 +1672,7 @@ int main(int argc, char *argv[]) infostream<<"Selected world: "<<worldspec.name <<" ["<<worldspec.path<<"]"<<std::endl; } - - // Only refresh if so requested - if(menudata.only_refresh){ - infostream<<"Refreshing menu"<<std::endl; - continue; - } - // Create new world if requested - if(menudata.create_world_name != L"") - { - std::string path = porting::path_user + DIR_DELIM - "worlds" + DIR_DELIM - + wide_to_narrow(menudata.create_world_name); - // Create world if it doesn't exist - if(!initializeWorld(path, menudata.create_world_gameid)){ - error_message = wgettext("Failed to initialize world"); - errorstream<<wide_to_narrow(error_message)<<std::endl; - continue; - } - g_settings->set("selected_world_path", path); - g_settings->set("selected_mainmenu_game", menudata.create_world_gameid); - continue; - } - // If local game if(current_address == "") { @@ -2085,8 +1712,10 @@ int main(int argc, char *argv[]) } // Break out of menu-game loop to shut down cleanly - if(device->run() == false || kill == true) + if(device->run() == false || kill == true) { + g_settings->updateConfigFile(configpath.c_str()); break; + } /* Run game @@ -2138,11 +1767,11 @@ int main(int argc, char *argv[]) break; } } // Menu-game loop - - + + g_menuclouds->drop(); g_menucloudsmgr->drop(); - + delete input; /* diff --git a/src/map.cpp b/src/map.cpp index cf7dd6f9f..272bc9322 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -3237,17 +3237,18 @@ v2s16 ServerMap::getSectorPos(std::string dirname) { unsigned int x, y; int r; - size_t spos = dirname.rfind(DIR_DELIM_C) + 1; - assert(spos != std::string::npos); - if(dirname.size() - spos == 8) + std::string component; + fs::RemoveLastPathComponent(dirname, &component, 1); + if(component.size() == 8) { // Old layout - r = sscanf(dirname.substr(spos).c_str(), "%4x%4x", &x, &y); + r = sscanf(component.c_str(), "%4x%4x", &x, &y); } - else if(dirname.size() - spos == 3) + else if(component.size() == 3) { // New layout - r = sscanf(dirname.substr(spos-4).c_str(), "%3x" DIR_DELIM "%3x", &x, &y); + fs::RemoveLastPathComponent(dirname, &component, 2); + r = sscanf(component.c_str(), "%3x" DIR_DELIM "%3x", &x, &y); // Sign-extend the 12 bit values up to 16 bits... if(x&0x800) x|=0xF000; if(y&0x800) y|=0xF000; diff --git a/src/mods.cpp b/src/mods.cpp index 9bcf73aa7..75c2dd89c 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "mods.h" +#include "main.h" #include "filesys.h" #include "strfnd.h" #include "log.h" @@ -25,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "strfnd.h" #include <cctype> +#include "convert_json.h" static bool parseDependsLine(std::istream &is, std::string &dep, std::set<char> &symbols) @@ -389,3 +391,29 @@ void ModConfiguration::resolveDependencies() // Step 4: write back list of unsatisfied mods m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end()); } + +#if USE_CURL +Json::Value getModstoreUrl(std::string url) +{ + struct curl_slist *chunk = NULL; + + bool special_http_header = true; + + try{ + special_http_header = g_settings->getBool("modstore_disable_special_http_header"); + } + catch(SettingNotFoundException &e) { + } + + if (special_http_header) + chunk = curl_slist_append(chunk, "Accept: application/vnd.minetest.mmdb-v1+json"); + + Json::Value retval = fetchJsonValue(url,chunk); + + if (chunk != NULL) + curl_slist_free_all(chunk); + + return retval; +} + +#endif diff --git a/src/mods.h b/src/mods.h index a8100fcfd..eb453bf6a 100644 --- a/src/mods.h +++ b/src/mods.h @@ -29,6 +29,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <map> #include <exception> #include <list> +#include "json/json.h" +#include "config.h" + +#if USE_CURL +#include <curl/curl.h> +#endif #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" @@ -154,4 +160,66 @@ private: }; +#if USE_CURL +Json::Value getModstoreUrl(std::string url); +#else +inline Json::Value getModstoreUrl(std::string url) { + return Json::Value(); +} +#endif + +struct ModLicenseInfo { + int id; + std::string shortinfo; + std::string url; +}; + +struct ModAuthorInfo { + int id; + std::string username; +}; + +struct ModStoreMod { + int id; + std::string title; + std::string basename; + ModAuthorInfo author; + float rating; + bool valid; +}; + +struct ModStoreCategoryInfo { + int id; + std::string name; +}; + +struct ModStoreVersionEntry { + int id; + std::string date; + std::string file; + bool approved; + //ugly version number + int mtversion; +}; + +struct ModStoreModDetails { + /* version_set?? */ + std::vector<ModStoreCategoryInfo> categories; + ModAuthorInfo author; + ModLicenseInfo license; + int id; + std::string title; + std::string basename; + std::string description; + std::string repository; + float rating; + std::vector<std::string> depends; + std::vector<std::string> softdeps; + + std::string download_url; + std::string screenshot_url; + std::vector<ModStoreVersionEntry> versions; + bool valid; +}; + #endif diff --git a/src/serverlist.cpp b/src/serverlist.cpp index ea5a616c2..6a68e0eac 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "log.h" #include "json/json.h" +#include "convert_json.h" #if USE_CURL #include <curl/curl.h> #endif @@ -68,35 +69,22 @@ std::vector<ServerListSpec> getLocal() #if USE_CURL - -static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) -{ - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - - std::vector<ServerListSpec> getOnline() { - std::string liststring; - CURL *curl; + Json::Value root = fetchJsonValue((g_settings->get("serverlist_url")+"/list").c_str(),0); - curl = curl_easy_init(); - if (curl) - { - CURLcode res; - - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_URL, (g_settings->get("serverlist_url")+"/list").c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring); + std::vector<ServerListSpec> serverlist; - res = curl_easy_perform(curl); - if (res != CURLE_OK) - errorstream<<"Serverlist at url "<<g_settings->get("serverlist_url")<<" not found (internet connection?)"<<std::endl; - curl_easy_cleanup(curl); + if (root.isArray()) { + for (unsigned int i = 0; i < root.size(); i++) + { + if (root[i].isObject()) { + serverlist.push_back(root[i]); + } + } } - return ServerList::deSerializeJson(liststring); + + return serverlist; } #endif @@ -189,30 +177,6 @@ std::string serialize(std::vector<ServerListSpec> serverlist) return liststring; } -std::vector<ServerListSpec> deSerializeJson(std::string liststring) -{ - std::vector<ServerListSpec> serverlist; - Json::Value root; - Json::Reader reader; - std::istringstream stream(liststring); - if (!liststring.size()) { - return serverlist; - } - if (!reader.parse( stream, root ) ) - { - errorstream << "Failed to parse server list " << reader.getFormattedErrorMessages(); - return serverlist; - } - if (root["list"].isArray()) - for (unsigned int i = 0; i < root["list"].size(); i++) - { - if (root["list"][i].isObject()) { - serverlist.push_back(root["list"][i]); - } - } - return serverlist; -} - std::string serializeJson(std::vector<ServerListSpec> serverlist) { Json::Value root; diff --git a/src/test.cpp b/src/test.cpp index b66f65daf..e609fe26f 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "log.h" #include "util/string.h" +#include "filesys.h" #include "voxelalgorithms.h" #include "inventory.h" #include "util/numeric.h" @@ -171,6 +172,212 @@ struct TestUtilities: public TestBase } }; +struct TestPath: public TestBase +{ + // adjusts a POSIX path to system-specific conventions + // -> changes '/' to DIR_DELIM + // -> absolute paths start with "C:\\" on windows + std::string p(std::string path) + { + for(size_t i = 0; i < path.size(); ++i){ + if(path[i] == '/'){ + path.replace(i, 1, DIR_DELIM); + i += std::string(DIR_DELIM).size() - 1; // generally a no-op + } + } + + #ifdef _WIN32 + if(path[0] == '\\') + path = "C:" + path; + #endif + + return path; + } + + void Run() + { + std::string path, result, removed; + + /* + Test fs::IsDirDelimiter + */ + UASSERT(fs::IsDirDelimiter('/') == true); + UASSERT(fs::IsDirDelimiter('A') == false); + UASSERT(fs::IsDirDelimiter(0) == false); + #ifdef _WIN32 + UASSERT(fs::IsDirDelimiter('\\') == true); + #else + UASSERT(fs::IsDirDelimiter('\\') == false); + #endif + + /* + Test fs::PathStartsWith + */ + { + const int numpaths = 12; + std::string paths[numpaths] = { + "", + p("/"), + p("/home/user/minetest"), + p("/home/user/minetest/bin"), + p("/home/user/.minetest"), + p("/tmp/dir/file"), + p("/tmp/file/"), + p("/tmP/file"), + p("/tmp"), + p("/tmp/dir"), + p("/home/user2/minetest/worlds"), + p("/home/user2/minetest/world"), + }; + /* + expected fs::PathStartsWith results + 0 = returns false + 1 = returns true + 2 = returns false on windows, false elsewhere + 3 = returns true on windows, true elsewhere + 4 = returns true if and only if + FILESYS_CASE_INSENSITIVE is true + */ + int expected_results[numpaths][numpaths] = { + {1,2,0,0,0,0,0,0,0,0,0,0}, + {1,1,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,0,0,0,0,0,0,0,0}, + {1,1,0,0,1,0,0,0,0,0,0,0}, + {1,1,0,0,0,1,0,0,1,1,0,0}, + {1,1,0,0,0,0,1,4,1,0,0,0}, + {1,1,0,0,0,0,4,1,4,0,0,0}, + {1,1,0,0,0,0,0,0,1,0,0,0}, + {1,1,0,0,0,0,0,0,1,1,0,0}, + {1,1,0,0,0,0,0,0,0,0,1,0}, + {1,1,0,0,0,0,0,0,0,0,0,1}, + }; + + for (int i = 0; i < numpaths; i++) + for (int j = 0; j < numpaths; j++){ + /*verbosestream<<"testing fs::PathStartsWith(\"" + <<paths[i]<<"\", \"" + <<paths[j]<<"\")"<<std::endl;*/ + bool starts = fs::PathStartsWith(paths[i], paths[j]); + int expected = expected_results[i][j]; + if(expected == 0){ + UASSERT(starts == false); + } + else if(expected == 1){ + UASSERT(starts == true); + } + #ifdef _WIN32 + else if(expected == 2){ + UASSERT(starts == false); + } + else if(expected == 3){ + UASSERT(starts == true); + } + #else + else if(expected == 2){ + UASSERT(starts == true); + } + else if(expected == 3){ + UASSERT(starts == false); + } + #endif + else if(expected == 4){ + UASSERT(starts == (bool)FILESYS_CASE_INSENSITIVE); + } + } + } + + /* + Test fs::RemoveLastPathComponent + */ + UASSERT(fs::RemoveLastPathComponent("") == ""); + path = p("/home/user/minetest/bin/..//worlds/world1"); + result = fs::RemoveLastPathComponent(path, &removed, 0); + UASSERT(result == path); + UASSERT(removed == ""); + result = fs::RemoveLastPathComponent(path, &removed, 1); + UASSERT(result == p("/home/user/minetest/bin/..//worlds")); + UASSERT(removed == p("world1")); + result = fs::RemoveLastPathComponent(path, &removed, 2); + UASSERT(result == p("/home/user/minetest/bin/..")); + UASSERT(removed == p("worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 3); + UASSERT(result == p("/home/user/minetest/bin")); + UASSERT(removed == p("../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 4); + UASSERT(result == p("/home/user/minetest")); + UASSERT(removed == p("bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 5); + UASSERT(result == p("/home/user")); + UASSERT(removed == p("minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 6); + UASSERT(result == p("/home")); + UASSERT(removed == p("user/minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 7); + #ifdef _WIN32 + UASSERT(result == "C:"); + #else + UASSERT(result == ""); + #endif + UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); + + /* + Now repeat the test with a trailing delimiter + */ + path = p("/home/user/minetest/bin/..//worlds/world1/"); + result = fs::RemoveLastPathComponent(path, &removed, 0); + UASSERT(result == path); + UASSERT(removed == ""); + result = fs::RemoveLastPathComponent(path, &removed, 1); + UASSERT(result == p("/home/user/minetest/bin/..//worlds")); + UASSERT(removed == p("world1")); + result = fs::RemoveLastPathComponent(path, &removed, 2); + UASSERT(result == p("/home/user/minetest/bin/..")); + UASSERT(removed == p("worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 3); + UASSERT(result == p("/home/user/minetest/bin")); + UASSERT(removed == p("../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 4); + UASSERT(result == p("/home/user/minetest")); + UASSERT(removed == p("bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 5); + UASSERT(result == p("/home/user")); + UASSERT(removed == p("minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 6); + UASSERT(result == p("/home")); + UASSERT(removed == p("user/minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 7); + #ifdef _WIN32 + UASSERT(result == "C:"); + #else + UASSERT(result == ""); + #endif + UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); + + /* + Test fs::RemoveRelativePathComponent + */ + path = p("/home/user/minetest/bin"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == path); + path = p("/home/user/minetest/bin/../worlds/world1"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/home/user/minetest/worlds/world1")); + path = p("/home/user/minetest/bin/../worlds/world1/"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/home/user/minetest/worlds/world1")); + path = p("."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == ""); + path = p("./subdir/../.."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == ""); + path = p("/a/b/c/.././../d/../e/f/g/../h/i/j/../../../.."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/a/e")); + } +}; + struct TestSettings: public TestBase { void Run() @@ -1785,6 +1992,7 @@ void run_tests() infostream<<"run_tests() started"<<std::endl; TEST(TestUtilities); + TEST(TestPath); TEST(TestSettings); TEST(TestCompress); TEST(TestSerialization); diff --git a/textures/base/pack/no_screenshot.png b/textures/base/pack/no_screenshot.png Binary files differnew file mode 100644 index 000000000..e309a3eed --- /dev/null +++ b/textures/base/pack/no_screenshot.png |