From 779410c7829db802230a62c63a4871cf636ffc80 Mon Sep 17 00:00:00 2001 From: Signal Date: Fri, 11 Jul 2025 17:11:52 +0000 Subject: [PATCH] Update mainmenu/init.lua --- mainmenu/init.lua | 318 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 259 insertions(+), 59 deletions(-) diff --git a/mainmenu/init.lua b/mainmenu/init.lua index f34327c..a4c5a3b 100644 --- a/mainmenu/init.lua +++ b/mainmenu/init.lua @@ -77,32 +77,123 @@ local default_game_menu = [[ scroll_container[${@WIDTH * 0.1},${@HEIGHT * 0.1};${@list_width},${@HEIGHT * 0.8};worldscroll;vertical;;0,0] @foreach:@WORLDS:worlds @if:@i % 2:sel - style[.select_world_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] - style[.select_world_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] - style[.select_world_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_world_${@path};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_world_${@path}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_world_${@path}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] @else:sel - style[.select_world_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] - style[.select_world_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] - style[.select_world_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_world_${@path};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_world_${@path}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_world_${@path}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] @endif:sel - button[0,${(@i +-1) * 0.5};${@list_width},0.5;.select_world_${@name};${@name}] + button[0,${(@i +-1) * 0.5};${@list_width},0.5;.select_world_${@path};${@name}] @endforeach:worlds scroll_container_end[] @if:@selected_world:fi - box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1};0.1,${@HEIGHT * 0.8};#292d2fff] - scroll_container[${@WIDTH * 0.4},${@HEIGHT * 0.1};${@WIDTH * 0.8},${@HEIGHT * 0.8};worldmodsscroll;vertical;;0,0] - @foreach:@WORLDMODS:wm - label[0,${@i};${@name}] - @endforeach:wm + box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1 +-0.1};0.1,${@HEIGHT * 0.8 + 0.2};#292d2fff] + scroll_container[${@WIDTH * 0.4 + 0.05},${@HEIGHT * 0.1};${@WIDTH * 0.8 +-0.05},${@HEIGHT * 0.8};worlconfigscroll;vertical;;0,0] + @set:j:0 + + @if:@setting_damage:fii + @if:@damage_enabled:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_damage_enabled_to_0;] + @else:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_damage_enabled_to_1;] + @endif:fiii + label[0.5,${@j + 0.25};Damage] + @set:j:@j + 0.5 + @else:fii + + @endif:fii + + @if:@setting_creative:fii + @if:@creative_enabled:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_creative_enabled_to_0;] + @else:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_creative_enabled_to_1;] + @endif:fiii + label[0.5,${@j + 0.25};Creative] + @set:j:@j + 0.5 + @else:fii + + @endif:fii + + @set:play_str:Play + + @if:@setting_server:fii + @if:@server_enabled:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_server_enabled_to_0;] + @set:play_str:Host server + @else:fiii + image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_server_enabled_to_1;] + @set:play_str:Play + @endif:fiii + + label[0.5,${@j + 0.25};Server] + @set:j:@j + 0.5 + @else:fii + + @endif:fii + + @set:j:@j + 1 + + button[${@WIDTH * 0.05},${@j};${@WIDTH * 0.4},0.75;.overlay_dialog_modconfig;Configure mods] + button[${@WIDTH * 0.05},${@j + 1};${@WIDTH * 0.4},0.75;.play;${@play_str}] + scroll_container_end[] - scrollbar[-800,6;0,2;vertical;worldmodsscroll;] + scrollbar[-800,6;0,2;vertical;worldconfigscroll;] @else:fi @endif:fi scrollbaroptions[arrows=hide] scrollbar[-800,6;0,2;vertical;worldscroll;] + +image[${@WIDTH * 0.05 +-0.1},${@HEIGHT * 0.05 +-0.1};${@WIDTH * 0.4 + 0.2},${@HEIGHT * 0.9 +-0.75};$DEFAULT_ASSET_PATH/btn_bg.png;8,8] +hypertext[${@WIDTH * 0.05},${@HEIGHT * 0.05};${@WIDTH * 0.4},0.75;;Available mods] +scroll_container[${@WIDTH * 0.05},${@HEIGHT * 0.05 + 0.75};${@WIDTH * 0.4},${@HEIGHT * 0.9 +-1.7};modsscroll;vertical;;0,0] + @foreach:@MODS:m + @if:@i % 2:sel + style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + @else:sel + style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + @endif:sel + button[0,${@i * 0.5 +-0.5};${@WIDTH * 0.4},0.5;.select_mod_${@name};${@name}] + @endforeach:m +scroll_container_end[] +scrollbar[-800,6;0,2;vertical;modsscroll;] + +image[${@WIDTH * 0.55 +-0.1},${@HEIGHT * 0.05 +-0.1};${@WIDTH * 0.4 + 0.2},${@HEIGHT * 0.9 +-0.75};$DEFAULT_ASSET_PATH/btn_bg.png;8,8] +hypertext[${@WIDTH * 0.55},${@HEIGHT * 0.05};${@WIDTH * 0.4},0.75;;Mods for ${@selected_world_name}] +scroll_container[${@WIDTH * 0.55},${@HEIGHT * 0.05 + 0.75};${@WIDTH * 0.4},${@HEIGHT * 0.9 +-1.7};worldmodsscroll;vertical;;0,0] + @foreach:@WORLDMODS:wm + @if:@i % 2:sel + style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] + @else:sel + style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] + @endif:sel + button[0,${@i * 0.5 +-0.5};${@WIDTH * 0.4},0.5;.select_mod_${@name};${@name}] + @endforeach:wm +scroll_container_end[] +scrollbar[-800,6;0,2;vertical;worldmodsscroll;] + +image_button[${@WIDTH * 0.45},${@HEIGHT * 0.5 +-(@WIDTH * 0.1)};${@WIDTH * 0.1},${@WIDTH * 0.1};$DEFAULT_ASSET_PATH/arrow_right.png;.add_mod_to_world;] +image_button[${@WIDTH * 0.45},${@HEIGHT * 0.5};${@WIDTH * 0.1},${@WIDTH * 0.1};$DEFAULT_ASSET_PATH/arrow_left.png;.remove_mod_from_world;] +tooltip[.add_mod_to_world;Add mod to world;#444;#aaa] +tooltip[.remove_mod_from_world;Remove mod from world;#444;#aaa] + +button[${@WIDTH * 0.1},${@HEIGHT * 0.95 +-0.75};${@WIDTH * 0.4},0.75;.unoverlay_dialog;Cancel] +button[${@WIDTH * 0.5},${@HEIGHT * 0.95 +-0.75};${@WIDTH * 0.4},0.75;.unoverlay_dialog;Confirm] + + label[2,2;Add World, asset path is $ASSET_PATH] button[2,3;3,1;.show_dialog_main;back] @@ -177,6 +268,15 @@ local function get_game_info(game) end end +local function get_world_index(path) + for i, x in ipairs(minetest.get_worlds()) do + if x.path == path then + return i + end + end + return -1 +end + local function get_worlds_for_game(id) local out = {} for _, x in ipairs(minetest.get_worlds()) do @@ -210,12 +310,50 @@ function get_mods_for_game(id) return out end +local function get_game_mods(id) + local out = {} + local base = get_game_info(id).path.."/mods/" + for _, x in pairs(minetest.get_dir_list(base, true)) do + out[#out +1] = { + name = x, + path = base..x + } + end + return out +end + function get_mods_for_world(world) - local config = minetest.check_mod_configuration(world) + local conf = Settings(world.."/world.mt") + local mods = {} + local game + local game_mods = {} + for k, v in pairs(conf:to_table()) do + if k == "gameid" then + game = v + elseif k:sub(1, 9) == "load_mod_" then + if v ~= "false" and v ~= "nil" and v then + mods[k:sub(10)] = minetest.get_user_path().."/"..v + end + end + end + if game then + for _, x in ipairs(get_game_mods(game)) do + game_mods[x.name] = x.path + end + end + local config = minetest.check_mod_configuration(world, mods) +-- print(dump(config)) for _, x in ipairs(config.unsatisfied_mods) do x.unsatisfied = true config.satisfied_mods[#config.satisfied_mods +1] = x end + if game then + for _, x in ipairs(config.satisfied_mods) do + if game_mods[x.name] then + x.game_provided = true + end + end + end return config.satisfied_mods end @@ -503,40 +641,36 @@ local function build_template_dialog(fs, depth) local i = 0 -- "(.-\n?)%s-@foreach:([^:]+):(%l*)\n(.-)\n%s-@endforeach:%3(\n?.*)" -- Extract foreach loops - local prev = 1 + local prev = 0 while i < 1000 do - local unfound = 0 - local a, b, pattern, name, content = fs:find("@foreach:([^:]+):(%w*)\n(.-)\n%s-@endforeach:%2", prev) + local fe_start, fe_end, fe_pattern, fe_name, fe_content = fs:find("@foreach:([^:]+):(%w-)\n(.-)\n%s-@endforeach:%2", prev) + local if_start, if_end, if_expr, if_name, if_content, else_content = fs:find("@if:([^:]+):(%w-)\n(.-)\n%s-@else:%2\n(.-)\n%s-@endif:%2", prev) -- print(string.rep("-", 20)..(depth or 0)) - if a then + if not fe_start and not if_start then break end + if fe_start and fe_start < (if_start or math.huge) then -- print("for each "..pattern.." ("..name..")\n"..content.."\nend for each ("..name..")") - dialog[#dialog +1] = build_template_dialog(fs:sub(prev, a -1), (depth or 0) +1) + dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, fe_start -1), (depth or 0) +1) dialog[#dialog +1] = { - foreach = pattern:trim(), - name = name, - content = build_template_dialog(content:trim(), (depth or 0) +1) + foreach = fe_pattern:trim(), + name = fe_name, + content = build_template_dialog(fe_content:trim(), (depth or 0) +1) } - prev = b + prev = fe_end i = i +1 - else - unfound = unfound +1 end - local a, b, expr, name, content, else_content = fs:find("@if:([^:]+):(%w*)\n(.-)\n%s-@else:%2\n(.-\n?)@endif:%2", prev) - if a then + + if if_start and if_start < (fe_start or math.huge) then -- print("if "..expr.." ("..name..")\n"..content.."\nend if ("..name..")") - dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, a -1), (depth or 0) +1) + dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, if_start -1), (depth or 0) +1) dialog[#dialog +1] = { - condition = expr:trim(), - name = name, - content = build_template_dialog(content:trim(), (depth or 0) +1), + condition = if_expr:trim(), + name = if_name, + content = build_template_dialog(if_content:trim(), (depth or 0) +1), else_content = build_template_dialog(else_content:trim(), (depth or 0) +1) } - prev = b + prev = if_end i = i +1 - else - unfound = unfound +1 end - if unfound > 1 then break end end if prev > 1 then @@ -681,6 +815,10 @@ local function evaluate_template_expression(expr, vars, depth) vars = {item = vars} end + -- Check for operators early, because variables may contain punctuation when expanded. + -- A class is used instead of %p because %p matches @, which is used for variables. + local has_operators = expr:find("[|&><=+*/%%^]") + -- Expand all variables so we can deal with a constexpr. local offset = 1 while offset < 100000 do @@ -691,6 +829,9 @@ local function evaluate_template_expression(expr, vars, depth) expr = expr:sub(1, a -1)..result..expr:sub(b +1) offset = a +#result end + + -- If there are no operators, this is a constant expression and we can just return. + if not has_operators then return expr end -- Condense sub-expressions. local offset = 1 @@ -703,9 +844,6 @@ local function evaluate_template_expression(expr, vars, depth) offset = a +#result end - -- If there are no operators, this is a constant expression and we can just return. - if not expr:find("%p") then return expr end - -- Expression parsing local tree = split_template_expression(expr) return tostring(reduce_template_expression(tree)) @@ -713,23 +851,22 @@ end -- Do variable interpolation for the given formspec using the provided variable table. local function evaluate_template_block(fs, vars) - -- Assignment statements - local offset = 1 - while offset < #fs do - local a, b, name, expr = fs:find("@set:([%a_]+):([^\n]+)", offset) - if not a then break end - vars[name] = evaluate_template_expression(expr, vars) - fs = fs:sub(1, a -1)..fs:sub(b +1) - offset = a - end - -- Interpolations - offset = 1 - while offset < #fs do - local a, b, expr = fs:find("%${([^}]-)}", offset) - if not a then break end - local result = evaluate_template_expression(expr, vars) - fs = fs:sub(1, a -1)..result..fs:sub(b +1) - offset = a +#result + local offset = 0 + while offset < 100000 do + local s_start, s_end, s_name, s_expr = fs:find("@set:@?([%w_]+):([^\n]+)", offset) + local i_start, i_end, i_expr = fs:find("%${([^}]-)}", offset) + if not s_start and not i_start then break end + if s_start and s_start < (i_start or math.huge) then + -- Assignment statements + vars[s_name] = evaluate_template_expression(s_expr, vars) + fs = fs:sub(1, s_start -1)..fs:sub(s_end +1) + offset = s_start + elseif i_start then + -- Interpolations + local result = evaluate_template_expression(i_expr, vars) + fs = fs:sub(1, i_start -1)..result..fs:sub(i_end +1) + offset = i_start +#result + end end return fs end @@ -747,7 +884,7 @@ local function evaluate_template_foreach(loop, vars) elseif var == "MODS" then list = get_mods_for_game(state.current_game.id) elseif var == "WORLDMODS" then - list = state.current_world and get_mods_for_world(state.current_world) or {} + list = state.menu_vars.selected_world and get_mods_for_world(state.menu_vars.selected_world) or {} else list = vars[var] end @@ -777,6 +914,7 @@ local function evaluate_template_conditional(cond, vars) else out = out..evaluate_game_dialog(cond.else_content, vars).."\n" end +-- print("Evaluated condition `"..cond.condition.."` as "..out) return out end @@ -889,6 +1027,25 @@ function show_game_menu(args) if not game.menu then local file = read_file(game.path.."/menu/mainmenu.txt") or default_game_menu + game.disabled_settings = {} + local settings = Settings(game.path.."/game.conf") + + local disabled_settings = string.split(settings:get("disabled_settings") or "", ",") + for _, x in ipairs(disabled_settings) do + x = x:trim() + if x == "enable_damage" then + game.disabled_settings.damage = false + elseif x == "!enable_damage" then + game.disabled_settings.damage = true + elseif x == "creative_mode" then + game.disabled_settings.creative = false + elseif x == "!creative_mode" then + game.disabled_settings.creative = true + elseif x == "enable_server" then + game.disabled_settings.server = false + end + end + local overlays = {} local backgrounds = {} local headers = {} @@ -948,6 +1105,12 @@ function show_game_menu(args) end game.menu = build_game_menu(file) + + state.menu_vars = {} + + state.menu_vars.setting_damage = game.disabled_settings.damage == nil + state.menu_vars.setting_creative = game.disabled_settings.creative == nil + state.menu_vars.setting_server = game.disabled_settings.server == nil end if args.show_dialog then @@ -957,6 +1120,9 @@ function show_game_menu(args) elseif args.unoverlay_dialog and #state.menu_current > 1 then state.menu_current[#state.menu_current] = nil end + + state.menu_vars.WIDTH = size.x + state.menu_vars.HEIGHT = size.y local fs = "" @@ -966,8 +1132,14 @@ function show_game_menu(args) " end + for i, x in ipairs(state.menu_current) do - fs = fs..evaluate_game_dialog(game.menu[x], setmetatable({WIDTH = size.x, HEIGHT = size.y}, {__index = state.menu_vars})) + if i > 1 then + fs = fs.."\ + box[0,0;"..size.x..","..size.y..";#0009]\ + " + end + fs = fs..evaluate_game_dialog(game.menu[x], state.menu_vars) end -- print(fs) @@ -1616,7 +1788,17 @@ function minetest.button_handler(data) end elseif state.loc == "game" then for k, v in pairs(data) do - if k:sub(1, string.len(".show_dialog_")) == ".show_dialog_" then + if k == ".play" then + gamedata = { + playername = "singleplayer", + password = "", + address = nil, + port = nil, + selected_world = get_world_index(state.menu_vars.selected_world), + singleplayer = true + } + minetest.start() + elseif k:sub(1, string.len(".show_dialog_")) == ".show_dialog_" then show_game_menu { show_dialog = k:sub(string.len(".show_dialog_>")) } @@ -1626,13 +1808,31 @@ function minetest.button_handler(data) } elseif k:sub(1, string.len(".select_world_")) == ".select_world_" then state.menu_vars.selected_world = k:sub(string.len(".select_world_>")) + state.menu_vars.selected_world_name = k:match "/([^/]+)$" or "" + local conf = Settings(state.menu_vars.selected_world.."/world.mt") + local de = conf:get("enable_damage") + if de == "true" then + state.menu_vars.damage_enabled = true + elseif de == "false" then + state.menu_vars.damage_enabled = false + else + state.menu_vars.creative_damage = not state.current_game.disabled_settings.damage + end + local cm = conf:get("creative_mode") + if cm == "true" then + state.menu_vars.creative_enabled = true + elseif cm == "false" then + state.menu_vars.creative_enabled = false + else + state.menu_vars.creative_enabled = not not state.current_game.disabled_settings.creative + end show_game_menu() elseif k == ".unoverlay_dialog" then show_game_menu { unoverlay_dialog = true } elseif k:sub(1, string.len(".set_")) == ".set_" then - local name, value = k:match "%.set_(.-)_to_(.*)" + local name, value = k:match "%.set_(.+)_to_(.*)" state.menu_vars[name] = value == "" and "0" or value show_game_menu() end